Skip to content

#1071: reject invalid XML element names in XML.toString (CWE-91)#1072

Open
mechko wants to merge 1 commit into
stleary:masterfrom
mechko:1071-xml-tostring-name-validation
Open

#1071: reject invalid XML element names in XML.toString (CWE-91)#1072
mechko wants to merge 1 commit into
stleary:masterfrom
mechko:1071-xml-tostring-name-validation

Conversation

@mechko

@mechko mechko commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #1071. Implements the throw-on-invalid-name approach discussed in #294 / #123, which also resolves the long-standing well-formedness question in #166 / #308.

Problem

XML.toString emits JSONObject keys verbatim as XML tag names. A key containing <, > or / breaks out of its element and injects arbitrary sibling structure:

{"a/><injected>evil</injected><a": ""}
    → <root><a/><injected>evil</injected><a/></root>

Values already go through escape(); keys/tagName do not.

Fix

Validate every key and tagName against the XML 1.0 (5th ed.) Name production before emitting; throw JSONException on mismatch.

  • mustBeXmlName(String) — throws if the string is null/empty or contains a code point outside the Name production
  • isXmlNameStart(int) / isXmlNameChar(int) — the two productions, code-point aware (handles supplementary planes)
  • Called once for tagName at the top of the private toString overload, and once per key at the top of the key loop (the cDataTagName sentinel is skipped since it never becomes a tag)

<, >, /, whitespace, ", &, @ are all outside NameChar, so the injection vector is closed. Keys that are already valid XML Names are unaffected — mustBeXmlName is a no-op for them.

Behaviour change

XML.toString now throws for keys that are not valid XML Names, where previously it emitted malformed XML:

Key Before After
"a/><injected>…" element injection JSONException
"123foo" <123foo> (invalid XML) JSONException
"has space" <has space> (invalid XML) JSONException
"foo@bar" <foo@bar> (invalid XML) JSONException
"ns:name", "a-b.c_d", "élément" unchanged unchanged

The two existing shouldHandleIllegalJSONNodeNames tests already documented the old output as "invalid XML" / "possible bug"; they've been rewritten to assert the throw.

Verification

$ java -cp target/classes:. XmlRepro
Exception in thread "main" org.json.JSONException: 'a/><injected>evil</injected><a' is not a valid XML element name.
        at org.json.XML.mustBeXmlName(XML.java:259)
        at org.json.XML.toString(XML.java:1063)

mvn test: 791 run, 0 fail, 0 error, 6 skipped (pre-existing).

Tests

🤖 Generated with Claude Code

XML.toString emitted JSONObject keys verbatim as tag names, so a key
containing '<', '>' or '/' broke out of its element and injected
arbitrary sibling structure into the output. Per stleary#294/stleary#123 the agreed
approach is to throw on invalid input rather than mangle it.

- Add mustBeXmlName / isXmlNameStart / isXmlNameChar implementing the
  XML 1.0 (5th ed.) Name production, code-point aware.
- Validate tagName at method entry and each key at the top of the key
  loop (skipping the cDataTagName sentinel).
- Rewrite XMLTest.shouldHandleIllegalJSONNodeNames and
  XMLConfigurationTest.shouldHandleIllegalJSONNodeNames (previously
  documenting the pass-through behaviour) to assert the throw.
- Add XMLTest.toStringRejectsElementInjectionInKey covering the stleary#1071
  payload and an invalid caller-supplied tagName.
- Add XMLTest.toStringAcceptsValidXmlNames covering hyphen/dot/
  underscore/colon, Latin-1 letters, and the cDataTagName sentinel.

Fixes stleary#1071. Also resolves the long-standing well-formedness question
in stleary#166 / stleary#294 / stleary#308.

Co-Authored-By: Claude <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XML.toString: unescaped keys allow XML element injection (CWE-91) — proposal to throw per #294

2 participants