@@ -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+
352411from
353412 PropNameTracking cfg , DataFlow:: PathNode source , DataFlow:: PathNode sink , EnumeratedPropName enum ,
354- Node base , Node prop , Node rhs
413+ Node base
355414where
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+ )
363424select 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