Skip to content

Commit 57a976d

Browse files
authored
Merge pull request #555 from xiemaisi/js/invalid-dynamic-method-call
Approved by esben-semmle
2 parents e66691a + 39f1c79 commit 57a976d

22 files changed

+660
-105
lines changed

change-notes/1.19/analysis-javascript.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@
2424
| Enabling Node.js integration for Electron web content renderers (`js/enabling-electron-renderer-node-integration`) | security, frameworks/electron, external/cwe/cwe-094 | Highlights Electron web content renderer preferences with Node.js integration enabled, indicating a violation of [CWE-94](https://cwe.mitre.org/data/definitions/94.html). Results are not shown on LGTM by default. |
2525
| File data in outbound network request | security, external/cwe/cwe-200 | Highlights locations where file data is sent in a network request. Results are not shown on LGTM by default. |
2626
| Host header poisoning in email generation | security, external/cwe/cwe-640 | Highlights code that generates emails with links that can be hijacked by HTTP host header poisoning, indicating a violation of [CWE-640](https://cwe.mitre.org/data/definitions/640.html). Results shown on LGTM by default. |
27-
| Unsafe dynamic method access (`js/unsafe-dynamic-method-access` ) | security, external/cwe/cwe-094 | Highlights code that invokes a user-controlled method on an object with unsafe methods. Results are shown on LGTM by default. |
2827
| Replacement of a substring with itself (`js/identity-replacement`) | correctness, security, external/cwe/cwe-116 | Highlights string replacements that replace a string with itself, which usually indicates a mistake. Results shown on LGTM by default. |
2928
| Stored cross-site scripting (`js/stored-xss`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights uncontrolled stored values flowing into HTML content, indicating a violation of [CWE-079](https://cwe.mitre.org/data/definitions/79.html). Results shown on LGTM by default. |
3029
| Unclear precedence of nested operators (`js/unclear-operator-precedence`) | maintainability, correctness, external/cwe/cwe-783 | Highlights nested binary operators whose relative precedence is easy to misunderstand. Results shown on LGTM by default. |
3130
| Unneeded defensive code | correctness, external/cwe/cwe-570, external/cwe/cwe-571 | Highlights locations where defensive code is not needed. Results are shown on LGTM by default. |
31+
| Unsafe dynamic method access (`js/unsafe-dynamic-method-access` ) | security, external/cwe/cwe-094 | Highlights code that invokes a user-controlled method on an object with unsafe methods. Results are shown on LGTM by default. |
32+
| Unvalidated dynamic method access (`js/unvalidated-dynamic-method-call` ) | security, external/cwe/cwe-754 | Highlights code that invokes a user-controlled method without guarding against exceptional circumstances. Results are shown on LGTM by default. |
3233
| Useless assignment to property | maintainability | Highlights property assignments whose value is always overwritten. Results are shown on LGTM by default. |
3334
| User-controlled data in file | security, external/cwe/cwe-912 | Highlights locations where user-controlled data is written to a file. Results are not shown on LGTM by default. |
3435

@@ -44,11 +45,11 @@
4445
| Duplicate 'if' condition | Lower severity | The severity of this rule has been revised to "warning". |
4546
| Duplicate switch case | Lower severity | The severity of this rule has been revised to "warning". |
4647
| Information exposure through a stack trace | More results | This rule now also flags cases where the entire exception object (including the stack trace) may be exposed. |
47-
| Missing CSRF middleware | Fewer false-positive results | This rule now recognizes additional CSRF protection middlewares. |
4848
| Missing 'this' qualifier | Fewer false-positive results | This rule now recognizes additional intentional calls to global functions. |
49+
| Missing CSRF middleware | Fewer false-positive results | This rule now recognizes additional CSRF protection middlewares. |
4950
| Missing variable declaration | Lower severity | The severity of this rule has been revised to "warning". |
5051
| Regular expression injection | Fewer false-positive results | This rule now identifies calls to `String.prototype.search` with more precision. |
51-
| Remote property injection | Fewer results | The precision of this rule has been revised to "medium". Results are no longer shown on LGTM by default. |
52+
| Remote property injection | Fewer results | The precision of this rule has been revised to "medium". Furthermore, it no longer flags dynamic method calls, which are now handled by two new queries. Results are no longer shown on LGTM by default. |
5253
| Self assignment | Fewer false-positive results | This rule now ignores self-assignments preceded by a JSDoc comment with a `@type` tag. |
5354
| Server-side URL redirect | Fewer false-positive results | This rule now recognizes safe redirects in more cases. |
5455
| Server-side URL redirect | More results | This rule now recognizes redirection calls in more cases. |

javascript/config/suites/javascript/security

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
+ semmlecode-javascript-queries/Security/CWE-640/HostHeaderPoisoningInEmailGeneration.ql: /Security/CWE/CWE-640
2727
+ semmlecode-javascript-queries/Security/CWE-643/XpathInjection.ql: /Security/CWE/CWE-643
2828
+ semmlecode-javascript-queries/Security/CWE-730/RegExpInjection.ql: /Security/CWE/CWE-730
29+
+ semmlecode-javascript-queries/Security/CWE-754/UnvalidatedDynamicMethodCall.ql: /Security/CWE/CWE-754
2930
+ semmlecode-javascript-queries/Security/CWE-770/MissingRateLimiting.ql: /Security/CWE/CWE-770
3031
+ semmlecode-javascript-queries/Security/CWE-776/XmlBomb.ql: /Security/CWE/CWE-776
3132
+ semmlecode-javascript-queries/Security/CWE-798/HardcodedCredentials.ql: /Security/CWE/CWE-798

javascript/ql/src/Security/CWE-400/RemotePropertyInjection.qhelp

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,89 +6,80 @@
66
<overview>
77
<p>
88
Dynamically computing object property names from untrusted input
9-
may have multiple undesired consequences. For example,
10-
if the property access is used as part of a write, an
11-
attacker may overwrite vital properties of objects, such as
12-
<code>__proto__</code>. This attack is known as <i>prototype
13-
pollution attack</i> and may serve as a vehicle for denial-of-service
14-
attacks. A similar attack vector, is to replace the
15-
<code>toString</code> property of an object with a primitive.
16-
Whenever <code>toString</code> is then called on that object, either
17-
explicitly or implicitly as part of a type coercion, an exception
9+
may have multiple undesired consequences. For example,
10+
if the property access is used as part of a write, an
11+
attacker may overwrite vital properties of objects, such as
12+
<code>__proto__</code>. This attack is known as <i>prototype
13+
pollution attack</i> and may serve as a vehicle for denial-of-service
14+
attacks. A similar attack vector, is to replace the
15+
<code>toString</code> property of an object with a primitive.
16+
Whenever <code>toString</code> is then called on that object, either
17+
explicitly or implicitly as part of a type coercion, an exception
1818
will be raised.
1919
</p>
2020

2121
<p>
22-
Moreover, if the dynamically computed property is
23-
used as part of a method call, the attacker may trigger
24-
the execution of unwanted functions such as the
25-
<code>Function</code> constructor or the
26-
<code>eval</code> method, which can be used
27-
for code injection.
28-
</p>
29-
30-
<p>
31-
Additionally, if the name of an HTTP header is user-controlled,
32-
an attacker may exploit this to overwrite security-critical headers
33-
such as <code>Access-Control-Allow-Origin</code> or
22+
Moreover, if the name of an HTTP header is user-controlled,
23+
an attacker may exploit this to overwrite security-critical headers
24+
such as <code>Access-Control-Allow-Origin</code> or
3425
<code>Content-Security-Policy</code>.
3526
</p>
3627
</overview>
3728

3829
<recommendation>
3930
<p>
4031
The most common case in which prototype pollution vulnerabilities arise
41-
is when JavaScript objects are used for implementing map data
42-
structures. This case should be avoided whenever possible by using the
43-
ECMAScript 2015 <code>Map</code> instead. When this is not possible, an
44-
alternative fix is to prepend untrusted input with a marker character
45-
such as <code>$</code>, before using it in properties accesses. In this way,
46-
the attacker does not have access to built-in properties which do not
47-
start with the chosen character.
32+
is when JavaScript objects are used for implementing map data
33+
structures. This case should be avoided whenever possible by using the
34+
ECMAScript 2015 <code>Map</code> instead. When this is not possible, an
35+
alternative fix is to prepend untrusted input with a marker character
36+
such as <code>$</code>, before using it in properties accesses. In this way,
37+
the attacker does not have access to built-in properties which do not
38+
start with the chosen character.
4839
</p>
4940
<p>
50-
When using user input as part of header or method names, a sanitization
51-
step should be performed on the input to ensure that the name does not
52-
clash with existing property and header names such as
53-
<code>__proto__</code> or <code>Content-Security-Policy</code>.
41+
When using user input as part of a header name, a sanitization
42+
step should be performed on the input to ensure that the name does not
43+
clash with existing header names such as
44+
<code>Content-Security-Policy</code>.
5445
</p>
5546
</recommendation>
5647

5748
<example>
5849
<p>
59-
In the example below, the dynamically computed property
60-
<code>prop</code> is accessed on <code>myObj</code> using a
50+
In the example below, the dynamically computed property
51+
<code>prop</code> is accessed on <code>myObj</code> using a
6152
user-controlled value.
6253
</p>
6354

6455
<sample src="examples/RemotePropertyInjection.js"/>
6556

6657
<p>
67-
This is not secure since an attacker may exploit this code to
58+
This is not secure since an attacker may exploit this code to
6859
overwrite the property <code>__proto__</code> with an empty function.
69-
If this happens, the concatenation in the <code>console.log</code>
70-
argument will fail with a confusing message such as
60+
If this happens, the concatenation in the <code>console.log</code>
61+
argument will fail with a confusing message such as
7162
"Function.prototype.toString is not generic". If the application does
7263
not properly handle this error, this scenario may result in a serious
73-
denial-of-service attack. The fix is to prepend the user-controlled
74-
string with a marker character such as <code>$</code> which will
75-
prevent arbitrary property names from being overwritten.
64+
denial-of-service attack. The fix is to prepend the user-controlled
65+
string with a marker character such as <code>$</code> which will
66+
prevent arbitrary property names from being overwritten.
7667
</p>
7768

7869
<sample src="examples/RemotePropertyInjection_fixed.js"/>
7970
</example>
8071

8172
<references>
82-
<li>Prototype pollution attacks:
73+
<li>Prototype pollution attacks:
8374
<a href="https://github.com/electron/electron/pull/9287">electron</a>,
8475
<a href="https://hackerone.com/reports/310443">lodash</a>,
8576
<a href="https://nodesecurity.io/advisories/566">hoek</a>.
8677
</li>
87-
<li> Penetration testing report:
78+
<li> Penetration testing report:
8879
<a href="http://seclists.org/pen-test/2009/Mar/67">
8980
header name injection attack</a>
9081
</li>
91-
<li> npm blog post:
82+
<li> npm blog post:
9283
<a href="https://blog.liftsecurity.io/2015/01/14/the-dangers-of-square-bracket-notation#lift-security">
9384
dangers of square bracket notation</a>
9485
</li>

javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
/**
22
* @name Remote property injection
3-
* @description Allowing writes to arbitrary properties or calls to arbitrary
4-
* methods of an object may lead to denial-of-service attacks.
5-
*
3+
* @description Allowing writes to arbitrary properties of an object may lead to
4+
* denial-of-service attacks.
65
* @kind path-problem
76
* @problem.severity warning
87
* @precision medium
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
JavaScript makes it easy to look up object properties dynamically at runtime. In particular, methods
9+
can be looked up by name and then called. However, if he method name is user controlled, an attacker
10+
could choose a name that makes the application invoke an unexpected method, which may cause a runtime
11+
exception. If this exception is not handled, it could be used to mount a denial-of-service attack.
12+
</p>
13+
<p>
14+
For example, there might not be a method of the given name or the result of the lookup might not be
15+
a function, which would cause the method call to throw a <code>TypeError</code> at runtime.
16+
</p>
17+
<p>
18+
Another, more subtle example is where the result of the lookup is a standard library method from
19+
<code>Object.prototype</code>, which most objects have on their prototype chain. Examples of such
20+
methods include <code>valueOf</code>, <code>hasOwnProperty</code> and <code>__defineSetter__</code>.
21+
If the method call passes the wrong number or kind of arguments to these methods, they will
22+
throw an exception.
23+
</p>
24+
</overview>
25+
26+
<recommendation>
27+
<p>
28+
It is best to avoid dynamic method lookup involving user-controlled names altogether, for instance
29+
by using a <code>Map</code> instead of a plain object.
30+
</p>
31+
<p>
32+
If the dynamic method lookup cannot be avoided, consider whitelisting permitted method names. At
33+
the very least, check that the method is an own property and not inherited from the prototype object.
34+
If the object on which the method is looked up contains properties that are not methods, you
35+
should additionally check that the result of the lookup is a function. Even if the object only
36+
contains methods it is still a good idea to perform this check in case other properties are
37+
added to the object later on.
38+
</p>
39+
</recommendation>
40+
41+
<example>
42+
<p>
43+
In the following example, an HTTP request parameter <code>action</code> property is used to dynamically
44+
look up a function in the <code>actions</code> map, which is then invoked with the <code>payload</code>
45+
parameter as its argument.
46+
</p>
47+
48+
<sample src="examples/UnvalidatedDynamicMethodCall.js" />
49+
50+
<p>
51+
The intention is to allow clients to invoke the <code>play</code> or <code>pause</code> method, but there
52+
is no check that <code>action</code> is actually the name of a method stored in <code>actions</code>.
53+
If, for example, <code>action</code> is <code>rewind</code>, <code>action</code> will be <code>undefined</code>
54+
and the call will result in a runtime error.
55+
</p>
56+
57+
<p>
58+
The easiest way to prevent this is to turn <code>actions</code> into a <code>Map</code> and using
59+
<code>Map.prototype.has</code> to check whether the method name is valid before looking it up.
60+
</p>
61+
62+
<sample src="examples/UnvalidatedDynamicMethodCallGood.js" />
63+
64+
<p>
65+
If <code>actions</code> cannot be turned into a <code>Map</code>, a <code>hasOwnProperty</code>
66+
check should be added to validate the method name:
67+
</p>
68+
69+
<sample src="examples/UnvalidatedDynamicMethodCallGood2.js" />
70+
71+
</example>
72+
73+
<references>
74+
<li>
75+
OWASP:
76+
<a href="https://www.owasp.org/index.php/Denial_of_Service">Denial of Service</a>.
77+
</li>
78+
<li>
79+
MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map">Map</a>.
80+
</li>
81+
<li>
82+
MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype">Object.prototype</a>.
83+
</li>
84+
</references>
85+
86+
</qhelp>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @name Unvalidated dynamic method call
3+
* @description Calling a method with a user-controlled name may dispatch to
4+
* an unexpected target, which could cause an exception.
5+
* @kind path-problem
6+
* @problem.severity warning
7+
* @precision high
8+
* @id js/unvalidated-dynamic-method-call
9+
* @tags security
10+
* external/cwe/cwe-754
11+
*/
12+
13+
import javascript
14+
import semmle.javascript.security.dataflow.UnvalidatedDynamicMethodCall::UnvalidatedDynamicMethodCall
15+
import DataFlow::PathGraph
16+
17+
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
18+
where cfg.hasFlowPath(source, sink)
19+
select sink.getNode(), source, sink,
20+
"Invocation of method with $@ name may dispatch to unexpected target and cause an exception.",
21+
source.getNode(), "user-controlled"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
var express = require('express');
2+
var app = express();
3+
4+
var actions = {
5+
play(data) {
6+
// ...
7+
},
8+
pause(data) {
9+
// ...
10+
}
11+
}
12+
13+
app.get('/perform/:action/:payload', function(req, res) {
14+
let action = actions[req.params.action];
15+
// BAD: `action` may not be a function
16+
res.end(action(req.params.payload));
17+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
var express = require('express');
2+
var app = express();
3+
4+
var actions = new Map();
5+
actions.put("play", function play(data) {
6+
// ...
7+
});
8+
actions.put("pause", function pause(data) {
9+
// ...
10+
});
11+
12+
app.get('/perform/:action/:payload', function(req, res) {
13+
if (actions.has(req.params.action)) {
14+
let action = actions.get(req.params.action);
15+
// GOOD: `action` is either the `play` or the `pause` function from above
16+
res.end(action(req.params.payload));
17+
} else {
18+
res.end("Unsupported action.");
19+
}
20+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
var express = require('express');
2+
var app = express();
3+
4+
var actions = {
5+
play(data) {
6+
// ...
7+
},
8+
pause(data) {
9+
// ...
10+
}
11+
}
12+
13+
app.get('/perform/:action/:payload', function(req, res) {
14+
if (actions.hasOwnProperty(req.params.action)) {
15+
let action = actions[req.params.action];
16+
if (typeof action === 'function') {
17+
// GOOD: `action` is an own method of `actions`
18+
res.end(action(req.params.payload));
19+
return;
20+
}
21+
}
22+
res.end("Unsupported action.");
23+
});

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,12 @@ module PropertyInjection {
3636
// Assume that a value that is invoked can refer to a function.
3737
exists (node.getAnInvocation())
3838
}
39+
40+
/**
41+
* Holds if the `node` is of form `Object.create(null)` and so it has no prototype.
42+
*/
43+
predicate isPrototypeLessObject(DataFlow::MethodCallNode node) {
44+
node = DataFlow::globalVarRef("Object").getAMethodCall("create") and
45+
node.getArgument(0).asExpr() instanceof NullLiteral
46+
}
3947
}

0 commit comments

Comments
 (0)