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

Skip to content

Commit f1f4592

Browse files
committed
JS: Port PrototypePollutingAssignment
1 parent 81d2721 commit f1f4592

6 files changed

Lines changed: 286 additions & 307 deletions

File tree

javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentCustomizations.qll

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,30 @@ module PrototypePollutingAssignment {
3838
*/
3939
abstract class Sanitizer extends DataFlow::Node { }
4040

41+
/**
42+
* A barrier guard for prototype-polluting assignments.
43+
*/
44+
abstract class BarrierGuard extends DataFlow::Node {
45+
/**
46+
* Holds if this node acts as a barrier for data flow, blocking further flow from `e` if `this` evaluates to `outcome`.
47+
*/
48+
predicate blocksExpr(boolean outcome, Expr e) { none() }
49+
50+
/**
51+
* Holds if this node acts as a barrier for `label`, blocking further flow from `e` if `this` evaluates to `outcome`.
52+
*/
53+
predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) { none() }
54+
}
55+
56+
/** A subclass of `BarrierGuard` that is used for backward compatibility with the old data flow library. */
57+
abstract class BarrierGuardLegacy extends BarrierGuard, TaintTracking::SanitizerGuardNode {
58+
override predicate sanitizes(boolean outcome, Expr e) { this.blocksExpr(outcome, e) }
59+
60+
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
61+
this.blocksExpr(outcome, e, label)
62+
}
63+
}
64+
4165
/** A flow label representing the `Object.prototype` value. */
4266
abstract class ObjectPrototype extends DataFlow::FlowLabel {
4367
ObjectPrototype() { this = "Object.prototype" }
@@ -46,7 +70,9 @@ module PrototypePollutingAssignment {
4670
/** The base of an assignment or extend call, as a sink for `Object.prototype` references. */
4771
private class DefaultSink extends Sink {
4872
DefaultSink() {
49-
this = any(DataFlow::PropWrite write).getBase()
73+
// Avoid using PropWrite here as we only want assignments that can mutate a pre-existing object,
74+
// so not object literals or array literals.
75+
this = any(AssignExpr assign).getTarget().(PropAccess).getBase().flow()
5076
or
5177
this = any(ExtendCall c).getDestinationOperand()
5278
or
@@ -67,7 +93,9 @@ module PrototypePollutingAssignment {
6793
* A parameter of an exported function, seen as a source prototype-polluting assignment.
6894
*/
6995
class ExternalInputSource extends Source {
70-
ExternalInputSource() { this = Exports::getALibraryInputParameter() }
96+
ExternalInputSource() {
97+
this = Exports::getALibraryInputParameter() and not this instanceof RemoteFlowSource
98+
}
7199

72100
override string describe() { result = "library input" }
73101
}

javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentQuery.qll

Lines changed: 109 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,18 @@ private class ConcreteObjectPrototype extends ObjectPrototype {
1919
}
2020

2121
/** A taint-tracking configuration for reasoning about prototype-polluting assignments. */
22-
class Configuration extends TaintTracking::Configuration {
23-
Configuration() { this = "PrototypePollutingAssignment" }
22+
module PrototypePollutingAssignmentConfig implements DataFlow::StateConfigSig {
23+
class FlowState = DataFlow::FlowLabel;
2424

25-
override predicate isSource(DataFlow::Node node) { node instanceof Source }
25+
predicate isSource(DataFlow::Node node, DataFlow::FlowLabel label) {
26+
node instanceof Source and label.isTaint()
27+
}
2628

27-
override predicate isSink(DataFlow::Node node, DataFlow::FlowLabel lbl) {
29+
predicate isSink(DataFlow::Node node, DataFlow::FlowLabel lbl) {
2830
node.(Sink).getAFlowLabel() = lbl
2931
}
3032

31-
override predicate isSanitizer(DataFlow::Node node) {
33+
predicate isBarrier(DataFlow::Node node) {
3234
node instanceof Sanitizer
3335
or
3436
// Concatenating with a string will in practice prevent the string `__proto__` from arising.
@@ -53,17 +55,24 @@ class Configuration extends TaintTracking::Configuration {
5355
not replace.getRawReplacement().getStringValue() = ""
5456
)
5557
)
58+
or
59+
node = DataFlow::MakeBarrierGuard<BarrierGuard>::getABarrierNode()
5660
}
5761

58-
override predicate isSanitizerOut(DataFlow::Node node, DataFlow::FlowLabel lbl) {
62+
predicate isBarrierOut(DataFlow::Node node, DataFlow::FlowLabel lbl) {
5963
// Suppress the value-preserving step src -> dst in `extend(dst, src)`. This is modeled as a value-preserving
6064
// step because it preserves all properties, but the destination is not actually Object.prototype.
6165
node = any(ExtendCall call).getASourceOperand() and
6266
lbl instanceof ObjectPrototype
6367
}
6468

65-
override predicate isAdditionalFlowStep(
66-
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl
69+
predicate isBarrierIn(DataFlow::Node node, DataFlow::FlowLabel lbl) {
70+
// FIXME: This should only be an in-barrier for the corresponding flow state, but flow-state specific in-barriers are not supported right now.
71+
isSource(node, lbl)
72+
}
73+
74+
predicate isAdditionalFlowStep(
75+
DataFlow::Node pred, DataFlow::FlowLabel inlbl, DataFlow::Node succ, DataFlow::FlowLabel outlbl
6776
) {
6877
// Step from x -> obj[x] while switching to the ObjectPrototype label
6978
// (If `x` can have the value `__proto__` then the result can be Object.prototype)
@@ -91,7 +100,80 @@ class Configuration extends TaintTracking::Configuration {
91100
outlbl instanceof ObjectPrototype
92101
)
93102
or
94-
DataFlow::localFieldStep(pred, succ) and inlbl = outlbl
103+
// TODO: local field step becomes a jump step, resulting in FPs (closure-lib)
104+
// TODO: localFieldStep is too expensive with dataflow2
105+
// DataFlow::localFieldStep(pred, succ)
106+
none()
107+
or
108+
inlbl.isTaint() and
109+
TaintTracking::defaultTaintStep(pred, succ) and
110+
inlbl = outlbl
111+
}
112+
113+
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
114+
115+
predicate isBarrier(DataFlow::Node node, DataFlow::FlowLabel lbl) {
116+
lbl.isTaint() and
117+
TaintTracking::defaultSanitizer(node)
118+
or
119+
// Don't propagate into the receiver, as the method lookups will generally fail on Object.prototype.
120+
node instanceof DataFlow::ThisNode and
121+
lbl instanceof ObjectPrototype
122+
or
123+
node = DataFlow::MakeLabeledBarrierGuard<BarrierGuard>::getABarrierNode(lbl)
124+
}
125+
}
126+
127+
/** Taint-tracking for reasoning about prototype-polluting assignments. */
128+
module PrototypePollutingAssignmentFlow =
129+
DataFlow::GlobalWithState<PrototypePollutingAssignmentConfig>;
130+
131+
/**
132+
* Holds if the given `source, sink` pair should not be reported, as we don't have enough
133+
* confidence in the alert given that source is a library input.
134+
*/
135+
bindingset[source, sink]
136+
predicate isIgnoredLibraryFlow(ExternalInputSource source, Sink sink) {
137+
exists(source) and
138+
// filter away paths that start with library inputs and end with a write to a fixed property.
139+
exists(DataFlow::PropWrite write | sink = write.getBase() |
140+
// fixed property name
141+
exists(write.getPropertyName())
142+
or
143+
// non-string property name (likely number)
144+
exists(Expr prop | prop = write.getPropertyNameExpr() |
145+
not prop.analyze().getAType() = TTString()
146+
)
147+
)
148+
}
149+
150+
/**
151+
* DEPRECATED. Use the `PrototypePollutingAssignmentFlow` module instead.
152+
*/
153+
deprecated class Configuration extends TaintTracking::Configuration {
154+
Configuration() { this = "PrototypePollutingAssignment" }
155+
156+
override predicate isSource(DataFlow::Node node) { node instanceof Source }
157+
158+
override predicate isSink(DataFlow::Node node, DataFlow::FlowLabel lbl) {
159+
node.(Sink).getAFlowLabel() = lbl
160+
}
161+
162+
override predicate isSanitizer(DataFlow::Node node) {
163+
PrototypePollutingAssignmentConfig::isBarrier(node)
164+
}
165+
166+
override predicate isSanitizerOut(DataFlow::Node node, DataFlow::FlowLabel lbl) {
167+
// Suppress the value-preserving step src -> dst in `extend(dst, src)`. This is modeled as a value-preserving
168+
// step because it preserves all properties, but the destination is not actually Object.prototype.
169+
node = any(ExtendCall call).getASourceOperand() and
170+
lbl instanceof ObjectPrototype
171+
}
172+
173+
override predicate isAdditionalFlowStep(
174+
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl
175+
) {
176+
PrototypePollutingAssignmentConfig::isAdditionalFlowStep(pred, inlbl, succ, outlbl)
95177
}
96178

97179
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
@@ -174,51 +256,49 @@ private predicate isPropertyPresentOnObjectPrototype(string prop) {
174256
}
175257

176258
/** A check of form `e.prop` where `prop` is not present on `Object.prototype`. */
177-
private class PropertyPresenceCheck extends TaintTracking::LabeledSanitizerGuardNode,
178-
DataFlow::ValueNode
179-
{
259+
private class PropertyPresenceCheck extends BarrierGuardLegacy, DataFlow::ValueNode {
180260
override PropAccess astNode;
181261

182262
PropertyPresenceCheck() {
183263
astNode = any(ConditionGuardNode c).getTest() and // restrict size of charpred
184264
not isPropertyPresentOnObjectPrototype(astNode.getPropertyName())
185265
}
186266

187-
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
267+
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
188268
e = astNode.getBase() and
189269
outcome = true and
190270
label instanceof ObjectPrototype
191271
}
192272
}
193273

194274
/** A check of form `"prop" in e` where `prop` is not present on `Object.prototype`. */
195-
private class InExprCheck extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::ValueNode {
275+
private class InExprCheck extends BarrierGuardLegacy, DataFlow::ValueNode {
196276
override InExpr astNode;
197277

198278
InExprCheck() {
199279
not isPropertyPresentOnObjectPrototype(astNode.getLeftOperand().getStringValue())
200280
}
201281

202-
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
282+
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
203283
e = astNode.getRightOperand() and
204284
outcome = true and
205285
label instanceof ObjectPrototype
206286
}
207287
}
208288

209289
/** A check of form `e instanceof X`, which is always false for `Object.prototype`. */
210-
private class InstanceofCheck extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::ValueNode {
290+
private class InstanceofCheck extends BarrierGuardLegacy, DataFlow::ValueNode {
211291
override InstanceofExpr astNode;
212292

213-
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
293+
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
214294
e = astNode.getLeftOperand() and
215295
outcome = true and
216296
label instanceof ObjectPrototype
217297
}
218298
}
219299

220300
/** A check of form `typeof e === "string"`. */
221-
private class TypeofCheck extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::ValueNode {
301+
private class TypeofCheck extends BarrierGuardLegacy, DataFlow::ValueNode {
222302
override EqualityTest astNode;
223303
Expr operand;
224304
boolean polarity;
@@ -231,28 +311,28 @@ private class TypeofCheck extends TaintTracking::LabeledSanitizerGuardNode, Data
231311
)
232312
}
233313

234-
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
314+
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
235315
polarity = outcome and
236316
e = operand and
237317
label instanceof ObjectPrototype
238318
}
239319
}
240320

241321
/** A guard that checks whether `x` is a number. */
242-
class NumberGuard extends TaintTracking::SanitizerGuardNode instanceof DataFlow::CallNode {
322+
class NumberGuard extends BarrierGuardLegacy instanceof DataFlow::CallNode {
243323
Expr x;
244324
boolean polarity;
245325

246326
NumberGuard() { TaintTracking::isNumberGuard(this, x, polarity) }
247327

248-
override predicate sanitizes(boolean outcome, Expr e) { e = x and outcome = polarity }
328+
override predicate blocksExpr(boolean outcome, Expr e) { e = x and outcome = polarity }
249329
}
250330

251331
/** A call to `Array.isArray`, which is false for `Object.prototype`. */
252-
private class IsArrayCheck extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::CallNode {
332+
private class IsArrayCheck extends BarrierGuardLegacy, DataFlow::CallNode {
253333
IsArrayCheck() { this = DataFlow::globalVarRef("Array").getAMemberCall("isArray") }
254334

255-
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
335+
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
256336
e = this.getArgument(0).asExpr() and
257337
outcome = true and
258338
label instanceof ObjectPrototype
@@ -262,12 +342,12 @@ private class IsArrayCheck extends TaintTracking::LabeledSanitizerGuardNode, Dat
262342
/**
263343
* Sanitizer guard of form `x !== "__proto__"`.
264344
*/
265-
private class EqualityCheck extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode {
345+
private class EqualityCheck extends BarrierGuardLegacy, DataFlow::ValueNode {
266346
override EqualityTest astNode;
267347

268348
EqualityCheck() { astNode.getAnOperand().getStringValue() = "__proto__" }
269349

270-
override predicate sanitizes(boolean outcome, Expr e) {
350+
override predicate blocksExpr(boolean outcome, Expr e) {
271351
e = astNode.getAnOperand() and
272352
outcome = astNode.getPolarity().booleanNot()
273353
}
@@ -276,10 +356,10 @@ private class EqualityCheck extends TaintTracking::SanitizerGuardNode, DataFlow:
276356
/**
277357
* Sanitizer guard of the form `x.includes("__proto__")`.
278358
*/
279-
private class IncludesCheck extends TaintTracking::LabeledSanitizerGuardNode, InclusionTest {
359+
private class IncludesCheck extends BarrierGuardLegacy, InclusionTest {
280360
IncludesCheck() { this.getContainedNode().mayHaveStringValue("__proto__") }
281361

282-
override predicate sanitizes(boolean outcome, Expr e) {
362+
override predicate blocksExpr(boolean outcome, Expr e) {
283363
e = this.getContainerNode().asExpr() and
284364
outcome = this.getPolarity().booleanNot()
285365
}
@@ -288,7 +368,7 @@ private class IncludesCheck extends TaintTracking::LabeledSanitizerGuardNode, In
288368
/**
289369
* A sanitizer guard that checks tests whether `x` is included in a list like `["__proto__"].includes(x)`.
290370
*/
291-
private class DenyListInclusionGuard extends TaintTracking::SanitizerGuardNode, InclusionTest {
371+
private class DenyListInclusionGuard extends BarrierGuardLegacy, InclusionTest {
292372
DenyListInclusionGuard() {
293373
this.getContainerNode()
294374
.getALocalSource()
@@ -297,7 +377,7 @@ private class DenyListInclusionGuard extends TaintTracking::SanitizerGuardNode,
297377
.mayHaveStringValue("__proto__")
298378
}
299379

300-
override predicate sanitizes(boolean outcome, Expr e) {
380+
override predicate blocksExpr(boolean outcome, Expr e) {
301381
e = this.getContainedNode().asExpr() and
302382
outcome = super.getPolarity().booleanNot()
303383
}

javascript/ql/src/Security/CWE-915/PrototypePollutingAssignment.ql

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919

2020
import javascript
2121
import semmle.javascript.security.dataflow.PrototypePollutingAssignmentQuery
22-
import DataFlow::PathGraph
22+
import PrototypePollutingAssignmentFlow::PathGraph
2323

24-
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
25-
where cfg.hasFlowPath(source, sink)
24+
from
25+
PrototypePollutingAssignmentFlow::PathNode source, PrototypePollutingAssignmentFlow::PathNode sink
26+
where
27+
PrototypePollutingAssignmentFlow::flowPath(source, sink) and
28+
not isIgnoredLibraryFlow(source.getNode(), sink.getNode())
2629
select sink, source, sink,
2730
"This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@.",
2831
source.getNode(), source.getNode().(Source).describe()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| query-tests/Security/CWE-915/PrototypePollutingAssignment/lib.js:70 | expected an alert, but found none | NOT OK | Config |

javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingAssignment/Consistency.ql

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ import javascript
22
import testUtilities.ConsistencyChecking
33
import semmle.javascript.security.dataflow.PrototypePollutingAssignmentQuery
44

5-
class Config extends ConsistencyConfiguration, Configuration {
5+
class Config extends ConsistencyConfiguration {
6+
Config() { this = "Config" }
7+
68
override File getAFile() { any() }
9+
10+
override DataFlow::Node getAnAlert() {
11+
exists(DataFlow::Node source |
12+
PrototypePollutingAssignmentFlow::flow(source, result) and
13+
not isIgnoredLibraryFlow(source, result)
14+
)
15+
}
716
}

0 commit comments

Comments
 (0)