mirror of
https://github.com/github/codeql.git
synced 2026-01-06 03:00:24 +01:00
Merge pull request #4778 from asgerf/js/more-prototype-pollution
Approved by erik-krogh, mchammer01
This commit is contained in:
@@ -15,5 +15,6 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Potential type confusion for $@.", source.getNode(),
|
||||
"HTTP request parameter"
|
||||
select sink.getNode(), source, sink,
|
||||
"Potential type confusion as $@ may be either an array or a string.", source.getNode(),
|
||||
"this HTTP request parameter"
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Most JavaScript objects inherit the properties of the built-in <code>Object.prototype</code> object.
|
||||
Prototype pollution is a type of vulnerability in which an attacker is able to modify <code>Object.prototype</code>.
|
||||
Since most objects inherit from the compromised <code>Object.prototype</code> object, the attacker can use this
|
||||
to tamper with the application logic, and often escalate to remote code execution or cross-site scripting.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
One way to cause prototype pollution is by modifying an object obtained via a user-controlled property name.
|
||||
Most objects have a special <code>__proto__</code> property that refers to <code>Object.prototype</code>.
|
||||
An attacker can abuse this special property to trick the application into performing unintended modifications
|
||||
of <code>Object.prototype</code>.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Use an associative data structure that is resilient to untrusted key values, such as a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map">Map</a>.
|
||||
In some cases, a prototype-less object created with <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create">Object.create(null)</a>
|
||||
may be preferable.
|
||||
</p>
|
||||
<p>
|
||||
Alternatively, restrict the computed property name so it can't clash with a built-in property, either by
|
||||
prefixing it with a constant string, or by rejecting inputs that don't conform to the expected format.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the example below, the untrusted value <code>req.params.id</code> is used as the property name
|
||||
<code>req.session.todos[id]</code>. If a malicious user passes in the ID value <code>__proto__</code>,
|
||||
the variable <code>todo</code> will then refer to <code>Object.prototype</code>.
|
||||
Finally, the modification of <code>todo</code> then allows the attacker to inject arbitrary properties
|
||||
onto <code>Object.prototype</code>.
|
||||
</p>
|
||||
|
||||
<sample src="examples/PrototypePollutingAssignment.js"/>
|
||||
|
||||
<p>
|
||||
One way to fix this is to use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map">Map</a> objects to associate key/value pairs
|
||||
instead of regular objects, as shown below:
|
||||
</p>
|
||||
|
||||
<sample src="examples/PrototypePollutingAssignmentFixed.js"/>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>MDN:
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto">Object.prototype.__proto__</a>
|
||||
</li>
|
||||
<li>MDN:
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map">Map</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @name Prototype-polluting assignment
|
||||
* @description Modifying an object obtained via a user-controlled property name may
|
||||
* lead to accidental mutation of the built-in Object prototype,
|
||||
* and possibly escalate to remote code execution or cross-site scripting.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id js/prototype-polluting-assignment
|
||||
* @tags security
|
||||
* external/cwe/cwe-078
|
||||
* external/cwe/cwe-079
|
||||
* external/cwe/cwe-094
|
||||
* external/cwe/cwe-400
|
||||
* external/cwe/cwe-915
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.PrototypePollutingAssignment::PrototypePollutingAssignment
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink, source, sink,
|
||||
"This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@.",
|
||||
source.getNode(), "here"
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<p>
|
||||
Only merge or assign a property recursively when it is an own property of the <em>destination</em> object.
|
||||
Alternatively, blacklist the property names <code>__proto__</code> and <code>constructor</code>
|
||||
Alternatively, block the property names <code>__proto__</code> and <code>constructor</code>
|
||||
from being merged or assigned to.
|
||||
</p>
|
||||
</recommendation>
|
||||
@@ -39,7 +39,7 @@
|
||||
This function recursively copies properties from <code>src</code> to <code>dst</code>:
|
||||
</p>
|
||||
|
||||
<sample src="examples/PrototypePollutionUtility.js"/>
|
||||
<sample src="examples/PrototypePollutingFunction.js"/>
|
||||
|
||||
<p>
|
||||
However, if <code>src</code> is the object <code>{"__proto__": {"isAdmin": true}}</code>,
|
||||
@@ -51,13 +51,13 @@
|
||||
are merged recursively:
|
||||
</p>
|
||||
|
||||
<sample src="examples/PrototypePollutionUtility_fixed.js"/>
|
||||
<sample src="examples/PrototypePollutingFunction_fixed.js"/>
|
||||
|
||||
<p>
|
||||
Alternatively, blacklist the <code>__proto__</code> and <code>constructor</code> properties:
|
||||
Alternatively, block the <code>__proto__</code> and <code>constructor</code> properties:
|
||||
</p>
|
||||
|
||||
<sample src="examples/PrototypePollutionUtility_fixed2.js"/>
|
||||
<sample src="examples/PrototypePollutingFunction_fixed2.js"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
@@ -1,14 +1,17 @@
|
||||
/**
|
||||
* @name Prototype pollution in utility function
|
||||
* @description Recursively assigning properties on objects may cause
|
||||
* accidental modification of a built-in prototype object.
|
||||
* @name Prototype-polluting function
|
||||
* @description Functions recursively assigning properties on objects may be
|
||||
* the cause of accidental modification of a built-in prototype object.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id js/prototype-pollution-utility
|
||||
* @tags security
|
||||
* external/cwe/cwe-078
|
||||
* external/cwe/cwe-079
|
||||
* external/cwe/cwe-094
|
||||
* external/cwe/cwe-400
|
||||
* external/cwe/cwe-471
|
||||
* external/cwe/cwe-915
|
||||
*/
|
||||
|
||||
import javascript
|
||||
@@ -276,14 +279,14 @@ class PropNameTracking extends DataFlow::Configuration {
|
||||
}
|
||||
|
||||
override predicate isBarrierGuard(DataFlow::BarrierGuardNode node) {
|
||||
node instanceof BlacklistEqualityGuard or
|
||||
node instanceof WhitelistEqualityGuard or
|
||||
node instanceof DenyListEqualityGuard or
|
||||
node instanceof AllowListEqualityGuard or
|
||||
node instanceof HasOwnPropertyGuard or
|
||||
node instanceof InExprGuard or
|
||||
node instanceof InstanceOfGuard or
|
||||
node instanceof TypeofGuard or
|
||||
node instanceof BlacklistInclusionGuard or
|
||||
node instanceof WhitelistInclusionGuard or
|
||||
node instanceof DenyListInclusionGuard or
|
||||
node instanceof AllowListInclusionGuard or
|
||||
node instanceof IsPlainObjectGuard
|
||||
}
|
||||
}
|
||||
@@ -291,11 +294,11 @@ class PropNameTracking extends DataFlow::Configuration {
|
||||
/**
|
||||
* Sanitizer guard of form `x === "__proto__"` or `x === "constructor"`.
|
||||
*/
|
||||
class BlacklistEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNode {
|
||||
class DenyListEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNode {
|
||||
override EqualityTest astNode;
|
||||
string propName;
|
||||
|
||||
BlacklistEqualityGuard() {
|
||||
DenyListEqualityGuard() {
|
||||
astNode.getAnOperand().getStringValue() = propName and
|
||||
propName = unsafePropName()
|
||||
}
|
||||
@@ -310,10 +313,10 @@ class BlacklistEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNod
|
||||
/**
|
||||
* An equality test with something other than `__proto__` or `constructor`.
|
||||
*/
|
||||
class WhitelistEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNode {
|
||||
class AllowListEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNode {
|
||||
override EqualityTest astNode;
|
||||
|
||||
WhitelistEqualityGuard() {
|
||||
AllowListEqualityGuard() {
|
||||
not astNode.getAnOperand().getStringValue() = unsafePropName() and
|
||||
astNode.getAnOperand() instanceof Literal
|
||||
}
|
||||
@@ -427,10 +430,10 @@ class TypeofGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::ValueNode
|
||||
/**
|
||||
* A check of form `["__proto__"].includes(x)` or similar.
|
||||
*/
|
||||
class BlacklistInclusionGuard extends DataFlow::LabeledBarrierGuardNode, InclusionTest {
|
||||
class DenyListInclusionGuard extends DataFlow::LabeledBarrierGuardNode, InclusionTest {
|
||||
UnsafePropLabel label;
|
||||
|
||||
BlacklistInclusionGuard() {
|
||||
DenyListInclusionGuard() {
|
||||
exists(DataFlow::ArrayCreationNode array |
|
||||
array.getAnElement().getStringValue() = label and
|
||||
array.flowsTo(getContainerNode())
|
||||
@@ -447,8 +450,8 @@ class BlacklistInclusionGuard extends DataFlow::LabeledBarrierGuardNode, Inclusi
|
||||
/**
|
||||
* A check of form `xs.includes(x)` or similar, which sanitizes `x` in the true case.
|
||||
*/
|
||||
class WhitelistInclusionGuard extends DataFlow::LabeledBarrierGuardNode {
|
||||
WhitelistInclusionGuard() {
|
||||
class AllowListInclusionGuard extends DataFlow::LabeledBarrierGuardNode {
|
||||
AllowListInclusionGuard() {
|
||||
this instanceof TaintTracking::PositiveIndexOfSanitizer
|
||||
or
|
||||
this instanceof TaintTracking::MembershipTestSanitizer and
|
||||
@@ -34,7 +34,7 @@
|
||||
and then copied into a new object:
|
||||
</p>
|
||||
|
||||
<sample src="examples/PrototypePollution1.js"/>
|
||||
<sample src="examples/PrototypePollutingMergeCall1.js"/>
|
||||
|
||||
<p>
|
||||
Prior to lodash 4.17.11 this would be vulnerable to prototype pollution. An attacker could send the following GET request:
|
||||
@@ -47,7 +47,7 @@
|
||||
Fix this by updating the lodash version:
|
||||
</p>
|
||||
|
||||
<sample src="examples/PrototypePollution_fixed.json"/>
|
||||
<sample src="examples/PrototypePollutingMergeCall_fixed.json"/>
|
||||
|
||||
<p>
|
||||
Note that some web frameworks, such as Express, parse query parameters using extended URL-encoding
|
||||
@@ -56,7 +56,7 @@
|
||||
The example below would also be susceptible to prototype pollution:
|
||||
</p>
|
||||
|
||||
<sample src="examples/PrototypePollution2.js"/>
|
||||
<sample src="examples/PrototypePollutingMergeCall2.js"/>
|
||||
|
||||
<p>
|
||||
In the above example, an attacker can cause prototype pollution by sending the following GET request:
|
||||
@@ -1,14 +1,18 @@
|
||||
/**
|
||||
* @name Prototype pollution
|
||||
* @name Prototype-polluting merge call
|
||||
* @description Recursively merging a user-controlled object into another object
|
||||
* can allow an attacker to modify the built-in Object prototype.
|
||||
* can allow an attacker to modify the built-in Object prototype,
|
||||
* and possibly escalate to remote code execution or cross-site scripting.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id js/prototype-pollution
|
||||
* @tags security
|
||||
* external/cwe/cwe-250
|
||||
* external/cwe/cwe-078
|
||||
* external/cwe/cwe-079
|
||||
* external/cwe/cwe-094
|
||||
* external/cwe/cwe-400
|
||||
* external/cwe/cwe-915
|
||||
*/
|
||||
|
||||
import javascript
|
||||
@@ -0,0 +1,11 @@
|
||||
let express = require('express');
|
||||
|
||||
express.put('/todos/:id', (req, res) => {
|
||||
let id = req.params.id;
|
||||
let items = req.session.todos[id];
|
||||
if (!items) {
|
||||
items = req.session.todos[id] = {};
|
||||
}
|
||||
items[req.query.name] = req.query.text;
|
||||
res.end(200);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
let express = require('express');
|
||||
|
||||
express.put('/todos/:id', (req, res) => {
|
||||
let id = req.params.id;
|
||||
let items = req.session.todos.get(id);
|
||||
if (!items) {
|
||||
items = new Map();
|
||||
req.sessions.todos.set(id, items);
|
||||
}
|
||||
items.set(req.query.name, req.query.text);
|
||||
res.end(200);
|
||||
});
|
||||
Reference in New Issue
Block a user