Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<XmlRecord> result = client.main("application/xml", new XmlRecord("xml content"));
assertEquals("xml content", result.body().getValue());
Expand All @@ -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 = "<!DOCTYPE foo [ <!ENTITY xxe SYSTEM \"file:///etc/passwd\"> ]><foo>&xxe;</foo>";
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<XmlRecord> 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 = "<!ENTITY xxe SYSTEM \"file:///etc/passwd\"><foo>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<XmlRecord> 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);
Expand Down
Loading