Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bec522f

Browse files
committed
small changes based on review feedback
1 parent 72bbd4d commit bec522f

12 files changed

Lines changed: 133 additions & 123 deletions

javascript/ql/src/Security/CWE-834/TaintedLength.qhelp

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,18 @@
77
<p>
88
Iterating the elements of an untrusted object using the
99
<code>.length</code> property can lead to a server looping
10-
indefinitely or crashing. Thereby creating a
10+
indefinitely or crashing. This looping or crashing creates a
1111
denial-of-service or DOS.
12-
This happens when an attacker creates a JSON object with an
13-
absurdly large number in the .length property that the server then
14-
loops through.
15-
The problem can also happen when using utility methods from Lodash or
16-
Underscore that operate on array-like values.
17-
As a simple example of how a DOS can happen, this code will crash most
18-
servers with an out-of-memory exception
19-
<code>_.map({length:999999999})</code>.
12+
This happens when the server expects an array but an attacker creates
13+
a JSON object with an absurdly large number in the .length property
14+
that the server then loops through.
2015
</p>
2116
</overview>
2217

2318
<recommendation>
2419
<p>
25-
The attack is easy to prevent and there are multiple ways of doing so.
26-
Forcing the user controlled object to be an array or preventing the
27-
<code>.length</code> property from being too large can limit the
28-
impact of the attack.
29-
Alternatively the loop can exit early if the currently iterated element
30-
is seen to be <code>undefined</code>, as the attacker cannot create an
31-
array-like object with non-<code>undefined</code> values for an
32-
unlimited amount of array elements.
33-
Accessing a property of the currently iterated element will also
34-
prevent the attack, as a null-pointer exception will occur in the first
35-
iteration where the element is <code>undefined</code>.
20+
Either force the user controlled object to be an array or limit the
21+
size of the <code>.length</code> property.
3622
</p>
3723
</recommendation>
3824

javascript/ql/src/Security/CWE-834/TaintedLength.ql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
/**
2-
* @name Tainted .length looping
2+
* @name Tainted .length in loop condition
33
* @description If server-side code iterates over a user-controlled object with
4-
* an arbitrary .length value, then an attacker can trick the server
4+
* an arbitrary .length value an attacker can trick the server
55
* to loop infinitely.
66
* @kind path-problem
77
* @problem.severity warning
88
* @id js/tainted-length-looping
99
* @tags security
10-
* @precision low
10+
* @precision medium
1111
*/
1212

1313
import javascript

javascript/ql/src/semmle/javascript/security/dataflow/TaintedLengthCustomizations.qll

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Provides default sources, sinks and sanitisers for reasoning about
3-
* DOS attacks using objects with unbounded length object.
3+
* DOS attacks using objects with unbounded length property.
44
* As well as extension points for adding your own.
55
*/
66

