Harden GMLReader against XXE (disable DTDs and external entities) - #1221
Harden GMLReader against XXE (disable DTDs and external entities)#1221Nexory wants to merge 3 commits into
Conversation
| fact.setFeature("http://xml.org/sax/features/external-general-entities", false); | ||
| fact.setFeature("http://xml.org/sax/features/external-parameter-entities", false); | ||
| fact.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); |
There was a problem hiding this comment.
The three additional features are over-kill: without a DOCTYPE declaration, there is no external subset nor external entities.
Also note that this will fail on Android and any JAXP implementation that doesn't support FEATURE_SECURE_PROCESSING (required by the JAXP specification) or disallow-doctype-decl (only supported by Xerces-derivatives). Even if Android's expat based parser does not support these feature, it is in practice safe to use, because it does not resolve entities by default.
Shameless advertising: we will release Apache Commons XML in the next couple of weeks, which handles all these subtleties of JAXP implementations. The library can be both used as external dependency or shaded with <minimizeJar> and delegates the hassle of properly configuring an XML parser upstream.
|
Thanks, both points are right and the push has them. On the portability one: On the three extra features, I first wanted to keep them and argue that they
All five throw there, including the two in the SAX namespace, so those three are One consequence of the guard is worth writing down, since it is the trade you Two corrections to my own PR while I am here. The description claimed A new dependency is out of scope for this PR. |
GMLReader configured the SAX parser with only namespace-awareness and validation disabled, leaving DOCTYPE processing and external entity resolution enabled. GML is commonly read from untrusted sources (files, WFS responses, uploads), so a crafted document could disclose local files or trigger SSRF via an external entity (XXE). Enable JAXP secure processing and disable DTDs and external entities on the SAXParserFactory. There is no behaviour change for valid GML, and no signature change (setFeature only throws SAXException subclasses, which are already declared). This mirrors the KMLReader hardening in locationtech#1204. Adds GMLReaderXXETest: without the fix the external entity is resolved and a DOCTYPE is accepted; with it both are rejected and benign GML still parses. Signed-off-by: Nexory <St4yl3r30@hotmail.de>
SAXParserFactory.setFeature throws SAXNotRecognizedException for a feature name the implementation does not recognize, and that class extends SAXException, which read() already declares. The first version of this change therefore compiled but would abort the read on such an implementation, and the caller could not tell that apart from malformed XML. Rejecting the DOCTYPE is what does the work: without one there is no internal or external subset, so no entity can be declared in the first place. The three other feature names are gone, since they add nothing where that applies. Android's parser is skipped by name. It refuses every feature outside the SAX namespace and does not resolve external references anyway, so there is nothing to configure. Everywhere else the features are set without a guard: measured on crimson 1.1.3, a guarded version parses the XXE payload and resolves the entity, while an unguarded one throws while configuring. A parser that cannot be configured should fail here rather than read untrusted input unhardened. Also fixes two problems in the test: it asserted only inside the catch block, so it would have passed without running a single assertion had read() returned normally, and it was missing the license header that CONTRIBUTING.md requires.
1930b13 to
32c2d6d
Compare
|
You are right, and the suggestion is in. I had written that the guard turns a loud failure into a silent absence of Measured on the same XXE payload, guarded against unguarded:
So on a parser that cannot be configured the read now fails instead of quietly I took the factory class name from AOSP rather than the suggestion alone:
|
jodygarnett
left a comment
There was a problem hiding this comment.
Thanks for the suggestion/improvement. Some feedback provided below.
- Set the parser features in a try/catch and log a warning instead of letting an unconfigurable parser fail the read. This also removes the factory class name check. - Shorten the comment. - Move the tests into GMLReaderTest and drop the benign parse case, which GMLReaderTest already covers.
|
Done, all three:
The try/catch also removed the factory class name check, so that is one less Two things I cannot decide, both yours.
The other is the trade-off the try/catch makes. Measured on the same payload:
So on a parser that cannot be configured, the reader now parses untrusted GML While checking for duplicates I found that The APIs differ in whether they force the question: I have built it the way you asked. Say which of the two you want and I will make
|
Problem
GMLReader.read()builds itsSAXParserFactorywith onlysetNamespaceAware(false)andsetValidating(false), so DOCTYPE processing and external entity resolution stay enabled. GML is routinely read from untrusted input (files, WFS responses, uploads), so a crafted document can disclose local files or trigger SSRF via an external entity (XXE):Fix
Enable JAXP secure processing and disable DTDs / external entities on the factory (
disallow-doctype-decl,external-general-entities,external-parameter-entities,load-external-dtd). This is the same hardening merged for the siblingKMLReaderin #1204 (which setSUPPORT_DTD=falseandIS_SUPPORTING_EXTERNAL_ENTITIES=false);GMLReaderuses SAX (SAXParserFactory) rather than StAX, so the equivalent is expressed as parser features, but the effect (no DTDs, no external entities) is identical. No behaviour change for valid GML, and no signature change (setFeatureonly throwsSAXExceptionsubclasses, already declared).read(String, ...)delegates toread(Reader, ...), so both public entry points are covered.Test
GMLReaderXXETestdemonstrates the bug in the absence of the fix (per CONTRIBUTING): without the changetestExternalEntityIsNotResolvedleaks the referenced file content andtestDoctypeIsRejectedfails; with it both pass andtestBenignGmlStillParsesconfirms valid GML is unaffected. Fulljts-coresuite: 2298 tests, 0 failures.