Skip to content

Commit c25dd4b

Browse files
authored
Merge pull request #3363 from ggolawski/xslt-injection
CodeQL query to detect XSLT injections
2 parents 1dae99e + 0f555d4 commit c25dd4b

25 files changed

+677
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import javax.xml.XMLConstants;
2+
import javax.xml.transform.TransformerFactory;
3+
import javax.xml.transform.stream.StreamResult;
4+
import javax.xml.transform.stream.StreamSource;
5+
6+
public void transform(Socket socket, String inputXml) throws Exception {
7+
StreamSource xslt = new StreamSource(socket.getInputStream());
8+
StreamSource xml = new StreamSource(new StringReader(inputXml));
9+
StringWriter result = new StringWriter();
10+
TransformerFactory factory = TransformerFactory.newInstance();
11+
12+
// BAD: User provided XSLT stylesheet is processed
13+
factory.newTransformer(xslt).transform(xml, new StreamResult(result));
14+
15+
// GOOD: The secure processing mode is enabled
16+
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
17+
factory.newTransformer(xslt).transform(xml, new StreamResult(result));
18+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>XSLT (Extensible Stylesheet Language Transformations) is a language for transforming XML
7+
documents into other XML documents or other formats. Processing of unvalidated XSLT stylesheet can
8+
let attacker to read arbitrary files from the filesystem or to execute arbitrary code.</p>
9+
</overview>
10+
11+
<recommendation>
12+
<p>The general recommendation is to not process untrusted XSLT stylesheets. If user provided
13+
stylesheets must be processed, enable the secure processing mode.</p>
14+
</recommendation>
15+
16+
<example>
17+
<p>In the following examples, the code accepts an XSLT stylesheet from the user and processes it.
18+
</p>
19+
20+
<p>In the first example, the user provided XSLT stylesheet is parsed and processed.</p>
21+
22+
<p>In the second example, secure processing mode is enabled.</p>
23+
24+
<sample src="XsltInjection.java" />
25+
</example>
26+
27+
<references>
28+
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/XSLT">XSLT</a>.</li>
29+
<li>Java Tutorial: <a href="https://docs.oracle.com/javase/tutorial/jaxp/xslt/transformingXML.html">Transforming XML Data with XSLT</a>.</li>
30+
<li><a href="https://blog.hunniccyber.com/ektron-cms-remote-code-execution-xslt-transform-injection-java/">XSLT Injection Basics</a>.</li>
31+
</references>
32+
</qhelp>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @name XSLT transformation with user-controlled stylesheet
3+
* @description Doing an XSLT transformation with user-controlled stylesheet can lead to
4+
* information disclosure or execution of arbitrary code.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id java/xslt-injection
9+
* @tags security
10+
* external/cwe/cwe-074
11+
*/
12+
13+
import java
14+
import semmle.code.java.dataflow.FlowSources
15+
import XsltInjectionLib
16+
import DataFlow::PathGraph
17+
18+
from DataFlow::PathNode source, DataFlow::PathNode sink, XsltInjectionFlowConfig conf
19+
where conf.hasFlowPath(source, sink)
20+
select sink.getNode(), source, sink, "XSLT transformation might include stylesheet from $@.",
21+
source.getNode(), "this user input"
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
import java
2+
import semmle.code.java.dataflow.FlowSources
3+
import semmle.code.java.security.XmlParsers
4+
import DataFlow
5+
6+
/**
7+
* A taint-tracking configuration for unvalidated user input that is used in XSLT transformation.
8+
*/
9+
class XsltInjectionFlowConfig extends TaintTracking::Configuration {
10+
XsltInjectionFlowConfig() { this = "XsltInjectionFlowConfig" }
11+
12+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
13+
14+
override predicate isSink(DataFlow::Node sink) { sink instanceof XsltInjectionSink }
15+
16+
override predicate isSanitizer(DataFlow::Node node) {
17+
node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType
18+
}
19+
20+
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
21+
xmlStreamReaderStep(node1, node2) or
22+
xmlEventReaderStep(node1, node2) or
23+
staxSourceStep(node1, node2) or
24+
documentBuilderStep(node1, node2) or
25+
domSourceStep(node1, node2) or
26+
newTransformerOrTemplatesStep(node1, node2) or
27+
newTransformerFromTemplatesStep(node1, node2) or
28+
xsltCompilerStep(node1, node2) or
29+
xsltExecutableStep(node1, node2) or
30+
xsltPackageStep(node1, node2)
31+
}
32+
}
33+
34+
/** The class `javax.xml.transform.stax.StAXSource`. */
35+
class TypeStAXSource extends Class {
36+
TypeStAXSource() { this.hasQualifiedName("javax.xml.transform.stax", "StAXSource") }
37+
}
38+
39+
/** The class `javax.xml.transform.dom.DOMSource`. */
40+
class TypeDOMSource extends Class {
41+
TypeDOMSource() { this.hasQualifiedName("javax.xml.transform.dom", "DOMSource") }
42+
}
43+
44+
/** The interface `javax.xml.transform.Templates`. */
45+
class TypeTemplates extends Interface {
46+
TypeTemplates() { this.hasQualifiedName("javax.xml.transform", "Templates") }
47+
}
48+
49+
/** The method `net.sf.saxon.s9api.XsltTransformer.transform`. */
50+
class XsltTransformerTransformMethod extends Method {
51+
XsltTransformerTransformMethod() {
52+
this.getDeclaringType().hasQualifiedName("net.sf.saxon.s9api", "XsltTransformer") and
53+
this.hasName("transform")
54+
}
55+
}
56+
57+
/** The method `net.sf.saxon.s9api.Xslt30Transformer.transform`. */
58+
class Xslt30TransformerTransformMethod extends Method {
59+
Xslt30TransformerTransformMethod() {
60+
this.getDeclaringType().hasQualifiedName("net.sf.saxon.s9api", "Xslt30Transformer") and
61+
this.hasName("transform")
62+
}
63+
}
64+
65+
/** The method `net.sf.saxon.s9api.Xslt30Transformer.applyTemplates`. */
66+
class Xslt30TransformerApplyTemplatesMethod extends Method {
67+
Xslt30TransformerApplyTemplatesMethod() {
68+
this.getDeclaringType().hasQualifiedName("net.sf.saxon.s9api", "Xslt30Transformer") and
69+
this.hasName("applyTemplates")
70+
}
71+
}
72+
73+
/** The method `net.sf.saxon.s9api.Xslt30Transformer.callFunction`. */
74+
class Xslt30TransformerCallFunctionMethod extends Method {
75+
Xslt30TransformerCallFunctionMethod() {
76+
this.getDeclaringType().hasQualifiedName("net.sf.saxon.s9api", "Xslt30Transformer") and
77+
this.hasName("callFunction")
78+
}
79+
}
80+
81+
/** The method `net.sf.saxon.s9api.Xslt30Transformer.callTemplate`. */
82+
class Xslt30TransformerCallTemplateMethod extends Method {
83+
Xslt30TransformerCallTemplateMethod() {
84+
this.getDeclaringType().hasQualifiedName("net.sf.saxon.s9api", "Xslt30Transformer") and
85+
this.hasName("callTemplate")
86+
}
87+
}
88+
89+
/** The class `net.sf.saxon.s9api.XsltCompiler`. */
90+
class TypeXsltCompiler extends Class {
91+
TypeXsltCompiler() { this.hasQualifiedName("net.sf.saxon.s9api", "XsltCompiler") }
92+
}
93+
94+
/** The class `net.sf.saxon.s9api.XsltExecutable`. */
95+
class TypeXsltExecutable extends Class {
96+
TypeXsltExecutable() { this.hasQualifiedName("net.sf.saxon.s9api", "XsltExecutable") }
97+
}
98+
99+
/** The class `net.sf.saxon.s9api.XsltPackage`. */
100+
class TypeXsltPackage extends Class {
101+
TypeXsltPackage() { this.hasQualifiedName("net.sf.saxon.s9api", "XsltPackage") }
102+
}
103+
104+
/** A data flow sink for unvalidated user input that is used in XSLT transformation. */
105+
class XsltInjectionSink extends DataFlow::ExprNode {
106+
XsltInjectionSink() {
107+
exists(MethodAccess ma, Method m | m = ma.getMethod() and ma.getQualifier() = this.getExpr() |
108+
ma instanceof TransformerTransform or
109+
m instanceof XsltTransformerTransformMethod or
110+
m instanceof Xslt30TransformerTransformMethod or
111+
m instanceof Xslt30TransformerApplyTemplatesMethod or
112+
m instanceof Xslt30TransformerCallFunctionMethod or
113+
m instanceof Xslt30TransformerCallTemplateMethod
114+
)
115+
}
116+
}
117+
118+
/**
119+
* Holds if `n1` to `n2` is a dataflow step that converts between `InputStream` or `Reader` and
120+
* `XMLStreamReader`, i.e. `XMLInputFactory.createXMLStreamReader(tainted)`.
121+
*/
122+
predicate xmlStreamReaderStep(ExprNode n1, ExprNode n2) {
123+
exists(XmlInputFactoryStreamReader xmlStreamReader |
124+
n1.asExpr() = xmlStreamReader.getSink() and
125+
n2.asExpr() = xmlStreamReader
126+
)
127+
}
128+
129+
/**
130+
* Holds if `n1` to `n2` is a dataflow step that converts between `InputStream` or `Reader` and
131+
* `XMLEventReader`, i.e. `XMLInputFactory.createXMLEventReader(tainted)`.
132+
*/
133+
predicate xmlEventReaderStep(ExprNode n1, ExprNode n2) {
134+
exists(XmlInputFactoryEventReader xmlEventReader |
135+
n1.asExpr() = xmlEventReader.getSink() and
136+
n2.asExpr() = xmlEventReader
137+
)
138+
}
139+
140+
/**
141+
* Holds if `n1` to `n2` is a dataflow step that converts between `XMLStreamReader` or
142+
* `XMLEventReader` and `StAXSource`, i.e. `new StAXSource(tainted)`.
143+
*/
144+
predicate staxSourceStep(ExprNode n1, ExprNode n2) {
145+
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeStAXSource |
146+
n1.asExpr() = cc.getAnArgument() and
147+
n2.asExpr() = cc
148+
)
149+
}
150+
151+
/**
152+
* Holds if `n1` to `n2` is a dataflow step that converts between `InputStream` and `Document`,
153+
* i.e. `DocumentBuilder.parse(tainted)`.
154+
*/
155+
predicate documentBuilderStep(ExprNode n1, ExprNode n2) {
156+
exists(DocumentBuilderParse documentBuilder |
157+
n1.asExpr() = documentBuilder.getSink() and
158+
n2.asExpr() = documentBuilder
159+
)
160+
}
161+
162+
/**
163+
* Holds if `n1` to `n2` is a dataflow step that converts between `Document` and `DOMSource`, i.e.
164+
* `new DOMSource(tainted)`.
165+
*/
166+
predicate domSourceStep(ExprNode n1, ExprNode n2) {
167+
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeDOMSource |
168+
n1.asExpr() = cc.getAnArgument() and
169+
n2.asExpr() = cc
170+
)
171+
}
172+
173+
/**
174+
* A data flow configuration for secure processing feature that is enabled on `TransformerFactory`.
175+
*/
176+
private class TransformerFactoryWithSecureProcessingFeatureFlowConfig extends DataFlow2::Configuration {
177+
TransformerFactoryWithSecureProcessingFeatureFlowConfig() {
178+
this = "TransformerFactoryWithSecureProcessingFeatureFlowConfig"
179+
}
180+
181+
override predicate isSource(DataFlow::Node src) {
182+
exists(Variable v | v = src.asExpr().(VarAccess).getVariable() |
183+
exists(TransformerFactoryFeatureConfig config | config.getQualifier() = v.getAnAccess() |
184+
config.enables(configSecureProcessing())
185+
)
186+
)
187+
}
188+
189+
override predicate isSink(DataFlow::Node sink) {
190+
exists(MethodAccess ma |
191+
sink.asExpr() = ma.getQualifier() and
192+
ma.getMethod().getDeclaringType() instanceof TransformerFactory
193+
)
194+
}
195+
196+
override int fieldFlowBranchLimit() { result = 0 }
197+
}
198+
199+
/** A `ParserConfig` specific to `TransformerFactory`. */
200+
private class TransformerFactoryFeatureConfig extends ParserConfig {
201+
TransformerFactoryFeatureConfig() {
202+
exists(Method m |
203+
m = this.getMethod() and
204+
m.getDeclaringType() instanceof TransformerFactory and
205+
m.hasName("setFeature")
206+
)
207+
}
208+
}
209+
210+
/**
211+
* Holds if `n1` to `n2` is a dataflow step that converts between `Source` and `Transformer` or
212+
* `Templates`, i.e. `TransformerFactory.newTransformer(tainted)` or
213+
* `TransformerFactory.newTemplates(tainted)`.
214+
*/
215+
predicate newTransformerOrTemplatesStep(ExprNode n1, ExprNode n2) {
216+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
217+
n1.asExpr() = ma.getAnArgument() and
218+
n2.asExpr() = ma and
219+
(
220+
m.getDeclaringType() instanceof TransformerFactory and m.hasName("newTransformer")
221+
or
222+
m.getDeclaringType() instanceof TransformerFactory and m.hasName("newTemplates")
223+
) and
224+
not exists(TransformerFactoryWithSecureProcessingFeatureFlowConfig conf |
225+
conf.hasFlowToExpr(ma.getQualifier())
226+
)
227+
)
228+
}
229+
230+
/**
231+
* Holds if `n1` to `n2` is a dataflow step that converts between `Templates` and `Transformer`,
232+
* i.e. `tainted.newTransformer()`.
233+
*/
234+
predicate newTransformerFromTemplatesStep(ExprNode n1, ExprNode n2) {
235+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
236+
n1.asExpr() = ma.getQualifier() and
237+
n2.asExpr() = ma and
238+
m.getDeclaringType() instanceof TypeTemplates and
239+
m.hasName("newTransformer")
240+
)
241+
}
242+
243+
/**
244+
* Holds if `n1` to `n2` is a dataflow step that converts between `Source` or `URI` and
245+
* `XsltExecutable` or `XsltPackage`, i.e. `XsltCompiler.compile(tainted)` or
246+
* `XsltCompiler.loadExecutablePackage(tainted)` or `XsltCompiler.compilePackage(tainted)` or
247+
* `XsltCompiler.loadLibraryPackage(tainted)`.
248+
*/
249+
predicate xsltCompilerStep(ExprNode n1, ExprNode n2) {
250+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
251+
n1.asExpr() = ma.getArgument(0) and
252+
n2.asExpr() = ma and
253+
m.getDeclaringType() instanceof TypeXsltCompiler and
254+
(
255+
m.hasName("compile") or
256+
m.hasName("loadExecutablePackage") or
257+
m.hasName("compilePackage") or
258+
m.hasName("loadLibraryPackage")
259+
)
260+
)
261+
}
262+
263+
/**
264+
* Holds if `n1` to `n2` is a dataflow step that converts between `XsltExecutable` and
265+
* `XsltTransformer` or `Xslt30Transformer`, i.e. `XsltExecutable.load()` or
266+
* `XsltExecutable.load30()`.
267+
*/
268+
predicate xsltExecutableStep(ExprNode n1, ExprNode n2) {
269+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
270+
n1.asExpr() = ma.getQualifier() and
271+
n2.asExpr() = ma and
272+
m.getDeclaringType() instanceof TypeXsltExecutable and
273+
(m.hasName("load") or m.hasName("load30"))
274+
)
275+
}
276+
277+
/**
278+
* Holds if `n1` to `n2` is a dataflow step that converts between `XsltPackage` and
279+
* `XsltExecutable`, i.e. `XsltPackage.link()`.
280+
*/
281+
predicate xsltPackageStep(ExprNode n1, ExprNode n2) {
282+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
283+
n1.asExpr() = ma.getQualifier() and
284+
n2.asExpr() = ma and
285+
m.getDeclaringType() instanceof TypeXsltPackage and
286+
m.hasName("link")
287+
)
288+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,3 +1172,15 @@ class SimpleXMLFormatterCall extends XmlParserCall {
11721172

11731173
override predicate isSafe() { none() }
11741174
}
1175+
1176+
/** A configuration for secure processing. */
1177+
Expr configSecureProcessing() {
1178+
result.(ConstantStringExpr).getStringValue() =
1179+
"http://javax.xml.XMLConstants/feature/secure-processing"
1180+
or
1181+
exists(Field f |
1182+
result = f.getAnAccess() and
1183+
f.hasName("FEATURE_SECURE_PROCESSING") and
1184+
f.getDeclaringType() instanceof XmlConstants
1185+
)
1186+
}

0 commit comments

Comments
 (0)