mirror of
https://github.com/github/codeql.git
synced 2026-05-01 19:55:15 +02:00
Merge pull request #555 from xiemaisi/js/invalid-dynamic-method-call
Approved by esben-semmle
This commit is contained in:
@@ -24,11 +24,12 @@
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| Useless assignment to property | maintainability | Highlights property assignments whose value is always overwritten. Results are shown on LGTM by default. |
|
||||
| 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. |
|
||||
|
||||
@@ -44,11 +45,11 @@
|
||||
| Duplicate 'if' condition | Lower severity | The severity of this rule has been revised to "warning". |
|
||||
| Duplicate switch case | Lower severity | The severity of this rule has been revised to "warning". |
|
||||
| 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. |
|
||||
| Missing CSRF middleware | Fewer false-positive results | This rule now recognizes additional CSRF protection middlewares. |
|
||||
| Missing 'this' qualifier | Fewer false-positive results | This rule now recognizes additional intentional calls to global functions. |
|
||||
| Missing CSRF middleware | Fewer false-positive results | This rule now recognizes additional CSRF protection middlewares. |
|
||||
| Missing variable declaration | Lower severity | The severity of this rule has been revised to "warning". |
|
||||
| Regular expression injection | Fewer false-positive results | This rule now identifies calls to `String.prototype.search` with more precision. |
|
||||
| Remote property injection | Fewer results | The precision of this rule has been revised to "medium". Results are no longer shown on LGTM by default. |
|
||||
| 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. |
|
||||
| Self assignment | Fewer false-positive results | This rule now ignores self-assignments preceded by a JSDoc comment with a `@type` tag. |
|
||||
| Server-side URL redirect | Fewer false-positive results | This rule now recognizes safe redirects in more cases. |
|
||||
| Server-side URL redirect | More results | This rule now recognizes redirection calls in more cases. |
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
+ semmlecode-javascript-queries/Security/CWE-640/HostHeaderPoisoningInEmailGeneration.ql: /Security/CWE/CWE-640
|
||||
+ semmlecode-javascript-queries/Security/CWE-643/XpathInjection.ql: /Security/CWE/CWE-643
|
||||
+ semmlecode-javascript-queries/Security/CWE-730/RegExpInjection.ql: /Security/CWE/CWE-730
|
||||
+ semmlecode-javascript-queries/Security/CWE-754/UnvalidatedDynamicMethodCall.ql: /Security/CWE/CWE-754
|
||||
+ semmlecode-javascript-queries/Security/CWE-770/MissingRateLimiting.ql: /Security/CWE/CWE-770
|
||||
+ semmlecode-javascript-queries/Security/CWE-776/XmlBomb.ql: /Security/CWE/CWE-776
|
||||
+ semmlecode-javascript-queries/Security/CWE-798/HardcodedCredentials.ql: /Security/CWE/CWE-798
|
||||
|
||||
@@ -6,31 +6,22 @@
|
||||
<overview>
|
||||
<p>
|
||||
Dynamically computing object property names from untrusted input
|
||||
may have multiple undesired consequences. For example,
|
||||
if the property access is used as part of a write, an
|
||||
attacker may overwrite vital properties of objects, such as
|
||||
<code>__proto__</code>. This attack is known as <i>prototype
|
||||
pollution attack</i> and may serve as a vehicle for denial-of-service
|
||||
attacks. A similar attack vector, is to replace the
|
||||
<code>toString</code> property of an object with a primitive.
|
||||
Whenever <code>toString</code> is then called on that object, either
|
||||
explicitly or implicitly as part of a type coercion, an exception
|
||||
may have multiple undesired consequences. For example,
|
||||
if the property access is used as part of a write, an
|
||||
attacker may overwrite vital properties of objects, such as
|
||||
<code>__proto__</code>. This attack is known as <i>prototype
|
||||
pollution attack</i> and may serve as a vehicle for denial-of-service
|
||||
attacks. A similar attack vector, is to replace the
|
||||
<code>toString</code> property of an object with a primitive.
|
||||
Whenever <code>toString</code> is then called on that object, either
|
||||
explicitly or implicitly as part of a type coercion, an exception
|
||||
will be raised.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Moreover, if the dynamically computed property is
|
||||
used as part of a method call, the attacker may trigger
|
||||
the execution of unwanted functions such as the
|
||||
<code>Function</code> constructor or the
|
||||
<code>eval</code> method, which can be used
|
||||
for code injection.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Additionally, if the name of an HTTP header is user-controlled,
|
||||
an attacker may exploit this to overwrite security-critical headers
|
||||
such as <code>Access-Control-Allow-Origin</code> or
|
||||
Moreover, if the name of an HTTP header is user-controlled,
|
||||
an attacker may exploit this to overwrite security-critical headers
|
||||
such as <code>Access-Control-Allow-Origin</code> or
|
||||
<code>Content-Security-Policy</code>.
|
||||
</p>
|
||||
</overview>
|
||||
@@ -38,57 +29,57 @@
|
||||
<recommendation>
|
||||
<p>
|
||||
The most common case in which prototype pollution vulnerabilities arise
|
||||
is when JavaScript objects are used for implementing map data
|
||||
structures. This case should be avoided whenever possible by using the
|
||||
ECMAScript 2015 <code>Map</code> instead. When this is not possible, an
|
||||
alternative fix is to prepend untrusted input with a marker character
|
||||
such as <code>$</code>, before using it in properties accesses. In this way,
|
||||
the attacker does not have access to built-in properties which do not
|
||||
start with the chosen character.
|
||||
is when JavaScript objects are used for implementing map data
|
||||
structures. This case should be avoided whenever possible by using the
|
||||
ECMAScript 2015 <code>Map</code> instead. When this is not possible, an
|
||||
alternative fix is to prepend untrusted input with a marker character
|
||||
such as <code>$</code>, before using it in properties accesses. In this way,
|
||||
the attacker does not have access to built-in properties which do not
|
||||
start with the chosen character.
|
||||
</p>
|
||||
<p>
|
||||
When using user input as part of header or method names, a sanitization
|
||||
step should be performed on the input to ensure that the name does not
|
||||
clash with existing property and header names such as
|
||||
<code>__proto__</code> or <code>Content-Security-Policy</code>.
|
||||
When using user input as part of a header name, a sanitization
|
||||
step should be performed on the input to ensure that the name does not
|
||||
clash with existing header names such as
|
||||
<code>Content-Security-Policy</code>.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the example below, the dynamically computed property
|
||||
<code>prop</code> is accessed on <code>myObj</code> using a
|
||||
In the example below, the dynamically computed property
|
||||
<code>prop</code> is accessed on <code>myObj</code> using a
|
||||
user-controlled value.
|
||||
</p>
|
||||
|
||||
<sample src="examples/RemotePropertyInjection.js"/>
|
||||
|
||||
<p>
|
||||
This is not secure since an attacker may exploit this code to
|
||||
This is not secure since an attacker may exploit this code to
|
||||
overwrite the property <code>__proto__</code> with an empty function.
|
||||
If this happens, the concatenation in the <code>console.log</code>
|
||||
argument will fail with a confusing message such as
|
||||
If this happens, the concatenation in the <code>console.log</code>
|
||||
argument will fail with a confusing message such as
|
||||
"Function.prototype.toString is not generic". If the application does
|
||||
not properly handle this error, this scenario may result in a serious
|
||||
denial-of-service attack. The fix is to prepend the user-controlled
|
||||
string with a marker character such as <code>$</code> which will
|
||||
prevent arbitrary property names from being overwritten.
|
||||
denial-of-service attack. The fix is to prepend the user-controlled
|
||||
string with a marker character such as <code>$</code> which will
|
||||
prevent arbitrary property names from being overwritten.
|
||||
</p>
|
||||
|
||||
<sample src="examples/RemotePropertyInjection_fixed.js"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Prototype pollution attacks:
|
||||
<li>Prototype pollution attacks:
|
||||
<a href="https://github.com/electron/electron/pull/9287">electron</a>,
|
||||
<a href="https://hackerone.com/reports/310443">lodash</a>,
|
||||
<a href="https://nodesecurity.io/advisories/566">hoek</a>.
|
||||
</li>
|
||||
<li> Penetration testing report:
|
||||
<li> Penetration testing report:
|
||||
<a href="http://seclists.org/pen-test/2009/Mar/67">
|
||||
header name injection attack</a>
|
||||
</li>
|
||||
<li> npm blog post:
|
||||
<li> npm blog post:
|
||||
<a href="https://blog.liftsecurity.io/2015/01/14/the-dangers-of-square-bracket-notation#lift-security">
|
||||
dangers of square bracket notation</a>
|
||||
</li>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* @name Remote property injection
|
||||
* @description Allowing writes to arbitrary properties or calls to arbitrary
|
||||
* methods of an object may lead to denial-of-service attacks.
|
||||
*
|
||||
* @description Allowing writes to arbitrary properties of an object may lead to
|
||||
* denial-of-service attacks.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
JavaScript makes it easy to look up object properties dynamically at runtime. In particular, methods
|
||||
can be looked up by name and then called. However, if he method name is user controlled, an attacker
|
||||
could choose a name that makes the application invoke an unexpected method, which may cause a runtime
|
||||
exception. If this exception is not handled, it could be used to mount a denial-of-service attack.
|
||||
</p>
|
||||
<p>
|
||||
For example, there might not be a method of the given name or the result of the lookup might not be
|
||||
a function, which would cause the method call to throw a <code>TypeError</code> at runtime.
|
||||
</p>
|
||||
<p>
|
||||
Another, more subtle example is where the result of the lookup is a standard library method from
|
||||
<code>Object.prototype</code>, which most objects have on their prototype chain. Examples of such
|
||||
methods include <code>valueOf</code>, <code>hasOwnProperty</code> and <code>__defineSetter__</code>.
|
||||
If the method call passes the wrong number or kind of arguments to these methods, they will
|
||||
throw an exception.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
It is best to avoid dynamic method lookup involving user-controlled names altogether, for instance
|
||||
by using a <code>Map</code> instead of a plain object.
|
||||
</p>
|
||||
<p>
|
||||
If the dynamic method lookup cannot be avoided, consider whitelisting permitted method names. At
|
||||
the very least, check that the method is an own property and not inherited from the prototype object.
|
||||
If the object on which the method is looked up contains properties that are not methods, you
|
||||
should additionally check that the result of the lookup is a function. Even if the object only
|
||||
contains methods it is still a good idea to perform this check in case other properties are
|
||||
added to the object later on.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the following example, an HTTP request parameter <code>action</code> property is used to dynamically
|
||||
look up a function in the <code>actions</code> map, which is then invoked with the <code>payload</code>
|
||||
parameter as its argument.
|
||||
</p>
|
||||
|
||||
<sample src="examples/UnvalidatedDynamicMethodCall.js" />
|
||||
|
||||
<p>
|
||||
The intention is to allow clients to invoke the <code>play</code> or <code>pause</code> method, but there
|
||||
is no check that <code>action</code> is actually the name of a method stored in <code>actions</code>.
|
||||
If, for example, <code>action</code> is <code>rewind</code>, <code>action</code> will be <code>undefined</code>
|
||||
and the call will result in a runtime error.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The easiest way to prevent this is to turn <code>actions</code> into a <code>Map</code> and using
|
||||
<code>Map.prototype.has</code> to check whether the method name is valid before looking it up.
|
||||
</p>
|
||||
|
||||
<sample src="examples/UnvalidatedDynamicMethodCallGood.js" />
|
||||
|
||||
<p>
|
||||
If <code>actions</code> cannot be turned into a <code>Map</code>, a <code>hasOwnProperty</code>
|
||||
check should be added to validate the method name:
|
||||
</p>
|
||||
|
||||
<sample src="examples/UnvalidatedDynamicMethodCallGood2.js" />
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/Denial_of_Service">Denial of Service</a>.
|
||||
</li>
|
||||
<li>
|
||||
MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map">Map</a>.
|
||||
</li>
|
||||
<li>
|
||||
MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype">Object.prototype</a>.
|
||||
</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @name Unvalidated dynamic method call
|
||||
* @description Calling a method with a user-controlled name may dispatch to
|
||||
* an unexpected target, which could cause an exception.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id js/unvalidated-dynamic-method-call
|
||||
* @tags security
|
||||
* external/cwe/cwe-754
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.UnvalidatedDynamicMethodCall::UnvalidatedDynamicMethodCall
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"Invocation of method with $@ name may dispatch to unexpected target and cause an exception.",
|
||||
source.getNode(), "user-controlled"
|
||||
@@ -0,0 +1,17 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
var actions = {
|
||||
play(data) {
|
||||
// ...
|
||||
},
|
||||
pause(data) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/perform/:action/:payload', function(req, res) {
|
||||
let action = actions[req.params.action];
|
||||
// BAD: `action` may not be a function
|
||||
res.end(action(req.params.payload));
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
var actions = new Map();
|
||||
actions.put("play", function play(data) {
|
||||
// ...
|
||||
});
|
||||
actions.put("pause", function pause(data) {
|
||||
// ...
|
||||
});
|
||||
|
||||
app.get('/perform/:action/:payload', function(req, res) {
|
||||
if (actions.has(req.params.action)) {
|
||||
let action = actions.get(req.params.action);
|
||||
// GOOD: `action` is either the `play` or the `pause` function from above
|
||||
res.end(action(req.params.payload));
|
||||
} else {
|
||||
res.end("Unsupported action.");
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
var actions = {
|
||||
play(data) {
|
||||
// ...
|
||||
},
|
||||
pause(data) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/perform/:action/:payload', function(req, res) {
|
||||
if (actions.hasOwnProperty(req.params.action)) {
|
||||
let action = actions[req.params.action];
|
||||
if (typeof action === 'function') {
|
||||
// GOOD: `action` is an own method of `actions`
|
||||
res.end(action(req.params.payload));
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.end("Unsupported action.");
|
||||
});
|
||||
@@ -36,4 +36,12 @@ module PropertyInjection {
|
||||
// Assume that a value that is invoked can refer to a function.
|
||||
exists (node.getAnInvocation())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `node` is of form `Object.create(null)` and so it has no prototype.
|
||||
*/
|
||||
predicate isPrototypeLessObject(DataFlow::MethodCallNode node) {
|
||||
node = DataFlow::globalVarRef("Object").getAMethodCall("create") and
|
||||
node.getArgument(0).asExpr() instanceof NullLiteral
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Provides a taint tracking configuration for reasoning about injections in
|
||||
* property names, used either for writing into a property, into a header or
|
||||
* Provides a taint tracking configuration for reasoning about injections in
|
||||
* property names, used either for writing into a property, into a header or
|
||||
* for calling an object's method.
|
||||
*/
|
||||
|
||||
@@ -18,11 +18,11 @@ module RemotePropertyInjection {
|
||||
* A data flow sink for remote property injection.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
|
||||
|
||||
/**
|
||||
* Gets a string to identify the different types of sinks.
|
||||
*/
|
||||
abstract string getMessage();
|
||||
abstract string getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,58 +52,40 @@ module RemotePropertyInjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source for remote property
|
||||
* injection.
|
||||
* A source of remote user input, considered as a flow source for remote property
|
||||
* injection.
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source {
|
||||
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for property writes with dynamically computed property name.
|
||||
* A sink for property writes with dynamically computed property name.
|
||||
*/
|
||||
class PropertyWriteSink extends Sink, DataFlow::ValueNode {
|
||||
PropertyWriteSink() {
|
||||
exists (DataFlow::PropWrite pw | astNode = pw.getPropertyNameExpr()) or
|
||||
exists (DeleteExpr expr | expr.getOperand().(PropAccess).getPropertyNameExpr() = astNode)
|
||||
exists (DataFlow::PropWrite pw | astNode = pw.getPropertyNameExpr()) or
|
||||
exists (DeleteExpr expr | expr.getOperand().(PropAccess).getPropertyNameExpr() = astNode)
|
||||
}
|
||||
|
||||
override string getMessage() {
|
||||
result = " a property name to write to."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A sink for method calls using dynamically computed method names.
|
||||
*/
|
||||
class MethodCallSink extends Sink, DataFlow::ValueNode {
|
||||
MethodCallSink() {
|
||||
exists (DataFlow::PropRead pr | astNode = pr.getPropertyNameExpr() |
|
||||
exists (pr.getAnInvocation()) and
|
||||
|
||||
// Omit sinks covered by the UnsafeDynamicMethodAccess query
|
||||
not PropertyInjection::hasUnsafeMethods(pr.getBase().getALocalSource())
|
||||
)
|
||||
}
|
||||
|
||||
override string getMessage() {
|
||||
result = " a method name to be called."
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for HTTP header writes with dynamically computed header name.
|
||||
* This sink avoids double-flagging by ignoring `SetMultipleHeaders` since
|
||||
* the multiple headers use case consists of an objects containing different
|
||||
* header names as properties. This case is already handled by
|
||||
* `PropertyWriteSink`.
|
||||
* A sink for HTTP header writes with dynamically computed header name.
|
||||
* This sink avoids double-flagging by ignoring `SetMultipleHeaders` since
|
||||
* the multiple headers use case consists of an objects containing different
|
||||
* header names as properties. This case is already handled by
|
||||
* `PropertyWriteSink`.
|
||||
*/
|
||||
class HeaderNameSink extends Sink, DataFlow::ValueNode {
|
||||
HeaderNameSink() {
|
||||
exists (HTTP::ExplicitHeaderDefinition hd |
|
||||
not hd instanceof Express::SetMultipleHeaders and
|
||||
astNode = hd.getNameExpr()
|
||||
)
|
||||
exists (HTTP::ExplicitHeaderDefinition hd |
|
||||
not hd instanceof Express::SetMultipleHeaders and
|
||||
astNode = hd.getNameExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override string getMessage() {
|
||||
|
||||
@@ -73,14 +73,6 @@ module UnsafeDynamicMethodAccess {
|
||||
PropertyInjection::hasUnsafeMethods(node) // Redefined here so custom queries can override it
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `node` is of form `Object.create(null)` and so it has no prototype.
|
||||
*/
|
||||
predicate isPrototypeLessObject(DataFlow::MethodCallNode node) {
|
||||
node = DataFlow::globalVarRef("Object").getAMethodCall("create") and
|
||||
node.getArgument(0).asExpr() instanceof NullLiteral
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel, DataFlow::FlowLabel dstlabel) {
|
||||
// Reading a property of the global object or of a function
|
||||
exists (DataFlow::PropRead read |
|
||||
@@ -92,7 +84,7 @@ module UnsafeDynamicMethodAccess {
|
||||
or
|
||||
// Reading a chain of properties from any object with a prototype can lead to Function
|
||||
exists (PropertyProjection proj |
|
||||
not isPrototypeLessObject(proj.getObject().getALocalSource()) and
|
||||
not PropertyInjection::isPrototypeLessObject(proj.getObject().getALocalSource()) and
|
||||
src = proj.getASelector() and
|
||||
dst = proj and
|
||||
(srclabel = data() or srclabel = taint()) and
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about unvalidated dynamic
|
||||
* method calls.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.frameworks.Express
|
||||
import PropertyInjectionShared
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
|
||||
module UnvalidatedDynamicMethodCall {
|
||||
private import DataFlow::FlowLabel
|
||||
|
||||
/**
|
||||
* A data flow source for unvalidated dynamic method calls.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the flow label relevant for this source.
|
||||
*/
|
||||
DataFlow::FlowLabel getFlowLabel() {
|
||||
result = data()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow sink for unvalidated dynamic method calls.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the flow label relevant for this sink
|
||||
*/
|
||||
abstract DataFlow::FlowLabel getFlowLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer for unvalidated dynamic method calls.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node {
|
||||
abstract predicate sanitizes(DataFlow::Node source, DataFlow::Node sink, DataFlow::FlowLabel lbl);
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow label describing values read from a user-controlled property that
|
||||
* may not be functions.
|
||||
*/
|
||||
private class MaybeNonFunction extends DataFlow::FlowLabel {
|
||||
MaybeNonFunction() { this = "MaybeNonFunction" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow label describing values read from a user-controlled property that
|
||||
* may originate from a prototype object.
|
||||
*/
|
||||
private class MaybeFromProto extends DataFlow::FlowLabel {
|
||||
MaybeFromProto() { this = "MaybeFromProto" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about unvalidated dynamic method calls.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnvalidatedDynamicMethodCall" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
|
||||
source.(Source).getFlowLabel() = label
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
|
||||
sink.(Sink).getFlowLabel() = label
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node nd) {
|
||||
super.isSanitizer(nd) or
|
||||
nd instanceof PropertyInjection::Sanitizer
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel, DataFlow::FlowLabel dstlabel) {
|
||||
exists (DataFlow::PropRead read |
|
||||
src = read.getPropertyNameExpr().flow() and
|
||||
dst = read and
|
||||
(srclabel = data() or srclabel = taint()) and
|
||||
(dstlabel instanceof MaybeNonFunction
|
||||
or
|
||||
// a property of `Object.create(null)` cannot come from a prototype
|
||||
not PropertyInjection::isPrototypeLessObject(read.getBase().getALocalSource()) and
|
||||
dstlabel instanceof MaybeFromProto) and
|
||||
// avoid overlapping results with unsafe dynamic method access query
|
||||
not PropertyInjection::hasUnsafeMethods(read.getBase().getALocalSource())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a source for unvalidated dynamic method calls.
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source {
|
||||
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
|
||||
}
|
||||
|
||||
/**
|
||||
* The page URL considered as a flow source for unvalidated dynamic method calls.
|
||||
*/
|
||||
class DocumentUrlAsSource extends Source {
|
||||
DocumentUrlAsSource() { isDocumentURL(asExpr()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A function invocation of an unsafe function, as a sink for remote unvalidated dynamic method calls.
|
||||
*/
|
||||
class CalleeAsSink extends Sink {
|
||||
InvokeExpr invk;
|
||||
|
||||
CalleeAsSink() {
|
||||
this = invk.getCallee().flow() and
|
||||
// don't flag invocations inside a try-catch
|
||||
not invk.getASuccessor() instanceof CatchClause
|
||||
}
|
||||
|
||||
override DataFlow::FlowLabel getFlowLabel() {
|
||||
result instanceof MaybeNonFunction and
|
||||
// don't flag if the type inference can prove that it is a function;
|
||||
// this complements the `FunctionCheck` sanitizer below: the type inference can
|
||||
// detect more checks locally, but doesn't provide inter-procedural reasoning
|
||||
this.analyze().getAType() != TTFunction()
|
||||
or
|
||||
result instanceof MaybeFromProto
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A check of the form `typeof x === 'function'`, which sanitizes away the `MaybeNonFunction`
|
||||
* taint kind.
|
||||
*/
|
||||
class FunctionCheck extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::ValueNode {
|
||||
override EqualityTest astNode;
|
||||
TypeofExpr t;
|
||||
|
||||
FunctionCheck() {
|
||||
astNode.getAnOperand().getStringValue() = "function" and
|
||||
astNode.getAnOperand().getUnderlyingValue() = t
|
||||
}
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
outcome = astNode.getPolarity() and
|
||||
e = t.getOperand().getUnderlyingValue()
|
||||
}
|
||||
|
||||
override DataFlow::FlowLabel getALabel() {
|
||||
result instanceof MaybeNonFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ nodes
|
||||
| tst.js:8:13:8:52 | myCoolL ... rolled) |
|
||||
| tst.js:8:28:8:51 | req.que ... trolled |
|
||||
| tst.js:9:8:9:11 | prop |
|
||||
| tst.js:11:16:11:19 | prop |
|
||||
| tst.js:13:15:13:18 | prop |
|
||||
| tst.js:14:31:14:34 | prop |
|
||||
| tst.js:16:10:16:13 | prop |
|
||||
@@ -12,7 +11,6 @@ nodes
|
||||
| tstNonExpr.js:8:17:8:23 | userVal |
|
||||
edges
|
||||
| tst.js:8:6:8:52 | prop | tst.js:9:8:9:11 | prop |
|
||||
| tst.js:8:6:8:52 | prop | tst.js:11:16:11:19 | prop |
|
||||
| tst.js:8:6:8:52 | prop | tst.js:13:15:13:18 | prop |
|
||||
| tst.js:8:6:8:52 | prop | tst.js:14:31:14:34 | prop |
|
||||
| tst.js:8:6:8:52 | prop | tst.js:16:10:16:13 | prop |
|
||||
@@ -22,7 +20,6 @@ edges
|
||||
| tstNonExpr.js:5:17:5:23 | req.url | tstNonExpr.js:5:7:5:23 | userVal |
|
||||
#select
|
||||
| tst.js:9:8:9:11 | prop | tst.js:8:28:8:51 | req.que ... trolled | tst.js:9:8:9:11 | prop | A $@ is used as a property name to write to. | tst.js:8:28:8:51 | req.que ... trolled | user-provided value |
|
||||
| tst.js:11:16:11:19 | prop | tst.js:8:28:8:51 | req.que ... trolled | tst.js:11:16:11:19 | prop | A $@ is used as a method name to be called. | tst.js:8:28:8:51 | req.que ... trolled | user-provided value |
|
||||
| tst.js:13:15:13:18 | prop | tst.js:8:28:8:51 | req.que ... trolled | tst.js:13:15:13:18 | prop | A $@ is used as a property name to write to. | tst.js:8:28:8:51 | req.que ... trolled | user-provided value |
|
||||
| tst.js:14:31:14:34 | prop | tst.js:8:28:8:51 | req.que ... trolled | tst.js:14:31:14:34 | prop | A $@ is used as a property name to write to. | tst.js:8:28:8:51 | req.que ... trolled | user-provided value |
|
||||
| tst.js:16:10:16:13 | prop | tst.js:8:28:8:51 | req.que ... trolled | tst.js:16:10:16:13 | prop | A $@ is used as a property name to write to. | tst.js:8:28:8:51 | req.que ... trolled | user-provided value |
|
||||
|
||||
@@ -5,14 +5,14 @@ var myObj = {}
|
||||
|
||||
app.get('/user/:id', function(req, res) {
|
||||
myCoolLocalFct(req.query.userControlled);
|
||||
var prop = myCoolLocalFct(req.query.userControlled);
|
||||
var prop = myCoolLocalFct(req.query.userControlled);
|
||||
myObj[prop] = 23; // NOT OK
|
||||
myObj.prop = 23; // OK
|
||||
var x = myObj[prop]; // NOT OK
|
||||
x(23);
|
||||
delete myObj[prop]; // NOT OK
|
||||
var x = myObj[prop]; // NOT OK, but flagged by different query
|
||||
x(23);
|
||||
delete myObj[prop]; // NOT OK
|
||||
Object.defineProperty(myObj, prop, {value: 24}); // NOT OK
|
||||
var headers = {};
|
||||
var headers = {};
|
||||
headers[prop] = 42; // NOT OK
|
||||
res.set(headers);
|
||||
myCoolLocalFct[req.query.x](); // OK - flagged by method name injection
|
||||
@@ -21,5 +21,5 @@ app.get('/user/:id', function(req, res) {
|
||||
function myCoolLocalFct(x) {
|
||||
var result = x;
|
||||
return result.substring(0, result.length);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// copied from tests for `UnsafeDynamicMethodAccess.ql` to check that they do not overlap
|
||||
|
||||
let obj = {};
|
||||
|
||||
window.addEventListener('message', (ev) => {
|
||||
let message = JSON.parse(ev.data);
|
||||
window[message.name](message.payload); // NOT OK, but reported by UnsafeDynamicMethodAccess.ql
|
||||
new window[message.name](message.payload); // NOT OK, but reported by UnsafeDynamicMethodAccess.ql
|
||||
window["HTMLElement" + message.name](message.payload); // OK - concatenation restricts choice of methods
|
||||
window[`HTMLElement${message.name}`](message.payload); // OK - concatenation restricts choice of methods
|
||||
|
||||
function f() {}
|
||||
f[message.name](message.payload)(); // NOT OK, but reported by UnsafeDynamicMethodAccess.ql
|
||||
|
||||
obj[message.name](message.payload); // NOT OK
|
||||
|
||||
window[ev](ev); // NOT OK, but reported by UnsafeDynamicMethodAccess.ql
|
||||
});
|
||||
@@ -0,0 +1,134 @@
|
||||
nodes
|
||||
| UnsafeDynamicMethodAccess.js:5:37:5:38 | ev |
|
||||
| UnsafeDynamicMethodAccess.js:6:9:6:37 | message |
|
||||
| UnsafeDynamicMethodAccess.js:6:19:6:37 | JSON.parse(ev.data) |
|
||||
| UnsafeDynamicMethodAccess.js:6:30:6:31 | ev |
|
||||
| UnsafeDynamicMethodAccess.js:6:30:6:36 | ev.data |
|
||||
| UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] |
|
||||
| UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] |
|
||||
| UnsafeDynamicMethodAccess.js:15:9:15:15 | message |
|
||||
| UnsafeDynamicMethodAccess.js:15:9:15:20 | message.name |
|
||||
| UnvalidatedDynamicMethodCall.js:14:7:14:41 | action |
|
||||
| UnvalidatedDynamicMethodCall.js:14:7:14:41 | action |
|
||||
| UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] |
|
||||
| UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] |
|
||||
| UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action |
|
||||
| UnvalidatedDynamicMethodCall.js:15:11:15:16 | action |
|
||||
| UnvalidatedDynamicMethodCall.js:15:11:15:16 | action |
|
||||
| tst.js:6:39:6:40 | ev |
|
||||
| tst.js:7:9:7:39 | name |
|
||||
| tst.js:7:16:7:34 | JSON.parse(ev.data) |
|
||||
| tst.js:7:16:7:39 | JSON.pa ... a).name |
|
||||
| tst.js:7:27:7:28 | ev |
|
||||
| tst.js:7:27:7:33 | ev.data |
|
||||
| tst.js:9:5:9:16 | obj[ev.data] |
|
||||
| tst.js:9:5:9:16 | obj[ev.data] |
|
||||
| tst.js:9:9:9:10 | ev |
|
||||
| tst.js:9:9:9:15 | ev.data |
|
||||
| tst.js:11:5:11:13 | obj[name] |
|
||||
| tst.js:11:5:11:13 | obj[name] |
|
||||
| tst.js:11:9:11:12 | name |
|
||||
| tst.js:17:9:17:22 | fn |
|
||||
| tst.js:17:9:17:22 | fn |
|
||||
| tst.js:17:14:17:22 | obj[name] |
|
||||
| tst.js:17:14:17:22 | obj[name] |
|
||||
| tst.js:17:18:17:21 | name |
|
||||
| tst.js:18:5:18:6 | fn |
|
||||
| tst.js:18:5:18:6 | fn |
|
||||
| tst.js:19:9:19:31 | fn |
|
||||
| tst.js:20:7:20:8 | fn |
|
||||
| tst.js:21:7:21:15 | obj[name] |
|
||||
| tst.js:21:7:21:15 | obj[name] |
|
||||
| tst.js:21:11:21:14 | name |
|
||||
| tst.js:22:11:22:12 | fn |
|
||||
| tst.js:26:7:26:15 | obj[name] |
|
||||
| tst.js:26:7:26:15 | obj[name] |
|
||||
| tst.js:26:11:26:14 | name |
|
||||
| tst.js:28:7:28:15 | obj[name] |
|
||||
| tst.js:28:7:28:15 | obj[name] |
|
||||
| tst.js:28:11:28:14 | name |
|
||||
| tst.js:47:39:47:40 | ev |
|
||||
| tst.js:48:9:48:39 | name |
|
||||
| tst.js:48:16:48:34 | JSON.parse(ev.data) |
|
||||
| tst.js:48:16:48:39 | JSON.pa ... a).name |
|
||||
| tst.js:48:27:48:28 | ev |
|
||||
| tst.js:48:27:48:33 | ev.data |
|
||||
| tst.js:49:9:49:23 | fn |
|
||||
| tst.js:49:14:49:23 | obj2[name] |
|
||||
| tst.js:49:19:49:22 | name |
|
||||
| tst.js:50:5:50:6 | fn |
|
||||
edges
|
||||
| UnsafeDynamicMethodAccess.js:5:37:5:38 | ev | UnsafeDynamicMethodAccess.js:6:30:6:31 | ev |
|
||||
| UnsafeDynamicMethodAccess.js:6:9:6:37 | message | UnsafeDynamicMethodAccess.js:15:9:15:15 | message |
|
||||
| UnsafeDynamicMethodAccess.js:6:19:6:37 | JSON.parse(ev.data) | UnsafeDynamicMethodAccess.js:6:9:6:37 | message |
|
||||
| UnsafeDynamicMethodAccess.js:6:30:6:31 | ev | UnsafeDynamicMethodAccess.js:6:30:6:36 | ev.data |
|
||||
| UnsafeDynamicMethodAccess.js:6:30:6:36 | ev.data | UnsafeDynamicMethodAccess.js:6:19:6:37 | JSON.parse(ev.data) |
|
||||
| UnsafeDynamicMethodAccess.js:15:9:15:15 | message | UnsafeDynamicMethodAccess.js:15:9:15:20 | message.name |
|
||||
| UnsafeDynamicMethodAccess.js:15:9:15:20 | message.name | UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] |
|
||||
| UnsafeDynamicMethodAccess.js:15:9:15:20 | message.name | UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] |
|
||||
| UnvalidatedDynamicMethodCall.js:14:7:14:41 | action | UnvalidatedDynamicMethodCall.js:15:11:15:16 | action |
|
||||
| UnvalidatedDynamicMethodCall.js:14:7:14:41 | action | UnvalidatedDynamicMethodCall.js:15:11:15:16 | action |
|
||||
| UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] | UnvalidatedDynamicMethodCall.js:14:7:14:41 | action |
|
||||
| UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] | UnvalidatedDynamicMethodCall.js:14:7:14:41 | action |
|
||||
| UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] |
|
||||
| UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] |
|
||||
| tst.js:6:39:6:40 | ev | tst.js:7:27:7:28 | ev |
|
||||
| tst.js:6:39:6:40 | ev | tst.js:9:9:9:10 | ev |
|
||||
| tst.js:7:9:7:39 | name | tst.js:11:9:11:12 | name |
|
||||
| tst.js:7:9:7:39 | name | tst.js:17:18:17:21 | name |
|
||||
| tst.js:7:9:7:39 | name | tst.js:21:11:21:14 | name |
|
||||
| tst.js:7:9:7:39 | name | tst.js:26:11:26:14 | name |
|
||||
| tst.js:7:9:7:39 | name | tst.js:28:11:28:14 | name |
|
||||
| tst.js:7:16:7:34 | JSON.parse(ev.data) | tst.js:7:16:7:39 | JSON.pa ... a).name |
|
||||
| tst.js:7:16:7:39 | JSON.pa ... a).name | tst.js:7:9:7:39 | name |
|
||||
| tst.js:7:27:7:28 | ev | tst.js:7:27:7:33 | ev.data |
|
||||
| tst.js:7:27:7:33 | ev.data | tst.js:7:16:7:34 | JSON.parse(ev.data) |
|
||||
| tst.js:9:9:9:10 | ev | tst.js:9:9:9:15 | ev.data |
|
||||
| tst.js:9:9:9:15 | ev.data | tst.js:9:5:9:16 | obj[ev.data] |
|
||||
| tst.js:9:9:9:15 | ev.data | tst.js:9:5:9:16 | obj[ev.data] |
|
||||
| tst.js:11:9:11:12 | name | tst.js:11:5:11:13 | obj[name] |
|
||||
| tst.js:11:9:11:12 | name | tst.js:11:5:11:13 | obj[name] |
|
||||
| tst.js:17:9:17:22 | fn | tst.js:18:5:18:6 | fn |
|
||||
| tst.js:17:9:17:22 | fn | tst.js:18:5:18:6 | fn |
|
||||
| tst.js:17:9:17:22 | fn | tst.js:19:9:19:31 | fn |
|
||||
| tst.js:17:14:17:22 | obj[name] | tst.js:17:9:17:22 | fn |
|
||||
| tst.js:17:14:17:22 | obj[name] | tst.js:17:9:17:22 | fn |
|
||||
| tst.js:17:18:17:21 | name | tst.js:17:14:17:22 | obj[name] |
|
||||
| tst.js:17:18:17:21 | name | tst.js:17:14:17:22 | obj[name] |
|
||||
| tst.js:19:9:19:31 | fn | tst.js:20:7:20:8 | fn |
|
||||
| tst.js:19:9:19:31 | fn | tst.js:22:11:22:12 | fn |
|
||||
| tst.js:21:11:21:14 | name | tst.js:21:7:21:15 | obj[name] |
|
||||
| tst.js:21:11:21:14 | name | tst.js:21:7:21:15 | obj[name] |
|
||||
| tst.js:26:11:26:14 | name | tst.js:26:7:26:15 | obj[name] |
|
||||
| tst.js:26:11:26:14 | name | tst.js:26:7:26:15 | obj[name] |
|
||||
| tst.js:28:11:28:14 | name | tst.js:28:7:28:15 | obj[name] |
|
||||
| tst.js:28:11:28:14 | name | tst.js:28:7:28:15 | obj[name] |
|
||||
| tst.js:47:39:47:40 | ev | tst.js:48:27:48:28 | ev |
|
||||
| tst.js:48:9:48:39 | name | tst.js:49:19:49:22 | name |
|
||||
| tst.js:48:16:48:34 | JSON.parse(ev.data) | tst.js:48:16:48:39 | JSON.pa ... a).name |
|
||||
| tst.js:48:16:48:39 | JSON.pa ... a).name | tst.js:48:9:48:39 | name |
|
||||
| tst.js:48:27:48:28 | ev | tst.js:48:27:48:33 | ev.data |
|
||||
| tst.js:48:27:48:33 | ev.data | tst.js:48:16:48:34 | JSON.parse(ev.data) |
|
||||
| tst.js:49:9:49:23 | fn | tst.js:50:5:50:6 | fn |
|
||||
| tst.js:49:14:49:23 | obj2[name] | tst.js:49:9:49:23 | fn |
|
||||
| tst.js:49:19:49:22 | name | tst.js:49:14:49:23 | obj2[name] |
|
||||
#select
|
||||
| UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | UnsafeDynamicMethodAccess.js:5:37:5:38 | ev | UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | UnsafeDynamicMethodAccess.js:5:37:5:38 | ev | user-controlled |
|
||||
| UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | UnsafeDynamicMethodAccess.js:5:37:5:38 | ev | UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | UnsafeDynamicMethodAccess.js:5:37:5:38 | ev | user-controlled |
|
||||
| UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | user-controlled |
|
||||
| UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | user-controlled |
|
||||
| tst.js:9:5:9:16 | obj[ev.data] | tst.js:6:39:6:40 | ev | tst.js:9:5:9:16 | obj[ev.data] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:9:5:9:16 | obj[ev.data] | tst.js:6:39:6:40 | ev | tst.js:9:5:9:16 | obj[ev.data] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:11:5:11:13 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:11:5:11:13 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:11:5:11:13 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:11:5:11:13 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:18:5:18:6 | fn | tst.js:6:39:6:40 | ev | tst.js:18:5:18:6 | fn | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:18:5:18:6 | fn | tst.js:6:39:6:40 | ev | tst.js:18:5:18:6 | fn | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:20:7:20:8 | fn | tst.js:6:39:6:40 | ev | tst.js:20:7:20:8 | fn | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:21:7:21:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:21:7:21:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:21:7:21:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:21:7:21:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:22:11:22:12 | fn | tst.js:6:39:6:40 | ev | tst.js:22:11:22:12 | fn | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:26:7:26:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:26:7:26:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:26:7:26:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:26:7:26:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:28:7:28:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:28:7:28:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:28:7:28:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:28:7:28:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
|
||||
| tst.js:50:5:50:6 | fn | tst.js:47:39:47:40 | ev | tst.js:50:5:50:6 | fn | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:47:39:47:40 | ev | user-controlled |
|
||||
@@ -0,0 +1,16 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
var actions = {
|
||||
play(data) {
|
||||
// ...
|
||||
},
|
||||
pause(data) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/perform/:action/:payload', function(req, res) {
|
||||
let action = actions[req.params.action];
|
||||
res.end(action(req.params.payload));
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-754/UnvalidatedDynamicMethodCall.ql
|
||||
@@ -0,0 +1,19 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
var actions = new Map();
|
||||
actions.put("play", function (data) {
|
||||
// ...
|
||||
});
|
||||
actions.put("pause", function(data) {
|
||||
// ...
|
||||
});
|
||||
|
||||
app.get('/perform/:action/:payload', function(req, res) {
|
||||
if (actions.has(req.params.action)) {
|
||||
let action = actions.get(req.params.action);
|
||||
res.end(action(req.params.payload));
|
||||
} else {
|
||||
res.end("Unsupported action.");
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
var actions = {
|
||||
play(data) {
|
||||
// ...
|
||||
},
|
||||
pause(data) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/perform/:action/:payload', function(req, res) {
|
||||
if (actions.hasOwnProperty(req.params.action)) {
|
||||
let action = actions[req.params.action];
|
||||
if (typeof action === 'function') {
|
||||
res.end(action(req.params.payload));
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.end("Unsupported action.");
|
||||
});
|
||||
54
javascript/ql/test/query-tests/Security/CWE-754/tst.js
Normal file
54
javascript/ql/test/query-tests/Security/CWE-754/tst.js
Normal file
@@ -0,0 +1,54 @@
|
||||
(function() {
|
||||
let obj = {
|
||||
foo() {}
|
||||
};
|
||||
|
||||
window.addEventListener('message', (ev) => {
|
||||
let name = JSON.parse(ev.data).name;
|
||||
|
||||
obj[ev.data](); // NOT OK: might not be a function
|
||||
|
||||
obj[name](); // NOT OK: might not be a function
|
||||
|
||||
try {
|
||||
obj[name](); // OK: exception is caught
|
||||
} catch(e) {}
|
||||
|
||||
let fn = obj[name];
|
||||
fn(); // NOT OK: might not be a function
|
||||
if (typeof fn == 'function') {
|
||||
fn(); // NOT OK: might be `valueOf`
|
||||
obj[name](); // NOT OK: might be `__defineSetter__`
|
||||
new fn(); // NOT OK: might be `valueOf` or `toString`
|
||||
}
|
||||
|
||||
if (obj[name])
|
||||
obj[name](); // NOT OK
|
||||
if (typeof obj[name] === 'function')
|
||||
obj[name](); // NOT OK
|
||||
|
||||
if (obj.hasOwnProperty(name)) {
|
||||
obj[name](); // NOT OK, but not flagged
|
||||
}
|
||||
|
||||
let key = "$" + name;
|
||||
obj[key](); // NOT OK, but not flagged
|
||||
if (typeof obj[key] === 'function')
|
||||
obj[key](); // OK
|
||||
|
||||
if (typeof fn === 'function') {
|
||||
fn.apply(obj); // OK
|
||||
}
|
||||
});
|
||||
|
||||
let obj2 = Object.create(null);
|
||||
obj2.foo = function() {};
|
||||
|
||||
window.addEventListener('message', (ev) => {
|
||||
let name = JSON.parse(ev.data).name;
|
||||
let fn = obj2[name];
|
||||
fn(); // NOT OK: might not be a function
|
||||
if (typeof fn == 'function')
|
||||
fn(); // OK: cannot be from prototype
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user