diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/http/codec/JAXBDecoder.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/http/codec/JAXBDecoder.java index 47077041981e2..223a28c693fb2 100644 --- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/http/codec/JAXBDecoder.java +++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/http/codec/JAXBDecoder.java @@ -21,9 +21,12 @@ import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; -import javax.xml.transform.stream.StreamSource; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.sax.SAXSource; import org.talend.sdk.component.api.service.http.Decoder; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; import lombok.AllArgsConstructor; @@ -35,13 +38,20 @@ public class JAXBDecoder implements Decoder { @Override public Object decode(final byte[] value, final Type expectedType) { try { + // Harden against XXE by configuring XMLReader + final SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setFeature("http://xml.org/sax/features/external-general-entities", false); + spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + spf.setNamespaceAware(true); + final XMLReader xmlReader = spf.newSAXParser().getXMLReader(); final Class key = Class.class.cast(expectedType); return jaxbContexts .get(key) .createUnmarshaller() - .unmarshal(new StreamSource(new ByteArrayInputStream(value)), key) + .unmarshal(new SAXSource(xmlReader, new InputSource(new ByteArrayInputStream(value))), key) .getValue(); - } catch (final JAXBException e) { + } catch (final JAXBException | org.xml.sax.SAXException | javax.xml.parsers.ParserConfigurationException e) { throw new IllegalArgumentException(e); } diff --git a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/HttpClientFactoryImplTest.java b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/HttpClientFactoryImplTest.java index dfdbcf62d1f05..8a265f3a8b3d1 100644 --- a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/HttpClientFactoryImplTest.java +++ b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/HttpClientFactoryImplTest.java @@ -31,6 +31,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.StringReader; import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.InetSocketAddress; @@ -222,8 +223,8 @@ void requestWithXML() throws IOException { try { server.start(); - final ResponseXml client = newDefaultFactory().create(ResponseXml.class, null); - client.base("http://localhost:" + server.getAddress().getPort() + "/api"); + final ResponseXml client = newDefaultFactory().create(ResponseXml.class, + "http://localhost:" + server.getAddress().getPort() + "/api"); final Response result = client.main("application/xml", new XmlRecord("xml content")); assertEquals("xml content", result.body().getValue()); @@ -234,6 +235,63 @@ void requestWithXML() throws IOException { } } + @Test + void requestWithXMLXXE() throws IOException { + final HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); + server.createContext("/").setHandler(httpExchange -> { + final Headers headers = httpExchange.getResponseHeaders(); + headers.set("content-type", "application/xml;charset=UTF-8"); + final byte[] bytes; + String xmlContent = " ]>&xxe;"; + try (final BufferedReader in = + new BufferedReader(new StringReader(xmlContent))) { + bytes = in.lines().collect(joining("\n")).getBytes(StandardCharsets.UTF_8); + } + httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, bytes.length); + httpExchange.getResponseBody().write(bytes); + httpExchange.close(); + }); + + try { + server.start(); + final ResponseXml client = newDefaultFactory().create(ResponseXml.class, + "http://localhost:" + server.getAddress().getPort() + "/api"); + final Response result = client.main("application/xml", new XmlRecord("xml content")); + assertThrows(java.lang.IllegalArgumentException.class, result::body, + "lineNumber: 1; columnNumber: 10; DOCTYPE is disallowed when the feature \"http://apache.org/xml/features/disallow-doctype-decl\" set to true."); + } finally { + server.stop(0); + } + } + + @Test + void requestWithInvalidXML() throws IOException { + final HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); + server.createContext("/").setHandler(httpExchange -> { + final Headers headers = httpExchange.getResponseHeaders(); + headers.set("content-type", "application/xml;charset=UTF-8"); + final byte[] bytes; + String xmlContent = "invalid;"; + try (final BufferedReader in = + new BufferedReader(new StringReader(xmlContent))) { + bytes = in.lines().collect(joining("\n")).getBytes(StandardCharsets.UTF_8); + } + httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, bytes.length); + httpExchange.getResponseBody().write(bytes); + httpExchange.close(); + }); + + try { + server.start(); + final ResponseXml client = newDefaultFactory().create(ResponseXml.class, + "http://localhost:" + server.getAddress().getPort() + "/api"); + final Response result = client.main("application/xml", new XmlRecord("xml content")); + assertThrows(java.lang.IllegalArgumentException.class, result::body); + } finally { + server.stop(0); + } + } + @Test void requestWithJSON() throws IOException { final HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);