mirror of
https://github.com/github/codeql.git
synced 2026-05-10 01:10:09 +02:00
expand the js/exception-xss to handle more types of exceptional flow
This commit is contained in:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user