JS: Flow label -> flow state in TaintedPath

This commit is contained in:
Asger F
2024-11-29 14:10:12 +01:00
parent b8d652c5b2
commit 0802107d9a
2 changed files with 174 additions and 60 deletions

View File

@@ -11,16 +11,22 @@ module TaintedPath {
* A data flow source for tainted-path vulnerabilities.
*/
abstract class Source extends DataFlow::Node {
/** Gets a flow label denoting the type of value for which this is a source. */
DataFlow::FlowLabel getAFlowLabel() { result instanceof Label::PosixPath }
/** Gets a flow state denoting the type of value for which this is a source. */
FlowState getAFlowState() { result instanceof FlowState::PosixPath }
/** DEPRECATED. Use `getAFlowState()` instead. */
deprecated DataFlow::FlowLabel getAFlowLabel() { result = this.getAFlowState().toFlowLabel() }
}
/**
* A data flow sink for tainted-path vulnerabilities.
*/
abstract class Sink extends DataFlow::Node {
/** Gets a flow label denoting the type of value for which this is a sink. */
DataFlow::FlowLabel getAFlowLabel() { result instanceof Label::PosixPath }
/** Gets a flow state denoting the type of value for which this is a sink. */
FlowState getAFlowState() { result instanceof FlowState::PosixPath }
/** DEPRECATED. Use `getAFlowState()` instead. */
deprecated DataFlow::FlowLabel getAFlowLabel() { result = this.getAFlowState().toFlowLabel() }
}
/**
@@ -38,16 +44,16 @@ module TaintedPath {
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, Label::toFlowState(label))
}
}
@@ -65,7 +71,23 @@ module TaintedPath {
deprecated class BarrierGuardNode = BarrierGuard;
module Label {
private newtype TFlowState =
TPosixPath(FlowState::Normalization normalization, FlowState::Relativeness relativeness) or
TSplitPath()
private class FlowStateImpl extends TFlowState {
/** Gets a string representation of this flow state. */
abstract string toString();
/** DEPRECATED. Gets the corresponding flow label, for backwards compatibility. */
abstract deprecated DataFlow::FlowLabel toFlowLabel();
}
/** The flow state to associate with a tainted value. See also `FlowState::PosixPath`. */
final class FlowState = FlowStateImpl;
/** Module containing details of individual flow states. */
module FlowState {
/**
* A string indicating if a path is normalized, that is, whether internal `../` components
* have been removed.
@@ -81,6 +103,90 @@ module TaintedPath {
Relativeness() { this = "relative" or this = "absolute" }
}
/**
* A flow state representing a Posix path.
*
* There are currently four flow states, representing the different combinations of
* normalization and absoluteness.
*/
class PosixPath extends FlowStateImpl, TPosixPath {
Normalization normalization;
Relativeness relativeness;
PosixPath() { this = TPosixPath(normalization, relativeness) }
/** Gets a string indicating whether this path is normalized. */
Normalization getNormalization() { result = normalization }
/** Gets a string indicating whether this path is relative. */
Relativeness getRelativeness() { result = relativeness }
/** Holds if this path is normalized. */
predicate isNormalized() { normalization = "normalized" }
/** Holds if this path is not normalized. */
predicate isNonNormalized() { normalization = "raw" }
/** Holds if this path is relative. */
predicate isRelative() { relativeness = "relative" }
/** Holds if this path is relative. */
predicate isAbsolute() { relativeness = "absolute" }
/** Gets the path label with normalized flag set to true. */
PosixPath toNormalized() {
result.isNormalized() and
result.getRelativeness() = this.getRelativeness()
}
/** Gets the path label with normalized flag set to true. */
PosixPath toNonNormalized() {
result.isNonNormalized() and
result.getRelativeness() = this.getRelativeness()
}
/** Gets the path label with absolute flag set to true. */
PosixPath toAbsolute() {
result.isAbsolute() and
result.getNormalization() = this.getNormalization()
}
/** Gets the path label with absolute flag set to true. */
PosixPath toRelative() {
result.isRelative() and
result.getNormalization() = this.getNormalization()
}
/** Holds if this path may contain `../` components. */
predicate canContainDotDotSlash() {
// Absolute normalized path is the only combination that cannot contain `../`.
not (this.isNormalized() and this.isAbsolute())
}
override string toString() { result = normalization + "-" + relativeness + "-posix-path" }
deprecated override Label::PosixPath toFlowLabel() {
result.getNormalization() = normalization and result.getRelativeness() = relativeness
}
}
/**
* A flow label representing an array of path elements that may include "..".
*/
class SplitPath extends FlowStateImpl, TSplitPath {
override string toString() { result = "splitPath" }
deprecated override Label::SplitPath toFlowLabel() { any() }
}
}
deprecated module Label {
FlowState toFlowState(DataFlow::FlowLabel label) { result.toFlowLabel() = label }
class Normalization = FlowState::Normalization;
class Relativeness = FlowState::Relativeness;
/**
* A flow label representing a Posix path.
*
@@ -380,14 +486,14 @@ module TaintedPath {
class StartsWithDotDotSanitizer extends BarrierGuard instanceof StringOps::StartsWith {
StartsWithDotDotSanitizer() { isDotDotSlashPrefix(super.getSubstring()) }
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
// Sanitize in the false case for:
// .startsWith(".")
// .startsWith("..")
// .startsWith("../")
outcome = super.getPolarity().booleanNot() and
e = super.getBaseString().asExpr() and
exists(Label::PosixPath posixPath | posixPath = label |
exists(FlowState::PosixPath posixPath | posixPath = state |
posixPath.isNormalized() and
posixPath.isRelative()
)
@@ -422,10 +528,10 @@ module TaintedPath {
not startsWith.getSubstring().getStringValue() = "/"
}
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
outcome = startsWith.getPolarity() and
e = startsWith.getBaseString().asExpr() and
exists(Label::PosixPath posixPath | posixPath = label |
exists(FlowState::PosixPath posixPath | posixPath = state |
posixPath.isAbsolute() and
posixPath.isNormalized()
)
@@ -457,9 +563,9 @@ module TaintedPath {
) // !x.startsWith("/home") does not guarantee that x is not absolute
}
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
e = operand.asExpr() and
exists(Label::PosixPath posixPath | posixPath = label |
exists(FlowState::PosixPath posixPath | posixPath = state |
outcome = polarity and posixPath.isRelative()
or
negatable = true and
@@ -475,10 +581,10 @@ module TaintedPath {
class ContainsDotDotSanitizer extends BarrierGuard instanceof StringOps::Includes {
ContainsDotDotSanitizer() { isDotDotSlashPrefix(super.getSubstring()) }
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
e = super.getBaseString().asExpr() and
outcome = super.getPolarity().booleanNot() and
label.(Label::PosixPath).canContainDotDotSlash() // can still be bypassed by normalized absolute path
state.(FlowState::PosixPath).canContainDotDotSlash() // can still be bypassed by normalized absolute path
}
}
@@ -488,10 +594,10 @@ module TaintedPath {
class ContainsDotDotRegExpSanitizer extends BarrierGuard instanceof StringOps::RegExpTest {
ContainsDotDotRegExpSanitizer() { super.getRegExp().getAMatchedString() = [".", "..", "../"] }
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
e = super.getStringOperand().asExpr() and
outcome = super.getPolarity().booleanNot() and
label.(Label::PosixPath).canContainDotDotSlash() // can still be bypassed by normalized absolute path
state.(FlowState::PosixPath).canContainDotDotSlash() // can still be bypassed by normalized absolute path
}
}
@@ -590,11 +696,11 @@ module TaintedPath {
)
}
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
(
onlyNormalizedAbsolutePaths = true and
label.(Label::PosixPath).isNormalized() and
label.(Label::PosixPath).isAbsolute()
state.(FlowState::PosixPath).isNormalized() and
state.(FlowState::PosixPath).isAbsolute()
or
onlyNormalizedAbsolutePaths = false
) and
@@ -661,12 +767,12 @@ module TaintedPath {
private class FsPathSinkWithoutUpwardNavigation extends FsPathSink {
FsPathSinkWithoutUpwardNavigation() { fileSystemAccess.isUpwardNavigationRejected(this) }
override DataFlow::FlowLabel getAFlowLabel() {
override FlowState getAFlowState() {
// The protection is ineffective if the ../ segments have already
// cancelled out against the intended root dir using path.join or similar.
// Only flag normalized paths, as this corresponds to the output
// of a normalizing call that had a malicious input.
result.(Label::PosixPath).isNormalized()
result.(FlowState::PosixPath).isNormalized()
}
}
@@ -760,17 +866,26 @@ module TaintedPath {
}
/**
* Holds if there is a step `src -> dst` mapping `srclabel` to `dstlabel` relevant for path traversal vulnerabilities.
* DEPRECATED. Use `isAdditionalFlowStep` instead.
*/
predicate isAdditionalTaintedPathFlowStep(
deprecated predicate isAdditionalTaintedPathFlowStep(
DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel,
DataFlow::FlowLabel dstlabel
) {
isPosixPathStep(src, dst, srclabel, dstlabel)
isAdditionalFlowStep(src, Label::toFlowState(srclabel), dst, Label::toFlowState(dstlabel))
}
/**
* Holds if there is a step `src -> dst` mapping `srclabel` to `dstlabel` relevant for path traversal vulnerabilities.
*/
predicate isAdditionalFlowStep(
DataFlow::Node src, FlowState srclabel, DataFlow::Node dst, FlowState dstlabel
) {
isPosixPathStep(src, srclabel, dst, dstlabel)
or
// Ignore all preliminary sanitization after decoding URI components
srclabel instanceof Label::PosixPath and
dstlabel instanceof Label::PosixPath and
srclabel instanceof FlowState::PosixPath and
dstlabel instanceof FlowState::PosixPath and
(
TaintTracking::uriStep(src, dst)
or
@@ -814,8 +929,8 @@ module TaintedPath {
exists(StringSplitCall mcn | dst = mcn and mcn.getBaseString() = src |
if mcn.getSeparator() = "/"
then
srclabel.(Label::PosixPath).canContainDotDotSlash() and
dstlabel instanceof Label::SplitPath
srclabel.(FlowState::PosixPath).canContainDotDotSlash() and
dstlabel instanceof FlowState::SplitPath
else srclabel = dstlabel
)
or
@@ -825,21 +940,21 @@ module TaintedPath {
name = "pop" or
name = "shift"
) and
srclabel instanceof Label::SplitPath and
dstlabel.(Label::PosixPath).canContainDotDotSlash()
srclabel instanceof FlowState::SplitPath and
dstlabel.(FlowState::PosixPath).canContainDotDotSlash()
or
(
name = "slice" or
name = "splice" or
name = "concat"
) and
dstlabel instanceof Label::SplitPath and
srclabel instanceof Label::SplitPath
dstlabel instanceof FlowState::SplitPath and
srclabel instanceof FlowState::SplitPath
or
name = "join" and
mcn.getArgument(0).mayHaveStringValue("/") and
srclabel instanceof Label::SplitPath and
dstlabel.(Label::PosixPath).canContainDotDotSlash()
srclabel instanceof FlowState::SplitPath and
dstlabel.(FlowState::PosixPath).canContainDotDotSlash()
)
or
// prefix.concat(path)
@@ -847,8 +962,8 @@ module TaintedPath {
mcn.getMethodName() = "concat" and mcn.getAnArgument() = src
|
dst = mcn and
dstlabel instanceof Label::SplitPath and
srclabel instanceof Label::SplitPath
dstlabel instanceof FlowState::SplitPath and
srclabel instanceof FlowState::SplitPath
)
or
// reading unknown property of split path
@@ -862,8 +977,8 @@ module TaintedPath {
binop.getAnOperand().getIntValue() = 1 and
binop.getAnOperand().(PropAccess).getPropertyName() = "length"
) and
srclabel instanceof Label::SplitPath and
dstlabel.(Label::PosixPath).canContainDotDotSlash()
srclabel instanceof FlowState::SplitPath and
dstlabel.(FlowState::PosixPath).canContainDotDotSlash()
)
or
exists(API::CallNode call | call = API::moduleImport("slash").getACall() |
@@ -884,14 +999,14 @@ module TaintedPath {
src = join.getASpreadArgument() and
dst = join and
(
srclabel.(Label::PosixPath).canContainDotDotSlash()
srclabel.(FlowState::PosixPath).canContainDotDotSlash()
or
srclabel instanceof Label::SplitPath
srclabel instanceof FlowState::SplitPath
) and
dstlabel.(Label::PosixPath).isNormalized() and
dstlabel.(FlowState::PosixPath).isNormalized() and
if isRelative(join.getArgument(0).getStringValue())
then dstlabel.(Label::PosixPath).isRelative()
else dstlabel.(Label::PosixPath).isAbsolute()
then dstlabel.(FlowState::PosixPath).isRelative()
else dstlabel.(FlowState::PosixPath).isAbsolute()
)
}
@@ -900,7 +1015,8 @@ module TaintedPath {
* standard taint step `src -> dst` should be suppressed.
*/
private predicate isPosixPathStep(
DataFlow::Node src, DataFlow::Node dst, Label::PosixPath srclabel, Label::PosixPath dstlabel
DataFlow::Node src, FlowState::PosixPath srclabel, DataFlow::Node dst,
FlowState::PosixPath dstlabel
) {
// path.normalize() and similar
exists(NormalizingPathCall call |

View File

@@ -8,14 +8,15 @@
*/
import javascript
private import TaintedPathCustomizations
private import TaintedPathCustomizations::TaintedPath
// Materialize flow labels
private class ConcretePosixPath extends Label::PosixPath {
deprecated private class ConcretePosixPath extends Label::PosixPath {
ConcretePosixPath() { this = this }
}
private class ConcreteSplitPath extends Label::SplitPath {
deprecated private class ConcreteSplitPath extends Label::SplitPath {
ConcreteSplitPath() { this = this }
}
@@ -23,20 +24,18 @@ private class ConcreteSplitPath extends Label::SplitPath {
* A taint-tracking configuration for reasoning about tainted-path vulnerabilities.
*/
module TaintedPathConfig implements DataFlow::StateConfigSig {
class FlowState = DataFlow::FlowLabel;
class FlowState = TaintedPath::FlowState;
predicate isSource(DataFlow::Node source, DataFlow::FlowLabel state) {
state = source.(Source).getAFlowLabel()
predicate isSource(DataFlow::Node source, FlowState state) {
state = source.(Source).getAFlowState()
}
predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel state) {
state = sink.(Sink).getAFlowLabel()
}
predicate isSink(DataFlow::Node sink, FlowState state) { state = sink.(Sink).getAFlowState() }
predicate isBarrier(DataFlow::Node node, DataFlow::FlowLabel label) {
node instanceof Sanitizer and exists(label)
predicate isBarrier(DataFlow::Node node, FlowState state) {
node instanceof Sanitizer and exists(state)
or
node = DataFlow::MakeLabeledBarrierGuard<BarrierGuard>::getABarrierNode(label)
node = DataFlow::MakeStateBarrierGuard<FlowState, BarrierGuard>::getABarrierNode(state)
}
predicate isBarrier(DataFlow::Node node) {
@@ -44,10 +43,9 @@ module TaintedPathConfig implements DataFlow::StateConfigSig {
}
predicate isAdditionalFlowStep(
DataFlow::Node node1, DataFlow::FlowLabel state1, DataFlow::Node node2,
DataFlow::FlowLabel state2
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
) {
isAdditionalTaintedPathFlowStep(node1, node2, state1, state2)
TaintedPath::isAdditionalFlowStep(node1, state1, node2, state2)
}
}