Skip to content

fix: parse XML bodies whose elements are named after HTML void elements - #22

Open
quirky4 wants to merge 1 commit into
mainfrom
def-51266-xml-param-autoclose
Open

fix: parse XML bodies whose elements are named after HTML void elements#22
quirky4 wants to merge 1 commit into
mainfrom
def-51266-xml-param-autoclose

Conversation

@quirky4

@quirky4 quirky4 commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Fixes DEF-51266.

Problem

The XML request-body decoder ran with dec.AutoClose = xml.HTMLAutoClose, which
self-closes every element on Go's HTML void list — including param, link,
input and col. Those are ordinary container elements in XML-RPC, Atom and
SOAP, so once the decoder invented their end tag, the document's real end tag
arrived as a stray one and decoding aborted:

XML syntax error on line 5: unexpected end element

ProcessRequest returned that error before reaching col.Set, so REQUEST_XML
stayed empty and REQBODY_ERROR was set. Every rule keyed on XML variables
matched nothing: XML-RPC brute-force, pingback and multicall-amplification
detection (77317950/51, 77350179/81, 77317980, 77231011) fired ~0 times on the
Coraza fleet against millions on Apache/LiteSpeed, and the XMLRpcBruteforce
captcha and www-brute RBL blocking downstream never triggered. In practice this
means essentially every WordPress xmlrpc.php call was uninspected.

Fix

Remove the one line. Strict = false and Entity = xml.HTMLEntity are unchanged.

Does this reduce the leniency PR corazawaf#622 added?

No — it strictly increases it. This is the main thing worth reviewing, since the
line was added deliberately to be lenient with malformed payloads.

readXML collects attribute values off StartElement and text off CharData.
It never inspects EndElement and never builds a tree, so self-closing an
element cannot change any member it collects. AutoClose's only observable
effect here was on the error path — and Strict = false does not forgive a
stray end tag, because it invents missing end tags rather than tolerating
extra ones.

The flexibility for malformed payloads rests on two mechanisms this PR does not
touch: Strict = false lets an unterminated element be closed by an ancestor's
end tag, and the unexpected EOF branch tolerates a body cut short.

Measured over 738 payloads (hand-written malformed/HTML-shaped bodies plus
deterministic mutations — truncations, byte deletions, injected <br>), parsed
both with and without AutoClose:

Payloads that lost an attribute or text node 0
Payloads that gained members 50
Byte-identical output 688
Parse failures with AutoClose 316
Parse failures without 281

Testing

  • TestXMLHTMLVoidElementNames — XML-RPC wp.getUsersBlogs and
    system.multicall, Atom <link>, plus two rows pinning that unterminated
    void elements still decode (the invariant the fix relies on).
  • One xmlrpc_multicall row added to the existing TestXMLOrdinaryDocumentsAccepted
    table, which goes through ProcessRequest — the bug was that
    ProcessRequest returned before populating, which a readXML-level test
    cannot catch.
  • Mutation-tested: restoring AutoClose fails all four regression cases, the
    collection-level one reporting rejected a 16794 byte document holding 0 members — the ticket's symptom verbatim.
  • Full suite, go vet and gofmt clean.

Out of scope

The XML://methodName/text() XPath selectors the rules would rather key on are
still unsupported (only //@* and /* keys exist) — this is the engine half
only; the rule-side rewrite is a separate WPT item, upstream
#1322.

Upstream

  • Still unfixed at corazawaf/coraza HEAD; no open upstream PR touches
    internal/bodyprocessors/xml.go.
  • #1441 reports this exact
    bug with the same wp.getUsersBlogs payload, but the root cause was never
    identified there — AutoClose is mentioned nowhere upstream (no issue, PR or
    discussion). Reporters work around it by disabling CRS rules 200000/200002.
  • #622 introduced the line to
    add "flexible XML body processing for malformed payloads", with no test
    exercising it.
  • #1452, in this fork since
    f18e237, added unexpected EOF tolerance but does not cover unexpected end element.

An upstream PR and a root-cause comment on corazawaf#1441 are follow-ups.

…ts (DEF-51266)

The XML request-body decoder ran with AutoClose = xml.HTMLAutoClose, so it
self-closed every element on Go's HTML void list. param, link, input and col
are ordinary container elements in XML-RPC, Atom and SOAP vocabularies, and
once the decoder had invented their end tag, their real one aborted decoding
with "unexpected end element". ProcessRequest returned before populating the
collection, leaving REQUEST_XML empty and REQBODY_ERROR set, so every rule
keyed on XML variables matched nothing: XML-RPC brute-force, pingback and
multicall-amplification detection never fired, and the captcha and RBL
blocking downstream of it never triggered.

Nothing depended on AutoClose for leniency. readXML reads attribute values off
StartElement and text off CharData and never inspects EndElement, so
self-closing an element changed no member it collects; the only effect it had
here was turning a document's own end tag into a stray one, which Strict false
does not forgive because it invents missing end tags rather than tolerating
extra ones. What the flexibility for malformed payloads actually rests on is
untouched: an element the body never terminates is closed by an ancestor's end
tag under Strict false, and a body cut short is tolerated by the unexpected EOF
branch. Across 738 malformed, truncated and HTML-shaped payloads parsed both
ways, none lost an attribute or text node and 35 fewer failed to parse.

AutoClose entered in corazawaf#622, which added
flexible XML body processing for malformed payloads and shipped no test
exercising it. The defect is still present upstream at HEAD and is reported,
with the same wp.getUsersBlogs body used in the regression test here, at
corazawaf#1441, where the cause has not been
identified and reporters instead disable CRS rules 200000 and 200002.
corazawaf#1452, in this fork since f18e237,
added the unexpected EOF tolerance but does not cover unexpected end element.
The XML:// XPath selectors these rules would rather key on remain unsupported,
so this restores the //@* and /* keys only:
corazawaf#1322.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@quirky4
quirky4 requested a review from cl-vkuznetsov August 12, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant