XML in Production · Security and Integration

The 1 KB Request That Took the API Down

A SOAP endpoint expanded nested internal entities. One small request expanded to gigabytes in memory and took every worker down.

Create a free account to save this page, take notes and track your progress. Sign in

Scenario

Example team: a legacy SOAP API used by travel agents parses requests with a library configured years ago to "support entities". Rate limits are by request count, and the largest request size allowed is 1 MB.

What you see in production

Within a minute of one agent's misbehaving script sending a few small requests, every API worker sits at 100% CPU with memory climbing until it's killed. Requests are tiny; nothing looks unusual in the access log.

The naive code

parser = etree.XMLParser(resolve_entities=True, huge_tree=True)   # "support entities", "allow big documents"
etree.fromstring(request_body, parser)

Reproduce it

Modern libxml2 has an amplification limit; the simulation shows how fast expansion grows and where the limit stops it:

Nested entitiesTry it yourself
from lxml import etree

def bomb(levels, fanout=10):
    decls = ['<!ENTITY e0 "lol">'] + [f'<!ENTITY e{i} "{f"&e{i - 1};" * fanout}">' for i in range(1, levels + 1)]
    return f"<!DOCTYPE b [{''.join(decls)}]><b>&e{levels};</b>".encode()

for levels in (3, 5, 9):
    data = bomb(levels)
    expanded = 3 * 10 ** levels
    try:
        doc = etree.fromstring(data, etree.XMLParser(resolve_entities=True))
        print(f"{levels} levels: {len(data)} bytes -> {len(doc.text):,} characters")
    except etree.XMLSyntaxError as e:
        print(f"{levels} levels: {len(data)} bytes -> would be {expanded:,} characters; stopped: {e.msg[:45]}")

try:
    doc = etree.fromstring(bomb(9), etree.XMLParser(resolve_entities=False, load_dtd=False))
    print("entities off:", repr(doc.text))
except etree.XMLSyntaxError as e:
    print("entities off: still rejected while reading the declarations:", e.msg[:45])
print("pre-check:", "rejected, DOCTYPE not allowed" if b"<!DOCTYPE" in bomb(9) else "accepted")

Output

3 levels: 209 bytes -> 3,000 characters
5 levels: 319 bytes -> would be 300,000 characters; stopped: Maximum entity amplification factor exceeded,
9 levels: 539 bytes -> would be 3,000,000,000 characters; stopped: Maximum entity amplification factor exceeded,
entities off: still rejected while reading the declarations: Maximum entity amplification factor exceeded,
pre-check: rejected, DOCTYPE not allowed

Root cause

Internal entities can reference other entities; ten references per level for nine levels gives 10^9 copies (the "billion laughs" attack, CWE-776). Size limits on the request don't help, because the cost appears after parsing starts. Options like huge_tree=True disable built-in protections.

The fix

  • Disable DTD processing for request bodies (the same setting that stops XXE).
  • Keep library protections on: don't set huge_tree=True or raise entity limits for untrusted input.
  • Put limits around parsing: maximum body size, a parse timeout, and memory limits per worker so one request can't take the whole pool.

Trade-offs

  • Legitimate entity use in request bodies is extremely rare; turning DTDs off almost never breaks real clients.
  • Worker memory limits and timeouts protect against other expensive inputs too (deep nesting, huge attributes).

Detect it in production

  • Alerts on worker restarts, OOM kills and CPU saturation without a matching traffic increase.
  • Log and block request bodies containing <!ENTITY.

Key points

  • Small XML can expand enormously; size limits on the request aren't enough.
  • Disable DTDs, keep parser limits, and bound each request's CPU and memory.

Practice questions

What is a billion laughs attack and how do you prevent it?

Answer

A document defines entities that reference other entities many times, so a tiny file expands to gigabytes when parsed (CWE-776). Disable DTD/entity processing for untrusted input and keep the parser's expansion limits on.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?