diff --git a/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java b/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java index 7bef25880f..6f43e1198b 100644 --- a/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java +++ b/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java @@ -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); + } SAXParser parser = fact.newSAXParser(); diff --git a/modules/core/src/test/java/org/locationtech/jts/io/gml2/GMLReaderTest.java b/modules/core/src/test/java/org/locationtech/jts/io/gml2/GMLReaderTest.java index debbb660da..96ddaddf4c 100644 --- a/modules/core/src/test/java/org/locationtech/jts/io/gml2/GMLReaderTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/io/gml2/GMLReaderTest.java @@ -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 = "\n" + + " ]>\n" + + "&xxe;"; + 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(); + } + } + + public void testDoctypeIsRejected() throws Exception { + String gml = "\n\n" + + "5,10"; + try { + new GMLReader().read(gml, null); + fail("expected a DOCTYPE to be rejected"); + } + catch (Exception e) { + } + } }