Skip to content

Commit 793446b

Browse files
committed
Java: Diff-informed XSS.ql
This change makes the XSS query fully diff-informed, including the discovery of sinks. This involved making a helper data-flow analysis diff-informed, which required punching through some abstraction layers. An alternative would have been to use `DataFlow::SimpleGlobal` or other means to make the analysis faster, but I didn't want to risk removing good taint steps such as wrapping one `OutputStream` in another.
1 parent 964a0a5 commit 793446b

File tree

5 files changed

+102
-35
lines changed

5 files changed

+102
-35
lines changed

java/ql/lib/semmle/code/java/frameworks/JaxWS.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ class JaxRSConsumesAnnotation extends JaxRSAnnotation {
417417
}
418418

419419
/** A default sink representing methods susceptible to XSS attacks. */
420-
private class JaxRSXssSink extends XssSink {
420+
private class JaxRSXssSink extends AbstractXssSink {
421421
JaxRSXssSink() {
422422
exists(JaxRsResourceMethod resourceMethod, ReturnStmt rs |
423423
resourceMethod = any(JaxRsResourceClass resourceClass).getAResourceMethod() and

java/ql/lib/semmle/code/java/frameworks/spring/SpringHttp.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ private predicate specifiesContentType(SpringRequestMappingMethod method) {
4646
exists(method.getAProducesExpr())
4747
}
4848

49-
private class SpringXssSink extends XSS::XssSink {
49+
private class SpringXssSink extends XSS::AbstractXssSink {
5050
SpringXssSink() {
5151
exists(SpringRequestMappingMethod requestMappingMethod, ReturnStmt rs |
5252
requestMappingMethod = rs.getEnclosingCallable() and

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

Lines changed: 94 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,36 @@ private import semmle.code.java.dataflow.ExternalFlow
1313
private import semmle.code.java.dataflow.FlowSources
1414
private import semmle.code.java.dataflow.FlowSinks
1515

16-
/** A sink that represent a method that outputs data without applying contextual output encoding. */
17-
abstract class XssSink extends ApiSinkNode { }
18-
1916
/** A sanitizer that neutralizes dangerous characters that can be used to perform a XSS attack. */
2017
abstract class XssSanitizer extends DataFlow::Node { }
2118

19+
/**
20+
* A sink that represent a method that outputs data without applying contextual
21+
* output encoding. Extend this class to add more sinks that should be
22+
* considered XSS sinks by every query. To find the full set of XSS sinks, use
23+
* `XssSink` instead.
24+
*/
25+
abstract class AbstractXssSink extends DataFlow::Node { }
26+
27+
/** A default sink representing methods susceptible to XSS attacks. */
28+
private class DefaultXssSink extends AbstractXssSink {
29+
DefaultXssSink() { sinkNode(this, ["html-injection", "js-injection"]) }
30+
}
31+
32+
/**
33+
* A sink that represent a method that outputs data without applying contextual
34+
* output encoding. To add more sinks, extend `AbstractXssSink` rather than
35+
* this class. To find XSS sinks efficiently for a diff-informed query, use the
36+
* `XssDiffInformed` module instead.
37+
*/
38+
final class XssSink extends ApiSinkNode instanceof XssDiffInformed<xssNotDiffInformed/0>::XssSink {
39+
}
40+
2241
/**
2342
* A sink that represent a method that outputs data without applying contextual output encoding,
2443
* and which should truncate flow paths such that downstream sinks are not flagged as well.
2544
*/
26-
abstract class XssSinkBarrier extends XssSink { }
45+
abstract class XssSinkBarrier extends AbstractXssSink { }
2746

2847
/**
2948
* A unit class for adding additional taint steps.
@@ -39,19 +58,6 @@ class XssAdditionalTaintStep extends Unit {
3958
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
4059
}
4160

42-
/** A default sink representing methods susceptible to XSS attacks. */
43-
private class DefaultXssSink extends XssSink {
44-
DefaultXssSink() {
45-
sinkNode(this, ["html-injection", "js-injection"])
46-
or
47-
exists(MethodCall ma |
48-
ma.getMethod() instanceof WritingMethod and
49-
XssVulnerableWriterSourceToWritingMethodFlow::flowToExpr(ma.getQualifier()) and
50-
this.asExpr() = ma.getArgument(_)
51-
)
52-
}
53-
}
54-
5561
/** A default sanitizer that considers numeric and boolean typed data safe for writing to output. */
5662
private class DefaultXssSanitizer extends XssSanitizer {
5763
DefaultXssSanitizer() {
@@ -62,20 +68,6 @@ private class DefaultXssSanitizer extends XssSanitizer {
6268
}
6369
}
6470

65-
/** A configuration that tracks data from a servlet writer to an output method. */
66-
private module XssVulnerableWriterSourceToWritingMethodFlowConfig implements DataFlow::ConfigSig {
67-
predicate isSource(DataFlow::Node src) { src instanceof XssVulnerableWriterSourceNode }
68-
69-
predicate isSink(DataFlow::Node sink) {
70-
exists(MethodCall ma |
71-
sink.asExpr() = ma.getQualifier() and ma.getMethod() instanceof WritingMethod
72-
)
73-
}
74-
}
75-
76-
private module XssVulnerableWriterSourceToWritingMethodFlow =
77-
TaintTracking::Global<XssVulnerableWriterSourceToWritingMethodFlowConfig>;
78-
7971
/** A method that can be used to output data to an output stream or writer. */
8072
private class WritingMethod extends Method {
8173
WritingMethod() {
@@ -113,6 +105,77 @@ class XssVulnerableWriterSourceNode extends ApiSourceNode {
113105
XssVulnerableWriterSourceNode() { this.asExpr() instanceof XssVulnerableWriterSource }
114106
}
115107

108+
/** A nullary predicate. */
109+
signature predicate xssNullaryPredicate();
110+
111+
/** Holds always. Use this predicate as parameter to `XssDiffInformed` to disable diff-informed mode. */
112+
predicate xssNotDiffInformed() { any() }
113+
114+
/**
115+
* A module for finding XSS sinks faster in a diff-informed query. The
116+
* `hasSourceInDiffRange` parameter should hold if the overall data-flow
117+
* configuration of the query has any sources in the diff range.
118+
*/
119+
module XssDiffInformed<xssNullaryPredicate/0 hasSourceInDiffRange> {
120+
final private class Node = DataFlow::Node;
121+
122+
/**
123+
* A diff-informed replacement for the top-level `XssSink`, omitting for
124+
* efficiency some sinks that would never be reported by a diff-informed
125+
* query.
126+
*/
127+
final class XssSink extends Node {
128+
XssSink() {
129+
this instanceof AbstractXssSink
130+
or
131+
isResponseWriterSink(this)
132+
}
133+
}
134+
135+
predicate isResponseWriterSink(DataFlow::Node sink) {
136+
exists(MethodCall ma |
137+
// This code mirrors `getASelectedSinkLocation`.
138+
ma.getMethod() instanceof WritingMethod and
139+
XssVulnerableWriterSourceToWritingMethodFlow::flowToExpr(ma.getQualifier()) and
140+
sink.asExpr() = ma.getArgument(_)
141+
)
142+
}
143+
144+
/** A configuration that tracks data from a servlet writer to an output method. */
145+
private module XssVulnerableWriterSourceToWritingMethodFlowConfig implements DataFlow::ConfigSig {
146+
predicate isSource(DataFlow::Node src) { src instanceof XssVulnerableWriterSourceNode }
147+
148+
predicate isSink(DataFlow::Node sink) {
149+
exists(MethodCall ma |
150+
sink.asExpr() = ma.getQualifier() and ma.getMethod() instanceof WritingMethod
151+
)
152+
}
153+
154+
predicate observeDiffInformedIncrementalMode() {
155+
// Since this configuration is for finding sinks to be used in a main
156+
// data-flow configuration, this configuration should only restrict the
157+
// sinks to be found if there are no main-configuration sources in the
158+
// diff range. That's because if there is such a source, we need to
159+
// report query results for it even with sinks outside the diff range.
160+
not hasSourceInDiffRange()
161+
}
162+
163+
// The sources are not exposed outside this file module, so we know the
164+
// query will not select them.
165+
Location getASelectedSourceLocation(DataFlow::Node source) { none() }
166+
167+
Location getASelectedSinkLocation(DataFlow::Node sink) {
168+
// This code mirrors `isResponseWriterSink`.
169+
exists(MethodCall ma | result = ma.getAnArgument().getLocation() |
170+
sink.asExpr() = ma.getQualifier() and ma.getMethod() instanceof WritingMethod
171+
)
172+
}
173+
}
174+
175+
private module XssVulnerableWriterSourceToWritingMethodFlow =
176+
TaintTracking::Global<XssVulnerableWriterSourceToWritingMethodFlowConfig>;
177+
}
178+
116179
/**
117180
* Holds if `s` is an HTTP Content-Type vulnerable to XSS.
118181
*/

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ private import semmle.code.java.security.XSS
1111
deprecated module XssLocalConfig implements DataFlow::ConfigSig {
1212
predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }
1313

14-
predicate isSink(DataFlow::Node sink) { sink instanceof XssSink }
14+
predicate isSink(DataFlow::Node sink) {
15+
sink instanceof XssDiffInformed<XssLocalFlow::hasSourceInDiffRange/0>::XssSink
16+
}
1517

1618
predicate isBarrier(DataFlow::Node node) { node instanceof XssSanitizer }
1719

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import semmle.code.java.security.XSS
1111
module XssConfig implements DataFlow::ConfigSig {
1212
predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
1313

14-
predicate isSink(DataFlow::Node sink) { sink instanceof XssSink }
14+
predicate isSink(DataFlow::Node sink) {
15+
sink instanceof XssDiffInformed<XssFlow::hasSourceInDiffRange/0>::XssSink
16+
}
1517

1618
predicate isBarrier(DataFlow::Node node) { node instanceof XssSanitizer }
1719

0 commit comments

Comments
 (0)