🚀 Submit a Challenge — SecDim AppSec Village CTF at DEF CON 34 and Win a ROG Xbox Ally

The Missing XXE Controls in JavaScript XML Parsers

Blog5 min read

At the start of this project, I expected an XML parser to take a file or a raw XML stream, parse it cleanly into a usable structure, and expose solid, well-documented functionality similar to what exists in other ecosystems. I assumed that resolving entities, both internal and external, would be something a mature parser could do with clear configuration options.

But I soon realised this assumption did not hold in the JavaScript ecosystem. One of the first constraints I identified was that there is no widely loved, actively maintained JavaScript library that can reliably convert XML to JSON and support both internal and external entity resolution in a controlled way. Every library I evaluated was missing something critical: some were unmaintained, some did not support external entities at all, others lacked meaningful security controls, and some were extremely difficult to install or configure.

Surveying Available XML Parsers in JavaScript

Over the course of the project, I explored several XML parsers, including:

  • fast-xml-parser
  • sax-js
  • xml-js
  • libxmljs
  • libxmljs2

The first parser I experimented with was fast-xml-parser. It was easy to use, actively maintained, and popular, which initially made it seem like the best candidate. However, while it could expand internal entities, it did not support resolving external entities at all, and it did not provide meaningful security controls around entity expansion. This immediately limited its usefulness for demonstrating XXE behaviour beyond very basic cases.

As I continued testing other libraries, the same pattern kept appearing. Most JavaScript XML parsers either supported internal entity expansion or avoided entity resolution entirely for security reasons. None of the popular or well-maintained options behaved in a way that allowed me to meaningfully demonstrate both a vulnerable and a secure configuration.

At one point, I explored whether strict string matching or manual payload filtering could be used to block malicious inputs. While this technically worked in some cases, it was not a practical or realistic solution. A parser should have proper built-in options for secure configuration rather than relying on fragile string checks. Other ecosystems, such as Java and Python, provide these kinds of controls, further highlighting the limitations of the JavaScript ecosystem.

Library Limitations and Maintenance Issues

Eventually, I came across libxmljs, which was one of the few JavaScript libraries that could resolve both internal and external entities. However, this library introduced a new set of problems. It was no longer actively maintained, had poor installation documentation, and repeatedly failed to install correctly due to native build and dependency issues.

Later, I discovered libxmljs2, a fork of libxmljs. While also unmaintained, it at least installed successfully and exposed the functionality I needed. At this point, it became clear that there was no “ideal” option available. I had to choose between a maintained library that lacked the required functionality and an unmaintained one that behaved correctly for the purposes of the SecDim secure coding challenge.

I ultimately chose libxmljs2 because it allowed me to demonstrate real XXE behaviour, even though it came with clear trade-offs.

Design Shift and Final Approach

The decision to use an unpopular and unmaintained dependency marked a shift in my thinking. Instead of trying to find a perfect library, I focused on whether the parser could accurately demonstrate the vulnerability and its mitigation. The structure of the application itself remained largely the same; the key difference was the underlying dependency and how the parsing options were configured.

The application accepts XML input through either automated security tests or manual curl requests. The request body is passed directly to the parser with configurable options, and the parsed output is returned to the user.

Vulnerable Configuration and Parser Options

To make the application vulnerable, the parser is configured with noent: true (which enables entity substitution) and nonet: false (which allows network access for fetching external entities). This enables both internal and external entity resolution as normal XML parsers do.

With noent: true, nonet: false entities are substituted, and network fetches are permitted, enabling both internal and external entity resolution.

Withnoent: false, nonet: true entity substitution is disabled, and network access is blocked. However, libxmljs2 still expands internal entities even with noent: false, which is a key limitation of the library’s security controls.

This means that even in the “secured” configuration, internal entities can still be expanded, highlighting an important gap in the library’s security controls.

const xmlDoc = libxmljs.parseXml(xmlBody, {
  noent: true,
  nonet: false,
});

Exploitation

Insecure behaviour in this application manifests in two primary ways. External entity resolution can expose sensitive system information, such as /etc/passwd, while internal entities can be abused to perform denial-of-service attacks through payload expansion.

External Entity Exploitation Example

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
    <!ELEMENT root ANY>
    <!ENTITY file SYSTEM "file:///etc/passwd">
]>
<root>
    <data>&file;</data>
</root>

Internal Entity DoS Exploitation Example

This is commonly known as a “Billion Laughs” attack, in which nested entity references expand exponentially, consuming memory and CPU and causing a denial of service:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE lolz [
    <!ENTITY lol "lol">
    <!ELEMENT lolz (#PCDATA)>
    <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
    <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
    <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
    <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
    <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
    <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
    <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
    <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
    <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

A successful exploit results in unauthorised access to system information or resource exhaustion, depending on the payload used.

Manual Verification and Security Testing

To verify the vulnerability manually, I used the following curl command and modified the XML payload file to test different attack scenarios:

curl -i -X POST http://localhost:8080/parse \
  -H "Content-Type: application/xml" \
  --data-binary @lol.xml

By observing the server responses and parsed output, I was able to confirm when entities were being expanded and when the mitigation options were taking effect.

Final Outcome and Limitations

The final implementation successfully resolves internal and external entities in its vulnerable configuration and defends against external entity resolution when mitigation options are applied. The parser itself was intentionally kept simple, as the goal of the challenge is to focus on XML parsing behaviour rather than application logic.

One major remaining limitation is the lack of a robust defence against internal entity expansion. With more time, I would explore alternative strategies or additional validation layers to mitigate this issue more effectively.

Reflection and Lessons Learned

This project significantly changed my understanding of XML parsing in JavaScript. I learned that “it parses” does not mean “it is secure” and that many libraries prioritise ease of use or safety by omission functionality rather than offering fine-grained security controls. The repeated failures and dead ends shaped the final design by forcing me to focus on behaviour rather than popularity or maintenance status.

Most importantly, this project reinforced that secure development often involves working within imperfect ecosystems and making deliberate, well-documented trade-offs. If I were to approach a similar challenge again, I would evaluate parser behaviour much earlier and be more sceptical of assumptions based on popularity or documentation alone.

This blog post has been authored by Mahyar Sabzevary during his internship at SecDim.

Questions or comments? Discuss this post on SecDim Community â†’

Try it yourself

Find, Hack and Fix Your First Vulnerability

Reading about security bugs is one thing — fixing one is how the skill sticks. Play a free challenge from the wargame, no setup required.