-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathSSRF.qll
More file actions
163 lines (145 loc) · 5.68 KB
/
SSRF.qll
File metadata and controls
163 lines (145 loc) · 5.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import javascript
import semmle.javascript.security.dataflow.RequestForgeryCustomizations
import semmle.javascript.security.dataflow.UrlConcatenation
module SsrfConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RequestForgery::Source }
predicate isSink(DataFlow::Node sink) { sink instanceof RequestForgery::Sink }
predicate isBarrier(DataFlow::Node node) {
node instanceof RequestForgery::Sanitizer or
node = DataFlow::MakeBarrierGuard<BarrierGuard>::getABarrierNode()
}
private predicate hasSanitizingSubstring(DataFlow::Node nd) {
nd.getStringValue().regexpMatch(".*[?#].*")
or
hasSanitizingSubstring(StringConcatenation::getAnOperand(nd))
or
hasSanitizingSubstring(nd.getAPredecessor())
}
private predicate strictSanitizingPrefixEdge(DataFlow::Node source, DataFlow::Node sink) {
exists(DataFlow::Node operator, int n |
StringConcatenation::taintStep(source, sink, operator, n) and
hasSanitizingSubstring(StringConcatenation::getOperand(operator, [0 .. n - 1]))
)
}
predicate isBarrierOut(DataFlow::Node node) { strictSanitizingPrefixEdge(node, _) }
predicate observeDiffInformedIncrementalMode() { any() }
}
module SsrfFlow = TaintTracking::Global<SsrfConfig>;
/**
* DEPRECATED. Use the `SsrfFlow` module instead.
*/
deprecated class Configuration extends TaintTracking::Configuration {
Configuration() { this = "SSRF" }
}
/**
* A sanitizer for ternary operators.
*
* This sanitizers models the next example:
* let valid = req.params.id ? Number.isInteger(req.params.id) : false
* if (valid) { sink(req.params.id) }
*
* This sanitizer models this way of using ternary operators,
* when the sanitizer guard is used as any of the branches
* instead of being used as the condition.
*
* This sanitizer sanitize the corresponding if statement branch.
*/
class TernaryOperatorSanitizer extends RequestForgery::Sanitizer {
TernaryOperatorSanitizer() {
exists(
TaintTracking::AdditionalBarrierGuard guard, IfStmt ifStmt, DataFlow::Node taintedInput,
boolean outcome, Stmt r, DataFlow::Node falseNode
|
ifStmt.getCondition().flow().getAPredecessor+() = guard and
ifStmt.getCondition().flow().getAPredecessor+() = falseNode and
falseNode.asExpr().(BooleanLiteral).mayHaveBooleanValue(false) and
not ifStmt.getCondition() instanceof LogicalBinaryExpr and
guard.blocksExpr(outcome, taintedInput.asExpr()) and
(
outcome = true and r = ifStmt.getThen() and not ifStmt.getCondition() instanceof LogNotExpr
or
outcome = false and r = ifStmt.getElse() and not ifStmt.getCondition() instanceof LogNotExpr
or
outcome = false and r = ifStmt.getThen() and ifStmt.getCondition() instanceof LogNotExpr
or
outcome = true and r = ifStmt.getElse() and ifStmt.getCondition() instanceof LogNotExpr
) and
r.getFirstControlFlowNode()
.getBasicBlock()
.(ReachableBasicBlock)
.dominates(this.getBasicBlock())
)
}
}
/** A barrier guard for this SSRF query. */
abstract class BarrierGuard extends DataFlow::Node {
/** Holds if flow through `e` should be blocked, provided this evaluates to `outcome`. */
abstract predicate blocksExpr(boolean outcome, Expr e);
}
/**
* This sanitizer guard is another way of modeling the example from above
* In this case:
* let valid = req.params.id ? Number.isInteger(req.params.id) : false
* if (!valid) { return }
* sink(req.params.id)
*
* The previous sanitizer is not enough,
* because we are sanitizing the entire if statement branch
* but we need to sanitize the use of this variable from now on.
*
* Thats why we model this sanitizer guard which says that
* the result of the ternary operator execution is a sanitizer guard.
*/
class TernaryOperatorSanitizerGuard extends BarrierGuard {
TaintTracking::AdditionalBarrierGuard originalGuard;
TernaryOperatorSanitizerGuard() {
this.getAPredecessor+().asExpr().(BooleanLiteral).mayHaveBooleanValue(false) and
this.getAPredecessor+() = originalGuard and
not this.asExpr() instanceof LogicalBinaryExpr
}
override predicate blocksExpr(boolean outcome, Expr e) {
not this.asExpr() instanceof LogNotExpr and
originalGuard.blocksExpr(outcome, e)
or
exists(boolean originalOutcome |
this.asExpr() instanceof LogNotExpr and
originalGuard.blocksExpr(originalOutcome, e) and
(
originalOutcome = true and outcome = false
or
originalOutcome = false and outcome = true
)
)
}
}
/**
* A call to Number.isInteger seen as a sanitizer guard because a number can't be used to exploit a SSRF.
*/
class IntegerCheck extends DataFlow::CallNode, BarrierGuard {
IntegerCheck() { this = DataFlow::globalVarRef("Number").getAMemberCall("isInteger") }
override predicate blocksExpr(boolean outcome, Expr e) {
outcome = true and
e = this.getArgument(0).asExpr()
}
}
/**
* A call to validator's library methods.
* validator is a library which has a variety of input-validation functions. We are interesed in
* checking that source is a number (any type of number) or an alphanumeric value.
*/
class ValidatorCheck extends DataFlow::CallNode, BarrierGuard {
ValidatorCheck() {
exists(DataFlow::SourceNode mod, string method |
mod = DataFlow::moduleImport("validator") and
this = mod.getAChainedMethodCall(method) and
method in [
"isAlphanumeric", "isAlpha", "isDecimal", "isFloat", "isHexadecimal", "isHexColor",
"isInt", "isNumeric", "isOctal", "isUUID"
]
)
}
override predicate blocksExpr(boolean outcome, Expr e) {
outcome = true and
e = this.getArgument(0).asExpr()
}
}