mirror of
https://github.com/github/codeql.git
synced 2025-12-31 08:06:36 +01:00
1271 lines
45 KiB
Plaintext
1271 lines
45 KiB
Plaintext
/**
|
|
* Provides classes for performing customized taint tracking.
|
|
*
|
|
* The classes in this module allow performing inter-procedural taint tracking
|
|
* from a custom set of source nodes to a custom set of sink nodes. In addition
|
|
* to normal data flow edges, taint is propagated along _taint edges_ that do
|
|
* not preserve the value of their input but only its taintedness, such as taking
|
|
* substrings. As for data flow configurations, additional flow edges can be
|
|
* specified, and conversely certain nodes or edges can be designated as taint
|
|
* _sanitizers_ that block flow.
|
|
*
|
|
* NOTE: The API of this library is not stable yet and may change in
|
|
* the future.
|
|
*/
|
|
|
|
import javascript
|
|
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
|
private import semmle.javascript.Unit
|
|
private import semmle.javascript.dataflow.InferredTypes
|
|
private import semmle.javascript.internal.CachedStages
|
|
|
|
/**
|
|
* Provides classes for modeling taint propagation.
|
|
*/
|
|
module TaintTracking {
|
|
/**
|
|
* A data flow tracking configuration that considers taint propagation through
|
|
* objects, arrays, promises and strings in addition to standard data flow.
|
|
*
|
|
* If a different set of flow edges is desired, extend this class and override
|
|
* `isAdditionalTaintStep`.
|
|
*/
|
|
abstract class Configuration extends DataFlow::Configuration {
|
|
bindingset[this]
|
|
Configuration() { any() }
|
|
|
|
/**
|
|
* Holds if `source` is a relevant taint source.
|
|
*
|
|
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
|
*/
|
|
// overridden to provide taint-tracking specific qldoc
|
|
override predicate isSource(DataFlow::Node source) { super.isSource(source) }
|
|
|
|
/**
|
|
* Holds if `sink` is a relevant taint sink.
|
|
*
|
|
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
|
*/
|
|
// overridden to provide taint-tracking specific qldoc
|
|
override predicate isSink(DataFlow::Node sink) { super.isSink(sink) }
|
|
|
|
/**
|
|
* Holds if the intermediate node `node` is a taint sanitizer, that is,
|
|
* tainted values can not flow into or out of `node`.
|
|
*
|
|
* Note that this only blocks flow through nodes that operate directly on the tainted value.
|
|
* An object _containing_ a tainted value in a property can still flow into and out of `node`.
|
|
* To block such objects, override `isBarrier` or use a labeled sanitizer to block the `data` flow label.
|
|
*
|
|
* For operations that _check_ if a value is tainted or safe, use `isSanitizerGuard` instead.
|
|
*/
|
|
predicate isSanitizer(DataFlow::Node node) { none() }
|
|
|
|
/**
|
|
* Holds if flow into `node` is prohibited.
|
|
*/
|
|
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
|
|
|
/**
|
|
* Holds if flow out `node` is prohibited.
|
|
*/
|
|
predicate isSanitizerOut(DataFlow::Node node) { none() }
|
|
|
|
/**
|
|
* Holds if flow into `node` is prohibited for the flow label `lbl`.
|
|
*/
|
|
predicate isSanitizerIn(DataFlow::Node node, DataFlow::FlowLabel lbl) { none() }
|
|
|
|
/**
|
|
* Holds if flow out `node` is prohibited for the flow label `lbl`.
|
|
*/
|
|
predicate isSanitizerOut(DataFlow::Node node, DataFlow::FlowLabel lbl) { none() }
|
|
|
|
/** Holds if the edge from `pred` to `succ` is a taint sanitizer. */
|
|
predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/** Holds if the edge from `pred` to `succ` is a taint sanitizer for data labelled with `lbl`. */
|
|
predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel lbl) {
|
|
none()
|
|
}
|
|
|
|
/**
|
|
* Holds if data flow node `guard` can act as a sanitizer when appearing
|
|
* in a condition.
|
|
*
|
|
* For example, if `guard` is the comparison expression in
|
|
* `if(x == 'some-constant'){ ... x ... }`, it could sanitize flow of
|
|
* `x` into the "then" branch.
|
|
*
|
|
* Node that this only handles checks that operate directly on the tainted value.
|
|
* Objects that _contain_ a tainted value in a property may still flow across the check.
|
|
* To block such objects, use a labeled sanitizer guard to block the `data` label.
|
|
*/
|
|
predicate isSanitizerGuard(SanitizerGuardNode guard) { none() }
|
|
|
|
override predicate isLabeledBarrier(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
|
super.isLabeledBarrier(node, lbl)
|
|
or
|
|
this.isSanitizer(node) and lbl.isTaint()
|
|
}
|
|
|
|
override predicate isBarrier(DataFlow::Node node) {
|
|
super.isBarrier(node)
|
|
or
|
|
// For variable accesses we block both the data and taint label, as a falsy value
|
|
// can't be an object, and thus can't have any tainted properties.
|
|
node instanceof DataFlow::VarAccessBarrier
|
|
}
|
|
|
|
final override predicate isBarrierEdge(
|
|
DataFlow::Node source, DataFlow::Node sink, DataFlow::FlowLabel lbl
|
|
) {
|
|
super.isBarrierEdge(source, sink, lbl)
|
|
or
|
|
this.isSanitizerEdge(source, sink, lbl)
|
|
or
|
|
this.isSanitizerEdge(source, sink) and lbl.isTaint()
|
|
}
|
|
|
|
final override predicate isBarrierIn(DataFlow::Node node) { none() }
|
|
|
|
final override predicate isBarrierOut(DataFlow::Node node) { none() }
|
|
|
|
final override predicate isBarrierIn(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
|
this.isSanitizerIn(node, lbl)
|
|
or
|
|
this.isSanitizerIn(node) and lbl.isTaint()
|
|
}
|
|
|
|
final override predicate isBarrierOut(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
|
this.isSanitizerOut(node, lbl)
|
|
or
|
|
this.isSanitizerOut(node) and lbl.isTaint()
|
|
}
|
|
|
|
final override predicate isBarrierGuard(DataFlow::BarrierGuardNode guard) {
|
|
super.isBarrierGuard(guard) or
|
|
guard.(AdditionalSanitizerGuardNode).appliesTo(this) or
|
|
this.isSanitizerGuard(guard)
|
|
}
|
|
|
|
/**
|
|
* Holds if the additional taint propagation step from `pred` to `succ`
|
|
* must be taken into account in the analysis.
|
|
*/
|
|
predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
final override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
this.isAdditionalTaintStep(pred, succ) or
|
|
sharedTaintStep(pred, succ)
|
|
}
|
|
|
|
final override predicate isAdditionalFlowStep(
|
|
DataFlow::Node pred, DataFlow::Node succ, boolean valuePreserving
|
|
) {
|
|
this.isAdditionalFlowStep(pred, succ) and valuePreserving = false
|
|
}
|
|
|
|
override DataFlow::FlowLabel getDefaultSourceLabel() { result.isTaint() }
|
|
}
|
|
|
|
/**
|
|
* A `SanitizerGuardNode` that controls which taint tracking
|
|
* configurations it is used in.
|
|
*
|
|
* Note: For performance reasons, all subclasses of this class should be part
|
|
* of the standard library. Override `Configuration::isSanitizerGuard`
|
|
* for analysis-specific taint sanitizer guards.
|
|
*/
|
|
cached
|
|
abstract class AdditionalSanitizerGuardNode extends SanitizerGuardNode {
|
|
/**
|
|
* Holds if this guard applies to the flow in `cfg`.
|
|
*/
|
|
cached
|
|
abstract predicate appliesTo(Configuration cfg);
|
|
}
|
|
|
|
/**
|
|
* A node that can act as a sanitizer when appearing in a condition.
|
|
*
|
|
* To add a sanitizer guard to a configuration, define a subclass of this class overriding the
|
|
* `sanitizes` predicate, and then extend the configuration's `isSanitizerGuard` predicate to
|
|
* include the new class.
|
|
*
|
|
* Note that it is generally a good idea to make the characteristic predicate of sanitizer guard
|
|
* classes as precise as possible: if two subclasses of `SanitizerGuardNode` overlap, their
|
|
* implementations of `sanitizes` will _both_ apply to any configuration that includes either of
|
|
* them.
|
|
*/
|
|
abstract class SanitizerGuardNode extends DataFlow::BarrierGuardNode {
|
|
override predicate blocks(boolean outcome, Expr e) { none() }
|
|
|
|
/**
|
|
* Holds if this node sanitizes expression `e`, provided it evaluates
|
|
* to `outcome`.
|
|
*/
|
|
abstract predicate sanitizes(boolean outcome, Expr e);
|
|
|
|
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
|
|
this.sanitizes(outcome, e) and label.isTaint()
|
|
or
|
|
this.sanitizes(outcome, e, label)
|
|
}
|
|
|
|
/**
|
|
* Holds if this node sanitizes expression `e`, provided it evaluates
|
|
* to `outcome`.
|
|
*/
|
|
predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) { none() }
|
|
}
|
|
|
|
/**
|
|
* A sanitizer guard node that only blocks specific flow labels.
|
|
*/
|
|
abstract class LabeledSanitizerGuardNode extends SanitizerGuardNode, DataFlow::BarrierGuardNode {
|
|
override predicate sanitizes(boolean outcome, Expr e) { none() }
|
|
}
|
|
|
|
/**
|
|
* A taint-propagating data flow edge that should be added to all taint tracking
|
|
* configurations in addition to standard data flow edges.
|
|
*
|
|
* This class is a singleton, and thus subclasses do not need to specify a characteristic predicate.
|
|
*
|
|
* Note: For performance reasons, all subclasses of this class should be part
|
|
* of the standard library. Override `Configuration::isAdditionalTaintStep`
|
|
* for analysis-specific taint steps.
|
|
*
|
|
* This class has multiple kinds of `step` predicates; these all have the same
|
|
* effect on taint-tracking configurations. However, the categorization of steps
|
|
* allows some data-flow configurations to opt in to specific kinds of taint steps.
|
|
*/
|
|
class SharedTaintStep extends Unit {
|
|
// Each step relation in this class should have a cached version in the `Cached` module
|
|
// and be included in the `sharedTaintStep` predicate.
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge.
|
|
*/
|
|
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through URI manipulation.
|
|
*
|
|
* Does not include string operations that aren't specific to URIs, such
|
|
* as concatenation and substring operations.
|
|
*/
|
|
predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge contributed by the heuristics library.
|
|
*
|
|
* Such steps are provided by the `semmle.javascript.heuristics` libraries
|
|
* and will default to be being empty if those libraries are not imported.
|
|
*/
|
|
predicate heuristicStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through persistent storage.
|
|
*/
|
|
predicate persistentStorageStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through the heap.
|
|
*/
|
|
predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through arrays.
|
|
*
|
|
* These steps considers an array to be tainted if it contains tainted elements.
|
|
*/
|
|
predicate arrayStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through the `state` or `props` or a React component.
|
|
*/
|
|
predicate viewComponentStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through string concatenation.
|
|
*/
|
|
predicate stringConcatenationStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through string manipulation (other than concatenation).
|
|
*/
|
|
predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through data serialization, such as `JSON.stringify`.
|
|
*/
|
|
predicate serializeStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through data deserialization, such as `JSON.parse`.
|
|
*/
|
|
predicate deserializeStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through a promise.
|
|
*
|
|
* These steps consider a promise object to tainted if it can resolve to
|
|
* a tainted value.
|
|
*/
|
|
predicate promiseStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
|
}
|
|
|
|
/**
|
|
* Module existing only to ensure all taint steps are cached as a single stage,
|
|
* and without the the `Unit` type column.
|
|
*/
|
|
cached
|
|
private module Cached {
|
|
cached
|
|
predicate forceStage() { Stages::Taint::ref() }
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge, which doesn't fit into a more specific category.
|
|
*/
|
|
cached
|
|
predicate genericStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).step(pred, succ)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge, contribued by the heuristics library.
|
|
*/
|
|
cached
|
|
predicate heuristicStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).heuristicStep(pred, succ)
|
|
}
|
|
|
|
/**
|
|
* Public taint step relations.
|
|
*/
|
|
cached
|
|
module Public {
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through a URI library function.
|
|
*/
|
|
cached
|
|
predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).uriStep(pred, succ)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pred -> succ` is a taint propagating data flow edge through persistent storage.
|
|
*/
|
|
cached
|
|
predicate persistentStorageStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).persistentStorageStep(pred, succ)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pred -> succ` is a taint propagating data flow edge through the heap.
|
|
*/
|
|
cached
|
|
predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).heapStep(pred, succ)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pred -> succ` is a taint propagating data flow edge through an array.
|
|
*/
|
|
cached
|
|
predicate arrayStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).arrayStep(pred, succ)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pred -> succ` is a taint propagating data flow edge through the
|
|
* properties of a view compenent, such as the `state` or `props` of a React component.
|
|
*/
|
|
cached
|
|
predicate viewComponentStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).viewComponentStep(pred, succ)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pred -> succ` is a taint propagating data flow edge through string
|
|
* concatenation.
|
|
*/
|
|
cached
|
|
predicate stringConcatenationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).stringConcatenationStep(pred, succ)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pred -> succ` is a taint propagating data flow edge through string manipulation
|
|
* (other than concatenation).
|
|
*/
|
|
cached
|
|
predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).stringManipulationStep(pred, succ)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through data serialization, such as `JSON.stringify`.
|
|
*/
|
|
cached
|
|
predicate serializeStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).serializeStep(pred, succ)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through data deserialization, such as `JSON.parse`.
|
|
*/
|
|
cached
|
|
predicate deserializeStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).deserializeStep(pred, succ)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pred` → `succ` should be considered a taint-propagating
|
|
* data flow edge through a promise.
|
|
*
|
|
* These steps consider a promise object to tainted if it can resolve to
|
|
* a tainted value.
|
|
*/
|
|
cached
|
|
predicate promiseStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
any(SharedTaintStep step).promiseStep(pred, succ)
|
|
}
|
|
}
|
|
}
|
|
|
|
import Cached::Public
|
|
|
|
/**
|
|
* Holds if `pred -> succ` is an edge used by all taint-tracking configurations.
|
|
*/
|
|
predicate sharedTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
Cached::genericStep(pred, succ) or
|
|
Cached::heuristicStep(pred, succ) or
|
|
uriStep(pred, succ) or
|
|
persistentStorageStep(pred, succ) or
|
|
heapStep(pred, succ) or
|
|
arrayStep(pred, succ) or
|
|
viewComponentStep(pred, succ) or
|
|
stringConcatenationStep(pred, succ) or
|
|
stringManipulationStep(pred, succ) or
|
|
serializeStep(pred, succ) or
|
|
deserializeStep(pred, succ) or
|
|
promiseStep(pred, succ)
|
|
}
|
|
|
|
/** Gets a data flow node referring to the client side URL. */
|
|
private DataFlow::SourceNode clientSideUrlRef(DataFlow::TypeTracker t) {
|
|
t.start() and
|
|
result.(ClientSideRemoteFlowSource).getKind().isUrl()
|
|
or
|
|
exists(DataFlow::TypeTracker t2 | result = clientSideUrlRef(t2).track(t2, t))
|
|
}
|
|
|
|
/** Gets a data flow node referring to the client side URL. */
|
|
private DataFlow::SourceNode clientSideUrlRef() {
|
|
result = clientSideUrlRef(DataFlow::TypeTracker::end())
|
|
}
|
|
|
|
/**
|
|
* Holds if `read` reads a property of the client-side URL, which is not tainted.
|
|
* In this case, the read is excluded from the default set of taint steps.
|
|
*/
|
|
private predicate isSafeClientSideUrlProperty(DataFlow::PropRead read) {
|
|
// Block all properties of client-side URLs, as .hash and .search are considered sources of their own
|
|
read = clientSideUrlRef().getAPropertyRead()
|
|
or
|
|
exists(StringSplitCall c |
|
|
c.getBaseString().getALocalSource() =
|
|
[DOM::locationRef(), DOM::locationRef().getAPropertyRead("href")] and
|
|
c.getSeparator() = "?" and
|
|
read = c.getAPropertyRead("0")
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge through object or array elements and
|
|
* promises.
|
|
*/
|
|
private class HeapTaintStep extends SharedTaintStep {
|
|
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
succ.(DataFlow::ObjectLiteralNode).getAComputedPropertyName() = pred
|
|
or
|
|
// spreading a tainted object into an object literal gives a tainted object
|
|
succ.(DataFlow::ObjectLiteralNode).getASpreadProperty() = pred
|
|
or
|
|
// spreading a tainted value into an array literal gives a tainted array
|
|
succ.(DataFlow::ArrayCreationNode).getASpreadArgument() = pred
|
|
or
|
|
// arrays with tainted elements and objects with tainted property names are tainted
|
|
succ.(DataFlow::ArrayCreationNode).getAnElement() = pred and
|
|
not any(PromiseAllCreation call).getArrayNode() = succ
|
|
or
|
|
// reading from a tainted object yields a tainted result
|
|
succ.(DataFlow::PropRead).getBase() = pred and
|
|
not (
|
|
AccessPath::DominatingPaths::hasDominatingWrite(succ) and
|
|
exists(succ.(DataFlow::PropRead).getPropertyName())
|
|
) and
|
|
not isSafeClientSideUrlProperty(succ) and
|
|
not ClassValidator::isAccessToSanitizedField(succ)
|
|
or
|
|
// iterating over a tainted iterator taints the loop variable
|
|
exists(ForOfStmt fos |
|
|
pred = DataFlow::valueNode(fos.getIterationDomain()) and
|
|
succ = DataFlow::lvalueNode(fos.getLValue())
|
|
)
|
|
or
|
|
// taint-tracking rest patterns in l-values. E.g. `const {...spread} = foo()` or `const [...spread] = foo()`.
|
|
exists(DestructuringPattern pattern |
|
|
pred = DataFlow::lvalueNode(pattern) and
|
|
succ = DataFlow::lvalueNode(pattern.getRest())
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge through persistent storage.
|
|
* Use `TaintTracking::persistentStorageStep` instead of accessing this class.
|
|
*/
|
|
private class PersistentStorageTaintStep extends SharedTaintStep {
|
|
override predicate persistentStorageStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(PersistentReadAccess read |
|
|
pred = read.getAWrite().getValue() and
|
|
succ = read
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge for assignments of the form `o[k] = v`, where
|
|
* one of the following holds:
|
|
*
|
|
* - `k` is not a constant and `o` refers to some object literal. The rationale
|
|
* here is that `o` is most likely being used like a dictionary object.
|
|
*
|
|
* - `k` refers to `o.length`, that is, the assignment is of form `o[o.length] = v`.
|
|
* In this case, the assignment behaves like `o.push(v)`.
|
|
*/
|
|
private class ComputedPropWriteTaintStep extends SharedTaintStep {
|
|
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(DataFlow::PropWrite assgn, DataFlow::SourceNode obj |
|
|
not exists(assgn.getPropertyName()) and
|
|
not assgn.getWriteNode() instanceof Property and // not a write inside an object literal
|
|
pred = assgn.getRhs() and
|
|
assgn = obj.getAPropertyWrite() and
|
|
succ = obj
|
|
|
|
|
obj instanceof DataFlow::ObjectLiteralNode
|
|
or
|
|
obj.getAPropertyRead("length").flowsToExpr(assgn.getPropertyNameExpr())
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge arising from string concatenations.
|
|
*
|
|
* Note that since we cannot easily distinguish string append from addition,
|
|
* we consider any `+` operation to propagate taint.
|
|
*/
|
|
class StringConcatenationTaintStep extends SharedTaintStep {
|
|
override predicate stringConcatenationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
StringConcatenation::taintStep(pred, succ)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge arising from string manipulation
|
|
* functions defined in the standard library.
|
|
*/
|
|
private class StringManipulationTaintStep extends SharedTaintStep {
|
|
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node target) {
|
|
exists(DataFlow::ValueNode succ | target = succ |
|
|
// string operations that propagate taint
|
|
exists(string name | name = succ.(DataFlow::MethodCallNode).getMethodName() |
|
|
pred = succ.(DataFlow::MethodCallNode).getReceiver() and
|
|
(
|
|
// sorted, interesting, properties of String.prototype
|
|
name =
|
|
[
|
|
"anchor", "big", "blink", "bold", "concat", "fixed", "fontcolor", "fontsize",
|
|
"italics", "link", "padEnd", "padStart", "repeat", "replace", "replaceAll", "slice",
|
|
"small", "split", "strike", "sub", "substr", "substring", "sup",
|
|
"toLocaleLowerCase", "toLocaleUpperCase", "toLowerCase", "toUpperCase", "trim",
|
|
"trimLeft", "trimRight"
|
|
]
|
|
or
|
|
// sorted, interesting, properties of Object.prototype
|
|
name = ["toString", "valueOf"]
|
|
or
|
|
// sorted, interesting, properties of Array.prototype
|
|
name = "join"
|
|
)
|
|
or
|
|
exists(int i | pred = succ.(DataFlow::MethodCallNode).getArgument(i) |
|
|
name = "concat"
|
|
or
|
|
name = ["replace", "replaceAll"] and i = 1
|
|
)
|
|
)
|
|
or
|
|
// standard library constructors that propagate taint: `RegExp` and `String`
|
|
exists(DataFlow::InvokeNode invk, string gv | gv = "RegExp" or gv = "String" |
|
|
succ = invk and
|
|
invk = DataFlow::globalVarRef(gv).getAnInvocation() and
|
|
pred = invk.getArgument(0)
|
|
)
|
|
or
|
|
// String.fromCharCode and String.fromCodePoint
|
|
exists(DataFlow::MethodCallNode mcn |
|
|
mcn = succ and
|
|
pred = mcn.getAnArgument() and
|
|
mcn.getMethodName() = ["fromCharCode", "fromCodePoint"]
|
|
)
|
|
or
|
|
// `(encode|decode)URI(Component)?` propagate taint
|
|
exists(DataFlow::CallNode c |
|
|
succ = c and
|
|
c =
|
|
DataFlow::globalVarRef([
|
|
"encodeURI", "decodeURI", "encodeURIComponent", "decodeURIComponent"
|
|
]).getACall() and
|
|
pred = c.getArgument(0)
|
|
)
|
|
or
|
|
// In and out of .replace callbacks
|
|
exists(StringReplaceCall call |
|
|
// Into the callback if the regexp does not sanitize matches
|
|
hasWildcardReplaceRegExp(call) and
|
|
pred = call.getReceiver() and
|
|
succ = call.getReplacementCallback().getParameter(0)
|
|
or
|
|
// Out of the callback
|
|
pred = call.getReplacementCallback().getReturnNode() and
|
|
succ = call
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
/** Holds if the given call takes a regexp containing a wildcard. */
|
|
pragma[noinline]
|
|
private predicate hasWildcardReplaceRegExp(StringReplaceCall call) {
|
|
RegExp::isWildcardLike(call.getRegExp().getRoot().getAChild*())
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge arising from string formatting.
|
|
*/
|
|
private class StringFormattingTaintStep extends SharedTaintStep {
|
|
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(PrintfStyleCall call |
|
|
call.returnsFormatted() and
|
|
succ = call
|
|
|
|
|
pred = call.getFormatString()
|
|
or
|
|
pred = call.getFormatArgument(_)
|
|
)
|
|
}
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private DataFlow::MethodCallNode execMethodCall() {
|
|
result.getMethodName() = "exec" and
|
|
exists(DataFlow::AnalyzedNode analyzed |
|
|
pragma[only_bind_into](analyzed) = result.getReceiver().analyze() and
|
|
analyzed.getAType() = TTRegExp()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A taint-propagating data flow edge from the first (and only) argument in a call to
|
|
* `RegExp.prototype.exec` to its result.
|
|
*/
|
|
private class RegExpExecTaintStep extends SharedTaintStep {
|
|
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(DataFlow::MethodCallNode call |
|
|
call = execMethodCall() and
|
|
call.getNumArgument() = 1 and
|
|
pred = call.getArgument(0) and
|
|
succ = call
|
|
)
|
|
}
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private DataFlow::MethodCallNode matchMethodCall() {
|
|
result.getMethodName() = "match" and
|
|
exists(DataFlow::AnalyzedNode analyzed |
|
|
pragma[only_bind_into](analyzed) = result.getArgument(0).analyze() and
|
|
analyzed.getAType() = TTRegExp()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge arising from calling `String.prototype.match()`.
|
|
*/
|
|
private class StringMatchTaintStep extends SharedTaintStep {
|
|
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(DataFlow::MethodCallNode call |
|
|
call = matchMethodCall() and
|
|
call.getNumArgument() = 1 and
|
|
pred = call.getReceiver() and
|
|
succ = call
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a local source of any part of the input to the given stringification `call`.
|
|
*/
|
|
pragma[nomagic]
|
|
private DataFlow::Node getAJsonLocalInput(JsonStringifyCall call) {
|
|
result = call.getInput()
|
|
or
|
|
exists(DataFlow::SourceNode source |
|
|
source = pragma[only_bind_out](getAJsonLocalInput(call)).getALocalSource()
|
|
|
|
|
result = source.getAPropertyWrite().getRhs()
|
|
or
|
|
result = source.(DataFlow::ObjectLiteralNode).getASpreadProperty()
|
|
or
|
|
result = source.(DataFlow::ArrayCreationNode).getASpreadArgument()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge arising from JSON unparsing.
|
|
*/
|
|
private class JsonStringifyTaintStep extends SharedTaintStep {
|
|
override predicate serializeStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(JsonStringifyCall call |
|
|
pred = getAJsonLocalInput(call) and
|
|
succ = call
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge arising from JSON parsing.
|
|
*/
|
|
private class JsonParserTaintStep extends SharedTaintStep {
|
|
override predicate deserializeStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(JsonParserCall call |
|
|
pred = call.getInput() and
|
|
succ = call.getOutput()
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds if `params` is a construction of a `URLSearchParams` that parses
|
|
* the parameters in `input`.
|
|
*/
|
|
predicate isUrlSearchParams(DataFlow::SourceNode params, DataFlow::Node input) {
|
|
exists(DataFlow::GlobalVarRefNode urlSearchParams, DataFlow::NewNode newUrlSearchParams |
|
|
urlSearchParams.getName() = "URLSearchParams" and
|
|
newUrlSearchParams = urlSearchParams.getAnInstantiation() and
|
|
params = newUrlSearchParams and
|
|
input = newUrlSearchParams.getArgument(0)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a pseudo-property a `URL` that stores a value that can be obtained
|
|
* with a `get` or `getAll` call to the `searchParams` property.
|
|
*/
|
|
private string hiddenUrlPseudoProperty() { result = "$hiddenSearchPararms" }
|
|
|
|
/**
|
|
* Gets a pseudo-property on a `URLSearchParams` that can be obtained
|
|
* with a `get` or `getAll` call.
|
|
*/
|
|
private string getableUrlPseudoProperty() { result = "$gettableSearchPararms" }
|
|
|
|
/**
|
|
* A taint propagating data flow edge arising from URL parameter parsing.
|
|
*/
|
|
private class UrlSearchParamsTaintStep extends DataFlow::SharedFlowStep {
|
|
/**
|
|
* Holds if `succ` is a `URLSearchParams` providing access to the
|
|
* parameters encoded in `pred`.
|
|
*/
|
|
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
|
isUrlSearchParams(succ, pred)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
|
|
*
|
|
* This step is used to model 3 facts:
|
|
* 1) A `URL` constructed using `url = new URL(input)` transfers taint from `input` to `url.searchParams`, `url.hash`, and `url.search`.
|
|
* 2) Accessing the `searchParams` on a `URL` results in a `URLSearchParams` object (See the loadStoreStep method on this class and hiddenUrlPseudoProperty())
|
|
* 3) A `URLSearchParams` object (either `url.searchParams` or `new URLSearchParams(input)`) has a tainted value,
|
|
* which can be accessed using a `get` or `getAll` call. (See getableUrlPseudoProperty())
|
|
*/
|
|
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
|
prop = ["searchParams", "hash", "search", hiddenUrlPseudoProperty()] and
|
|
exists(DataFlow::NewNode newUrl | succ = newUrl |
|
|
newUrl = DataFlow::globalVarRef("URL").getAnInstantiation() and
|
|
pred = newUrl.getArgument(0)
|
|
)
|
|
or
|
|
prop = getableUrlPseudoProperty() and
|
|
isUrlSearchParams(succ, pred)
|
|
}
|
|
|
|
/**
|
|
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
|
|
*
|
|
* This step is used to copy the value of our pseudo-property that can later be accessed using a `get` or `getAll` call.
|
|
* For an expression `url.searchParams`, the property `hiddenUrlPseudoProperty()` from the `url` object is stored in the property `getableUrlPseudoProperty()` on `url.searchParams`.
|
|
*/
|
|
override predicate loadStoreStep(
|
|
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
|
|
) {
|
|
loadProp = hiddenUrlPseudoProperty() and
|
|
storeProp = getableUrlPseudoProperty() and
|
|
exists(DataFlow::PropRead read | read = succ |
|
|
read.getPropertyName() = "searchParams" and
|
|
read.getBase() = pred
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
|
|
*
|
|
* This step is used to load the value stored in the pseudo-property `getableUrlPseudoProperty()`.
|
|
*/
|
|
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
|
prop = getableUrlPseudoProperty() and
|
|
// this is a call to `get` or `getAll` on a `URLSearchParams` object
|
|
exists(string m, DataFlow::MethodCallNode call | call = succ |
|
|
call.getMethodName() = m and
|
|
call.getReceiver() = pred and
|
|
m.matches("get%")
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge arising from sorting.
|
|
*/
|
|
private class SortTaintStep extends SharedTaintStep {
|
|
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(DataFlow::MethodCallNode call |
|
|
call.getMethodName() = "sort" and
|
|
pred = call.getReceiver() and
|
|
succ = call
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A taint step through an exception constructor, such as `x` to `new Error(x)`.
|
|
*/
|
|
class ErrorConstructorTaintStep extends SharedTaintStep {
|
|
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(DataFlow::NewNode invoke, string name |
|
|
invoke = DataFlow::globalVarRef(name).getAnInvocation() and
|
|
pred = invoke.getArgument(0) and
|
|
succ = invoke
|
|
|
|
|
name =
|
|
[
|
|
"Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError",
|
|
"URIError"
|
|
]
|
|
)
|
|
}
|
|
}
|
|
|
|
private module RegExpCaptureSteps {
|
|
/** Gets a reference to a string derived from the most recent RegExp match, such as `RegExp.$1`. */
|
|
private DataFlow::PropRead getAStaticCaptureRef() {
|
|
result =
|
|
DataFlow::globalVarRef("RegExp")
|
|
.getAPropertyRead([
|
|
"$" + [1 .. 9], "input", "lastMatch", "leftContext", "rightContext", "$&", "$^",
|
|
"$`"
|
|
])
|
|
}
|
|
|
|
/**
|
|
* Gets a control-flow node where `input` is used in a RegExp match.
|
|
*/
|
|
private ControlFlowNode getACaptureSetter(DataFlow::Node input) {
|
|
exists(DataFlow::MethodCallNode call | result = call.asExpr() |
|
|
call.getMethodName() = ["search", "replace", "replaceAll", "match"] and
|
|
input = call.getReceiver()
|
|
or
|
|
call.getMethodName() = ["test", "exec"] and input = call.getArgument(0)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a control-flow node that can locally reach the given static capture reference
|
|
* without passing through a capture setter.
|
|
*
|
|
* This is essentially an intraprocedural def-use analysis that ignores potential
|
|
* side effects from calls.
|
|
*/
|
|
private ControlFlowNode getANodeReachingCaptureRef(DataFlow::PropRead read) {
|
|
result = read.asExpr() and
|
|
read = getAStaticCaptureRef()
|
|
or
|
|
exists(ControlFlowNode mid |
|
|
result = getANodeReachingCaptureRefAux(read, mid) and
|
|
not mid = getACaptureSetter(_)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private ControlFlowNode getANodeReachingCaptureRefAux(
|
|
DataFlow::PropRead read, ControlFlowNode mid
|
|
) {
|
|
mid = getANodeReachingCaptureRef(read) and
|
|
result = mid.getAPredecessor()
|
|
}
|
|
|
|
/**
|
|
* A step `pred -> succ` from the input of a RegExp match to a static property of `RegExp`.
|
|
*/
|
|
private class StaticRegExpCaptureStep extends SharedTaintStep {
|
|
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
|
getACaptureSetter(pred) = getANodeReachingCaptureRef(succ)
|
|
or
|
|
exists(StringReplaceCall replace |
|
|
getANodeReachingCaptureRef(succ) =
|
|
replace.getReplacementCallback().getFunction().getEntry() and
|
|
pred = replace.getReceiver()
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A taint step through the Node.JS function `util.inspect(..)`.
|
|
*/
|
|
class UtilInspectTaintStep extends SharedTaintStep {
|
|
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(DataFlow::CallNode call |
|
|
call = DataFlow::moduleImport("util").getAMemberCall("inspect") and
|
|
call.getAnArgument() = pred and
|
|
succ = call
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A conditional checking a tainted string against a regular expression, which is
|
|
* considered to be a sanitizer for all configurations.
|
|
*/
|
|
class SanitizingRegExpTest extends AdditionalSanitizerGuardNode, DataFlow::ValueNode {
|
|
Expr expr;
|
|
boolean sanitizedOutcome;
|
|
|
|
SanitizingRegExpTest() {
|
|
exists(MethodCallExpr mce, Expr base, string m, Expr firstArg |
|
|
mce = astNode and mce.calls(base, m) and firstArg = mce.getArgument(0)
|
|
|
|
|
// /re/.test(u) or /re/.exec(u)
|
|
RegExp::isGenericRegExpSanitizer(RegExp::getRegExpObjectFromNode(base.flow()),
|
|
sanitizedOutcome) and
|
|
(m = "test" or m = "exec") and
|
|
firstArg = expr
|
|
or
|
|
// u.match(/re/) or u.match("re")
|
|
base = expr and
|
|
m = "match" and
|
|
RegExp::isGenericRegExpSanitizer(RegExp::getRegExpFromNode(firstArg.flow()),
|
|
sanitizedOutcome)
|
|
)
|
|
or
|
|
// m = /re/.exec(u) and similar
|
|
exists(SanitizingRegExpTest other |
|
|
other = DataFlow::valueNode(astNode.(AssignExpr).getRhs()) and
|
|
expr = other.getSanitizedExpr() and
|
|
sanitizedOutcome = other.getSanitizedOutcome()
|
|
)
|
|
}
|
|
|
|
private Expr getSanitizedExpr() { result = expr }
|
|
|
|
private boolean getSanitizedOutcome() { result = sanitizedOutcome }
|
|
|
|
override predicate sanitizes(boolean outcome, Expr e) {
|
|
outcome = sanitizedOutcome and
|
|
e = expr
|
|
}
|
|
|
|
override predicate appliesTo(Configuration cfg) { any() }
|
|
}
|
|
|
|
/**
|
|
* A check of the form `if(o.<contains>(x))`, which sanitizes `x` in its "then" branch.
|
|
*
|
|
* `<contains>` is one of: `contains`, `has`, `hasOwnProperty`
|
|
*
|
|
* Note that the `includes` method is covered by `MembershipTestSanitizer`.
|
|
*/
|
|
class WhitelistContainmentCallSanitizer extends AdditionalSanitizerGuardNode,
|
|
DataFlow::MethodCallNode
|
|
{
|
|
WhitelistContainmentCallSanitizer() {
|
|
this.getMethodName() = ["contains", "has", "hasOwnProperty", "hasOwn"]
|
|
}
|
|
|
|
override predicate sanitizes(boolean outcome, Expr e) {
|
|
exists(int propertyIndex |
|
|
if this.getMethodName() = "hasOwn" then propertyIndex = 1 else propertyIndex = 0
|
|
|
|
|
outcome = true and
|
|
e = this.getArgument(propertyIndex).asExpr()
|
|
)
|
|
}
|
|
|
|
override predicate appliesTo(Configuration cfg) { any() }
|
|
}
|
|
|
|
/**
|
|
* A check of the form `if(<isWhitelisted>(x))`, which sanitizes `x` in its "then" branch.
|
|
*
|
|
* `<isWhitelisted>` is a call with callee name 'safe', 'whitelist', 'allow', or similar.
|
|
*
|
|
* This sanitizer is not enabled by default.
|
|
*/
|
|
class AdHocWhitelistCheckSanitizer extends SanitizerGuardNode, DataFlow::CallNode {
|
|
AdHocWhitelistCheckSanitizer() {
|
|
this.getCalleeName()
|
|
.regexpMatch("(?i).*((?<!un)safe|whitelist|(?<!in)valid|allow|(?<!un)auth(?!or\\b)).*") and
|
|
this.getNumArgument() = 1
|
|
}
|
|
|
|
override predicate sanitizes(boolean outcome, Expr e) {
|
|
outcome = true and
|
|
e = this.getArgument(0).asExpr()
|
|
}
|
|
}
|
|
|
|
/** A check of the form `if(x in o)`, which sanitizes `x` in its "then" branch. */
|
|
class InSanitizer extends AdditionalSanitizerGuardNode, DataFlow::ValueNode {
|
|
override InExpr astNode;
|
|
|
|
override predicate sanitizes(boolean outcome, Expr e) {
|
|
outcome = true and
|
|
e = astNode.getLeftOperand()
|
|
}
|
|
|
|
override predicate appliesTo(Configuration cfg) { any() }
|
|
}
|
|
|
|
/** A check of the form `if(o[x] != undefined)`, which sanitizes `x` in its "then" branch. */
|
|
class UndefinedCheckSanitizer extends AdditionalSanitizerGuardNode, DataFlow::ValueNode {
|
|
Expr x;
|
|
override EqualityTest astNode;
|
|
|
|
UndefinedCheckSanitizer() {
|
|
exists(IndexExpr idx, DataFlow::AnalyzedNode undef |
|
|
astNode.hasOperands(idx, undef.asExpr())
|
|
|
|
|
// one operand is of the form `o[x]`
|
|
idx = astNode.getAnOperand() and
|
|
idx.getPropertyNameExpr() = x and
|
|
// and the other one is guaranteed to be `undefined`
|
|
unique(InferredType tp | tp = pragma[only_bind_into](undef.getAType())) = TTUndefined()
|
|
)
|
|
}
|
|
|
|
override predicate sanitizes(boolean outcome, Expr e) {
|
|
outcome = astNode.getPolarity().booleanNot() and
|
|
e = x
|
|
}
|
|
|
|
override predicate appliesTo(Configuration cfg) { any() }
|
|
}
|
|
|
|
/** A check of the form `type x === "undefined"`, which sanitized `x` in its "then" branch. */
|
|
class TypeOfUndefinedSanitizer extends AdditionalSanitizerGuardNode, DataFlow::ValueNode {
|
|
Expr x;
|
|
override EqualityTest astNode;
|
|
|
|
TypeOfUndefinedSanitizer() { isTypeofGuard(astNode, x, "undefined") }
|
|
|
|
override predicate sanitizes(boolean outcome, Expr e) {
|
|
outcome = astNode.getPolarity() and
|
|
e = x
|
|
}
|
|
|
|
override predicate appliesTo(Configuration cfg) { any() }
|
|
}
|
|
|
|
/**
|
|
* Holds if `test` is a guard that checks if `operand` is typeof `tag`.
|
|
*
|
|
* See `TypeOfUndefinedSanitizer` for example usage.
|
|
*/
|
|
predicate isTypeofGuard(EqualityTest test, Expr operand, TypeofTag tag) {
|
|
exists(Expr str, TypeofExpr typeof | test.hasOperands(str, typeof) |
|
|
str.mayHaveStringValue(tag) and
|
|
typeof.getOperand() = operand
|
|
)
|
|
}
|
|
|
|
/** A test for the value of `typeof x`, restricting the potential types of `x`. */
|
|
predicate isStringTypeGuard(EqualityTest test, Expr operand, boolean polarity) {
|
|
exists(TypeofTag tag | TaintTracking::isTypeofGuard(test, operand, tag) |
|
|
// typeof x === "string" sanitizes `x` when it evaluates to false
|
|
tag = "string" and
|
|
polarity = test.getPolarity().booleanNot()
|
|
or
|
|
// typeof x === "object" sanitizes `x` when it evaluates to true
|
|
tag != "string" and
|
|
polarity = test.getPolarity()
|
|
)
|
|
}
|
|
|
|
/** Holds if `guard` is a test that checks if `operand` is a number. */
|
|
predicate isNumberGuard(DataFlow::Node guard, Expr operand, boolean polarity) {
|
|
exists(DataFlow::CallNode isNaN |
|
|
isNaN = DataFlow::globalVarRef("isNaN").getACall() and guard = isNaN and polarity = false
|
|
|
|
|
operand = isNaN.getArgument(0).asExpr()
|
|
or
|
|
exists(DataFlow::CallNode parse |
|
|
parse = DataFlow::globalVarRef(["parseInt", "parseFloat"]).getACall()
|
|
|
|
|
parse = isNaN.getArgument(0) and
|
|
operand = parse.getArgument(0).asExpr()
|
|
)
|
|
or
|
|
exists(UnaryExpr unary | unary.getOperator() = ["+", "-"] |
|
|
unary = isNaN.getArgument(0).asExpr() and
|
|
operand = unary.getOperand()
|
|
)
|
|
or
|
|
exists(BinaryExpr bin | bin.getOperator() = ["+", "-"] |
|
|
bin = isNaN.getArgument(0).asExpr() and
|
|
operand = bin.getAnOperand() and
|
|
bin.getAnOperand() instanceof NumberLiteral
|
|
)
|
|
)
|
|
or
|
|
isTypeofGuard(guard.asExpr(), operand, "number") and
|
|
polarity = guard.asExpr().(EqualityTest).getPolarity()
|
|
}
|
|
|
|
/**
|
|
* A test of form `x.length === "0"`, preventing `x` from being tainted.
|
|
*/
|
|
class IsEmptyGuard extends AdditionalSanitizerGuardNode, DataFlow::ValueNode {
|
|
override EqualityTest astNode;
|
|
boolean polarity;
|
|
Expr operand;
|
|
|
|
IsEmptyGuard() {
|
|
astNode.getPolarity() = polarity and
|
|
astNode.getAnOperand().(ConstantExpr).getIntValue() = 0 and
|
|
exists(DataFlow::PropRead read | read.asExpr() = astNode.getAnOperand() |
|
|
read.getBase().asExpr() = operand and
|
|
read.getPropertyName() = "length"
|
|
)
|
|
}
|
|
|
|
override predicate sanitizes(boolean outcome, Expr e) { polarity = outcome and e = operand }
|
|
|
|
override predicate appliesTo(Configuration cfg) { any() }
|
|
}
|
|
|
|
/**
|
|
* A check of the form `whitelist.includes(x)` or equivalent, which sanitizes `x` in its "then" branch.
|
|
*/
|
|
class MembershipTestSanitizer extends AdditionalSanitizerGuardNode {
|
|
MembershipCandidate candidate;
|
|
|
|
MembershipTestSanitizer() { this = candidate.getTest() }
|
|
|
|
override predicate sanitizes(boolean outcome, Expr e) {
|
|
candidate = e.flow() and candidate.getTestPolarity() = outcome
|
|
}
|
|
|
|
override predicate appliesTo(Configuration cfg) { any() }
|
|
}
|
|
|
|
/**
|
|
* A check of form `x.indexOf(y) > 0` or similar, which sanitizes `y` in the "then" branch.
|
|
*
|
|
* The more typical case of `x.indexOf(y) >= 0` is covered by `MembershipTestSanitizer`.
|
|
*/
|
|
class PositiveIndexOfSanitizer extends AdditionalSanitizerGuardNode, DataFlow::ValueNode {
|
|
MethodCallExpr indexOf;
|
|
override RelationalComparison astNode;
|
|
|
|
PositiveIndexOfSanitizer() {
|
|
indexOf.getMethodName() = "indexOf" and
|
|
exists(int bound |
|
|
astNode.getGreaterOperand() = indexOf and
|
|
astNode.getLesserOperand().getIntValue() = bound and
|
|
bound >= 0
|
|
)
|
|
}
|
|
|
|
override predicate sanitizes(boolean outcome, Expr e) {
|
|
outcome = true and
|
|
e = indexOf.getArgument(0)
|
|
}
|
|
|
|
override predicate appliesTo(Configuration cfg) { any() }
|
|
}
|
|
|
|
/**
|
|
* An equality test on `e.origin` or `e.source` where `e` is a `postMessage` event object,
|
|
* considered as a sanitizer for `e`.
|
|
*/
|
|
private class PostMessageEventSanitizer extends AdditionalSanitizerGuardNode {
|
|
VarAccess event;
|
|
boolean polarity;
|
|
|
|
PostMessageEventSanitizer() {
|
|
event.mayReferToParameter(any(PostMessageEventHandler h).getEventParameter()) and
|
|
exists(DataFlow::PropRead read | read.accesses(event.flow(), ["origin", "source"]) |
|
|
exists(EqualityTest test | polarity = test.getPolarity() and this.getAstNode() = test |
|
|
test.getAnOperand().flow() = read
|
|
)
|
|
or
|
|
exists(InclusionTest test | polarity = test.getPolarity() and this = test |
|
|
test.getContainedNode() = read
|
|
)
|
|
)
|
|
}
|
|
|
|
override predicate sanitizes(boolean outcome, Expr e) {
|
|
outcome = polarity and
|
|
e = event
|
|
}
|
|
|
|
override predicate appliesTo(Configuration cfg) { any() }
|
|
}
|
|
}
|