Skip to content

Commit ff21662

Browse files
committed
Refactor XsltInjection.qll
1 parent 6967b06 commit ff21662

File tree

4 files changed

+258
-255
lines changed

4 files changed

+258
-255
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
lgtm,codescanning
2-
* The query "XSLT transformation with user-controlled stylesheet" (`java/xslt-injection`) has been promoted from experimental to the main query pack. Its results will now appear by default. This query was originally [submitted as an experimental query by @ggolawski](https://github.com/github/codeql/pull/3363)
2+
* The query "XSLT transformation with user-controlled stylesheet" (`java/xslt-injection`) has been promoted from experimental to the main query pack. Its results will now appear by default. This query was originally [submitted as an experimental query by @ggolawski](https://github.com/github/codeql/pull/3363).
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/** Provides classes to reason about XSLT injection vulnerabilities. */
2+
3+
import java
4+
import semmle.code.java.dataflow.DataFlow
5+
import semmle.code.java.dataflow.ExternalFlow
6+
import semmle.code.java.security.XmlParsers
7+
8+
/**
9+
* A data flow sink for unvalidated user input that is used in XSLT transformation.
10+
* Extend this class to add your own XSLT Injection sinks.
11+
*/
12+
abstract class XsltInjectionSink extends DataFlow::Node { }
13+
14+
/** A default sink representing methods susceptible to XSLT Injection attacks. */
15+
private class DefaultXsltInjectionSink extends XsltInjectionSink {
16+
DefaultXsltInjectionSink() { sinkNode(this, "xslt") }
17+
}
18+
19+
private class DefaultXsltInjectionSinkModel extends SinkModelCsv {
20+
override predicate row(string row) {
21+
row =
22+
[
23+
"javax.xml.transform;Transformer;false;transform;;;Argument[-1];xslt",
24+
"net.sf.saxon.s9api;XsltTransformer;false;transform;;;Argument[-1];xslt",
25+
"net.sf.saxon.s9api;Xslt30Transformer;false;transform;;;Argument[-1];xslt",
26+
"net.sf.saxon.s9api;Xslt30Transformer;false;applyTemplates;;;Argument[-1];xslt",
27+
"net.sf.saxon.s9api;Xslt30Transformer;false;callFunction;;;Argument[-1];xslt",
28+
"net.sf.saxon.s9api;Xslt30Transformer;false;callTemplate;;;Argument[-1];xslt"
29+
]
30+
}
31+
}
32+
33+
/**
34+
* A unit class for adding additional taint steps.
35+
*
36+
* Extend this class to add additional taint steps that should apply to the `XsltInjectionFlowConfig`.
37+
*/
38+
class XsltInjectionAdditionalTaintStep extends Unit {
39+
/**
40+
* Holds if the step from `node1` to `node2` should be considered a taint
41+
* step for the `XsltInjectionFlowConfig` configuration.
42+
*/
43+
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
44+
}
45+
46+
/** A set of additional taint steps to consider when taint tracking XSLT related data flows. */
47+
private class DefaultXsltInjectionAdditionalTaintStep extends XsltInjectionAdditionalTaintStep {
48+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
49+
xmlStreamReaderStep(node1, node2) or
50+
xmlEventReaderStep(node1, node2) or
51+
staxSourceStep(node1, node2) or
52+
documentBuilderStep(node1, node2) or
53+
domSourceStep(node1, node2) or
54+
newTransformerOrTemplatesStep(node1, node2) or
55+
newTransformerFromTemplatesStep(node1, node2) or
56+
xsltCompilerStep(node1, node2) or
57+
xsltExecutableStep(node1, node2) or
58+
xsltPackageStep(node1, node2)
59+
}
60+
}
61+
62+
/**
63+
* Holds if `n1` to `n2` is a dataflow step that converts between `InputStream` or `Reader` and
64+
* `XMLStreamReader`, i.e. `XMLInputFactory.createXMLStreamReader(tainted)`.
65+
*/
66+
private predicate xmlStreamReaderStep(DataFlow::Node n1, DataFlow::Node n2) {
67+
exists(XmlInputFactoryStreamReader xmlStreamReader |
68+
n1.asExpr() = xmlStreamReader.getSink() and
69+
n2.asExpr() = xmlStreamReader
70+
)
71+
}
72+
73+
/**
74+
* Holds if `n1` to `n2` is a dataflow step that converts between `InputStream` or `Reader` and
75+
* `XMLEventReader`, i.e. `XMLInputFactory.createXMLEventReader(tainted)`.
76+
*/
77+
private predicate xmlEventReaderStep(DataFlow::Node n1, DataFlow::Node n2) {
78+
exists(XmlInputFactoryEventReader xmlEventReader |
79+
n1.asExpr() = xmlEventReader.getSink() and
80+
n2.asExpr() = xmlEventReader
81+
)
82+
}
83+
84+
/**
85+
* Holds if `n1` to `n2` is a dataflow step that converts between `XMLStreamReader` or
86+
* `XMLEventReader` and `StAXSource`, i.e. `new StAXSource(tainted)`.
87+
*/
88+
private predicate staxSourceStep(DataFlow::Node n1, DataFlow::Node n2) {
89+
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeStAXSource |
90+
n1.asExpr() = cc.getAnArgument() and
91+
n2.asExpr() = cc
92+
)
93+
}
94+
95+
/**
96+
* Holds if `n1` to `n2` is a dataflow step that converts between `InputStream` and `Document`,
97+
* i.e. `DocumentBuilder.parse(tainted)`.
98+
*/
99+
private predicate documentBuilderStep(DataFlow::Node n1, DataFlow::Node n2) {
100+
exists(DocumentBuilderParse documentBuilder |
101+
n1.asExpr() = documentBuilder.getSink() and
102+
n2.asExpr() = documentBuilder
103+
)
104+
}
105+
106+
/**
107+
* Holds if `n1` to `n2` is a dataflow step that converts between `Document` and `DOMSource`, i.e.
108+
* `new DOMSource(tainted)`.
109+
*/
110+
private predicate domSourceStep(DataFlow::Node n1, DataFlow::Node n2) {
111+
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeDOMSource |
112+
n1.asExpr() = cc.getAnArgument() and
113+
n2.asExpr() = cc
114+
)
115+
}
116+
117+
/**
118+
* Holds if `n1` to `n2` is a dataflow step that converts between `Source` and `Transformer` or
119+
* `Templates`, i.e. `TransformerFactory.newTransformer(tainted)` or
120+
* `TransformerFactory.newTemplates(tainted)`.
121+
*/
122+
private predicate newTransformerOrTemplatesStep(DataFlow::Node n1, DataFlow::Node n2) {
123+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
124+
n1.asExpr() = ma.getAnArgument() and
125+
n2.asExpr() = ma and
126+
m.getDeclaringType() instanceof TransformerFactory and
127+
m.hasName(["newTransformer", "newTemplates"]) and
128+
not exists(TransformerFactoryWithSecureProcessingFeatureFlowConfig conf |
129+
conf.hasFlowToExpr(ma.getQualifier())
130+
)
131+
)
132+
}
133+
134+
/**
135+
* A data flow configuration for secure processing feature that is enabled on `TransformerFactory`.
136+
*/
137+
private class TransformerFactoryWithSecureProcessingFeatureFlowConfig extends DataFlow2::Configuration {
138+
TransformerFactoryWithSecureProcessingFeatureFlowConfig() {
139+
this = "TransformerFactoryWithSecureProcessingFeatureFlowConfig"
140+
}
141+
142+
override predicate isSource(DataFlow::Node src) {
143+
exists(Variable v | v = src.asExpr().(VarAccess).getVariable() |
144+
exists(TransformerFactoryFeatureConfig config | config.getQualifier() = v.getAnAccess() |
145+
config.enables(configSecureProcessing())
146+
)
147+
)
148+
}
149+
150+
override predicate isSink(DataFlow::Node sink) {
151+
exists(MethodAccess ma |
152+
sink.asExpr() = ma.getQualifier() and
153+
ma.getMethod().getDeclaringType() instanceof TransformerFactory
154+
)
155+
}
156+
157+
override int fieldFlowBranchLimit() { result = 0 }
158+
}
159+
160+
/** A `ParserConfig` specific to `TransformerFactory`. */
161+
private class TransformerFactoryFeatureConfig extends ParserConfig {
162+
TransformerFactoryFeatureConfig() {
163+
exists(Method m |
164+
m = this.getMethod() and
165+
m.getDeclaringType() instanceof TransformerFactory and
166+
m.hasName("setFeature")
167+
)
168+
}
169+
}
170+
171+
/**
172+
* Holds if `n1` to `n2` is a dataflow step that converts between `Templates` and `Transformer`,
173+
* i.e. `tainted.newTransformer()`.
174+
*/
175+
private predicate newTransformerFromTemplatesStep(DataFlow::Node n1, DataFlow::Node n2) {
176+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
177+
n1.asExpr() = ma.getQualifier() and
178+
n2.asExpr() = ma and
179+
m.getDeclaringType() instanceof TypeTemplates and
180+
m.hasName("newTransformer")
181+
)
182+
}
183+
184+
/**
185+
* Holds if `n1` to `n2` is a dataflow step that converts between `Source` or `URI` and
186+
* `XsltExecutable` or `XsltPackage`, i.e. `XsltCompiler.compile(tainted)` or
187+
* `XsltCompiler.loadExecutablePackage(tainted)` or `XsltCompiler.compilePackage(tainted)` or
188+
* `XsltCompiler.loadLibraryPackage(tainted)`.
189+
*/
190+
private predicate xsltCompilerStep(DataFlow::Node n1, DataFlow::Node n2) {
191+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
192+
n1.asExpr() = ma.getArgument(0) and
193+
n2.asExpr() = ma and
194+
m.getDeclaringType() instanceof TypeXsltCompiler and
195+
m.hasName(["compile", "loadExecutablePackage", "compilePackage", "loadLibraryPackage"])
196+
)
197+
}
198+
199+
/**
200+
* Holds if `n1` to `n2` is a dataflow step that converts between `XsltExecutable` and
201+
* `XsltTransformer` or `Xslt30Transformer`, i.e. `XsltExecutable.load()` or
202+
* `XsltExecutable.load30()`.
203+
*/
204+
private predicate xsltExecutableStep(DataFlow::Node n1, DataFlow::Node n2) {
205+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
206+
n1.asExpr() = ma.getQualifier() and
207+
n2.asExpr() = ma and
208+
m.getDeclaringType() instanceof TypeXsltExecutable and
209+
m.hasName(["load", "load30"])
210+
)
211+
}
212+
213+
/**
214+
* Holds if `n1` to `n2` is a dataflow step that converts between `XsltPackage` and
215+
* `XsltExecutable`, i.e. `XsltPackage.link()`.
216+
*/
217+
private predicate xsltPackageStep(DataFlow::Node n1, DataFlow::Node n2) {
218+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
219+
n1.asExpr() = ma.getQualifier() and
220+
n2.asExpr() = ma and
221+
m.getDeclaringType() instanceof TypeXsltPackage and
222+
m.hasName("link")
223+
)
224+
}
225+
226+
/** The class `javax.xml.transform.stax.StAXSource`. */
227+
private class TypeStAXSource extends Class {
228+
TypeStAXSource() { this.hasQualifiedName("javax.xml.transform.stax", "StAXSource") }
229+
}
230+
231+
/** The class `javax.xml.transform.dom.DOMSource`. */
232+
private class TypeDOMSource extends Class {
233+
TypeDOMSource() { this.hasQualifiedName("javax.xml.transform.dom", "DOMSource") }
234+
}
235+
236+
/** The interface `javax.xml.transform.Templates`. */
237+
private class TypeTemplates extends Interface {
238+
TypeTemplates() { this.hasQualifiedName("javax.xml.transform", "Templates") }
239+
}
240+
241+
/** The class `net.sf.saxon.s9api.XsltCompiler`. */
242+
private class TypeXsltCompiler extends Class {
243+
TypeXsltCompiler() { this.hasQualifiedName("net.sf.saxon.s9api", "XsltCompiler") }
244+
}
245+
246+
/** The class `net.sf.saxon.s9api.XsltExecutable`. */
247+
private class TypeXsltExecutable extends Class {
248+
TypeXsltExecutable() { this.hasQualifiedName("net.sf.saxon.s9api", "XsltExecutable") }
249+
}
250+
251+
/** The class `net.sf.saxon.s9api.XsltPackage`. */
252+
private class TypeXsltPackage extends Class {
253+
TypeXsltPackage() { this.hasQualifiedName("net.sf.saxon.s9api", "XsltPackage") }
254+
}

0 commit comments

Comments
 (0)