Merge pull request #2617 from asger-semmle/prototype-pollution-utility

Approved by esbena, mchammer01
This commit is contained in:
semmle-qlci 2020-01-16 13:02:07 +00:00 коммит произвёл GitHub
Родитель 8128d23b6e 7a1d068f1c
Коммит 4efc418e2c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 3254 добавлений и 0 удалений

Просмотреть файл

@ -21,6 +21,7 @@
| Cross-site scripting through exception (`js/xss-through-exception`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights potential XSS vulnerabilities where an exception is written to the DOM. Results are not shown on LGTM by default. |
| Regular expression always matches (`js/regex/always-matches`) | correctness, regular-expressions | Highlights regular expression checks that trivially succeed by matching an empty substring. Results are shown on LGTM by default. |
| Missing await (`js/missing-await`) | correctness | Highlights expressions that operate directly on a promise object in a nonsensical way, instead of awaiting its result. Results are shown on LGTM by default. |
| Prototype pollution in utility function (`js/prototype-pollution-utility`) | security, external/cwe/cwe-400, external/cwe/cwe-471 | Highlights recursive copying operations that are susceptible to prototype pollution. Results are shown on LGTM by default. |
## Changes to existing queries

Просмотреть файл

@ -31,6 +31,7 @@
+ semmlecode-javascript-queries/Security/CWE-352/MissingCsrfMiddleware.ql: /Security/CWE/CWE-352
+ semmlecode-javascript-queries/Security/CWE-400/RemotePropertyInjection.ql: /Security/CWE/CWE-400
+ semmlecode-javascript-queries/Security/CWE-400/PrototypePollution.ql: /Security/CWE/CWE-400
+ semmlecode-javascript-queries/Security/CWE-400/PrototypePollutionUtility.ql: /Security/CWE/CWE-400
+ semmlecode-javascript-queries/Security/CWE-502/UnsafeDeserialization.ql: /Security/CWE/CWE-502
+ semmlecode-javascript-queries/Security/CWE-506/HardcodedDataInterpretedAsCode.ql: /Security/CWE/CWE-506
+ semmlecode-javascript-queries/Security/CWE-601/ClientSideUrlRedirect.ql: /Security/CWE/CWE-601

Просмотреть файл

@ -0,0 +1,71 @@
<!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>, 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 through use of an unsafe <em>merge</em> or <em>extend</em> function
to recursively copy properties from one object to another.
Such a function has the potential to modify any object reachable from the destination object, and
the built-in <code>Object.prototype</code> is usually reachable through the special properties
<code>__proto__</code> and <code>constructor.prototype</code>.
</p>
</overview>
<recommendation>
<p>
The most effective place to guard against this is in the function that performs
the recursive copy.
</p>
<p>
Only merge 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>
from being merged.
</p>
</recommendation>
<example>
<p>
This function recursively copies properties from <code>src</code> to <code>dst</code>:
</p>
<sample src="examples/PrototypePollutionUtility.js"/>
<p>
However, if <code>src</code> is the object <code>{"__proto__": {"isAdmin": true}}</code>,
it will inject the property <code>isAdmin: true</code> in <code>Object.prototype</code>.
</p>
<p>
The issue can be fixed by ensuring that only own properties of the destination object
are merged recursively:
</p>
<sample src="examples/PrototypePollutionUtility_fixed.js"/>
<p>
Alternatively, blacklist the <code>__proto__</code> and <code>constructor</code> properties:
</p>
<sample src="examples/PrototypePollutionUtility_fixed2.js"/>
</example>
<references>
<li>Prototype pollution attacks:
<a href="https://hackerone.com/reports/380873">lodash</a>,
<a href="https://hackerone.com/reports/454365">jQuery</a>,
<a href="https://hackerone.com/reports/381185">extend</a>,
<a href="https://hackerone.com/reports/430291">just-extend</a>,
<a href="https://hackerone.com/reports/381194">merge.recursive</a>.
</li>
</references>
</qhelp>

Просмотреть файл

@ -0,0 +1,566 @@
/**
* @name Prototype pollution in utility function
* @description Recursively copying properties between objects may cause
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-400
* external/cwe/cwe-471
*/
import javascript
import DataFlow
import PathGraph
import semmle.javascript.dataflow.InferredTypes
/**
* Gets a node that refers to an element of `array`, likely obtained
* as a result of enumerating the elements of the array.
*/
SourceNode getAnEnumeratedArrayElement(SourceNode array) {
exists(MethodCallNode call, string name |
call = array.getAMethodCall(name) and
(name = "forEach" or name = "map") and
result = call.getCallback(0).getParameter(0)
)
or
exists(DataFlow::PropRead read |
read = array.getAPropertyRead() and
not exists(read.getPropertyName()) and
not read.getPropertyNameExpr().analyze().getAType() = TTString() and
result = read
)
}
/**
* A data flow node that refers to the name of a property obtained by enumerating
* the properties of some object.
*/
abstract class EnumeratedPropName extends DataFlow::Node {
/**
* Gets the object whose properties are being enumerated.
*
* For example, gets `src` in `for (var key in src)`.
*/
abstract DataFlow::Node getSourceObject();
/**
* Gets a local reference of the source object.
*/
SourceNode getASourceObjectRef() {
exists(SourceNode root, string path |
getSourceObject() = AccessPath::getAReferenceTo(root, path) and
result = AccessPath::getAReferenceTo(root, path)
)
or
result = getSourceObject().getALocalSource()
}
/**
* Gets a property read that accesses the corresponding property value in the source object.
*
* For example, gets `src[key]` in `for (var key in src) { src[key]; }`.
*/
PropRead getASourceProp() {
result = getASourceObjectRef().getAPropertyRead() and
result.getPropertyNameExpr().flow().getImmediatePredecessor*() = this
}
}
/**
* Property enumeration through `for-in` for `Object.keys` or similar.
*/
class ForInEnumeratedPropName extends EnumeratedPropName {
DataFlow::Node object;
ForInEnumeratedPropName() {
exists(ForInStmt stmt |
this = DataFlow::lvalueNode(stmt.getLValue()) and
object = stmt.getIterationDomain().flow()
)
or
exists(CallNode call |
call = globalVarRef("Object").getAMemberCall("keys")
or
call = globalVarRef("Object").getAMemberCall("getOwnPropertyNames")
or
call = globalVarRef("Reflect").getAMemberCall("ownKeys")
|
object = call.getArgument(0) and
this = getAnEnumeratedArrayElement(call)
)
}
override Node getSourceObject() { result = object }
}
/**
* Property enumeration through `Object.entries`.
*/
class EntriesEnumeratedPropName extends EnumeratedPropName {
CallNode entries;
SourceNode entry;
EntriesEnumeratedPropName() {
entries = globalVarRef("Object").getAMemberCall("entries") and
entry = getAnEnumeratedArrayElement(entries) and
this = entry.getAPropertyRead("0")
}
override DataFlow::Node getSourceObject() {
result = entries.getArgument(0)
}
override PropRead getASourceProp() {
result = super.getASourceProp()
or
result = entry.getAPropertyRead("1")
}
}
/**
* Holds if the properties of `node` are enumerated locally.
*/
predicate arePropertiesEnumerated(DataFlow::SourceNode node) {
node = any(EnumeratedPropName name).getASourceObjectRef()
}
/**
* A dynamic property access that is not obviously an array access.
*/
class DynamicPropRead extends DataFlow::SourceNode, DataFlow::ValueNode {
// Use IndexExpr instead of PropRead as we're not interested in implicit accesses like
// rest-patterns and for-of loops.
override IndexExpr astNode;
DynamicPropRead() {
not exists(astNode.getPropertyName()) and
// Exclude obvious array access
astNode.getPropertyNameExpr().analyze().getAType() = TTString()
}
/** Gets the base of the dynamic read. */
DataFlow::Node getBase() { result = astNode.getBase().flow() }
/**
* Holds if the value of this read was assigned to earlier in the same basic block.
*
* For example, this is true for `dst[x]` on line 2 below:
* ```js
* dst[x] = {};
* dst[x][y] = src[y];
* ```
*/
predicate hasDominatingAssignment() {
exists(DataFlow::PropWrite write, BasicBlock bb, int i, int j, SsaVariable ssaVar |
write = getBase().getALocalSource().getAPropertyWrite() and
bb.getNode(i) = write.getWriteNode() and
bb.getNode(j) = astNode and
i < j and
write.getPropertyNameExpr() = ssaVar.getAUse() and
astNode.getIndex() = ssaVar.getAUse()
)
}
}
/**
* Holds if there is a dynamic property assignment of form `base[prop] = rhs`
* which might act as the writing operation in a recursive merge function.
*
* Only assignments to pre-existing objects are of interest, so object/array literals
* are not included.
*
* Additionally, we ignore cases where the properties of `base` are enumerated, as this
* would typically not happen in a merge function.
*/
predicate dynamicPropWrite(DataFlow::Node base, DataFlow::Node prop, DataFlow::Node rhs) {
exists(AssignExpr write, IndexExpr index |
index = write.getLhs() and
base = index.getBase().flow() and
prop = index.getPropertyNameExpr().flow() and
rhs = write.getRhs().flow() and
not exists(prop.getStringValue()) and
not arePropertiesEnumerated(base.getALocalSource())
)
}
/** Gets the name of a property that can lead to `Object.prototype`. */
string unsafePropName() {
result = "__proto__"
or
result = "constructor"
}
/**
* Flow label representing an unsafe property name, or an object obtained
* by using such a property in a dynamic read.
*/
class UnsafePropLabel extends FlowLabel {
UnsafePropLabel() { this = unsafePropName() }
}
/**
* Tracks data from property enumerations to dynamic property writes.
*
* The intent is to find code of the general form:
* ```js
* function merge(dst, src) {
* for (var key in src)
* if (...)
* merge(dst[key], src[key])
* else
* dst[key] = src[key]
* }
* ```
*
* This configuration is used to find three separate data flow paths originating
* from a property enumeration, all leading to the same dynamic property write.
*
* In particular, the base and property name of the property write should all
* depend on the enumerated property name (`key`) and the right-hand side should
* depend on the source property (`src[key]`), while allowing steps of form
* `x -> x[p]` and `p -> x[p]`.
*
* Note that in the above example, the flow from `key` to the base of the write (`dst`)
* requires stepping through the recursive call.
* Such a path would be absent for a shallow copying operation, where the `dst` object
* isn't derived from a property of the source object.
*
* This configuration can't enforce that all three paths must end at the same
* dynamic property write, so we treat the paths independently here and check
* for coinciding paths afterwards. This means this configuration can't be used as
* a standalone configuration like in most path queries.
*/
class PropNameTracking extends DataFlow::Configuration {
PropNameTracking() { this = "PropNameTracking" }
override predicate isSource(DataFlow::Node node, FlowLabel label) {
label instanceof UnsafePropLabel and
exists(EnumeratedPropName prop |
node = prop
or
node = prop.getASourceProp()
)
}
override predicate isSink(DataFlow::Node node, FlowLabel label) {
label instanceof UnsafePropLabel and
(
dynamicPropWrite(node, _, _) or
dynamicPropWrite(_, node, _) or
dynamicPropWrite(_, _, node)
)
}
override predicate isAdditionalFlowStep(
DataFlow::Node pred, DataFlow::Node succ, FlowLabel predlbl, FlowLabel succlbl
) {
predlbl instanceof UnsafePropLabel and
succlbl = predlbl and
(
// Step through `p -> x[p]`
exists(PropRead read |
pred = read.getPropertyNameExpr().flow() and
not read.(DynamicPropRead).hasDominatingAssignment() and
succ = read
)
or
// Step through `x -> x[p]`
exists(DynamicPropRead read |
not read.hasDominatingAssignment() and
pred = read.getBase() and
succ = read
)
)
}
override predicate isBarrier(DataFlow::Node node) {
super.isBarrier(node)
or
exists(ConditionGuardNode guard, SsaRefinementNode refinement |
node = DataFlow::ssaDefinitionNode(refinement) and
refinement.getGuard() = guard and
guard.getTest() instanceof VarAccess and
guard.getOutcome() = false
)
}
override predicate isBarrierGuard(DataFlow::BarrierGuardNode node) {
node instanceof BlacklistEqualityGuard or
node instanceof WhitelistEqualityGuard or
node instanceof HasOwnPropertyGuard or
node instanceof InExprGuard or
node instanceof InstanceOfGuard or
node instanceof TypeofGuard or
node instanceof BlacklistInclusionGuard or
node instanceof WhitelistInclusionGuard
}
}
/**
* Sanitizer guard of form `x === "__proto__"` or `x === "constructor"`.
*/
class BlacklistEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNode {
override EqualityTest astNode;
string propName;
BlacklistEqualityGuard() {
astNode.getAnOperand().getStringValue() = propName and
propName = unsafePropName()
}
override predicate blocks(boolean outcome, Expr e, FlowLabel label) {
e = astNode.getAnOperand() and
outcome = astNode.getPolarity().booleanNot() and
label = propName
}
}
/**
* An equality test with something other than `__proto__` or `constructor`.
*/
class WhitelistEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNode {
override EqualityTest astNode;
WhitelistEqualityGuard() {
not astNode.getAnOperand().getStringValue() = unsafePropName() and
astNode.getAnOperand() instanceof Literal
}
override predicate blocks(boolean outcome, Expr e, FlowLabel label) {
e = astNode.getAnOperand() and
outcome = astNode.getPolarity() and
label instanceof UnsafePropLabel
}
}
/**
* Sanitizer guard for calls to `Object.prototype.hasOwnProperty`.
*
* A malicious source object will have `__proto__` and/or `constructor` as own properties,
* but the destination object generally doesn't. It is therefore only a sanitizer when
* used on the destination object.
*/
class HasOwnPropertyGuard extends DataFlow::BarrierGuardNode, CallNode {
HasOwnPropertyGuard() {
// Make sure we handle reflective calls since libraries love to do that.
getCalleeNode().getALocalSource().(DataFlow::PropRead).getPropertyName() = "hasOwnProperty" and
exists(getReceiver()) and
// Try to avoid `src.hasOwnProperty` by requiring that the receiver
// does not locally have its properties enumerated. Typically there is no
// reason to enumerate the properties of the destination object.
not arePropertiesEnumerated(getReceiver().getALocalSource())
}
override predicate blocks(boolean outcome, Expr e) {
e = getArgument(0).asExpr() and outcome = true
}
}
/**
* Sanitizer guard for `key in dst`.
*
* Since `"__proto__" in obj` and `"constructor" in obj` is true for most objects,
* this is seen as a sanitizer for `key` in the false outcome.
*/
class InExprGuard extends DataFlow::BarrierGuardNode, DataFlow::ValueNode {
override InExpr astNode;
InExprGuard() {
// Exclude tests of form `key in src` for the same reason as in HasOwnPropertyGuard
not arePropertiesEnumerated(astNode.getRightOperand().flow().getALocalSource())
}
override predicate blocks(boolean outcome, Expr e) {
e = astNode.getLeftOperand() and outcome = false
}
}
/**
* Sanitizer guard for `instanceof` expressions.
*
* `Object.prototype instanceof X` is never true, so this blocks the `__proto__` label.
*
* It is still possible to get to `Function.prototype` through `constructor.constructor.prototype`
* so we do not block the `constructor` label.
*/
class InstanceOfGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::ValueNode {
override InstanceOfExpr astNode;
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
e = astNode.getLeftOperand() and outcome = true and label = "__proto__"
}
}
/**
* Sanitizer guard of form `typeof x === "object"` or `typeof x === "function"`.
*
* The former blocks the `constructor` label as that payload must pass through a function,
* and the latter blocks the `__proto__` label as that only passes through objects.
*/
class TypeofGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::ValueNode {
override EqualityTest astNode;
TypeofExpr typeof;
string typeofStr;
TypeofGuard() {
typeof = astNode.getAnOperand() and
typeofStr = astNode.getAnOperand().getStringValue()
}
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
e = typeof.getOperand() and
outcome = astNode.getPolarity() and
(
typeofStr = "object" and
label = "constructor"
or
typeofStr = "function" and
label = "__proto__"
)
or
e = typeof.getOperand() and
outcome = astNode.getPolarity().booleanNot() and
(
// If something is not an object, sanitize object, as both must end
// in non-function prototype object.
typeofStr = "object" and
label instanceof UnsafePropLabel
or
typeofStr = "function" and
label = "constructor"
)
}
}
/**
* A check of form `["__proto__"].includes(x)` or similar.
*/
class BlacklistInclusionGuard extends DataFlow::LabeledBarrierGuardNode, InclusionTest {
UnsafePropLabel label;
BlacklistInclusionGuard() {
exists(DataFlow::ArrayCreationNode array |
array.getAnElement().getStringValue() = label and
array.flowsTo(getContainerNode())
)
}
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel lbl) {
outcome = getPolarity().booleanNot() and
e = getContainedNode().asExpr() and
label = lbl
}
}
/**
* A check of form `xs.includes(x)` or similar, which sanitizes `x` in the true case.
*/
class WhitelistInclusionGuard extends DataFlow::LabeledBarrierGuardNode {
WhitelistInclusionGuard() {
this instanceof TaintTracking::PositiveIndexOfSanitizer or
this instanceof TaintTracking::InclusionSanitizer
}
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel lbl) {
this.(TaintTracking::AdditionalSanitizerGuardNode).sanitizes(outcome, e) and
lbl instanceof UnsafePropLabel
}
}
/**
* Gets a meaningful name for `node` if possible.
*/
string getExprName(DataFlow::Node node) {
result = node.asExpr().(Identifier).getName()
or
result = node.asExpr().(DotExpr).getPropertyName()
}
/**
* Gets a name to display for `node`.
*/
string deriveExprName(DataFlow::Node node) {
result = getExprName(node)
or
not exists(getExprName(node)) and
result = "this object"
}
/**
* Holds if the dynamic property write `base[prop] = rhs` can pollute the prototype
* of `base` due to flow from `enum`.
*
* In most cases this will result in an alert, the exception being the case where
* `base` does not have a prototype at all.
*/
predicate isPrototypePollutingAssignment(Node base, Node prop, Node rhs, EnumeratedPropName enum) {
dynamicPropWrite(base, prop, rhs) and
exists(PropNameTracking cfg |
cfg.hasFlow(enum, base) and
cfg.hasFlow(enum, prop) and
cfg.hasFlow(enum.getASourceProp(), rhs)
)
}
/** Gets a data flow node leading to the base of a prototype-polluting assignment. */
private DataFlow::SourceNode getANodeLeadingToBase(DataFlow::TypeBackTracker t, Node base) {
t.start() and
isPrototypePollutingAssignment(base, _, _, _) and
result = base.getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 |
result = getANodeLeadingToBase(t2, base).backtrack(t2, t)
)
}
/**
* Gets a data flow node leading to the base of dynamic property read leading to a
* prototype-polluting assignment.
*
* For example, this is the `dst` in `dst[key1][key2] = ...`.
* This dynamic read is where the reference to a built-in prototype object is obtained,
* and we need this to ensure that this object actually has a prototype.
*/
private DataFlow::SourceNode getANodeLeadingToBaseBase(DataFlow::TypeBackTracker t, Node base) {
exists(DynamicPropRead read |
read = getANodeLeadingToBase(t, base) and
result = read.getBase().getALocalSource()
)
or
exists(DataFlow::TypeBackTracker t2 |
result = getANodeLeadingToBaseBase(t2, base).backtrack(t2, t)
)
}
DataFlow::SourceNode getANodeLeadingToBaseBase(Node base) {
result = getANodeLeadingToBaseBase(DataFlow::TypeBackTracker::end(), base)
}
/** A call to `Object.create(null)`. */
class ObjectCreateNullCall extends CallNode {
ObjectCreateNullCall() {
this = globalVarRef("Object").getAMemberCall("create") and
getArgument(0).asExpr() instanceof NullLiteral
}
}
from
PropNameTracking cfg, DataFlow::PathNode source, DataFlow::PathNode sink, EnumeratedPropName enum,
Node base
where
cfg.hasFlowPath(source, sink) and
isPrototypePollutingAssignment(base, _, _, enum) and
sink.getNode() = base and
source.getNode() = enum and
(
getANodeLeadingToBaseBase(base) instanceof ObjectLiteralNode
or
not getANodeLeadingToBaseBase(base) instanceof ObjectCreateNullCall
)
select base, source, sink,
"Properties are copied from $@ to $@ without guarding against prototype pollution.",
enum.getSourceObject(), deriveExprName(enum.getSourceObject()), base, deriveExprName(base)

