Imagine you’re building a secure one-time password (OTP) system for your application. You’ve got everything in place: encrypted passwords, validated user inputs, and a shiny REST API to generate and validate OTPs. But then, a sneaky vulnerability creeps in through an unexpected door — your XML parser. This is the story of how a common oversight in XML parsing, known as XML External Entity (XXE) attacks, can turn a robust system into a hacker’s playground, and how you can slam that door shut.
The Scene: A Vulnerable OTP Handler
Let’s set the stage. I recently reviewed a Java class called OTPHandler, designed to handle OTP generation and validation for a CMS application. It uses Spring’s RestTemplate to call an external OTP service and parses the XML response using JAXB, a popular Java library for XML binding. Here’s a simplified version of the problematic code:
JAXBContext jc = JAXBContext.newInstance(GeneratedOTP.class);Unmarshaller unmarshaller = jc.createUnmarshaller();GeneratedOTP otpResponse = (GeneratedOTP) unmarshaller.unmarshal(new StringReader(response));Looks harmless, right? The code takes an XML response, converts it into a Java object, and moves on. But there’s a hidden trap: by default, JAXB’s unmarshaller is vulnerable to XXE attacks. Let’s unpack what that means and why it’s a big deal.
What’s an XXE Attack, Anyway?
An XML External Entity (XXE) attack exploits XML parsers that process external entities — references in XML that can point to external resources like files or URLs. If an attacker crafts a malicious XML payload, they can trick the parser into doing things it shouldn’t, like:
- Reading sensitive files on the server (e.g., /etc/passwd).
- Making internal network requests to access private APIs or cloud metadata endpoints.
- Causing denial of service by overloading the parser with massive data (e.g., the infamous “billion laughs” attack).
In the OTPHandler case, the external OTP service returns XML, which is parsed without any safeguards. An attacker who can manipulate the response (e.g., via a man-in-the-middle attack or a compromised service) could send something like this:

If the parser processes this, it could embed the contents of /etc/passwd in the status field, exposing sensitive data to the attacker. Worse, in cloud environments, attackers could target metadata endpoints (e.g., AWS’s http://1xx.2xx.1x9.xx4/latest/meta-data/) to steal credentials.
Why This Happens
The root issue is that many XML parsers, including JAXB’s default unmarshaller, are configured to process external entities for flexibility. This was a reasonable default decades ago when XML was king, but today, it’s a security landmine. In OTPHandler, the code doesn’t explicitly disable external entity processing, leaving the door wide open for XXE attacks.
The Fix: Locking Down Your XML Parser
Thankfully, fixing this is straightforward. The trick is to configure the XML parser to ignore external entities and DTDs (Document Type Definitions), which are often used in XXE attacks. Here’s how to secure the OTPHandler code:
JAXBContext jc = JAXBContext.newInstance(GeneratedOTP.class);Unmarshaller unmarshaller = jc.createUnmarshaller();
// Create a secure XMLInputFactoryXMLInputFactory xif = XMLInputFactory.newInstance();xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
// Parse the XML safelyStringReader stringReader = new StringReader(response);XMLStreamReader xmlReader = xif.createXMLStreamReader(stringReader);GeneratedOTP otpResponse = (GeneratedOTP) unmarshaller.unmarshal(xmlReader);This code does three critical things:
- Disables external entities: Setting IS_SUPPORTING_EXTERNAL_ENTITIES to false prevents the parser from fetching external resources.
- Disables DTD processing: Turning off SUPPORT_DTD blocks DTD-based attacks, like billion laughs.
- Uses a secure stream reader: The XMLInputFactory ensures the XML is parsed with these restrictions, keeping the unmarshalling process safe.
Wrap this in a try-catch block for proper error handling, and you’re good to go:
try { // Above parsing code} catch (JAXBException e) { logger.error("XML parsing error", e); throw new RuntimeException("Failed to parse OTP response");} catch (XMLStreamException e) { logger.error("Invalid XML stream", e);j throw new RuntimeException("Invalid OTP response format");}Beyond the Quick Fix
While this fix is essential, it’s just the start. Here are some additional steps to harden your XML processing:
- Validate XML Schemas: Use an XSD schema to ensure the XML matches the expected format before parsing. This adds a layer of defense against malformed inputs.
- Limit Input Size: Cap the size of responses (e.g., 10KB) to prevent denial-of-service attacks from oversized payloads.
- Switch to JSON: If you control the API, consider JSON instead of XML. JSON parsers (like Jackson or Gson) are inherently safer, as they don’t support external entities.
- Monitor and Log Safely: Avoid logging raw XML responses, as they may contain sensitive data like OTPs or phone numbers. Log only non-sensitive metadata.
- Keep Dependencies: Regularly scan for vulnerabilities in JAXB and related libraries using tools like OWASP Dependency-Check.
The Bigger Lesson
The XXE issue in OTPHandler is a broader reminder: security vulnerabilities often hide in plain sight, even in well-intentioned code. XML parsing is a classic example, but similar traps exist in JSON deserialization, SQL queries, or even URL handling. As developers, we need to stay curious and skeptical, “What could go wrong if an attacker pokes at this code?”
This vulnerability also highlights the importance of secure defaults in libraries. Modern frameworks are moving toward safer defaults, but older tools like JAXB require manual configuration. Always check your libraries’ security documentation and assume nothing is safe out of the box.
Call to Action
If you’re working with XML in your applications, take a moment to audit your parsers. Are external entities disabled? Are you validating inputs? If you’re using JAXB, SAX, or DOM, double-check their configurations. A few lines of code could save you from a costly breach.
And don’t stop there. Share this knowledge with your team, run a security scan on your codebase, and consider a penetration test to uncover other hidden risks. Security is a team sport, and every step you take makes your application — and your users — safer.
Have you encountered XXE or other sneaky vulnerabilities in your projects? Drop a comment below — I’d love to hear your war stories and tips!