Skip to content

Commit 8d1e8e9

Browse files
committed
C++: Flow states and transformers.
1 parent 3aaa058 commit 8d1e8e9

3 files changed

Lines changed: 224 additions & 130 deletions

File tree

cpp/ql/src/Security/CWE/CWE-611/XercesXXE.ql

Lines changed: 194 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -10,131 +10,235 @@
1010
* external/cwe/cwe-611
1111
*/
1212

13+
// TODO: currently the file name is Xerces-specific but the query ID isn't.
14+
// Decide which design to go with.
1315
import cpp
1416
import semmle.code.cpp.ir.dataflow.DataFlow
1517
import DataFlow::PathGraph
1618
import semmle.code.cpp.ir.IR
1719

18-
class AbstractDOMParser extends Class {
19-
AbstractDOMParser() { this.hasName("AbstractDOMParser") }
20+
/**
21+
* A flow state representing a possible configuration of an XML object.
22+
*/
23+
abstract class XXEFlowState extends string {
24+
bindingset[this]
25+
XXEFlowState() { any() } // required constructor
26+
}
27+
28+
/**
29+
* An `Expr` that changes the configuration of an XML object, transforming the
30+
* `XXEFlowState` that flows through it.
31+
*/
32+
abstract class XXEFlowStateTranformer extends Expr {
33+
/**
34+
* Gets the flow state that `flowstate` is transformed into.
35+
*
36+
* Due to limitations of the implementation the result must always map to
37+
* itself, that is, it must be that:
38+
* ```
39+
* transform(tranform(x)) = tranform(x)
40+
* ```
41+
*/
42+
abstract XXEFlowState transform(XXEFlowState flowstate);
43+
}
44+
45+
/**
46+
* The `AbstractDOMParser` class.
47+
*/
48+
class AbstractDOMParserClass extends Class {
49+
AbstractDOMParserClass() { this.hasName("AbstractDOMParser") }
50+
}
51+
52+
/**
53+
* The `XercesDOMParser` class.
54+
*/
55+
class XercesDOMParserClass extends Class {
56+
XercesDOMParserClass() { this.hasName("XercesDOMParser") }
57+
}
58+
59+
/**
60+
* Gets a valid flow state for `XercesDOMParser` flow.
61+
*
62+
* These flow states take the form `XercesDOM-A-B`, where:
63+
* - A is 1 if `setDisableDefaultEntityResolution` is `true`, 0 otherwise.
64+
* - B is 1 if `setCreateEntityReferenceNodes` is `true`, 0 otherwise.
65+
*/
66+
predicate encodeXercesDOMFlowState(string flowstate, int a, int b) {
67+
flowstate = "XercesDOM-0-0" and a = 0 and b = 0
68+
or
69+
flowstate = "XercesDOM-0-1" and a = 0 and b = 1
70+
or
71+
flowstate = "XercesDOM-1-0" and a = 1 and b = 0
72+
or
73+
flowstate = "XercesDOM-1-1" and a = 1 and b = 1
2074
}
2175

22-
class XercesDOMParser extends Class {
23-
XercesDOMParser() { this.hasName("XercesDOMParser") }
76+
/**
77+
* A flow state representing the configuration of a `XercesDOMParser` object.
78+
*/
79+
class XercesDOMParserFlowState extends XXEFlowState {
80+
XercesDOMParserFlowState() { encodeXercesDOMFlowState(this, _, _) }
2481
}
2582

26-
class DisableDefaultEntityResolution extends Function {
27-
DisableDefaultEntityResolution() {
28-
this.getDeclaringType() instanceof AbstractDOMParser and
29-
this.hasName("setDisableDefaultEntityResolution")
83+
/**
84+
* The qualifier of a call to `AbstractDOMParser.setDisableDefaultEntityResolution`.
85+
*/
86+
class DisableDefaultEntityResolutionTranformer extends XXEFlowStateTranformer {
87+
Expr newValue;
88+
89+
DisableDefaultEntityResolutionTranformer() {
90+
exists(Call call, Function f |
91+
call.getTarget() = f and
92+
f.getDeclaringType() instanceof AbstractDOMParserClass and
93+
f.hasName("setDisableDefaultEntityResolution") and
94+
this = call.getQualifier() and
95+
newValue = call.getArgument(0)
96+
)
97+
}
98+
99+
final override XXEFlowState transform(XXEFlowState flowstate) {
100+
exists(int a, int b |
101+
encodeXercesDOMFlowState(flowstate, a, b) and
102+
(
103+
newValue.getValue().toInt() = 1 and // true
104+
encodeXercesDOMFlowState(result, 1, b)
105+
or
106+
not newValue.getValue().toInt() = 1 and // false or unknown
107+
encodeXercesDOMFlowState(result, 0, b)
108+
)
109+
)
30110
}
31111
}
32112

33-
class SetCreateEntityReferenceNodes extends Function {
34-
SetCreateEntityReferenceNodes() {
35-
this.getDeclaringType() instanceof AbstractDOMParser and
36-
this.hasName("setCreateEntityReferenceNodes")
113+
/**
114+
* The qualifier of a call to `AbstractDOMParser.setDisableDefaultEntityResolution`.
115+
*/
116+
class CreateEntityReferenceNodesTranformer extends XXEFlowStateTranformer {
117+
Expr newValue;
118+
119+
CreateEntityReferenceNodesTranformer() {
120+
exists(Call call, Function f |
121+
call.getTarget() = f and
122+
f.getDeclaringType() instanceof AbstractDOMParserClass and
123+
f.hasName("setCreateEntityReferenceNodes") and
124+
this = call.getQualifier() and
125+
newValue = call.getArgument(0)
126+
)
127+
}
128+
129+
final override XXEFlowState transform(XXEFlowState flowstate) {
130+
exists(int a, int b |
131+
encodeXercesDOMFlowState(flowstate, a, b) and
132+
(
133+
newValue.getValue().toInt() = 1 and // true
134+
encodeXercesDOMFlowState(result, a, 1)
135+
or
136+
not newValue.getValue().toInt() = 1 and // false or unknown
137+
encodeXercesDOMFlowState(result, a, 0)
138+
)
139+
)
37140
}
38141
}
39142

40-
class Parse extends Function {
41-
Parse() {
42-
this.getDeclaringType() instanceof AbstractDOMParser and
143+
/**
144+
* The `AbstractDOMParser.parse` method.
145+
*/
146+
class ParseFunction extends Function {
147+
ParseFunction() {
148+
this.getDeclaringType() instanceof AbstractDOMParserClass and
43149
this.hasName("parse")
44150
}
45151
}
46152

47153
/*
48-
class CreateLSParser extends Function {
49-
CreateLSParser() { this.hasName("createLSParser") }
50-
}
51-
52-
class SetSecurityManager extends Function {
53-
SetSecurityManager() { this.hasQualifiedName(_, "AbstractDOMParser", "setSecurityManager") }
54-
}
154+
* class CreateLSParser extends Function {
155+
* CreateLSParser() { this.hasName("createLSParser") }
156+
* }
157+
*
158+
* class SetSecurityManager extends Function {
159+
* SetSecurityManager() { this.hasQualifiedName(_, "AbstractDOMParser", "setSecurityManager") }
160+
* }
161+
*
162+
* class SAXParser extends Class {
163+
* SAXParser() { this.hasName("SAXParser") }
164+
* }
165+
*/
55166

56-
class SAXParser extends Class {
57-
SAXParser() { this.hasName("SAXParser") }
58-
}
59-
*/
167+
/**
168+
* Configuration for tracking Xerces library XML objects and their states.
169+
*/
60170
class XercesXXEConfiguration extends DataFlow::Configuration {
61171
XercesXXEConfiguration() { this = "XercesXXEConfiguration" }
62172

63-
override predicate isSource(DataFlow::Node node/*, string flowstate*/) {
173+
override predicate isSource(DataFlow::Node node, string flowstate) {
64174
// source is the write on `this` of a call to the XercesDOMParser
65175
// constructor.
66176
exists(CallInstruction call |
67-
call.getStaticCallTarget() = any(XercesDOMParser c).getAConstructor() and
68-
node.asInstruction().(WriteSideEffectInstruction).getDestinationAddress() =
69-
call.getThisArgument()/* and
70-
flowstate = "XercesDOM"*/
71-
)
72-
/*exists(Call call |
73-
call.getTarget() = any(XercesDOMParser c).getAConstructor() and
74-
node.asExpr() = call
75-
)*/
76-
/* or
77-
exists(Call call |
78-
call.getTarget() instanceof CreateLSParser and
79-
call = node.asExpr() and
80-
flowstate = "XercesDOM"
81-
)
82-
or
83-
exists(CallInstruction call |
177+
call.getStaticCallTarget() = any(XercesDOMParserClass c).getAConstructor() and
84178
node.asInstruction().(WriteSideEffectInstruction).getDestinationAddress() =
85179
call.getThisArgument() and
86-
call.getStaticCallTarget().(Constructor).getDeclaringType() instanceof SAXParser and
87-
flowstate = "SAXParser"
88-
)*/
89-
}
180+
encodeXercesDOMFlowState(flowstate, 0, 1) // default configuration
181+
)
182+
/*
183+
* or
184+
* exists(Call call |
185+
* call.getTarget() instanceof CreateLSParser and
186+
* call = node.asExpr() and
187+
* flowstate = "XercesDOM"
188+
* )
189+
* or
190+
* exists(CallInstruction call |
191+
* node.asInstruction().(WriteSideEffectInstruction).getDestinationAddress() =
192+
* call.getThisArgument() and
193+
* call.getStaticCallTarget().(Constructor).getDeclaringType() instanceof SAXParser and
194+
* flowstate = "SAXParser"
195+
* )
196+
*/
197+
198+
}
90199

91-
override predicate isSink(DataFlow::Node node) {
200+
override predicate isSink(DataFlow::Node node, string flowstate) {
92201
// sink is the read of the qualifier of a call to `parse`.
93-
exists(Call call/*, ReadSideEffectInstruction instr*/ |
94-
call.getTarget() instanceof Parse and
202+
exists(Call call |
203+
call.getTarget() instanceof ParseFunction and
95204
call.getQualifier() = node.asConvertedExpr()
96-
/*instr.getArgumentDef().getUnconvertedResultExpression() and
97-
node.asOperand() = instr.getSideEffectOperand()*/
98-
)
205+
) and
206+
flowstate instanceof XercesDOMParserFlowState and
207+
not encodeXercesDOMFlowState(flowstate, 1, 1) // safe configuration
99208
}
100209

101-
/*override predicate isAdditionalFlowStep(
210+
override predicate isAdditionalFlowStep(
102211
DataFlow::Node node1, string state1, DataFlow::Node node2, string state2
103212
) {
104-
exists(Call call |
105-
node1.asConvertedExpr() = call.getQualifier() and
106-
node2.asDefiningArgument() = call.getQualifier() and
107-
(
108-
call.getTarget() instanceof DisableDefaultEntityResolution and
109-
state1 = "XercesDOM" and
110-
state2 = "XercesDOM-DDER"
111-
or
112-
call.getTarget() instanceof SetCreateEntityReferenceNodes and
113-
state1 = "XercesDOM" and
114-
state2 = "XercesDOM-SCERN"
115-
)
116-
)
117-
}*/
213+
// create additional flow steps for `XXEFlowStateTranformer`s
214+
state2 = node2.asConvertedExpr().(XXEFlowStateTranformer).transform(state1) and
215+
DataFlow::simpleLocalFlowStep(node1, node2)
216+
/*
217+
* exists(CallInstruction call |
218+
* node.asInstruction().(WriteSideEffectInstruction).getDestinationAddress() =
219+
* call.getThisArgument() and
220+
* call.getStaticCallTarget().(Constructor).getDeclaringType() instanceof SAXParser and
221+
* flowstate = "SAXParser"
222+
* )
223+
*/
118224

119-
/*override predicate isBarrier(DataFlow::Node node, string flowstate) {
120-
exists(Call call |
121-
(
122-
flowstate = "XercesDOM-DDER" and
123-
call.getTarget() instanceof SetCreateEntityReferenceNodes
124-
or
125-
flowstate = "XercesDOM-SCERN" and
126-
call.getTarget() instanceof DisableDefaultEntityResolution
127-
) and
128-
call.getQualifier() = node.asDefiningArgument()
129-
)
130-
or
131-
exists(Call setSecurityManager |
132-
// todo: security manager setup
133-
flowstate = TODO
134-
setSecurityManager.getQualifier() = node.asDefiningArgument() and
135-
setSecurityManager.getTarget() instanceof SetSecurityManager
136-
)
137-
}*/
225+
}
226+
227+
override predicate isBarrierOut(DataFlow::Node node, string flowstate) {
228+
// when the flowstate is transformed at a call node, block the original
229+
// flowstate value.
230+
node.asConvertedExpr().(XXEFlowStateTranformer).transform(flowstate) != flowstate
231+
/*
232+
* or
233+
* exists(Call setSecurityManager |
234+
* // todo: security manager setup
235+
* flowstate = TODO
236+
* setSecurityManager.getQualifier() = node.asDefiningArgument() and
237+
* setSecurityManager.getTarget() instanceof SetSecurityManager
238+
* )
239+
*/
240+
241+
}
138242
}
139243

140244
/*
@@ -152,4 +256,5 @@ class XercesXXEConfiguration extends DataFlow::Configuration {
152256
from XercesXXEConfiguration conf, DataFlow::PathNode source, DataFlow::PathNode sink
153257
where conf.hasFlowPath(source, sink)
154258
select sink, source, sink,
155-
"This $@ is not configured to prevent an External Entity Expansion (XXE) attack.", source, "XML parser"
259+
"This $@ is not configured to prevent an External Entity Expansion (XXE) attack.", source,
260+
"XML parser"

0 commit comments

Comments
 (0)