Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bc78710

Browse files
asger-semmleasgerf
authored andcommitted
JS: Fix FPs from Object.create(null)
1 parent c889420 commit bc78710

3 files changed

Lines changed: 272 additions & 5 deletions

File tree

javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -349,17 +349,78 @@ string deriveExprName(DataFlow::Node node) {
349349
result = "this object"
350350
}
351351

352+
/**
353+
* Holds if the dynamic property write `base[prop] = rhs` can pollute the prototype
354+
* of `base` due to flow from `enum`.
355+
*
356+
* In most cases this will result in an alert, the exception being the case where
357+
* `base` does not have a prototype at all.
358+
*/
359+
predicate isPrototypePollutingAssignment(Node base, Node prop, Node rhs, EnumeratedPropName enum) {
360+
dynamicPropWrite(base, prop, rhs) and
361+
exists(PropNameTracking cfg |
362+
cfg.hasFlow(enum, base) and
363+
cfg.hasFlow(enum, prop) and
364+
cfg.hasFlow(enum, rhs) and
365+
cfg.hasFlow(enum.getASourceProp(), rhs)
366+
)
367+
}
368+
369+
/** Gets a data flow node leading to the base of a prototype-polluting assignment. */
370+
private DataFlow::SourceNode getANodeLeadingToBase(DataFlow::TypeBackTracker t, Node base) {
371+
t.start() and
372+
isPrototypePollutingAssignment(base, _, _, _) and
373+
result = base.getALocalSource()
374+
or
375+
exists(DataFlow::TypeBackTracker t2 |
376+
result = getANodeLeadingToBase(t2, base).backtrack(t2, t)
377+
)
378+
}
379+
380+
/**
381+
* Gets a data flow node leading to the base of dynamic property read leading to a
382+
* prototype-polluting assignment.
383+
*
384+
* For example, this is the `dst` in `dst[key1][key2] = ...`.
385+
* This dynamic read is where the reference to a built-in prototype object is obtained,
386+
* and we need this to ensure that this object actually has a prototype.
387+
*/
388+
private DataFlow::SourceNode getANodeLeadingToBaseBase(DataFlow::TypeBackTracker t, Node base) {
389+
exists(DynamicPropRead read |
390+
read = getANodeLeadingToBase(t, base) and
391+
result = read.getBase().getALocalSource()
392+
)
393+
or
394+
exists(DataFlow::TypeBackTracker t2 |
395+
result = getANodeLeadingToBaseBase(t2, base).backtrack(t2, t)
396+
)
397+
}
398+
399+
DataFlow::SourceNode getANodeLeadingToBaseBase(Node base) {
400+
result = getANodeLeadingToBaseBase(DataFlow::TypeBackTracker::end(), base)
401+
}
402+
403+
/** A call to `Object.create(null)`. */
404+
class ObjectCreateNullCall extends CallNode {
405+
ObjectCreateNullCall() {
406+
this = globalVarRef("Object").getAMemberCall("create") and
407+
getArgument(0).asExpr() instanceof NullLiteral
408+
}
409+
}
410+
352411
from
353412
PropNameTracking cfg, DataFlow::PathNode source, DataFlow::PathNode sink, EnumeratedPropName enum,
354-
Node base, Node prop, Node rhs
413+
Node base
355414
where
356415
cfg.hasFlowPath(source, sink) and
357-
dynamicPropWrite(base, prop, rhs) and
416+
isPrototypePollutingAssignment(base, _, _, enum) and
358417
sink.getNode() = base and
359418
source.getNode() = enum and
360-
cfg.hasFlow(enum, prop) and
361-
cfg.hasFlow(enum, rhs) and
362-
cfg.hasFlow(enum.getASourceProp(), rhs)
419+
(
420+
getANodeLeadingToBaseBase(base) instanceof ObjectLiteralNode
421+
or
422+
not getANodeLeadingToBaseBase(base) instanceof ObjectCreateNullCall
423+
)
363424
select base, source, sink,
364425
"Properties are copied from $@ to $@ without guarding against prototype pollution.",
365426
enum.getSourceObject(), deriveExprName(enum.getSourceObject()), base, deriveExprName(base)

0 commit comments

Comments
 (0)