mirror of
https://github.com/github/codeql.git
synced 2026-04-23 15:55:18 +02:00
JS: Migrate PrototypePollutingAssignment
This commit is contained in:
@@ -10,6 +10,37 @@ private import javascript
|
||||
* that my cause prototype pollution.
|
||||
*/
|
||||
module PrototypePollutingAssignment {
|
||||
private newtype TFlowState =
|
||||
TTaint() or
|
||||
TObjectPrototype()
|
||||
|
||||
/** A flow state to associate with a tracked value. */
|
||||
class FlowState extends TFlowState {
|
||||
/** Gets a string representation fo this flow state */
|
||||
string toString() {
|
||||
this = TTaint() and result = "taint"
|
||||
or
|
||||
this = TObjectPrototype() and result = "object-prototype"
|
||||
}
|
||||
|
||||
deprecated DataFlow::FlowLabel toFlowLabel() {
|
||||
this = TTaint() and result.isTaint()
|
||||
or
|
||||
this = TObjectPrototype() and result instanceof ObjectPrototype
|
||||
}
|
||||
}
|
||||
|
||||
/** Predicates for working with flow states. */
|
||||
module FlowState {
|
||||
deprecated FlowState fromFlowLabel(DataFlow::FlowLabel label) { result.toFlowLabel() = label }
|
||||
|
||||
/** A tainted value. */
|
||||
FlowState taint() { result = TTaint() }
|
||||
|
||||
/** A reference to `Object.prototype` obtained by reading from a tainted property name. */
|
||||
FlowState objectPrototype() { result = TObjectPrototype() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow source for untrusted data from which the special `__proto__` property name may be arise.
|
||||
*/
|
||||
@@ -30,7 +61,10 @@ module PrototypePollutingAssignment {
|
||||
* Use the `taint` label for untrusted property names, and the `ObjectPrototype` label for
|
||||
* object mutations.
|
||||
*/
|
||||
abstract DataFlow::FlowLabel getAFlowLabel();
|
||||
FlowState getAFlowState() { result = FlowState::objectPrototype() }
|
||||
|
||||
/** DEPRECATED. Use `getAFlowState()` instead. */
|
||||
deprecated DataFlow::FlowLabel getAFlowLabel() { result = this.getAFlowState().toFlowLabel() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,16 +82,16 @@ module PrototypePollutingAssignment {
|
||||
predicate blocksExpr(boolean outcome, Expr e) { none() }
|
||||
|
||||
/**
|
||||
* Holds if this node acts as a barrier for `label`, blocking further flow from `e` if `this` evaluates to `outcome`.
|
||||
* Holds if this node acts as a barrier for `state`, blocking further flow from `e` if `this` evaluates to `outcome`.
|
||||
*/
|
||||
predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) { none() }
|
||||
predicate blocksExpr(boolean outcome, Expr e, FlowState state) { none() }
|
||||
|
||||
/** DEPRECATED. Use `blocksExpr` instead. */
|
||||
deprecated predicate sanitizes(boolean outcome, Expr e) { this.blocksExpr(outcome, e) }
|
||||
|
||||
/** DEPRECATED. Use `blocksExpr` instead. */
|
||||
deprecated predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
|
||||
this.blocksExpr(outcome, e, label)
|
||||
this.blocksExpr(outcome, e, FlowState::fromFlowLabel(label))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +108,7 @@ module PrototypePollutingAssignment {
|
||||
}
|
||||
|
||||
/** A flow label representing the `Object.prototype` value. */
|
||||
abstract class ObjectPrototype extends DataFlow::FlowLabel {
|
||||
abstract deprecated class ObjectPrototype extends DataFlow::FlowLabel {
|
||||
ObjectPrototype() { this = "Object.prototype" }
|
||||
}
|
||||
|
||||
@@ -90,7 +124,7 @@ module PrototypePollutingAssignment {
|
||||
this = any(DeleteExpr del).getOperand().flow().(DataFlow::PropRef).getBase()
|
||||
}
|
||||
|
||||
override DataFlow::FlowLabel getAFlowLabel() { result instanceof ObjectPrototype }
|
||||
override FlowState getAFlowState() { result = FlowState::objectPrototype() }
|
||||
}
|
||||
|
||||
/** A remote flow source or location.{hash,search} as a taint source. */
|
||||
|
||||
@@ -11,24 +11,23 @@ private import javascript
|
||||
private import semmle.javascript.DynamicPropertyAccess
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
import PrototypePollutingAssignmentCustomizations::PrototypePollutingAssignment
|
||||
private import PrototypePollutingAssignmentCustomizations::PrototypePollutingAssignment as PrototypePollutingAssignment
|
||||
private import semmle.javascript.filters.ClassifyFiles as ClassifyFiles
|
||||
|
||||
// Materialize flow labels
|
||||
private class ConcreteObjectPrototype extends ObjectPrototype {
|
||||
deprecated private class ConcreteObjectPrototype extends ObjectPrototype {
|
||||
ConcreteObjectPrototype() { this = this }
|
||||
}
|
||||
|
||||
/** A taint-tracking configuration for reasoning about prototype-polluting assignments. */
|
||||
module PrototypePollutingAssignmentConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState = DataFlow::FlowLabel;
|
||||
class FlowState = PrototypePollutingAssignment::FlowState;
|
||||
|
||||
predicate isSource(DataFlow::Node node, DataFlow::FlowLabel label) {
|
||||
node instanceof Source and label.isTaint()
|
||||
predicate isSource(DataFlow::Node node, FlowState label) {
|
||||
node instanceof Source and label = FlowState::taint()
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
||||
node.(Sink).getAFlowLabel() = lbl
|
||||
}
|
||||
predicate isSink(DataFlow::Node node, FlowState lbl) { node.(Sink).getAFlowState() = lbl }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof Sanitizer
|
||||
@@ -59,28 +58,28 @@ module PrototypePollutingAssignmentConfig implements DataFlow::StateConfigSig {
|
||||
node = DataFlow::MakeBarrierGuard<BarrierGuard>::getABarrierNode()
|
||||
}
|
||||
|
||||
predicate isBarrierOut(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
||||
predicate isBarrierOut(DataFlow::Node node, FlowState lbl) {
|
||||
// Suppress the value-preserving step src -> dst in `extend(dst, src)`. This is modeled as a value-preserving
|
||||
// step because it preserves all properties, but the destination is not actually Object.prototype.
|
||||
node = any(ExtendCall call).getASourceOperand() and
|
||||
lbl instanceof ObjectPrototype
|
||||
lbl = FlowState::objectPrototype()
|
||||
}
|
||||
|
||||
predicate isBarrierIn(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
||||
predicate isBarrierIn(DataFlow::Node node, FlowState lbl) {
|
||||
// FIXME: This should only be an in-barrier for the corresponding flow state, but flow-state specific in-barriers are not supported right now.
|
||||
isSource(node, lbl)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(
|
||||
DataFlow::Node pred, DataFlow::FlowLabel inlbl, DataFlow::Node succ, DataFlow::FlowLabel outlbl
|
||||
DataFlow::Node pred, FlowState inlbl, DataFlow::Node succ, FlowState outlbl
|
||||
) {
|
||||
// Step from x -> obj[x] while switching to the ObjectPrototype label
|
||||
// (If `x` can have the value `__proto__` then the result can be Object.prototype)
|
||||
exists(DynamicPropRead read |
|
||||
pred = read.getPropertyNameNode() and
|
||||
succ = read and
|
||||
inlbl.isTaint() and
|
||||
outlbl instanceof ObjectPrototype and
|
||||
inlbl = FlowState::taint() and
|
||||
outlbl = FlowState::objectPrototype() and
|
||||
// Exclude cases where the property name came from a property enumeration.
|
||||
// If the property name is an own property of the base object, the read won't
|
||||
// return Object.prototype.
|
||||
@@ -96,8 +95,8 @@ module PrototypePollutingAssignmentConfig implements DataFlow::StateConfigSig {
|
||||
proj.isSingletonProjection() and
|
||||
pred = proj.getASelector() and
|
||||
succ = proj and
|
||||
inlbl.isTaint() and
|
||||
outlbl instanceof ObjectPrototype
|
||||
inlbl = FlowState::taint() and
|
||||
outlbl = FlowState::objectPrototype()
|
||||
)
|
||||
or
|
||||
// TODO: local field step becomes a jump step, resulting in FPs (closure-lib)
|
||||
@@ -105,22 +104,22 @@ module PrototypePollutingAssignmentConfig implements DataFlow::StateConfigSig {
|
||||
// DataFlow::localFieldStep(pred, succ)
|
||||
none()
|
||||
or
|
||||
inlbl.isTaint() and
|
||||
inlbl = FlowState::taint() and
|
||||
TaintTracking::defaultTaintStep(pred, succ) and
|
||||
inlbl = outlbl
|
||||
}
|
||||
|
||||
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
||||
lbl.isTaint() and
|
||||
predicate isBarrier(DataFlow::Node node, FlowState lbl) {
|
||||
lbl = FlowState::taint() and
|
||||
TaintTracking::defaultSanitizer(node)
|
||||
or
|
||||
// Don't propagate into the receiver, as the method lookups will generally fail on Object.prototype.
|
||||
node instanceof DataFlow::ThisNode and
|
||||
lbl instanceof ObjectPrototype
|
||||
lbl = FlowState::objectPrototype()
|
||||
or
|
||||
node = DataFlow::MakeLabeledBarrierGuard<BarrierGuard>::getABarrierNode(lbl)
|
||||
node = DataFlow::MakeStateBarrierGuard<FlowState, BarrierGuard>::getABarrierNode(lbl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +172,8 @@ deprecated class Configuration extends TaintTracking::Configuration {
|
||||
override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl
|
||||
) {
|
||||
PrototypePollutingAssignmentConfig::isAdditionalFlowStep(pred, inlbl, succ, outlbl)
|
||||
PrototypePollutingAssignmentConfig::isAdditionalFlowStep(pred, FlowState::fromFlowLabel(inlbl),
|
||||
succ, FlowState::fromFlowLabel(outlbl))
|
||||
}
|
||||
|
||||
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
|
||||
@@ -264,10 +264,10 @@ private class PropertyPresenceCheck extends BarrierGuard, DataFlow::ValueNode {
|
||||
not isPropertyPresentOnObjectPrototype(astNode.getPropertyName())
|
||||
}
|
||||
|
||||
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e, FlowState label) {
|
||||
e = astNode.getBase() and
|
||||
outcome = true and
|
||||
label instanceof ObjectPrototype
|
||||
label = FlowState::objectPrototype()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,10 +279,10 @@ private class InExprCheck extends BarrierGuard, DataFlow::ValueNode {
|
||||
not isPropertyPresentOnObjectPrototype(astNode.getLeftOperand().getStringValue())
|
||||
}
|
||||
|
||||
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e, FlowState label) {
|
||||
e = astNode.getRightOperand() and
|
||||
outcome = true and
|
||||
label instanceof ObjectPrototype
|
||||
label = FlowState::objectPrototype()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,10 +290,10 @@ private class InExprCheck extends BarrierGuard, DataFlow::ValueNode {
|
||||
private class InstanceofCheck extends BarrierGuard, DataFlow::ValueNode {
|
||||
override InstanceofExpr astNode;
|
||||
|
||||
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e, FlowState label) {
|
||||
e = astNode.getLeftOperand() and
|
||||
outcome = true and
|
||||
label instanceof ObjectPrototype
|
||||
label = FlowState::objectPrototype()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,10 +311,10 @@ private class TypeofCheck extends BarrierGuard, DataFlow::ValueNode {
|
||||
)
|
||||
}
|
||||
|
||||
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e, FlowState label) {
|
||||
polarity = outcome and
|
||||
e = operand and
|
||||
label instanceof ObjectPrototype
|
||||
label = FlowState::objectPrototype()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,10 +332,10 @@ class NumberGuard extends BarrierGuard instanceof DataFlow::CallNode {
|
||||
private class IsArrayCheck extends BarrierGuard, DataFlow::CallNode {
|
||||
IsArrayCheck() { this = DataFlow::globalVarRef("Array").getAMemberCall("isArray") }
|
||||
|
||||
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e, FlowState label) {
|
||||
e = this.getArgument(0).asExpr() and
|
||||
outcome = true and
|
||||
label instanceof ObjectPrototype
|
||||
label = FlowState::objectPrototype()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user