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) {
+ }
+ }
}