Skip to content

Commit f2ebb45

Browse files
replace uses of DocumentBuilderFactory
1 parent b3945db commit f2ebb45

File tree

6 files changed

+25
-79
lines changed

6 files changed

+25
-79
lines changed

libs/core/src/main/java/org/elasticsearch/core/XmlUtils.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import javax.xml.parsers.DocumentBuilder;
2121
import javax.xml.parsers.DocumentBuilderFactory;
2222
import javax.xml.parsers.ParserConfigurationException;
23+
import javax.xml.parsers.SAXParserFactory;
2324
import javax.xml.transform.Transformer;
2425
import javax.xml.transform.TransformerConfigurationException;
2526
import javax.xml.transform.TransformerException;
@@ -121,6 +122,20 @@ public static Validator getHardenedValidator(Schema schema) throws SAXNotSupport
121122
return validator;
122123
}
123124

125+
@SuppressForbidden(reason = "This is the only allowed way to construct a SAXParser")
126+
public static SAXParserFactory getHardenedSaxParserFactory() throws SAXNotSupportedException, SAXNotRecognizedException,
127+
ParserConfigurationException {
128+
var saxParserFactory = SAXParserFactory.newInstance();
129+
130+
saxParserFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
131+
saxParserFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
132+
saxParserFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
133+
saxParserFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
134+
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
135+
136+
return saxParserFactory;
137+
}
138+
124139
private static class ErrorHandler implements org.xml.sax.ErrorHandler {
125140
/**
126141
* Enabling schema validation with `setValidating(true)` in our

test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.core.Nullable;
2323
import org.elasticsearch.core.SuppressForbidden;
2424
import org.elasticsearch.core.Tuple;
25+
import org.elasticsearch.core.XmlUtils;
2526
import org.elasticsearch.logging.LogManager;
2627
import org.elasticsearch.logging.Logger;
2728
import org.elasticsearch.rest.RestStatus;
@@ -471,7 +472,7 @@ private static Tuple<String, BytesReference> parseRequestBody(final HttpExchange
471472

472473
static List<String> extractPartEtags(BytesReference completeMultipartUploadBody) {
473474
try {
474-
final var document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(completeMultipartUploadBody.streamInput());
475+
final var document = XmlUtils.getHardenedBuilderFactory().newDocumentBuilder().parse(completeMultipartUploadBody.streamInput());
475476
final var parts = document.getElementsByTagName("Part");
476477
final var result = new ArrayList<String>(parts.getLength());
477478
for (int partIndex = 0; partIndex < parts.getLength(); partIndex++) {

x-pack/plugin/identity-provider/src/main/java/org/elasticsearch/xpack/idp/saml/authn/SamlAuthnRequestValidator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.elasticsearch.common.Strings;
1414
import org.elasticsearch.core.Nullable;
1515
import org.elasticsearch.core.Streams;
16+
import org.elasticsearch.core.XmlUtils;
1617
import org.elasticsearch.rest.RestStatus;
1718
import org.elasticsearch.rest.RestUtils;
1819
import org.elasticsearch.xpack.idp.action.SamlValidateAuthnRequestResponse;

x-pack/plugin/identity-provider/src/main/java/org/elasticsearch/xpack/idp/saml/support/SamlFactory.java

Lines changed: 3 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.common.Strings;
1313
import org.elasticsearch.common.hash.MessageDigests;
1414
import org.elasticsearch.core.SuppressForbidden;
15+
import org.elasticsearch.core.XmlUtils;
1516
import org.opensaml.core.xml.XMLObject;
1617
import org.opensaml.core.xml.XMLObjectBuilderFactory;
1718
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
@@ -24,8 +25,6 @@
2425
import org.opensaml.security.credential.Credential;
2526
import org.opensaml.security.x509.X509Credential;
2627
import org.w3c.dom.Element;
27-
import org.xml.sax.SAXException;
28-
import org.xml.sax.SAXParseException;
2928

3029
import java.io.StringWriter;
3130
import java.io.Writer;
@@ -38,16 +37,12 @@
3837
import java.util.Objects;
3938
import java.util.stream.Collectors;
4039

41-
import javax.xml.XMLConstants;
4240
import javax.xml.namespace.QName;
4341
import javax.xml.parsers.DocumentBuilder;
44-
import javax.xml.parsers.DocumentBuilderFactory;
4542
import javax.xml.parsers.ParserConfigurationException;
4643
import javax.xml.transform.OutputKeys;
4744
import javax.xml.transform.Transformer;
48-
import javax.xml.transform.TransformerConfigurationException;
4945
import javax.xml.transform.TransformerException;
50-
import javax.xml.transform.TransformerFactory;
5146
import javax.xml.transform.dom.DOMSource;
5247
import javax.xml.transform.stream.StreamResult;
5348

@@ -143,7 +138,7 @@ public static <T extends XMLObject> T buildXmlObject(Element element, Class<T> t
143138
}
144139

145140
void print(Element element, Writer writer, boolean pretty) throws TransformerException {
146-
final Transformer serializer = getHardenedXMLTransformer();
141+
final Transformer serializer = XmlUtils.getHardenedXMLTransformer();
147142
if (pretty) {
148143
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
149144
}
@@ -217,57 +212,14 @@ public static Element toDomElement(XMLObject object) {
217212
}
218213
}
219214

220-
@SuppressForbidden(reason = "This is the only allowed way to construct a Transformer")
221-
public static Transformer getHardenedXMLTransformer() throws TransformerConfigurationException {
222-
final TransformerFactory tfactory = TransformerFactory.newInstance();
223-
tfactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
224-
tfactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
225-
tfactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
226-
tfactory.setAttribute("indent-number", 2);
227-
Transformer transformer = tfactory.newTransformer();
228-
transformer.setErrorListener(new SamlFactory.TransformerErrorListener());
229-
return transformer;
230-
}
231-
232215
/**
233216
* Constructs a DocumentBuilder with all the necessary features for it to be secure
234217
*
235218
* @throws ParserConfigurationException if one of the features can't be set on the DocumentBuilderFactory
236219
*/
237220
@SuppressForbidden(reason = "This is the only allowed way to construct a DocumentBuilder")
238221
public static DocumentBuilder getHardenedBuilder(String[] schemaFiles) throws ParserConfigurationException {
239-
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
240-
dbf.setNamespaceAware(true);
241-
// Ensure that Schema Validation is enabled for the factory
242-
dbf.setValidating(true);
243-
// Disallow internal and external entity expansion
244-
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
245-
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
246-
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
247-
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
248-
dbf.setFeature("http://xml.org/sax/features/validation", true);
249-
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
250-
dbf.setIgnoringComments(true);
251-
// This is required, otherwise schema validation causes signature invalidation
252-
dbf.setFeature("http://apache.org/xml/features/validation/schema/normalized-value", false);
253-
// Make sure that URL schema namespaces are not resolved/downloaded from URLs we do not control
254-
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "file,jar");
255-
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file,jar");
256-
dbf.setFeature("http://apache.org/xml/features/honour-all-schemaLocations", true);
257-
// Ensure we do not resolve XIncludes. Defaults to false, but set it explicitly to be future-proof
258-
dbf.setXIncludeAware(false);
259-
// Ensure we do not expand entity reference nodes
260-
dbf.setExpandEntityReferences(false);
261-
// Further limit danger from denial of service attacks
262-
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
263-
dbf.setAttribute("http://apache.org/xml/features/validation/schema", true);
264-
dbf.setAttribute("http://apache.org/xml/features/validation/schema-full-checking", true);
265-
dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", XMLConstants.W3C_XML_SCHEMA_NS_URI);
266-
// We ship our own xsd files for schema validation since we do not trust anyone else.
267-
dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", resolveSchemaFilePaths(schemaFiles));
268-
DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
269-
documentBuilder.setErrorHandler(new SamlFactory.DocumentBuilderErrorHandler());
270-
return documentBuilder;
222+
return XmlUtils.getHardenedBuilder(resolveSchemaFilePaths(schemaFiles));
271223
}
272224

