mirror of
https://github.com/github/codeql.git
synced 2026-04-29 02:35:15 +02:00
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:
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user