JavaScript: Restrict RemotePropertyInjection query to avoid double-reporting.

This query now only flags user-controlled property and header writes, method calls are handled by the new unsafe/unvalidated method call queries.
This commit is contained in:
Max Schaefer
2018-11-27 12:36:17 +00:00
parent 2889e07eb8
commit f1c538a97b
5 changed files with 62 additions and 93 deletions

View File

@@ -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>

View File

@@ -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

View File

@@ -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() {