273225
public static String getJavaAlorithmNameFromUri(String sigAlg) {
@@ -293,31 +245,6 @@ private static String[] resolveSchemaFilePaths(String[] relativePaths) {
293245
}).filter(Objects::nonNull).toArray(String[]::new);
294246
}
295247

296-
private static class DocumentBuilderErrorHandler implements org.xml.sax.ErrorHandler {
297-
/**
298-
* Enabling schema validation with `setValidating(true)` in our
299-
* DocumentBuilderFactory requires that we provide our own
300-
* ErrorHandler implementation
301-
*
302-
* @throws SAXException If the document we attempt to parse is not valid according to the specified schema.
303-
*/
304-
@Override
305-
public void warning(SAXParseException e) throws SAXException {
306-
LOGGER.debug("XML Parser error ", e);
307-
throw e;
308-
}
309-
310-
@Override
311-
public void error(SAXParseException e) throws SAXException {
312-
warning(e);
313-
}
314-
315-
@Override
316-
public void fatalError(SAXParseException e) throws SAXException {
317-
warning(e);
318-
}
319-
}
320-
321248
private static class TransformerErrorListener implements javax.xml.transform.ErrorListener {
322249

323250
@Override

x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/test/IdpSamlTestCase.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.apache.logging.log4j.Logger;
1212
import org.elasticsearch.action.ActionListener;
1313
import org.elasticsearch.common.ssl.PemUtils;
14+
import org.elasticsearch.core.XmlUtils;
1415
import org.elasticsearch.test.ESTestCase;
1516
import org.elasticsearch.test.FileMatchers;
1617
import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
@@ -145,7 +146,7 @@ protected <T extends XMLObject> T domElementToXmlObject(Element element, Class<T
145146
}
146147

147148
protected void print(Element element, Writer writer, boolean pretty) throws TransformerException {
148-
final Transformer serializer = SamlFactory.getHardenedXMLTransformer();
149+
final Transformer serializer = XmlUtils.getHardenedXMLTransformer();
149150
if (pretty) {
150151
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
151152
}

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlResponseHandlerTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.apache.xml.security.encryption.XMLCipher;
1313
import org.elasticsearch.core.TimeValue;
1414
import org.elasticsearch.core.Tuple;
15+
import org.elasticsearch.core.XmlUtils;
1516
import org.elasticsearch.xpack.core.watcher.watch.ClockMock;
1617
import org.junit.AfterClass;
1718
import org.junit.BeforeClass;
@@ -151,7 +152,7 @@ protected String randomId() {
151152
}
152153

153154
protected Document parseDocument(String xml) throws ParserConfigurationException, SAXException, IOException {
154-
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
155+
final DocumentBuilderFactory dbf = XmlUtils.getHardenedBuilderFactory();
155156
dbf.setNamespaceAware(true);
156157
final DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
157158
return documentBuilder.parse(new InputSource(new StringReader(xml)));

0 commit comments

Comments
 (0)