Просмотреть файл

@ -0,0 +1,10 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}

Просмотреть файл

@ -0,0 +1,10 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (dst.hasOwnProperty(key) && isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (key === "__proto__" || key === "constructor") continue;
if (isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}

Просмотреть файл

@ -3,6 +3,7 @@
*/
import javascript
private import semmle.javascript.dataflow.InferredTypes
deprecated
module GlobalAccessPath {
@ -34,6 +35,24 @@ module GlobalAccessPath {
}
}
/**
* Provides predicates for associating access paths with data flow nodes.
*
* For example, `AccessPath.getAReferenceTo(x)` can be used to obtain the global access path
* that `x` refers to, as in the following sample:
* ```
* function f() {
* let v = foo.bar; // reference to 'foo.bar'
* v.baz; // reference to 'foo.bar.baz'
* }
*
* (function(ns) {
* ns.x; // reference to 'NS.x'
* })(NS = NS || {});
* ```
*
* A pseudo-property named `[number]` is sometimes used to represent array indices within an access path.
*/
module AccessPath {
/**
* A source node that can be the root of an access path.
@ -80,6 +99,35 @@ module AccessPath {
result = base + "." + prop
}
/**
* Holds if `variable` is compared to the `length` property of something, indicating
* that, if used as a dynamic property name, it represents an array index.
*/
private predicate isLikelyArrayIndex(SsaVariable variable) {
exists(RelationalComparison cmp, DataFlow::PropRead length, Expr lengthUse |
length.getPropertyName() = "length" and
length.flowsToExpr(lengthUse) and
cmp.hasOperands(variable.getAUse(), lengthUse)
)
or
isLikelyArrayIndex(variable.getDefinition().(SsaRefinementNode).getAnInput())
}
/**
* Holds if `prop` likely accesses a non-constant array element.
*/
private predicate isLikelyDynamicArrayAccess(DataFlow::PropRead prop) {
// The implicit PropRead in a for-of loop is represented by its lvalue node
prop = DataFlow::lvalueNode(any(ForOfStmt stmt).getLValue())
or
// Match an index access x[i] where `i` is likely an array index variable.
not exists(prop.getPropertyName()) and
exists(SsaVariable indexVar |
isLikelyArrayIndex(indexVar) and
prop.getPropertyNameExpr() = indexVar.getAUse()
)
}
/**
* Gets the access path relative to `root` referred to by `node`.
*
@ -115,6 +163,9 @@ module AccessPath {
not node.accessesGlobal(_) and
exists(DataFlow::PropRead prop | node = prop |
result = join(fromReference(prop.getBase(), root), prop.getPropertyName())
or
isLikelyDynamicArrayAccess(prop) and
result = join(fromReference(prop.getBase(), root), "[number]")
)
or
exists(Closure::ClosureNamespaceAccess acc | node = acc |

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1 @@
Security/CWE-400/PrototypePollutionUtility.ql

Просмотреть файл

@ -0,0 +1,362 @@
import dummy from 'somewhere';
function copyUsingForIn(dst, src) {
for (let key in src) {
if (dst[key]) {
copyUsingForIn(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
function copyUsingKeys(dst, src) {
Object.keys(src).forEach(key => {
if (dst[key]) {
copyUsingKeys(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
});
}
function copyRest(dst, ...sources) {
for (let source of sources) {
for (let key in source) {
copyRestAux(dst, source[key], key);
}
}
}
function copyRestAux(dst, value, key) {
let dstValue = dst[key];
if (dstValue) {
copyRest(dstValue, value);
} else {
dst[key] = value; // NOT OK
}
}
function copyProtoGuarded(dst, src) {
for (let key in src) {
if (key === "__proto__") continue;
if (dst[key]) {
copyProtoGuarded(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
function copyCtorGuarded(dst, src) {
for (let key in src) {
if (key === "constructor") continue;
if (dst[key]) {
copyCtorGuarded(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
function copyDoubleGuarded(dst, src) {
for (let key in src) {
if (key === "constructor" || key === "__proto__") continue;
if (dst[key]) {
copyDoubleGuarded(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
function isSafe(key) {
return key !== "__proto__" && key !== "constructor" && key !== "prototype";
}
function copyComplex(dst, src) {
for (let key in src) {
if (isSafe(key)) {
if (dst[key]) {
copyComplex(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
}
function copyHasOwnProperty(dst, src) {
for (let key in src) {
// Guarding the recursive case by dst.hasOwnProperty is safe,
// since '__proto__' and 'constructor' are not own properties of the destination object.
if (dst.hasOwnProperty(key)) {
copyHasOwnProperty(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
function copyHasOwnPropertyBad(dst, src) {
for (let key in src) {
// Guarding using src.hasOwnProperty is *not* effective,
// since '__proto__' and 'constructor' are own properties in the payload.
if (!src.hasOwnProperty(key)) continue; // Not safe
if (dst[key]) {
copyHasOwnPropertyBad(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
let _hasOwnProp = Object.prototype.hasOwnProperty;
function copyHasOwnPropertyTearOff(dst, src) {
for (let key in src) {
if (_hasOwnProp.call(dst, key)) {
copyHasOwnPropertyTearOff(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
function shallowExtend(dst, src) {
for (let key in src) {
dst[key] = src[key]; // OK
}
}
function transform(src, fn) {
if (typeof src !== 'object') return fn(src);
for (let key in src) {
src[key] = transform(src[key], fn); // OK
}
return src;
}
function clone(src) {
if (typeof src !== 'object') return src;
let result = {};
for (let key in src) {
result[key] = clone(src[key]); // OK
}
return result;
}
function higherOrderRecursion(dst, src, callback) {
for (let key in src) {
if (dst[key]) {
callback(dst, src, key);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
function higherOrderRecursionEntry(dst, src) {
higherOrderRecursion(dst, src, (dst, src, key) => {
higherOrderRecursionEntry(dst[key], src[key]);
});
}
function instanceofObjectGuard(dst, src) {
for (let key in src) {
let dstValue = dst[key];
if (typeof dstValue === 'object' && dstValue instanceof Object) {
instanceofObjectGuard(dstValue, src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
let blacklist = ["__proto__", "constructor"];
function copyWithBlacklist(dst, src) {
for (let key in src) {
if (blacklist.indexOf(key) >= 0) continue;
if (dst[key]) {
copyWithBlacklist(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
function copyUsingPlainForLoop(dst, src) {
let keys = Object.keys(src);
for (let i = 0; i < keys.length; ++i) {
let key = keys[i];
if (dst[key]) {
copyUsingPlainForLoop(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
function copyUsingPlainForLoopNoAlias(dst, src) {
// Like copyUsingPlainForLoop, but with keys[i] duplicated at every use-site
let keys = Object.keys(src);
for (let i = 0; i < keys.length; ++i) {
if (dst[key]) {
copyUsingPlainForLoopNoAlias(dst[keys[i]], src[keys[i]]);
} else {
dst[keys[i]] = src[keys[i]]; // NOT OK - but not flagged
}
}
}
function deepSet(map, key1, key2, value) {
if (!map[key1]) {
map[key1] = Object.create(null);
}
map[key1][key2] = value; // OK
}
function deepSetCaller(data) {
let map1 = Object.create(null);
let map2 = Object.create(null);
for (let key in data) {
deepSet(map1, key, 'x', data[key]);
deepSet(map2, 'x', key, data[key]);
}
}
function deepSetBad(map, key1, key2, value) {
if (!map[key1]) {
map[key1] = Object.create(null);
}
map[key1][key2] = value; // NOT OK - object literal can flow here
}
function deepSetCallerBad(data) {
let map1 = Object.create(null);
for (let key in data) {
deepSetBad({}, key, 'x', data[key]); // oops
deepSetBad(map1, 'x', key, data[key]);
}
}
function maybeCopy(x) {
if (x && typeof x === 'object') {
return {...x};
} else {
return x;
}
}
function mergeWithCopy(dst, src) {
if (dst == null) return src;
let result = maybeCopy(dst);
for (let key in src) {
if (src.hasOwnProperty(key)) {
result[key] = mergeWithCopy(dst[key], src[key]); // OK
}
}
return result;
}
function copyUsingEntries(dst, src) {
Object.entries(src).forEach(entry => {
let key = entry[0];
let value = entry[1];
if (dst[key]) {
copyUsingEntries(dst[key], value);
} else {
dst[key] = value; // NOT OK
}
});
}
function copyUsingReflect(dst, src) {
Reflect.ownKeys(src).forEach(key => {
if (dst[key]) {
copyUsingReflect(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
});
}
function copyWithPath(dst, src, path) {
for (let key in src) {
if (src.hasOwnProperty(key)) {
if (dst[key]) {
copyWithPath(dst[key], src[key], path ? path + '.' + key : key);
} else {
let target = {};
target[path] = {};
target[path][key] = src[key]; // OK
doSomething(target);
}
}
}
return dst;
}
function typeofObjectTest(dst, src) {
for (let key in src) {
if (src.hasOwnProperty(key)) {
let value = src[key];
if (dst[key] && typeof value === 'object') {
typeofObjectTest(dst[key], value);
} else {
dst[key] = value; // NOT OK
}
}
}
}
function mergeRephinementNode(dst, src) {
for (let key in src) {
if (src.hasOwnProperty(key)) {
if (key === key && key === key) continue; // Create a phi-node of refinement nodes
let value = src[key];
if (dst[key] && typeof value === 'object') {
mergeRephinementNode(dst[key], value);
} else {
dst[key] = value; // NOT OK
}
}
}
}
function mergeSelective(dst, src) {
for (let key in src) {
if (src.hasOwnProperty(key)) {
// Only 'prefs' is merged recursively
if (key in dst && key !== 'prefs') {
continue;
}
if (dst[key]) {
mergeSelective(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
}
function isNonArrayObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
function mergePlainObjectsOnly(target, source) {
if (isNonArrayObject(target) && isNonArrayObject(source)) {
Object.keys(source).forEach(key => {
if (key === '__proto__') {
return;
}
if (isNonArrayObject(source[key]) && key in target) {
target[key] = mergePlainObjectsOnly(target[key], source[key], options);
} else {
target[key] = source[key]; // OK
}
});
}
return target;
}

Просмотреть файл

@ -0,0 +1,10 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}

Просмотреть файл

@ -0,0 +1,10 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (dst.hasOwnProperty(key) && isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (key === "__proto__" || key === "constructor") continue;
if (isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}