-
Notifications
You must be signed in to change notification settings - Fork 475
Harden GMLReader against XXE (disable DTDs and external entities) #1221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,10 @@ | |
| import java.io.Reader; | ||
| import java.io.StringReader; | ||
|
|
||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
|
|
||
| import javax.xml.XMLConstants; | ||
| import javax.xml.parsers.ParserConfigurationException; | ||
| import javax.xml.parsers.SAXParser; | ||
| import javax.xml.parsers.SAXParserFactory; | ||
|
|
@@ -25,6 +29,8 @@ | |
| import org.locationtech.jts.geom.PrecisionModel; | ||
| import org.xml.sax.InputSource; | ||
| import org.xml.sax.SAXException; | ||
| import org.xml.sax.SAXNotRecognizedException; | ||
| import org.xml.sax.SAXNotSupportedException; | ||
| import org.xml.sax.helpers.DefaultHandler; | ||
|
|
||
|
|
||
|
|
@@ -105,6 +111,15 @@ public Geometry read(Reader reader, GeometryFactory geometryFactory) throws SAXE | |
|
|
||
| fact.setNamespaceAware(false); | ||
| fact.setValidating(false); | ||
| // Harden against XXE, as GML is often read from untrusted sources. | ||
| try { | ||
| fact.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); | ||
| fact.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); | ||
| } | ||
| catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) { | ||
| Logger.getLogger(GMLReader.class.getName()) | ||
| .log(Level.WARNING, "SAX parser does not support XXE hardening", e); | ||
| } | ||
|
Comment on lines
+115
to
+122
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fail-open, and one try wraps both features. If Worse, swallowing turns a config failure into a working XXE. That is not hypothetical — the crimson 1.1.3 table in #1221 (comment) shows the try/catch path parses the payload and resolves the entity, while the unguarded path throws while configuring.
|
||
|
|
||
| SAXParser parser = fact.newSAXParser(); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| package org.locationtech.jts.io.gml2; | ||
|
|
||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
|
|
||
| import javax.xml.parsers.ParserConfigurationException; | ||
|
|
||
|
|
@@ -126,4 +128,37 @@ private void checkRead(String gml, String wktExpected, int srid) { | |
| checkEqual(expected, g); | ||
| assertEquals("SRID incorrect - ", srid, g.getSRID()); | ||
| } | ||
|
|
||
| public void testExternalEntityIsNotResolved() throws Exception { | ||
| File secretFile = File.createTempFile("jts-xxe", ".txt"); | ||
| String secret = "JTS-XXE-CANARY-SECRET"; | ||
| Files.write(secretFile.toPath(), secret.getBytes("UTF-8")); | ||
| try { | ||
| String gml = "<?xml version=\"1.0\"?>\n" | ||
| + "<!DOCTYPE foo [ <!ENTITY xxe SYSTEM \"" + secretFile.toURI() + "\"> ]>\n" | ||
| + "<gml:Point><gml:coordinates>&xxe;</gml:coordinates></gml:Point>"; | ||
| String observed; | ||
| try { | ||
| observed = String.valueOf(new GMLReader().read(gml, null)); | ||
| } | ||
| catch (Exception e) { | ||
| observed = String.valueOf(e.getMessage()); | ||
| } | ||
| assertFalse(observed.contains(secret)); | ||
| } | ||
| finally { | ||
| secretFile.delete(); | ||
| } | ||
| } | ||
|
Comment on lines
+132
to
+152
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Canary is structured correctly: the secret assert runs on both the success and exception paths, so this cannot pass by never asserting (the first-revision bug). Enough to lock the JDK/Xerces case. It does not lock the fail-open path. On a parser that rejects both features, |
||
|
|
||
| public void testDoctypeIsRejected() throws Exception { | ||
| String gml = "<?xml version=\"1.0\"?>\n<!DOCTYPE foo>\n" | ||
| + "<gml:Point><gml:coordinates>5,10</gml:coordinates></gml:Point>"; | ||
| try { | ||
| new GMLReader().read(gml, null); | ||
| fail("expected a DOCTYPE to be rejected"); | ||
| } | ||
| catch (Exception e) { | ||
| } | ||
| } | ||
|
Comment on lines
+155
to
+163
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fine for this JUnit 3 file. The empty |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two-feature set is the right pair on JDK Xerces: without a DOCTYPE there is no subset and no entity to resolve. The three extra Apache/SAX entity features were correctly dropped.
read(String, …)already delegates toread(Reader, …), so one factory site covers both entry points. DirectGMLHandleruse is caller-owned and out of scope; mention that in the class javadoc if you want, not as a blocker.Jody’s mechanical notes (short comment, tests in
GMLReaderTest, try/catch) are done. The remaining product question is fail-closed vs warn-and-continue — please fail closed, as above.Also: PR body still talks about
GMLReaderXXETestand the three extra features; update it. Commits 2 and 3 have noSigned-off-by(the first commit does); Eclipse DCO wants every commit.