fix(java): treat ACCESS_EXTERNAL_DTD as an XXE sanitizer - #4024
Conversation
documentbuilderfactory-disallow-doctype-decl-missing only recognized setFeature() as a defense, so a factory hardened through the JAXP property setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "") was still reported. Setting that property to the empty string stops the parser from resolving external DTDs and entities, which is the same protection the rule already accepts from the external-general-entities and external-parameter-entities features, so add it as a sanitizer. Signed-off-by: Eljees <3.14hell@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ec86e968d9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| - metavariable-regex: | ||
| metavariable: $ATTR | ||
| regex: ^(.*\.)?ACCESS_EXTERNAL_DTD$ |
There was a problem hiding this comment.
Restrict the sanitizer to the JAXP XMLConstants symbol
When application code has any other expression ending in ACCESS_EXTERNAL_DTD, this regex suppresses the XXE finding without checking what property that expression represents. For example, a local String ACCESS_EXTERNAL_DTD = XMLConstants.ACCESS_EXTERNAL_SCHEMA; followed by dbf.setAttribute(ACCESS_EXTERNAL_DTD, "") matches this sanitizer even though external DTD/entity access remains enabled. Match XMLConstants.ACCESS_EXTERNAL_DTD explicitly, and handle the statically imported form only when the corresponding javax.xml.XMLConstants static import is present.
Useful? React with 👍 / 👎.
| - patterns: | ||
| - pattern: $FACTORY.setAttribute($ATTR, "") | ||
| - metavariable-regex: | ||
| metavariable: $ATTR | ||
| regex: ^(.*\.)?ACCESS_EXTERNAL_DTD$ |
There was a problem hiding this comment.
Recognize the property in static initializers
When a static factory field is hardened in a static initializer and consumed from another method, this sanitizer is in a different taint scope from newDocumentBuilder() and does not prevent the field-use source from firing. The source section explicitly has pattern-not-inside exceptions for the existing setFeature mitigations but no equivalent for setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""), so the new mitigation still produces a false positive in that established static-field pattern.
Useful? React with 👍 / 👎.
| - patterns: | ||
| - pattern: $FACTORY.setAttribute($ATTR, "") | ||
| - metavariable-regex: | ||
| metavariable: $ATTR | ||
| regex: ^(.*\.)?ACCESS_EXTERNAL_DTD$ |
There was a problem hiding this comment.
Recognize the property when applied by a helper
When callers pass the factory to a local hardening helper that sets ACCESS_EXTERNAL_DTD, this direct-call sanitizer does not match the helper invocation, so the later newDocumentBuilder() remains reported. The rule already has a separate helper-method sanitizer branch for each accepted setFeature defense, but this change does not add the new setAttribute defense there; the same abstraction demonstrated by GoodDocumentBuilderFactoryCtr2 therefore stops working for the newly supported mitigation.
Useful? React with 👍 / 👎.
| - patterns: | ||
| - pattern: $FACTORY.setAttribute($ATTR, "") | ||
| - metavariable-regex: | ||
| metavariable: $ATTR | ||
| regex: ^(.*\.)?ACCESS_EXTERNAL_DTD$ |
There was a problem hiding this comment.
Re-taint factories when external DTD access is restored
When code first sets ACCESS_EXTERNAL_DTD to "" but later changes the same property to a permissive value such as "all" before constructing the builder, this by-side-effect sanitizer permanently clears the factory's taint and the sink is missed even though external entity resolution is enabled again. The analogous insecure setFeature(..., true/false) mutations have dedicated rules, but there is no rule or taint source for a nonempty ACCESS_EXTERNAL_DTD assignment, so this change introduces an uncovered XXE false negative unless such assignments re-taint the factory.
Useful? React with 👍 / 👎.
The first version of the sanitizer regex accepted any expression ending in ACCESS_EXTERNAL_DTD, so an unrelated application constant with the same trailing name would silently suppress the XXE finding. Anchor the regex to XMLConstants.ACCESS_EXTERNAL_DTD (optionally fully qualified) or the bare name from a static import, and add a test case with a look-alike constant that must still be reported. Signed-off-by: Eljees <3.14hell@gmail.com>
|
Good catch on the first point — fixed in 102f6ec. The regex accepted any expression ending in regex: ^(javax\.xml\.)?XMLConstants\.ACCESS_EXTERNAL_DTD$|^ACCESS_EXTERNAL_DTD$(the bare alternative keeps static imports working). I added a test case using a look-alike On the other three: they're all real, and all pre-existing shapes of the same limitation rather than something this PR introduces.
Happy to open a follow-up for the taint-scope items if that's useful. |
|
Ping — open since 28 July, no review yet. Checks are green on This addresses case 1 of #3831: a factory hardened through the JAXP properties ( The other three points raised in review are pre-existing shapes of the same limitation rather than something this PR introduces; detail is in the thread. Happy to rework it, or to close it if the change is not wanted in this shape. |
Addresses case 1 of #3831 (the false positive).
Problem
The rule only recognizes
setFeature(...)as a defense, so a factory hardened through the JAXP properties is still reported:Setting
ACCESS_EXTERNAL_DTDto the empty string stops the parser from resolving external DTDs and entities — the same protection the rule already accepts from theexternal-general-entities+external-parameter-entitiesfeatures, and the mitigation the OWASP XXE cheat sheet recommends when DOCTYPE declarations cannot be disabled outright.Change
One sanitizer alternative:
Two notes on scope:
ACCESS_EXTERNAL_DTD|ACCESS_EXTERNAL_SCHEMA. I deliberately match onlyACCESS_EXTERNAL_DTD:ACCESS_EXTERNAL_SCHEMArestricts schema resolution and does not stop external entity resolution, so accepting it on its own would turn the false positive into a false negative. Code that sets both (as above) is still sanitized, via the DTD property.XMLConstants.ACCESS_EXTERNAL_DTD) or imported statically.Cases 2 and 3 of the issue (false negatives on chained
DocumentBuilderFactory.newInstance().newDocumentBuilder()) are not addressed here — new sources interact with the rule's autofix, so they are better handled in a separate change.Tests
Added the reproducer as an
okcase in the test file, mirrored into.fixed.java.