Skip to content

Commit 30015ee

Browse files
authored
Merge pull request #4942 from esbena/js/reintroduce-resource-exhaustion
Approved by erik-krogh
2 parents 9cfbe6f + b90dd89 commit 30015ee

11 files changed

+439
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
8+
<p>
9+
10+
Applications are constrained by how many resources they can make use
11+
of. Failing to respect these constraints may cause the application to
12+
be unresponsive or crash. It is therefore problematic if attackers
13+
can control the sizes or lifetimes of allocated objects.
14+
15+
</p>
16+
17+
</overview>
18+
19+
<recommendation>
20+
21+
<p>
22+
23+
Ensure that attackers can not control object sizes and their
24+
lifetimes. If object sizes and lifetimes must be controlled by
25+
external parties, ensure you restrict the object sizes and lifetimes so that
26+
they are within acceptable ranges.
27+
28+
</p>
29+
30+
</recommendation>
31+
32+
<example>
33+
34+
<p>
35+
36+
The following example lets a user choose a delay after
37+
which a function is executed:
38+
39+
</p>
40+
41+
<sample src="examples/ResourceExhaustion_timeout.js" />
42+
43+
<p>
44+
45+
This is problematic because a large delay essentially makes the
46+
application wait indefinitely before executing the function. Repeated
47+
registrations of such delays will therefore use up all of the memory
48+
in the application.
49+
50+
A limit on the delay will prevent the attack:
51+
52+
</p>
53+
54+
<sample src="examples/ResourceExhaustion_timeout_fixed.js" />
55+
56+
57+
</example>
58+
59+
<references>
60+
61+
</references>
62+
63+
</qhelp>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @name Resource exhaustion
3+
* @description Allocating objects or timers with user-controlled
4+
* sizes or durations can cause resource exhaustion.
5+
* @kind path-problem
6+
* @problem.severity warning
7+
* @id js/resource-exhaustion
8+
* @precision high
9+
* @tags security
10+
* external/cwe/cwe-770
11+
*/
12+
13+
import javascript
14+
import DataFlow::PathGraph
15+
import experimental.semmle.javascript.security.dataflow.ResourceExhaustion::ResourceExhaustion
16+
17+
from Configuration dataflow, DataFlow::PathNode source, DataFlow::PathNode sink
18+
where dataflow.hasFlowPath(source, sink)
19+
select sink, source, sink, sink.getNode().(Sink).getProblemDescription() + " from $@.", source,
20+
"here"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
var http = require("http"),
2+
url = require("url");
3+
4+
var server = http.createServer(function(req, res) {
5+
var delay = parseInt(url.parse(req.url, true).query.delay);
6+
7+
setTimeout(f, delay); // BAD
8+
9+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
var http = require("http"),
2+
url = require("url");
3+
4+
var server = http.createServer(function(req, res) {
5+
var delay = parseInt(url.parse(req.url, true).query.delay);
6+
7+
if (delay > 1000) {
8+
res.statusCode = 400;
9+
res.end("Bad request.");
10+
return;
11+
}
12+
13+
setTimeout(f, delay); // GOOD
14+
15+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Provides a taint tracking configuration for reasoning about
3+
* resource exhaustion vulnerabilities (CWE-770).
4+
*
5+
* Note, for performance reasons: only import this file if
6+
* `ResourceExhaustion::Configuration` is needed, otherwise
7+
* `ResourceExhaustionCustomizations` should be imported instead.
8+
*/
9+
10+
import javascript
11+
import semmle.javascript.security.dataflow.LoopBoundInjectionCustomizations
12+
13+
module ResourceExhaustion {
14+
import ResourceExhaustionCustomizations::ResourceExhaustion
15+
16+
/**
17+
* A data flow configuration for resource exhaustion vulnerabilities.
18+
*/
19+
class Configuration extends TaintTracking::Configuration {
20+
Configuration() { this = "ResourceExhaustion" }
21+
22+
override predicate isSource(DataFlow::Node source) { source instanceof Source }
23+
24+
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
25+
26+
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node dst) {
27+
isNumericFlowStep(src, dst)
28+
or
29+
// reuse most existing taint steps
30+
isRestrictedAdditionalTaintStep(src, dst)
31+
}
32+
33+
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
34+
guard instanceof LoopBoundInjection::LengthCheckSanitizerGuard or
35+
guard instanceof UpperBoundsCheckSanitizerGuard
36+
}
37+
}
38+
39+
predicate isRestrictedAdditionalTaintStep(DataFlow::Node src, DataFlow::Node dst) {
40+
any(TaintTracking::AdditionalTaintStep dts).step(src, dst) and
41+
not dst.asExpr() instanceof AddExpr and
42+
not dst.(DataFlow::MethodCallNode).calls(src, "toString")
43+
}
44+
45+
/**
46+
* Holds if data may flow from `src` to `dst` as a number.
47+
*/
48+
predicate isNumericFlowStep(DataFlow::Node src, DataFlow::Node dst) {
49+
// steps that introduce or preserve a number
50+
dst.(DataFlow::PropRead).accesses(src, ["length", "size"])
51+
or
52+
exists(DataFlow::CallNode c |
53+
c = dst and
54+
src = c.getAnArgument()
55+
|
56+
c = DataFlow::globalVarRef("Math").getAMemberCall(_) or
57+
c = DataFlow::globalVarRef(["Number", "parseInt", "parseFloat"]).getACall()
58+
)
59+
or
60+
exists(Expr dstExpr, Expr srcExpr |
61+
dstExpr = dst.asExpr() and
62+
srcExpr = src.asExpr()
63+
|
64+
dstExpr.(BinaryExpr).getAnOperand() = srcExpr and
65+
not dstExpr instanceof AddExpr
66+
or
67+
dstExpr.(PlusExpr).getOperand() = srcExpr
68+
)
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for reasoning about
3+
* resource exhaustion vulnerabilities, as well as extension points for
4+
* adding your own.
5+
*/
6+
7+
import javascript
8+
9+
module ResourceExhaustion {
10+
/**
11+
* A data flow source for resource exhaustion vulnerabilities.
12+
*/
13+
abstract class Source extends DataFlow::Node { }
14+
15+
/**
16+
* A data flow sink for resource exhaustion vulnerabilities.
17+
*/
18+
abstract class Sink extends DataFlow::Node {
19+
/**
20+
* Gets a description of why this is a problematic sink.
21+
*/
22+
abstract string getProblemDescription();
23+
}
24+
25+
/**
26+
* A data flow sanitizer for resource exhaustion vulnerabilities.
27+
*/
28+
abstract class Sanitizer extends DataFlow::Node { }
29+
30+
/**
31+
* A sanitizer that blocks taint flow if the size of a number is limited.
32+
*/
33+
class UpperBoundsCheckSanitizerGuard extends TaintTracking::SanitizerGuardNode,
34+
DataFlow::ValueNode {
35+
override RelationalComparison astNode;
36+
37+
override predicate sanitizes(boolean outcome, Expr e) {
38+
true = outcome and
39+
e = astNode.getLesserOperand()
40+
or
41+
false = outcome and
42+
e = astNode.getGreaterOperand()
43+
}
44+
}
45+
46+
/** A source of remote user input, considered as a data flow source for resource exhaustion vulnerabilities. */
47+
class RemoteFlowSourceAsSource extends Source {
48+
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
49+
}
50+
51+
/**
52+
* A node that determines the repetitions of a string, considered as a data flow sink for resource exhaustion vulnerabilities.
53+
*/
54+
class StringRepetitionSink extends Sink {
55+
StringRepetitionSink() {
56+
exists(DataFlow::MethodCallNode repeat |
57+
repeat.getMethodName() = "repeat" and
58+
this = repeat.getArgument(0)
59+
)
60+
}
61+
62+
override string getProblemDescription() {
63+
result = "This creates a string with a user-controlled length"
64+
}
65+
}
66+
67+
/**
68+
* A node that determines the duration of a timer, considered as a data flow sink for resource exhaustion vulnerabilities.
69+
*/
70+
class TimerDurationSink extends Sink {
71+
TimerDurationSink() {
72+
this = DataFlow::globalVarRef(["setTimeout", "setInterval"]).getACall().getArgument(1) or
73+
this = LodashUnderscore::member(["delay", "throttle", "debounce"]).getACall().getArgument(1)
74+
}
75+
76+
override string getProblemDescription() {
77+
result = "This creates a timer with a user-controlled duration"
78+
}
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
nodes
2+
| documentaion-examples/ResourceExhaustion_timeout.js:5:6:5:59 | delay |
3+
| documentaion-examples/ResourceExhaustion_timeout.js:5:14:5:59 | parseIn ... .delay) |
4+
| documentaion-examples/ResourceExhaustion_timeout.js:5:23:5:46 | url.par ... , true) |
5+
| documentaion-examples/ResourceExhaustion_timeout.js:5:23:5:52 | url.par ... ).query |
6+
| documentaion-examples/ResourceExhaustion_timeout.js:5:23:5:58 | url.par ... y.delay |
7+
| documentaion-examples/ResourceExhaustion_timeout.js:5:33:5:39 | req.url |
8+
| documentaion-examples/ResourceExhaustion_timeout.js:5:33:5:39 | req.url |
9+
| documentaion-examples/ResourceExhaustion_timeout.js:7:16:7:20 | delay |
10+
| documentaion-examples/ResourceExhaustion_timeout.js:7:16:7:20 | delay |
11+
| resource-exhaustion.js:9:7:9:42 | s |
12+
| resource-exhaustion.js:9:11:9:34 | url.par ... , true) |
13+
| resource-exhaustion.js:9:11:9:40 | url.par ... ).query |
14+
| resource-exhaustion.js:9:11:9:42 | url.par ... query.s |
15+
| resource-exhaustion.js:9:21:9:27 | req.url |
16+
| resource-exhaustion.js:9:21:9:27 | req.url |
17+
| resource-exhaustion.js:10:7:10:21 | n |
18+
| resource-exhaustion.js:10:11:10:21 | parseInt(s) |
19+
| resource-exhaustion.js:10:20:10:20 | s |
20+
| resource-exhaustion.js:38:12:38:12 | n |
21+
| resource-exhaustion.js:38:12:38:12 | n |
22+
| resource-exhaustion.js:39:12:39:12 | s |
23+
| resource-exhaustion.js:39:12:39:12 | s |
24+
| resource-exhaustion.js:85:17:85:17 | n |
25+
| resource-exhaustion.js:85:17:85:17 | n |
26+
| resource-exhaustion.js:86:17:86:17 | s |
27+
| resource-exhaustion.js:86:17:86:17 | s |
28+
| resource-exhaustion.js:87:18:87:18 | n |
29+
| resource-exhaustion.js:87:18:87:18 | n |
30+
| resource-exhaustion.js:88:18:88:18 | s |
31+
| resource-exhaustion.js:88:18:88:18 | s |
32+
edges
33+
| documentaion-examples/ResourceExhaustion_timeout.js:5:6:5:59 | delay | documentaion-examples/ResourceExhaustion_timeout.js:7:16:7:20 | delay |
34+
| documentaion-examples/ResourceExhaustion_timeout.js:5:6:5:59 | delay | documentaion-examples/ResourceExhaustion_timeout.js:7:16:7:20 | delay |
35+
| documentaion-examples/ResourceExhaustion_timeout.js:5:14:5:59 | parseIn ... .delay) | documentaion-examples/ResourceExhaustion_timeout.js:5:6:5:59 | delay |
36+
| documentaion-examples/ResourceExhaustion_timeout.js:5:23:5:46 | url.par ... , true) | documentaion-examples/ResourceExhaustion_timeout.js:5:23:5:52 | url.par ... ).query |
37+
| documentaion-examples/ResourceExhaustion_timeout.js:5:23:5:52 | url.par ... ).query | documentaion-examples/ResourceExhaustion_timeout.js:5:23:5:58 | url.par ... y.delay |
38+
| documentaion-examples/ResourceExhaustion_timeout.js:5:23:5:58 | url.par ... y.delay | documentaion-examples/ResourceExhaustion_timeout.js:5:14:5:59 | parseIn ... .delay) |
39+
| documentaion-examples/ResourceExhaustion_timeout.js:5:33:5:39 | req.url | documentaion-examples/ResourceExhaustion_timeout.js:5:23:5:46 | url.par ... , true) |
40+
| documentaion-examples/ResourceExhaustion_timeout.js:5:33:5:39 | req.url | documentaion-examples/ResourceExhaustion_timeout.js:5:23:5:46 | url.par ... , true) |
41+
| resource-exhaustion.js:9:7:9:42 | s | resource-exhaustion.js:10:20:10:20 | s |
42+
| resource-exhaustion.js:9:7:9:42 | s | resource-exhaustion.js:39:12:39:12 | s |
43+
| resource-exhaustion.js:9:7:9:42 | s | resource-exhaustion.js:39:12:39:12 | s |
44+
| resource-exhaustion.js:9:7:9:42 | s | resource-exhaustion.js:86:17:86:17 | s |
45+
| resource-exhaustion.js:9:7:9:42 | s | resource-exhaustion.js:86:17:86:17 | s |
46+
| resource-exhaustion.js:9:7:9:42 | s | resource-exhaustion.js:88:18:88:18 | s |
47+
| resource-exhaustion.js:9:7:9:42 | s | resource-exhaustion.js:88:18:88:18 | s |
48+
| resource-exhaustion.js:9:11:9:34 | url.par ... , true) | resource-exhaustion.js:9:11:9:40 | url.par ... ).query |
49+
| resource-exhaustion.js:9:11:9:40 | url.par ... ).query | resource-exhaustion.js:9:11:9:42 | url.par ... query.s |
50+
| resource-exhaustion.js:9:11:9:42 | url.par ... query.s | resource-exhaustion.js:9:7:9:42 | s |
51+
| resource-exhaustion.js:9:21:9:27 | req.url | resource-exhaustion.js:9:11:9:34 | url.par ... , true) |
52+
| resource-exhaustion.js:9:21:9:27 | req.url | resource-exhaustion.js:9:11:9:34 | url.par ... , true) |
53+
| resource-exhaustion.js:10:7:10:21 | n | resource-exhaustion.js:38:12:38:12 | n |
54+
| resource-exhaustion.js:10:7:10:21 | n | resource-exhaustion.js:38:12:38:12 | n |
55+
| resource-exhaustion.js:10:7:10:21 | n | resource-exhaustion.js:85:17:85:17 | n |
56+
| resource-exhaustion.js:10:7:10:21 | n | resource-exhaustion.js:85:17:85:17 | n |
57+
| resource-exhaustion.js:10:7:10:21 | n | resource-exhaustion.js:87:18:87:18 | n |
58+
| resource-exhaustion.js:10:7:10:21 | n | resource-exhaustion.js:87:18:87:18 | n |
59+
| resource-exhaustion.js:10:11:10:21 | parseInt(s) | resource-exhaustion.js:10:7:10:21 | n |
60+
| resource-exhaustion.js:10:20:10:20 | s | resource-exhaustion.js:10:11:10:21 | parseInt(s) |
61+
#select
62+
| documentaion-examples/ResourceExhaustion_timeout.js:7:16:7:20 | delay | documentaion-examples/ResourceExhaustion_timeout.js:5:33:5:39 | req.url | documentaion-examples/ResourceExhaustion_timeout.js:7:16:7:20 | delay | This creates a timer with a user-controlled duration from $@. | documentaion-examples/ResourceExhaustion_timeout.js:5:33:5:39 | req.url | here |
63+
| resource-exhaustion.js:38:12:38:12 | n | resource-exhaustion.js:9:21:9:27 | req.url | resource-exhaustion.js:38:12:38:12 | n | This creates a string with a user-controlled length from $@. | resource-exhaustion.js:9:21:9:27 | req.url | here |
64+
| resource-exhaustion.js:39:12:39:12 | s | resource-exhaustion.js:9:21:9:27 | req.url | resource-exhaustion.js:39:12:39:12 | s | This creates a string with a user-controlled length from $@. | resource-exhaustion.js:9:21:9:27 | req.url | here |
65+
| resource-exhaustion.js:85:17:85:17 | n | resource-exhaustion.js:9:21:9:27 | req.url | resource-exhaustion.js:85:17:85:17 | n | This creates a timer with a user-controlled duration from $@. | resource-exhaustion.js:9:21:9:27 | req.url | here |
66+
| resource-exhaustion.js:86:17:86:17 | s | resource-exhaustion.js:9:21:9:27 | req.url | resource-exhaustion.js:86:17:86:17 | s | This creates a timer with a user-controlled duration from $@. | resource-exhaustion.js:9:21:9:27 | req.url | here |
67+
| resource-exhaustion.js:87:18:87:18 | n | resource-exhaustion.js:9:21:9:27 | req.url | resource-exhaustion.js:87:18:87:18 | n | This creates a timer with a user-controlled duration from $@. | resource-exhaustion.js:9:21:9:27 | req.url | here |
68+
| resource-exhaustion.js:88:18:88:18 | s | resource-exhaustion.js:9:21:9:27 | req.url | resource-exhaustion.js:88:18:88:18 | s | This creates a timer with a user-controlled duration from $@. | resource-exhaustion.js:9:21:9:27 | req.url | here |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE-770/ResourceExhaustion.ql
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
var http = require("http"),
2+
url = require("url");
3+
4+
var server = http.createServer(function(req, res) {
5+
var delay = parseInt(url.parse(req.url, true).query.delay);
6+
7+
setTimeout(f, delay); // BAD
8+
9+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
var http = require("http"),
2+
url = require("url");
3+
4+
var server = http.createServer(function(req, res) {
5+
var delay = parseInt(url.parse(req.url, true).query.delay);
6+
7+
if (delay > 1000) {
8+
res.statusCode = 400;
9+
res.end("Bad request.");
10+
return;
11+
}
12+
13+
setTimeout(f, delay); // GOOD
14+
15+
});

0 commit comments

Comments
 (0)