Skip to content

Commit 18dae28

Browse files
authored
Merge pull request #22269 from ppkarwasz/feat/xml-commons
Java: model org.apache.commons.xml XmlFactories as safe XXE sources
2 parents 42e8a32 + 619da40 commit 18dae28

11 files changed

Lines changed: 299 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: feature
3+
---
4+
* Factories returned by the Apache Commons Secure XML (`org.apache.commons.xml.secure`) hardening library's `SecureDocumentBuilderFactory`, `SecureSAXParserFactory`, `SecureXMLInputFactory`, `SecureTransformerFactory` and `SecureSchemaFactory` classes are now recognized as safely configured by the XXE query.
5+
* A new extensible class `SafeXmlFactorySource` was added to `semmle.code.java.security.XmlParsers` for modeling sources of pre-hardened JAXP factories.

java/ql/lib/semmle/code/java/frameworks/apache/CommonsXml.qll

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,33 @@ private module SafeDigesterFlowConfig implements DataFlow::ConfigSig {
9090
}
9191

9292
private module SafeDigesterFlow = DataFlow::Global<SafeDigesterFlowConfig>;
93+
94+
/**
95+
* A call to one of the static factory methods of the `org.apache.commons.xml.secure`
96+
* `SecureXxxFactory` classes of the Apache Commons Secure XML library.
97+
*
98+
* These methods mirror the JAXP factory entry points (`newInstance`, `newDefaultInstance`,
99+
* `newNSInstance`, `newFactory`, ...) and every one of them returns a fresh JAXP factory
100+
* that has already been hardened against XML external entity (XXE) attacks, so any parser
101+
* created from it is treated as safe.
102+
*
103+
* `SecureXPathFactory` is matched for completeness, but the XXE model has no `XPathFactory`
104+
* safety chain (the XXE sink for XPath is the document being evaluated, not the factory),
105+
* so it currently has no effect on XXE results.
106+
*/
107+
private class CommonsSecureXmlFactory extends SafeXmlFactorySource, MethodCall {
108+
CommonsSecureXmlFactory() {
109+
this.getMethod()
110+
.getDeclaringType()
111+
.hasQualifiedName("org.apache.commons.xml.secure",
112+
[
113+
"SecureDocumentBuilderFactory", "SecureSAXParserFactory", "SecureXMLInputFactory",
114+
"SecureTransformerFactory", "SecureSchemaFactory", "SecureXPathFactory"
115+
]) and
116+
this.getMethod()
117+
.hasName([
118+
"newDefaultInstance", "newDefaultNSInstance", "newInstance", "newNSInstance",
119+
"newDefaultFactory", "newFactory"
120+
])
121+
}
122+
}

java/ql/lib/semmle/code/java/security/XmlParsers.qll

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ abstract class ParserConfig extends MethodCall {
5656
}
5757
}
5858

