Skip to content

Security: one email can crash the inbound receiver — DMARC decompression bomb + XML entity-expansion (unauthenticated DoS) #129

Description

@mohamedboukari

Severity: Critical (decompression bomb) + Medium (entity expansion). Found in a security sweep of v0.8.0. Both are reachable by anyone who can open an SMTP connection to the inbound receiver (port 25/2525) and send one crafted message — no auth.

1. Decompression bomb (no output size cap)

src/modules/dmarc-reports/services/dmarc-parser.service.ts:61 / :69:

if (format === "gzip") return strFromU8(gunzipSync(bytes));  // no cap
const entries = unzipSync(bytes);                             // no cap

The inbound path caps the raw message at 10 MB, but a gzip/zip attachment inside it can expand ~1000:1 (fflate enforces no ratio limit). gunzipSync/unzipSync are synchronous — they allocate the whole output and block the event loop. A ~7 MB attachment → multiple GB → OOM / full freeze. The DMARC branch runs on every inbound message (smtp-receiver.service.ts:386), gated only by a spoofable looksLikeDmarcReport() (subject/from + zip/gzip content-type).

Exploit: email to any registered domain, Subject: DMARC aggregate report, 42 KB gzip → 5 GB → inbound receiver (and the whole process) goes down.

Fix: streaming/limited inflate with a hard maxOutputSize (e.g. 20–50 MB), abort past it; run off the main loop.

2. XML entity expansion (billion laughs)

dmarc-parser.service.ts:216:

const xmlParser = new XMLParser({ ignoreAttributes: true, parseTagValue: true });

processEntities defaults to enabled and there's no DOCTYPE rejection. fast-xml-parser doesn't fetch external entities (no classic XXE file-read/SSRF here), but internal nested <!ENTITY> definitions expand during the synchronous parse() and peg CPU / stall the loop.

Fix: pass processEntities: false and/or reject input containing <!DOCTYPE.

Impact

A mail platform whose inbound receiver can be crashed or frozen by a single crafted email is unusable as an MX — repeated outages from trivial mail is a reputation-ender.

Acceptance

  • Decompression capped (hard max output) and aborted past the cap; ideally non-blocking.
  • XMLParser created with processEntities: false (+ DOCTYPE reject).
  • Tests: a 1000:1 gzip attachment is rejected without OOM; a billion-laughs XML is rejected without CPU blowup.
  • THREAT_MODEL.md inbound section updated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:highHigh-priority workreliabilityReliability / fault tolerancesecuritySecurity issue or hardening

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions