expand the js/exception-xss to handle more types of exceptional flow

This commit is contained in:
Erik Krogh Kristensen
2019-11-29 13:22:33 +01:00
parent 3e0045f435
commit 62512dd3e9
4 changed files with 199 additions and 8 deletions

View File

@@ -209,6 +209,21 @@ private class PromiseFlowStep extends DataFlow::AdditionalFlowStep {
}
}
/**
* A data flow edge from the exceptional return of the promise executor to the promise catch handler.
*/
class PromiseExceptionalStep extends DataFlow::AdditionalFlowStep {
PromiseDefinition promise;
PromiseExceptionalStep() {
promise = this
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = promise.getExecutor().getExceptionalReturn() and
succ = promise.getACatchHandler().getParameter(0)
}
}
/**
* Holds if taint propagates from `pred` to `succ` through promises.
*/

View File

@@ -10,7 +10,8 @@ module ExceptionXss {
import DomBasedXssCustomizations::DomBasedXss as DomBasedXssCustom
import ReflectedXssCustomizations::ReflectedXss as ReflectedXssCustom
import Xss as Xss
private import semmle.javascript.dataflow.InferredTypes
/**
* Holds if `node` is unlikely to cause an exception containing sensitive information to be thrown.
*/
@@ -24,16 +25,31 @@ module ExceptionXss {
node = DataFlow::globalVarRef("console").getAMemberCall(_).getAnArgument()
}
/**
* Holds if `t` is `null` or `undefined`.
*/
private predicate isNullOrUndefined(InferredType t) {
t = TTNull() or
t = TTUndefined()
}
/**
* Holds if `node` can possibly cause an exception containing sensitive information to be thrown.
*/
predicate canThrowSensitiveInformation(DataFlow::Node node) {
not isUnlikelyToThrowSensitiveInformation(node) and
not isUnlikelyToThrowSensitiveInformation(node) and
(
// in the case of reflective calls the below ensures that both InvokeNodes have no known callee.
forex(DataFlow::InvokeNode call | node = call.getAnArgument() | not exists(call.getACallee()))
forex(DataFlow::InvokeNode call | call = getEnclosingCallNode(node) |
not exists(call.getACallee())
)
or
node.asExpr().getEnclosingStmt() instanceof ThrowStmt
or
exists(DataFlow::PropRef prop |
node.getEnclosingExpr() = prop.getPropertyNameExpr() and
isNullOrUndefined(prop.getBase().analyze().getAType())
)
)
}
@@ -47,6 +63,50 @@ module ExceptionXss {
NotYetThrown() { this = "NotYetThrown" }
}
// Consider using "if (err) {.. [do something with err] .. }" as an extra condition if there are too many FP's.
class Callback extends DataFlow::FunctionNode {
Callback() {
exists(DataFlow::CallNode call | call.getLastArgument().getAFunctionValue() = this) and
this.getNumParameter() = 2 and
this.getParameter(0).getName().regexpMatch("err.*") // Using "e" was considered. But that matches too many jQuery methods where "element" is shortened as "e".
}
DataFlow::Node getErrorParam() { result = this.getParameter(0) }
}
DataFlow::CallNode getEnclosingCallNode(DataFlow::Node node) {
result.getEnclosingExpr() = getEnclosingCall(node.getEnclosingExpr())
}
InvokeExpr getEnclosingCall(Expr e) {
exists(Expr arg | arg = result.getAnArgument() |
e.getParentExpr*() = arg and
not exists(Expr mid | mid = any(InvokeExpr i) or mid = any(Function f) |
e.getParentExpr+() = mid and mid.getParentExpr+() = result
)
)
}
// `someFunction(.. <pred> .., (<result>, value) => {...}).
DataFlow::Node getCallbackErrorParam(DataFlow::Node pred) {
exists(DataFlow::CallNode call, Callback callback |
getEnclosingCallNode(pred) = call and
call.getLastArgument() = callback and
result = callback.getErrorParam() and
not pred = callback
)
}
/**
* Gets the DataFlow::Node where an exception would flow to if `pred` is used in some context
* where an exception could potentially be thrown.
*/
DataFlow::Node getWhereExceptionWouldFlow(DataFlow::Node pred) {
result = pred.asExpr().getExceptionTarget()
or
result = getCallbackErrorParam(pred)
}
/**
* A taint-tracking configuration for reasoning about XSS with possible exceptional flow.
* Flow labels are used to ensure that we only report taint-flow that has been thrown in
@@ -65,16 +125,20 @@ module ExceptionXss {
override predicate isSanitizer(DataFlow::Node node) { node instanceof Xss::Shared::Sanitizer }
cached
override predicate isAdditionalFlowStep(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel inlbl,
DataFlow::FlowLabel outlbl
) {
inlbl instanceof NotYetThrown and (outlbl.isTaint() or outlbl instanceof NotYetThrown) and
succ = pred.asExpr().getExceptionTarget() and
canThrowSensitiveInformation(pred)
inlbl instanceof NotYetThrown and
(outlbl.isTaint() or outlbl instanceof NotYetThrown) and
canThrowSensitiveInformation(pred) and
succ = getWhereExceptionWouldFlow(pred)
or
// All the usual taint-flow steps apply on data-flow before it has been thrown in an exception.
this.isAdditionalFlowStep(pred, succ) and inlbl instanceof NotYetThrown and outlbl instanceof NotYetThrown
this.isAdditionalFlowStep(pred, succ) and
inlbl instanceof NotYetThrown and
outlbl instanceof NotYetThrown
}
}
}