-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathRequestForgery.qll
More file actions
240 lines (213 loc) · 7.97 KB
/
RequestForgery.qll
File metadata and controls
240 lines (213 loc) · 7.97 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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
deprecated module;
import csharp
module RequestForgery {
import semmle.code.csharp.controlflow.Guards
import semmle.code.csharp.frameworks.System
import semmle.code.csharp.frameworks.system.Web
import semmle.code.csharp.frameworks.Format
import semmle.code.csharp.security.dataflow.flowsources.FlowSources
/**
* A data flow source for server side request forgery vulnerabilities.
*/
abstract private class Source extends DataFlow::Node { }
/**
* A data flow sink for server side request forgery vulnerabilities.
*/
abstract private class Sink extends DataFlow::ExprNode { }
/**
* A data flow Barrier that blocks the flow of taint for
* server side request forgery vulnerabilities.
*/
abstract private class Barrier extends DataFlow::Node { }
/**
* A data flow configuration for detecting server side request forgery vulnerabilities.
*/
private module RequestForgeryFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof Source }
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isAdditionalFlowStep(DataFlow::Node prev, DataFlow::Node succ) {
interpolatedStringFlowStep(prev, succ)
or
stringReplaceStep(prev, succ)
or
uriCreationStep(prev, succ)
or
formatConvertStep(prev, succ)
or
toStringStep(prev, succ)
or
stringConcatStep(prev, succ)
or
stringFormatStep(prev, succ)
or
pathCombineStep(prev, succ)
}
predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
}
/**
* A data flow module for detecting server side request forgery vulnerabilities.
*/
module RequestForgeryFlow = DataFlow::Global<RequestForgeryFlowConfig>;
/**
* A dataflow source for Server Side Request Forgery(SSRF) Vulnerabilities.
*/
private class ThreatModelSource extends Source instanceof ActiveThreatModelSource { }
/**
* An url argument to a `HttpRequestMessage` constructor call
* taken as a sink for Server Side Request Forgery(SSRF) Vulnerabilities.
*/
private class SystemWebHttpRequestMessageSink extends Sink {
SystemWebHttpRequestMessageSink() {
exists(Class c | c.hasFullyQualifiedName("System.Net.Http", "HttpRequestMessage") |
c.getAConstructor().getACall().getArgument(1) = this.asExpr()
)
}
}
/**
* An argument to a `WebRequest.Create` call taken as a
* sink for Server Side Request Forgery(SSRF) Vulnerabilities. *
*/
private class SystemNetWebRequestCreateSink extends Sink {
SystemNetWebRequestCreateSink() {
exists(Method m |
m.getDeclaringType().hasFullyQualifiedName("System.Net", "WebRequest") and
m.hasName("Create")
|
m.getACall().getArgument(0) = this.asExpr()
)
}
}
/**
* An argument to a new HTTP Request call of a `System.Net.Http.HttpClient` object
* taken as a sink for Server Side Request Forgery(SSRF) Vulnerabilities.
*/
private class SystemNetHttpClientSink extends Sink {
SystemNetHttpClientSink() {
exists(Method m |
m.getDeclaringType().hasFullyQualifiedName("System.Net.Http", "HttpClient") and
m.hasName([
"DeleteAsync", "GetAsync", "GetByteArrayAsync", "GetStreamAsync", "GetStringAsync",
"PatchAsync", "PostAsync", "PutAsync"
])
|
m.getACall().getArgument(0) = this.asExpr()
)
}
}
/**
* An url argument to a method call of a `System.Net.WebClient` object
* taken as a sink for Server Side Request Forgery(SSRF) Vulnerabilities.
*/
private class SystemNetClientBaseAddressSink extends Sink {
SystemNetClientBaseAddressSink() {
exists(Property p, Type t |
p.hasName("BaseAddress") and
t = p.getDeclaringType() and
(
t.hasFullyQualifiedName("System.Net", "WebClient") or
t.hasFullyQualifiedName("System.Net.Http", "HttpClient")
)
|
p.getAnAssignedValue() = this.asExpr()
)
}
}
/**
* A method call which checks the base of the tainted uri is assumed
* to be a guard for Server Side Request Forgery(SSRF) Vulnerabilities.
* This guard considers all checks as valid.
*/
private predicate baseUriGuard(Guard g, Expr e, GuardValue v) {
g.(MethodCall).getTarget().hasFullyQualifiedName("System", "Uri", "IsBaseOf") and
// we consider any checks against the tainted value to sainitize the taint.
// This implies any check such as shown below block the taint flow.
// Uri url = new Uri("whitelist.com")
// if (url.isBaseOf(`taint1))
(e = g.(MethodCall).getArgument(0) or e = g.(MethodCall).getQualifier()) and
v.asBooleanValue() = true
}
private class BaseUriBarrier extends Barrier {
BaseUriBarrier() { this = DataFlow::BarrierGuard<baseUriGuard/3>::getABarrierNode() }
}
/**
* A method call which checks if the Uri starts with a white-listed string is assumed
* to be a guard for Server Side Request Forgery(SSRF) Vulnerabilities.
* This guard considers all checks as valid.
*/
private predicate stringStartsWithGuard(Guard g, Expr e, GuardValue v) {
g.(MethodCall).getTarget().hasFullyQualifiedName("System", "String", "StartsWith") and
// Any check such as the ones shown below
// "https://myurl.com/".startsWith(`taint`)
// `taint`.startsWith("https://myurl.com/")
// are assumed to sainitize the taint
(e = g.(MethodCall).getQualifier() or g.(MethodCall).getArgument(0) = e) and
v.asBooleanValue() = true
}
private class StringStartsWithBarrier extends Barrier {
StringStartsWithBarrier() {
this = DataFlow::BarrierGuard<stringStartsWithGuard/3>::getABarrierNode()
}
}
private predicate stringFormatStep(DataFlow::Node prev, DataFlow::Node succ) {
exists(FormatCall c | c.getArgument(0) = prev.asExpr() and c = succ.asExpr())
}
private predicate pathCombineStep(DataFlow::Node prev, DataFlow::Node succ) {
exists(MethodCall combineCall |
combineCall.getTarget().hasFullyQualifiedName("System.IO", "Path", "Combine") and
combineCall.getArgument(0) = prev.asExpr() and
combineCall = succ.asExpr()
)
}
private predicate uriCreationStep(DataFlow::Node prev, DataFlow::Node succ) {
exists(ObjectCreation oc |
oc.getTarget().getDeclaringType().hasFullyQualifiedName("System", "Uri") and
oc.getArgument(0) = prev.asExpr() and
oc = succ.asExpr()
)
}
private predicate interpolatedStringFlowStep(DataFlow::Node prev, DataFlow::Node succ) {
exists(InterpolatedStringExpr i |
// allow `$"http://{`taint`}/blabla/");"` or
// allow `$"https://{`taint`}/blabla/");"`
i.getText(0).getValue().matches(["http://", "http://"]) and
i.getInsert(1) = prev.asExpr() and
succ.asExpr() = i
or
// allow `$"{`taint`}/blabla/");"`
i.getInsert(0) = prev.asExpr() and
succ.asExpr() = i
)
}
private predicate stringReplaceStep(DataFlow::Node prev, DataFlow::Node succ) {
exists(MethodCall mc, SystemStringClass s |
mc = s.getReplaceMethod().getACall() and
mc.getQualifier() = prev.asExpr() and
succ.asExpr() = mc
)
}
private predicate stringConcatStep(DataFlow::Node prev, DataFlow::Node succ) {
exists(AddOperation a |
a.getLeftOperand() = prev.asExpr()
or
a.getRightOperand() = prev.asExpr() and
a.getLeftOperand().(StringLiteral).getValue() = ["http://", "https://"]
|
a = succ.asExpr()
)
}
private predicate formatConvertStep(DataFlow::Node prev, DataFlow::Node succ) {
exists(Method m |
m.hasFullyQualifiedName("System", "Convert",
["FromBase64String", "FromHexString", "FromBase64CharArray"]) and
m.getParameter(0) = prev.asParameter() and
succ.asExpr() = m.getACall()
)
}
private predicate toStringStep(DataFlow::Node prev, DataFlow::Node succ) {
exists(MethodCall ma |
ma.getTarget().hasName("ToString") and
ma.getQualifier() = prev.asExpr() and
succ.asExpr() = ma
)
}
}