mirror of
https://github.com/github/codeql.git
synced 2026-04-27 17:55:19 +02:00
Merge rc/1.19 into next.
This commit is contained in:
@@ -56,26 +56,60 @@ predicate calls(DataFlow::InvokeNode cs, Function callee, int imprecision) {
|
||||
|
||||
/**
|
||||
* Gets a function that may be invoked at `cs`, preferring callees that
|
||||
* are less likely to be derived due to analysis imprecision.
|
||||
* are less likely to be derived due to analysis imprecision and excluding
|
||||
* whitelisted call sites and callees. Additionally, `isNew` is bound to
|
||||
* `true` if `cs` is a `new` expression, and to `false` otherwise.
|
||||
*/
|
||||
Function getALikelyCallee(DataFlow::InvokeNode cs) {
|
||||
calls(cs, result, min(int p | calls(cs, _, p)))
|
||||
Function getALikelyCallee(DataFlow::InvokeNode cs, boolean isNew) {
|
||||
calls(cs, result, min(int p | calls(cs, _, p))) and
|
||||
not cs.isUncertain() and
|
||||
not whitelistedCall(cs) and
|
||||
not whitelistedCallee(result) and
|
||||
(cs instanceof DataFlow::NewNode and isNew = true
|
||||
or
|
||||
cs instanceof DataFlow::CallNode and isNew = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `f` should be whitelisted, either because it guards against
|
||||
* inconsistent `new` or we do not want to report it.
|
||||
*/
|
||||
predicate whitelistedCallee(Function f) {
|
||||
// externs are special, so don't flag them
|
||||
f.inExternsFile() or
|
||||
// illegal constructor calls are flagged by query 'Illegal invocation',
|
||||
// so don't flag them
|
||||
f instanceof Constructor or
|
||||
// if `f` itself guards against missing `new`, don't flag it
|
||||
guardsAgainstMissingNew(f)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` should be whitelisted because it cannot cause problems
|
||||
* with inconsistent `new`.
|
||||
*/
|
||||
predicate whitelistedCall(DataFlow::CallNode call) {
|
||||
// super constructor calls behave more like `new`, so don't flag them
|
||||
call.asExpr() instanceof SuperCall or
|
||||
// don't flag if there is a receiver object
|
||||
exists(call.getReceiver())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `new` or call (depending on whether `isNew` is true or false) of `f`
|
||||
* that comes first under a lexicographical ordering by file path, start line
|
||||
* and start column.
|
||||
*/
|
||||
DataFlow::InvokeNode getFirstInvocation(Function f, boolean isNew) {
|
||||
result = min(DataFlow::InvokeNode invk, string path, int line, int col |
|
||||
f = getALikelyCallee(invk, isNew) and invk.hasLocationInfo(path, line, col, _, _) |
|
||||
invk order by path, line, col
|
||||
)
|
||||
}
|
||||
|
||||
from Function f, DataFlow::NewNode new, DataFlow::CallNode call
|
||||
where // externs are special, so don't flag them
|
||||
not f.inExternsFile() and
|
||||
// illegal constructor calls are flagged by query 'Illegal invocation',
|
||||
// so don't flag them
|
||||
not f instanceof Constructor and
|
||||
f = getALikelyCallee(new) and
|
||||
f = getALikelyCallee(call) and
|
||||
not guardsAgainstMissingNew(f) and
|
||||
not new.isUncertain() and
|
||||
not call.isUncertain() and
|
||||
// super constructor calls behave more like `new`, so don't flag them
|
||||
not call.asExpr() instanceof SuperCall and
|
||||
// don't flag if there is a receiver object
|
||||
not exists(call.getReceiver())
|
||||
select (FirstLineOf)f, capitalize(f.describe()) + " is invoked as a constructor here $@, " +
|
||||
"and as a normal function here $@.", new, new.toString(), call, call.toString()
|
||||
where new = getFirstInvocation(f, true) and
|
||||
call = getFirstInvocation(f, false)
|
||||
select (FirstLineOf)f, capitalize(f.describe()) + " is sometimes invoked as a constructor " +
|
||||
"(for example $@), and sometimes as a normal function (for example $@).",
|
||||
new, "here", call, "here"
|
||||
|
||||
@@ -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,46 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Interpreting hard-coded data, such as string literals containing hexadecimal numbers,
|
||||
as code or as an import path is typical of malicious backdoor code that has been
|
||||
implanted into an otherwise trusted code base and is trying to hide its true purpose
|
||||
from casual readers or automated scanning tools.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Examine the code in question carefully to ascertain its provenance and its true purpose.
|
||||
If the code is benign, it should always be possible to rewrite it without relying
|
||||
on dynamically interpreting data as code, improving both clarity and safety.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
As an example of malicious code using this obfuscation technique, consider the following
|
||||
simplified version of a snippet of backdoor code that was discovered in a dependency of
|
||||
the popular <code>event-stream</code> npm package:
|
||||
</p>
|
||||
<sample src="examples/HardcodedDataInterpretedAsCode.js"/>
|
||||
<p>
|
||||
While this shows only the first few lines of code, it already looks very suspicious
|
||||
since it takes a hard-coded string literal, hex-decodes it and then uses it as an
|
||||
import path. The only reason to do so is to hide the name of the file being imported.
|
||||
</p>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/Trojan_Horse">Trojan Horse</a>.
|
||||
</li>
|
||||
<li>
|
||||
The npm Blog:
|
||||
<a href="https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident">Details about the event-stream incident</a>.
|
||||
</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name Hard-coded data interpreted as code
|
||||
* @description Transforming hard-coded data (such as hexadecimal constants) into code
|
||||
* to be executed is a technique often associated with backdoors and should
|
||||
* be avoided.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision medium
|
||||
* @id js/hardcoded-data-interpreted-as-code
|
||||
* @tags security
|
||||
* external/cwe/cwe-506
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.HardcodedDataInterpretedAsCode::HardcodedDataInterpretedAsCode
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"Hard-coded data from $@ is interpreted as " + sink.getNode().(Sink).getKind() + ".",
|
||||
source.getNode(), "here"
|
||||
@@ -0,0 +1,8 @@
|
||||
var r = require;
|
||||
|
||||
function e(r) {
|
||||
return Buffer.from(r, "hex").toString()
|
||||
}
|
||||
|
||||
// BAD: hexadecimal constant decoded and interpreted as import path
|
||||
var n = r(e("2e2f746573742f64617461"));
|
||||
@@ -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 the 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. In either case the method call will 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.");
|
||||
});
|
||||
@@ -132,7 +132,13 @@ predicate findNodeModulesFolder(Folder f, Folder nodeModules, int distance) {
|
||||
*/
|
||||
private class RequireVariable extends Variable {
|
||||
RequireVariable() {
|
||||
exists (ModuleScope m | this = m.getVariable("require"))
|
||||
this = any(ModuleScope m).getVariable("require")
|
||||
or
|
||||
// cover cases where we failed to detect Node.js code
|
||||
this.(GlobalVariable).getName() = "require"
|
||||
or
|
||||
// track through assignments to other variables
|
||||
this.getAnAssignedExpr().(VarAccess).getVariable() instanceof RequireVariable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +155,9 @@ private predicate moduleInFile(Module m, File f) {
|
||||
class Require extends CallExpr, Import {
|
||||
Require() {
|
||||
exists (RequireVariable req |
|
||||
this.getCallee() = req.getAnAccess()
|
||||
this.getCallee() = req.getAnAccess() and
|
||||
// `mjs` files explicitly disallow `require`
|
||||
getFile().getExtension() != "mjs"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -297,7 +297,22 @@ module NodeJSLib {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = tainted and succ = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A model of taint propagation through `new Buffer` and `Buffer.from`.
|
||||
*/
|
||||
private class BufferTaintStep extends TaintTracking::AdditionalTaintStep, DataFlow::InvokeNode {
|
||||
BufferTaintStep() {
|
||||
this = DataFlow::globalVarRef("Buffer").getAnInstantiation()
|
||||
or
|
||||
this = DataFlow::globalVarRef("Buffer").getAMemberInvocation("from")
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getArgument(0) and
|
||||
succ = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about hard-coded data
|
||||
* being interpreted as code.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.security.dataflow.CodeInjection
|
||||
|
||||
module HardcodedDataInterpretedAsCode {
|
||||
/**
|
||||
* A data flow source for hard-coded data.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node {
|
||||
/** Gets a flow label for which this is a source. */
|
||||
DataFlow::FlowLabel getLabel() {
|
||||
result = DataFlow::FlowLabel::data()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow sink for code injection.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
/** Gets a flow label for which this is a sink. */
|
||||
abstract DataFlow::FlowLabel getLabel();
|
||||
|
||||
/** Gets a description of what kind of sink this is. */
|
||||
abstract string getKind();
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer for hard-coded data.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node {}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about hard-coded data
|
||||
* being interpreted as code
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() {
|
||||
this = "HardcodedDataInterpretedAsCode"
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel lbl) {
|
||||
source.(Source).getLabel() = lbl
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node nd, DataFlow::FlowLabel lbl) {
|
||||
nd.(Sink).getLabel() = lbl
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A constant string consisting of eight or more hexadecimal characters (including at
|
||||
* least one digit), viewed as a source of hard-coded data that should not be
|
||||
* interpreted as code.
|
||||
*/
|
||||
private class DefaultSource extends Source, DataFlow::ValueNode {
|
||||
DefaultSource() {
|
||||
exists (string val | val = astNode.(Expr).getStringValue() |
|
||||
val.regexpMatch("[0-9a-fA-F]{8,}") and
|
||||
val.regexpMatch(".*[0-9].*")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A code injection sink; hard-coded data should not flow here.
|
||||
*/
|
||||
private class DefaultCodeInjectionSink extends Sink {
|
||||
DefaultCodeInjectionSink() { this instanceof CodeInjection::Sink }
|
||||
override DataFlow::FlowLabel getLabel() { result = DataFlow::FlowLabel::taint() }
|
||||
override string getKind() { result = "code" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to `require` path; hard-coded data should not flow here.
|
||||
*/
|
||||
private class RequireArgumentSink extends Sink {
|
||||
RequireArgumentSink() {
|
||||
this = any(Require r).getAnArgument().flow()
|
||||
}
|
||||
|
||||
override DataFlow::FlowLabel getLabel() {
|
||||
result = DataFlow::FlowLabel::data()
|
||||
or
|
||||
result = DataFlow::FlowLabel::taint()
|
||||
}
|
||||
|
||||
override string getKind() { result = "an import path" }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user