59+
/**
60+
* An expression that evaluates to a JAXP parser factory (such as a
61+
* `DocumentBuilderFactory` or `SAXParserFactory`) that has already been hardened
62+
* against XML external entity (XXE) attacks, for example by a helper library.
63+
*
64+
* Extend this class to model additional sources of pre-hardened JAXP factories.
65+
*/
66+
abstract class SafeXmlFactorySource extends Expr { }
67+
5968
/*
6069
* https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#jaxp-documentbuilderfactory-saxparserfactory-and-dom4j
6170
*/
@@ -156,6 +165,8 @@ private class DocumentBuilderConstruction extends MethodCall {
156165

157166
private predicate safeDocumentBuilderFactoryNode(DataFlow::Node src) {
158167
src.asExpr() instanceof SafeDocumentBuilderFactory
168+
or
169+
src.asExpr().(SafeXmlFactorySource).getType() instanceof DocumentBuilderFactory
159170
}
160171

161172
private module SafeDocumentBuilderFactoryToDocumentBuilderConstructionFlow =
@@ -219,6 +230,8 @@ class XmlInputFactoryStreamReader extends XmlParserCall {
219230

220231
private predicate safeXmlInputFactoryNode(DataFlow::Node src) {
221232
src.asExpr() instanceof SafeXmlInputFactory
233+
or
234+
src.asExpr().(SafeXmlFactorySource).getType() instanceof XmlInputFactory
222235
}
223236

224237
private module SafeXmlInputFactoryToXmlInputFactoryReaderFlow =
@@ -456,6 +469,8 @@ class SafeSaxParserFactory extends VarAccess {
456469

457470
private predicate safeSaxParserFactoryNode(DataFlow::Node src) {
458471
src.asExpr() instanceof SafeSaxParserFactory
472+
or
473+
src.asExpr().(SafeXmlFactorySource).getType() instanceof SaxParserFactory
459474
}
460475

461476
private module SafeSaxParserFactoryToNewSaxParserFlow =
@@ -831,6 +846,8 @@ class TransformerFactoryConfig extends TransformerConfig {
831846

832847
private predicate safeTransformerFactoryNode(DataFlow::Node src) {
833848
src.asExpr() instanceof SafeTransformerFactory
849+
or
850+
src.asExpr().(SafeXmlFactorySource).getType() instanceof TransformerFactory
834851
}
835852

836853
private module SafeTransformerFactoryFlow = DataFlow::SimpleGlobal<safeTransformerFactoryNode/1>;
@@ -920,6 +937,8 @@ class SchemaFactoryNewSchema extends XmlParserCall {
920937

921938
private predicate safeSchemaFactoryNode(DataFlow::Node src) {
922939
src.asExpr() instanceof SafeSchemaFactory
940+
or
941+
src.asExpr().(SafeXmlFactorySource).getType() instanceof SchemaFactory
923942
}
924943

925944
private module SafeSchemaFactoryToSchemaFactoryNewSchemaFlow =
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import java.net.Socket;
2+
3+
import javax.xml.XMLConstants;
4+
import javax.xml.parsers.DocumentBuilder;
5+
import javax.xml.parsers.DocumentBuilderFactory;
6+
import javax.xml.parsers.SAXParser;
7+
import javax.xml.parsers.SAXParserFactory;
8+
import javax.xml.stream.XMLInputFactory;
9+
import javax.xml.transform.Transformer;
10+
import javax.xml.transform.TransformerFactory;
11+
import javax.xml.transform.stream.StreamSource;
12+
import javax.xml.validation.Schema;
13+
import javax.xml.validation.SchemaFactory;
14+
15+
import org.xml.sax.XMLReader;
16+
import org.xml.sax.helpers.DefaultHandler;
17+
18+
import org.apache.commons.xml.secure.SecureDocumentBuilderFactory;
19+
import org.apache.commons.xml.secure.SecureSAXParserFactory;
20+
import org.apache.commons.xml.secure.SecureSchemaFactory;
21+
import org.apache.commons.xml.secure.SecureTransformerFactory;
22+
import org.apache.commons.xml.secure.SecureXMLInputFactory;
23+
24+
// Every factory returned by the `org.apache.commons.xml.secure.SecureXxxFactory` classes is
25+
// already hardened against XXE, so the parsers created from them must not be reported.
26+
public class SecureXmlFactoriesTests {
27+
28+
public void hardenedDocumentBuilder(Socket sock) throws Exception {
29+
DocumentBuilderFactory factory = SecureDocumentBuilderFactory.newInstance();
30+
DocumentBuilder builder = factory.newDocumentBuilder();
31+
builder.parse(sock.getInputStream()); // safe
32+
}
33+
34+
public void hardenedDocumentBuilderChained(Socket sock) throws Exception {
35+
SecureDocumentBuilderFactory.newDefaultNSInstance().newDocumentBuilder().parse(sock.getInputStream()); // safe
36+
}
37+
38+
public void hardenedSaxParser(Socket sock) throws Exception {
39+
SAXParserFactory factory = SecureSAXParserFactory.newInstance();
40+
SAXParser parser = factory.newSAXParser();
41+
parser.parse(sock.getInputStream(), new DefaultHandler()); // safe
42+
}
43+
44+
public void hardenedSaxParserXmlReader(Socket sock) throws Exception {
45+
SAXParser parser = SecureSAXParserFactory.newNSInstance().newSAXParser();
46+
XMLReader reader = parser.getXMLReader();
47+
reader.parse(new org.xml.sax.InputSource(sock.getInputStream())); // safe
48+
}
49+
50+
public void hardenedXmlInputFactory(Socket sock) throws Exception {
51+
XMLInputFactory factory = SecureXMLInputFactory.newFactory();
52+
factory.createXMLStreamReader(sock.getInputStream()); // safe
53+
factory.createXMLEventReader(sock.getInputStream()); // safe
54+
}
55+
56+
public void hardenedXmlInputFactoryDefault(Socket sock) throws Exception {
57+
XMLInputFactory factory = SecureXMLInputFactory.newDefaultFactory();
58+
factory.createXMLStreamReader(sock.getInputStream()); // safe
59+
}
60+
61+
public void hardenedTransformer(Socket sock) throws Exception {
62+
TransformerFactory tf = SecureTransformerFactory.newInstance();
63+
Transformer transformer = tf.newTransformer();
64+
transformer.transform(new StreamSource(sock.getInputStream()), null); // safe
65+
tf.newTransformer(new StreamSource(sock.getInputStream())); // safe
66+
}
67+
68+
public void hardenedTransformerDefault(Socket sock) throws Exception {
69+
TransformerFactory tf = SecureTransformerFactory.newDefaultInstance();
70+
tf.newTransformer(new StreamSource(sock.getInputStream())); // safe
71+
}
72+
73+
public void hardenedSchema(Socket sock) throws Exception {
74+
SchemaFactory factory = SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
75+
Schema schema = factory.newSchema(new StreamSource(sock.getInputStream())); // safe
76+
}
77+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/jdom-1.1.3:${testdir}/../../../stubs/dom4j-2.1.1:${testdir}/../../../stubs/simple-xml-2.7.1:${testdir}/../../../stubs/jaxb-api-2.3.1:${testdir}/../../../stubs/jaxen-1.2.0:${testdir}/../../../stubs/apache-commons-digester3-3.2:${testdir}/../../../stubs/servlet-api-2.4/:${testdir}/../../../stubs/rundeck-api-java-client-13.2:${testdir}/../../../stubs/springframework-5.8.x/:${testdir}/../../../stubs/mdht-1.2.0/:${testdir}/../../../stubs/woodstox-core-6.4.0
1+
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/jdom-1.1.3:${testdir}/../../../stubs/dom4j-2.1.1:${testdir}/../../../stubs/simple-xml-2.7.1:${testdir}/../../../stubs/jaxb-api-2.3.1:${testdir}/../../../stubs/jaxen-1.2.0:${testdir}/../../../stubs/apache-commons-digester3-3.2:${testdir}/../../../stubs/servlet-api-2.4/:${testdir}/../../../stubs/rundeck-api-java-client-13.2:${testdir}/../../../stubs/springframework-5.8.x/:${testdir}/../../../stubs/mdht-1.2.0/:${testdir}/../../../stubs/woodstox-core-6.4.0:${testdir}/../../../stubs/apache-commons-secure-xml-1.0.0

java/ql/test/stubs/apache-commons-secure-xml-1.0.0/org/apache/commons/xml/secure/SecureDocumentBuilderFactory.java

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

java/ql/test/stubs/apache-commons-secure-xml-1.0.0/org/apache/commons/xml/secure/SecureSAXParserFactory.java

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

java/ql/test/stubs/apache-commons-secure-xml-1.0.0/org/apache/commons/xml/secure/SecureSchemaFactory.java

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

java/ql/test/stubs/apache-commons-secure-xml-1.0.0/org/apache/commons/xml/secure/SecureTransformerFactory.java

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

java/ql/test/stubs/apache-commons-secure-xml-1.0.0/org/apache/commons/xml/secure/SecureXMLInputFactory.java

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)