@@ -11,7 +11,9 @@ module TaintedLength {
1111
import semmle.javascript.security.TaintedObject
1212
import DataFlow::PathGraph
1313

14-
// Heavily inspired by Expr::inNullSensitiveContext
14+
/**
15+
* Holds if an exception will be thrown whenever `e` evaluates to `undefined` or `null`.
16+
*/
1517
predicate isCrashingWithNullValues(Expr e) {
1618
exists(ExprOrStmt ctx |
1719
e = ctx.(PropAccess).getBase()
@@ -27,9 +29,10 @@ module TaintedLength {
2729
)
2830
}
2931

30-
// Inspired by LoopIterationSkippedDueToShifting::ArrayIterationLoop
31-
// Added some Dataflow to the .length access.
32-
// Added support for while/dowhile loops.
32+
/**
33+
* A loop that iterates through some array using the `length` property.
34+
* The loop is either of the style `for(..; i < arr.length;...)` or `while(i < arr.length) {..;i++;..}`.
35+
*/
3336
class ArrayIterationLoop extends Stmt {
3437
LocalVariable indexVariable;
3538
LoopStmt loop;
@@ -47,11 +50,17 @@ module TaintedLength {
4750
loop.getBody().getAChild*().(IncExpr).getOperand() = indexVariable.getAnAccess()
4851
)
4952
}
50-
53+
54+
/**
55+
* Gets the loop test of this loop.
56+
*/
5157
Expr getTest() {
5258
result = loop.getTest()
5359
}
5460

61+
/**
62+
* Gets the body of this loop.
63+
*/
5564
Stmt getBody() {
5665
result = loop.getBody()
5766
}
@@ -62,15 +71,21 @@ module TaintedLength {
6271
LocalVariable getIndexVariable() { result = indexVariable }
6372
}
6473

74+
/**
75+
* A data flow sink for untrusted user input that is being looped through.
76+
*/
6577
abstract class Sink extends DataFlow::Node { }
6678

67-
// for (..; .. sink.length; ...) ...
79+
/**
80+
* A loop that iterates over an array, such as `for (..; .. sink.length; ...) ...`
81+
*/
6882
private class LoopSink extends Sink {
6983
LoopSink() {
7084
exists(ArrayIterationLoop loop, Expr lengthAccess, DataFlow::PropRead lengthRead |
71-
loop.getTest().getAChild*() = lengthAccess and
85+
loop.getTest().(RelationalComparison).getGreaterOperand() = lengthAccess and
7286
lengthRead.flowsToExpr(lengthAccess) and
7387
lengthRead.accesses(this, "length") and
88+
7489
// In the DOS we are looking for arrayRead will evaluate to undefined.
7590
// If an obvious nullpointer happens on this undefined, then the DOS cannot happen.
7691
not exists(DataFlow::PropRead arrayRead, Expr throws |
@@ -81,7 +96,7 @@ module TaintedLength {
8196
arrayRead.flowsToExpr(throws) and
8297
isCrashingWithNullValues(throws)
8398
) and
84-
// The existance of some kind of early-exit usually indicates that the loop will stop early and no DOS happens.
99+
// The existence of some kind of early-exit usually indicates that the loop will stop early and no DOS happens.
85100
not exists(BreakStmt br | br.getTarget() = loop) and
86101
not exists(ReturnStmt ret |
87102
loop.getBody().getAChild*() = ret and
@@ -95,6 +110,9 @@ module TaintedLength {
95110
}
96111
}
97112

113+
/**
114+
* Holds if `name` is a method from lodash vulnerable to a DOS attack if called with a tained object.
115+
*/
98116
predicate loopableLodashMethod(string name) {
99117
name = "chunk" or
100118
name = "compact" or
@@ -158,17 +176,20 @@ module TaintedLength {
158176
name = "sortBy"
159177
}
160178

161-
// _.each(sink);
179+
/**
180+
* A method call to a lodash method that iterates over an array-like structure,
181+
* such as `_.filter(sink, ...)`
182+
*/
162183
private class LodashIterationSink extends Sink {
163184
DataFlow::CallNode call;
164185

165186
LodashIterationSink() {
166187
exists(string name |
167188
loopableLodashMethod(name) and
168-
call = any(LodashUnderscore::member(name)).getACall() and
189+
call = LodashUnderscore::member(name).getACall() and
169190
call.getArgument(0) = this and
170191

171-
// Here it is just assumed that the array elements is the first parameter in the callback function.
192+
// Here it is just assumed that the array element is the first parameter in the callback function.
172193
not exists(DataFlow::FunctionNode func, DataFlow::ParameterNode e |
173194
func.flowsTo(call.getAnArgument()) and
174195
e = func.getParameter(0) and
@@ -192,6 +213,9 @@ module TaintedLength {
192213
}
193214
}
194215

216+
/**
217+
* A source of objects that can cause DOS is looped over.
218+
*/
195219
abstract class Source extends DataFlow::Node { }
196220

197221
/**
@@ -201,7 +225,9 @@ module TaintedLength {
201225
TaintedObjectSource() { this instanceof TaintedObject::Source }
202226
}
203227

204-
228+
/**
229+
* A sanitizer that blocks taint flow if the array is checked to be an array using an `isArray` method.
230+
*/
205231
class IsArraySanitizerGuard extends TaintTracking::LabeledSanitizerGuardNode,
206232
DataFlow::ValueNode {
207233
override CallExpr astNode;
@@ -215,13 +241,16 @@ module TaintedLength {
215241
}
216242
}
217243

244+
/**
245+
* A sanitizer that blocks taint flow if the array is checked to be an array using an `X instanceof Array` check.
246+
*/
218247
class InstanceofArraySanitizerGuard extends TaintTracking::LabeledSanitizerGuardNode,
219248
DataFlow::ValueNode {
220249
override BinaryExpr astNode;
221250

222251
InstanceofArraySanitizerGuard() {
223252
astNode.getOperator() = "instanceof" and
224-
astNode.getRightOperand().(Identifier).getName() = "Array"
253+
DataFlow::globalVarRef("Array").flowsToExpr(astNode.getRightOperand())
225254
}
226255

227256
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
@@ -231,23 +260,25 @@ module TaintedLength {
231260
}
232261
}
233262

234-
// Does two things:
235-
// 1) Detects any length-check that limits the size of the .length property.
236-
// 2) Makes sure that only the first loop that is DOS-prone is selected by the query. (due to the .length test having outcome=false when exiting the loop).
263+
/**
264+
* A sanitizer that blocks taint flow if the length of an array is limited.
265+
*
266+
* Also implicitly makes sure that only the first DOS-prone loop is selected by the query. (as the .length test has outcome=false when exiting the loop).
267+
*/
237268
class LengthCheckSanitizerGuard extends TaintTracking::LabeledSanitizerGuardNode,
238269
DataFlow::ValueNode {
239270
override RelationalComparison astNode;
240271

241-
PropAccess propAccess;
272+
DataFlow::PropRead propRead;
242273

243274
LengthCheckSanitizerGuard() {
244-
propAccess = astNode.getGreaterOperand().getAChild*() and
245-
propAccess.getPropertyName() = "length"
275+
propRead.flowsToExpr(astNode.getGreaterOperand()) and
276+
propRead.getPropertyName() = "length"
246277
}
247278

248279
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
249280
false = outcome and
250-
astNode.getAChild*() = e and
281+
e = propRead.getBase().asExpr() and
251282
label = TaintedObject::label()
252283
}
253284
}
Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,57 @@
11
nodes
2-
| TaintedLengthBad.js:8:10:8:17 | req.body |
3-
| TaintedLengthBad.js:10:12:10:19 | req.body |
4-
| TaintedLengthBad.js:12:22:12:29 | req.body |
5-
| TaintedLengthBad.js:14:16:14:23 | req.body |
2+
| TaintedLengthBad.js:8:13:8:20 | req.body |
3+
| TaintedLengthBad.js:10:15:10:22 | req.body |
4+
| TaintedLengthBad.js:12:25:12:32 | req.body |
5+
| TaintedLengthBad.js:14:19:14:26 | req.body |
66
| TaintedLengthBad.js:17:18:17:20 | val |
7-
| TaintedLengthBad.js:21:22:21:24 | val |
8-
| TaintedLengthBad.js:26:20:26:22 | val |
9-
| TaintedLengthBad.js:30:13:30:15 | val |
10-
| TaintedLengthBad.js:37:30:37:32 | val |
11-
| TaintedLengthBad.js:40:12:40:14 | val |
12-
| TaintedLengthBad.js:49:24:49:26 | val |
13-
| TaintedLengthBad.js:54:22:54:24 | val |
7+
| TaintedLengthBad.js:22:25:22:27 | val |
8+
| TaintedLengthBad.js:27:20:27:22 | val |
9+
| TaintedLengthBad.js:32:16:32:18 | val |
10+
| TaintedLengthBad.js:38:30:38:32 | val |
11+
| TaintedLengthBad.js:41:15:41:17 | val |
12+
| TaintedLengthBad.js:50:24:50:26 | val |
13+
| TaintedLengthBad.js:55:25:55:27 | val |
1414
| TaintedLengthExitBad.js:8:9:8:16 | req.body |
1515
| TaintedLengthExitBad.js:10:9:10:16 | req.body |
1616
| TaintedLengthExitBad.js:12:10:12:17 | req.body |
1717
| TaintedLengthExitBad.js:14:14:14:21 | req.body |
1818
| TaintedLengthExitBad.js:17:17:17:19 | val |
1919
| TaintedLengthExitBad.js:20:22:20:24 | val |
20-
| TaintedLengthExitBad.js:30:17:30:19 | val |
21-
| TaintedLengthExitBad.js:33:22:33:24 | val |
22-
| TaintedLengthExitBad.js:46:18:46:20 | val |
23-
| TaintedLengthExitBad.js:49:22:49:24 | val |
24-
| TaintedLengthExitBad.js:59:22:59:24 | val |
25-
| TaintedLengthExitBad.js:60:8:60:10 | val |
26-
| TaintedLengthLodash.js:9:10:9:17 | req.body |
20+
| TaintedLengthExitBad.js:31:17:31:19 | val |
21+
| TaintedLengthExitBad.js:34:22:34:24 | val |
22+
| TaintedLengthExitBad.js:47:18:47:20 | val |
23+
| TaintedLengthExitBad.js:50:22:50:24 | val |
24+
| TaintedLengthExitBad.js:60:22:60:24 | val |
25+
| TaintedLengthExitBad.js:61:8:61:10 | val |
26+
| TaintedLengthLodash.js:9:13:9:20 | req.body |
2727
| TaintedLengthLodash.js:14:18:14:20 | val |
28-
| TaintedLengthLodash.js:15:10:15:12 | val |
28+
| TaintedLengthLodash.js:16:13:16:15 | val |
2929
edges
30-
| TaintedLengthBad.js:8:10:8:17 | req.body | TaintedLengthBad.js:17:18:17:20 | val |
31-
| TaintedLengthBad.js:10:12:10:19 | req.body | TaintedLengthBad.js:26:20:26:22 | val |
32-
| TaintedLengthBad.js:12:22:12:29 | req.body | TaintedLengthBad.js:37:30:37:32 | val |
33-
| TaintedLengthBad.js:14:16:14:23 | req.body | TaintedLengthBad.js:49:24:49:26 | val |
34-
| TaintedLengthBad.js:17:18:17:20 | val | TaintedLengthBad.js:21:22:21:24 | val |
35-
| TaintedLengthBad.js:26:20:26:22 | val | TaintedLengthBad.js:30:13:30:15 | val |
36-
| TaintedLengthBad.js:37:30:37:32 | val | TaintedLengthBad.js:40:12:40:14 | val |
37-
| TaintedLengthBad.js:49:24:49:26 | val | TaintedLengthBad.js:54:22:54:24 | val |
30+
| TaintedLengthBad.js:8:13:8:20 | req.body | TaintedLengthBad.js:17:18:17:20 | val |
31+
| TaintedLengthBad.js:10:15:10:22 | req.body | TaintedLengthBad.js:27:20:27:22 | val |
32+
| TaintedLengthBad.js:12:25:12:32 | req.body | TaintedLengthBad.js:38:30:38:32 | val |
33+
| TaintedLengthBad.js:14:19:14:26 | req.body | TaintedLengthBad.js:50:24:50:26 | val |
34+
| TaintedLengthBad.js:17:18:17:20 | val | TaintedLengthBad.js:22:25:22:27 | val |
35+
| TaintedLengthBad.js:27:20:27:22 | val | TaintedLengthBad.js:32:16:32:18 | val |
36+
| TaintedLengthBad.js:38:30:38:32 | val | TaintedLengthBad.js:41:15:41:17 | val |
37+
| TaintedLengthBad.js:50:24:50:26 | val | TaintedLengthBad.js:55:25:55:27 | val |
3838
| TaintedLengthExitBad.js:8:9:8:16 | req.body | TaintedLengthExitBad.js:17:17:17:19 | val |
39-
| TaintedLengthExitBad.js:10:9:10:16 | req.body | TaintedLengthExitBad.js:30:17:30:19 | val |
40-
| TaintedLengthExitBad.js:12:10:12:17 | req.body | TaintedLengthExitBad.js:46:18:46:20 | val |
41-
| TaintedLengthExitBad.js:14:14:14:21 | req.body | TaintedLengthExitBad.js:59:22:59:24 | val |
39+
| TaintedLengthExitBad.js:10:9:10:16 | req.body | TaintedLengthExitBad.js:31:17:31:19 | val |
40+
| TaintedLengthExitBad.js:12:10:12:17 | req.body | TaintedLengthExitBad.js:47:18:47:20 | val |
41+
| TaintedLengthExitBad.js:14:14:14:21 | req.body | TaintedLengthExitBad.js:60:22:60:24 | val |
4242
| TaintedLengthExitBad.js:17:17:17:19 | val | TaintedLengthExitBad.js:20:22:20:24 | val |
43-
| TaintedLengthExitBad.js:30:17:30:19 | val | TaintedLengthExitBad.js:33:22:33:24 | val |
44-
| TaintedLengthExitBad.js:46:18:46:20 | val | TaintedLengthExitBad.js:49:22:49:24 | val |
45-
| TaintedLengthExitBad.js:59:22:59:24 | val | TaintedLengthExitBad.js:60:8:60:10 | val |
46-
| TaintedLengthLodash.js:9:10:9:17 | req.body | TaintedLengthLodash.js:14:18:14:20 | val |
47-
| TaintedLengthLodash.js:14:18:14:20 | val | TaintedLengthLodash.js:15:10:15:12 | val |
43+
| TaintedLengthExitBad.js:31:17:31:19 | val | TaintedLengthExitBad.js:34:22:34:24 | val |
44+
| TaintedLengthExitBad.js:47:18:47:20 | val | TaintedLengthExitBad.js:50:22:50:24 | val |
45+
| TaintedLengthExitBad.js:60:22:60:24 | val | TaintedLengthExitBad.js:61:8:61:10 | val |
46+
| TaintedLengthLodash.js:9:13:9:20 | req.body | TaintedLengthLodash.js:14:18:14:20 | val |
47+
| TaintedLengthLodash.js:14:18:14:20 | val | TaintedLengthLodash.js:16:13:16:15 | val |
4848
#select
49-
| TaintedLengthBad.js:21:22:21:24 | val | TaintedLengthBad.js:8:10:8:17 | req.body | TaintedLengthBad.js:21:22:21:24 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthBad.js:8:10:8:17 | req.body | here |
50-
| TaintedLengthBad.js:30:13:30:15 | val | TaintedLengthBad.js:10:12:10:19 | req.body | TaintedLengthBad.js:30:13:30:15 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthBad.js:10:12:10:19 | req.body | here |
51-
| TaintedLengthBad.js:40:12:40:14 | val | TaintedLengthBad.js:12:22:12:29 | req.body | TaintedLengthBad.js:40:12:40:14 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthBad.js:12:22:12:29 | req.body | here |
52-
| TaintedLengthBad.js:54:22:54:24 | val | TaintedLengthBad.js:14:16:14:23 | req.body | TaintedLengthBad.js:54:22:54:24 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthBad.js:14:16:14:23 | req.body | here |
49+
| TaintedLengthBad.js:22:25:22:27 | val | TaintedLengthBad.js:8:13:8:20 | req.body | TaintedLengthBad.js:22:25:22:27 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthBad.js:8:13:8:20 | req.body | here |
50+
| TaintedLengthBad.js:32:16:32:18 | val | TaintedLengthBad.js:10:15:10:22 | req.body | TaintedLengthBad.js:32:16:32:18 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthBad.js:10:15:10:22 | req.body | here |
51+
| TaintedLengthBad.js:41:15:41:17 | val | TaintedLengthBad.js:12:25:12:32 | req.body | TaintedLengthBad.js:41:15:41:17 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthBad.js:12:25:12:32 | req.body | here |
52+
| TaintedLengthBad.js:55:25:55:27 | val | TaintedLengthBad.js:14:19:14:26 | req.body | TaintedLengthBad.js:55:25:55:27 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthBad.js:14:19:14:26 | req.body | here |
5353
| TaintedLengthExitBad.js:20:22:20:24 | val | TaintedLengthExitBad.js:8:9:8:16 | req.body | TaintedLengthExitBad.js:20:22:20:24 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthExitBad.js:8:9:8:16 | req.body | here |
54-
| TaintedLengthExitBad.js:33:22:33:24 | val | TaintedLengthExitBad.js:10:9:10:16 | req.body | TaintedLengthExitBad.js:33:22:33:24 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthExitBad.js:10:9:10:16 | req.body | here |
55-
| TaintedLengthExitBad.js:49:22:49:24 | val | TaintedLengthExitBad.js:12:10:12:17 | req.body | TaintedLengthExitBad.js:49:22:49:24 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthExitBad.js:12:10:12:17 | req.body | here |
56-
| TaintedLengthExitBad.js:60:8:60:10 | val | TaintedLengthExitBad.js:14:14:14:21 | req.body | TaintedLengthExitBad.js:60:8:60:10 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthExitBad.js:14:14:14:21 | req.body | here |
57-
| TaintedLengthLodash.js:15:10:15:12 | val | TaintedLengthLodash.js:9:10:9:17 | req.body | TaintedLengthLodash.js:15:10:15:12 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthLodash.js:9:10:9:17 | req.body | here |
54+
| TaintedLengthExitBad.js:34:22:34:24 | val | TaintedLengthExitBad.js:10:9:10:16 | req.body | TaintedLengthExitBad.js:34:22:34:24 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthExitBad.js:10:9:10:16 | req.body | here |
55+
| TaintedLengthExitBad.js:50:22:50:24 | val | TaintedLengthExitBad.js:12:10:12:17 | req.body | TaintedLengthExitBad.js:50:22:50:24 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthExitBad.js:12:10:12:17 | req.body | here |
56+
| TaintedLengthExitBad.js:61:8:61:10 | val | TaintedLengthExitBad.js:14:14:14:21 | req.body | TaintedLengthExitBad.js:61:8:61:10 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthExitBad.js:14:14:14:21 | req.body | here |
57+
| TaintedLengthLodash.js:16:13:16:15 | val | TaintedLengthLodash.js:9:13:9:20 | req.body | TaintedLengthLodash.js:16:13:16:15 | val | Iterating over user controlled object with an unbounded .length property $@. | TaintedLengthLodash.js:9:13:9:20 | req.body | here |

0 commit comments

Comments
 (0)