Over-claiming kills trust as fast as missing a bug. 'XML parser resolves external entities' is reachability - it is not the same finding as 'I read /etc/passwd'.
A scanner sends an XML document with a DOCTYPE and an external entity, the parser resolves it, and the report comes back screaming critical: "XXE - arbitrary file read and remote code execution." Except nothing was read. The parser merely demonstrated that it processes DTDs. That is a real fact about the parser, and it is a long way from a fact about your data. Treating the two as one number is how an XXE finding becomes the thing an auditor uses to discredit the whole report.
The honest question is narrow: did an external entity actually pull content into the response, or did the parser only prove it is willing to try? Those are two different states, and apPosture grades them as two different states.
"The parser resolves a DTD" is a capability claim. "An external entity returned the contents of a file in the response" is a consequence claim. A capability tells you the door has no lock; a consequence tells you someone walked through it carrying something out. Over-claiming collapses the first into the second, and that single shortcut is responsible for most of the inflated XXE criticals security teams have learned to ignore.
The distinction is not pedantic. A parser can accept a DOCTYPE and still fail to exfiltrate: network egress is blocked, the entity is never reflected back, the file scheme is filtered, the resolver is offline, or the response is consumed but never echoed to the client. Capability present, consequence absent. The grade has to reflect which one we actually observed in the response we got back, not which one is theoretically reachable from the parser configuration alone.
Our active XXE engine (CWE-611) walks an endpoint through two states. State A establishes capability. State B establishes consequence. The oracle that flips A to B is a single, strict fact about the response body: a benign marker we planted came back expanded, and a bogus-path control did not.
Two guards keep State B honest. First, the endpoint must already echo a unique benign marker before we test entities at all - that proves it parses and reflects our XML, so a positive is not just noise. Second, the entity output must differ from a bogus-path control and must not appear as a literal &e; in the body. A parser that echoes the raw entity reference has not resolved anything; only an expansion that the control lacks counts.
Same vulnerability class, four observable conditions, four honest grades. The first row is the over-claim every loud scanner ships; the rest are what we actually emit.
| What was observed | Severity | exploit_status |
|---|---|---|
| "DTD accepted, therefore RCE / file read" (the over-claim) | critical | asserted, never proven |
| Parser resolves a DTD, nothing returned in-band | high | reachable |
| Benign marker file returned in-band, control differs | critical | confirmed (in-band file read) |
| Out-of-band interaction received at our collaborator | critical | confirmed (blind / OOB, OAST only) |
Notice what the table refuses to do. It never writes "confirmed" next to a row where the only evidence is the parser's willingness. Reachable is a real and useful grade - it tells you the door is unlocked and belongs in the queue - but it is not the same line item as a read that produced bytes you can point at. The two confirmed rows share one property the asserted row lacks: each is backed by something we observed leaving the system, a file's contents in-band or an outbound interaction at our collaborator. That observation is the entire difference between a grade an auditor accepts and a grade an auditor strikes.
Confirming consequence does not require taking anything sensitive. The in-band oracle reads only ubiquitous, low-sensitivity files - /etc/hostname on Linux, c:/windows/win.ini on Windows - chosen because they exist almost everywhere and disclose nothing of value. We never point the entity at /etc/shadow, a private key, or an application secret. The proof is "a file outside the document was pulled into the response," demonstrated with a hostname, not with your credentials.
The blind path holds the same line. When out-of-band testing is configured, an external entity points at our collaborator and a received interaction is the proof - the server made the outbound request, which is itself the consequence. No file is read, no payload is weaponised, and the OAST path is off by default. Capability is tested safely; consequence is proven safely.
An XXE that resolves a DTD but cannot exfiltrate is still worth fixing, and we still raise it - graded reachable, ranked below the confirmed reads. That ordering is the product. A team working the queue top-down hits proven file disclosures and outbound SSRF first, then the latent parsers, and never burns a remediation cycle chasing a critical that turns out to be a parser flexing. exploit_status is not decoration; downstream it adds weight for confirmed, far less for reachable, and the rank order follows.
The remediation does not change between the states - disable DOCTYPE and external entity processing, reject DTDs, harden the parser configuration. What changes is the urgency we are willing to put our name to. "Parser enabled" earns a fix on the normal SLA; "benign marker returned in-band" earns the critical, because we watched the data leave the document and land in the response. We grade the second as critical only because we can show the bytes - and we refuse to grade the first that way precisely so the second is still believed.