mirror of
https://github.com/github/codeql.git
synced 2026-04-30 03:05:15 +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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ nodes
|
||||
| exception-xss.js:22:10:22:10 | e |
|
||||
| exception-xss.js:23:18:23:18 | e |
|
||||
| exception-xss.js:23:18:23:18 | e |
|
||||
| exception-xss.js:27:18:27:20 | foo |
|
||||
| exception-xss.js:28:10:28:10 | e |
|
||||
| exception-xss.js:29:18:29:18 | e |
|
||||
| exception-xss.js:29:18:29:18 | e |
|
||||
| exception-xss.js:33:11:33:22 | ["bar", foo] |
|
||||
| exception-xss.js:33:19:33:21 | foo |
|
||||
| exception-xss.js:34:10:34:10 | e |
|
||||
@@ -59,6 +63,32 @@ nodes
|
||||
| exception-xss.js:129:10:129:10 | e |
|
||||
| exception-xss.js:130:18:130:18 | e |
|
||||
| exception-xss.js:130:18:130:18 | e |
|
||||
| exception-xss.js:136:11:136:23 | req.params.id |
|
||||
| exception-xss.js:136:11:136:23 | req.params.id |
|
||||
| exception-xss.js:136:27:136:31 | error |
|
||||
| exception-xss.js:138:19:138:23 | error |
|
||||
| exception-xss.js:138:19:138:23 | error |
|
||||
| exception-xss.js:146:9:146:38 | foo |
|
||||
| exception-xss.js:146:15:146:31 | document.location |
|
||||
| exception-xss.js:146:15:146:31 | document.location |
|
||||
| exception-xss.js:146:15:146:38 | documen ... .search |
|
||||
| exception-xss.js:148:33:148:35 | foo |
|
||||
| exception-xss.js:148:55:148:55 | e |
|
||||
| exception-xss.js:149:22:149:22 | e |
|
||||
| exception-xss.js:149:22:149:22 | e |
|
||||
| exception-xss.js:153:9:153:11 | foo |
|
||||
| exception-xss.js:154:13:154:13 | e |
|
||||
| exception-xss.js:155:19:155:19 | e |
|
||||
| exception-xss.js:155:19:155:19 | e |
|
||||
| exception-xss.js:159:14:159:16 | foo |
|
||||
| exception-xss.js:160:13:160:13 | e |
|
||||
| exception-xss.js:161:19:161:19 | e |
|
||||
| exception-xss.js:161:19:161:19 | e |
|
||||
| exception-xss.js:174:25:174:43 | exceptional return of inner(foo, resolve) |
|
||||
| exception-xss.js:174:31:174:33 | foo |
|
||||
| exception-xss.js:174:53:174:53 | e |
|
||||
| exception-xss.js:175:22:175:22 | e |
|
||||
| exception-xss.js:175:22:175:22 | e |
|
||||
| tst.js:298:9:298:16 | location |
|
||||
| tst.js:298:9:298:16 | location |
|
||||
| tst.js:299:10:299:10 | e |
|
||||
@@ -73,6 +103,7 @@ edges
|
||||
| exception-xss.js:2:9:2:31 | foo | exception-xss.js:9:11:9:13 | foo |
|
||||
| exception-xss.js:2:9:2:31 | foo | exception-xss.js:15:9:15:11 | foo |
|
||||
| exception-xss.js:2:9:2:31 | foo | exception-xss.js:21:11:21:13 | foo |
|
||||
| exception-xss.js:2:9:2:31 | foo | exception-xss.js:27:18:27:20 | foo |
|
||||
| exception-xss.js:2:9:2:31 | foo | exception-xss.js:33:19:33:21 | foo |
|
||||
| exception-xss.js:2:9:2:31 | foo | exception-xss.js:46:16:46:18 | foo |
|
||||
| exception-xss.js:2:9:2:31 | foo | exception-xss.js:81:16:81:18 | foo |
|
||||
@@ -89,11 +120,16 @@ edges
|
||||
| exception-xss.js:16:10:16:10 | e | exception-xss.js:17:18:17:18 | e |
|
||||
| exception-xss.js:16:10:16:10 | e | exception-xss.js:17:18:17:18 | e |
|
||||
| exception-xss.js:21:11:21:13 | foo | exception-xss.js:21:11:21:21 | foo + "bar" |
|
||||
| exception-xss.js:21:11:21:13 | foo | exception-xss.js:22:10:22:10 | e |
|
||||
| exception-xss.js:21:11:21:21 | foo + "bar" | exception-xss.js:22:10:22:10 | e |
|
||||
| exception-xss.js:22:10:22:10 | e | exception-xss.js:23:18:23:18 | e |
|
||||
| exception-xss.js:22:10:22:10 | e | exception-xss.js:23:18:23:18 | e |
|
||||
| exception-xss.js:27:18:27:20 | foo | exception-xss.js:28:10:28:10 | e |
|
||||
| exception-xss.js:28:10:28:10 | e | exception-xss.js:29:18:29:18 | e |
|
||||
| exception-xss.js:28:10:28:10 | e | exception-xss.js:29:18:29:18 | e |
|
||||
| exception-xss.js:33:11:33:22 | ["bar", foo] | exception-xss.js:34:10:34:10 | e |
|
||||
| exception-xss.js:33:19:33:21 | foo | exception-xss.js:33:11:33:22 | ["bar", foo] |
|
||||
| exception-xss.js:33:19:33:21 | foo | exception-xss.js:34:10:34:10 | e |
|
||||
| exception-xss.js:34:10:34:10 | e | exception-xss.js:35:18:35:18 | e |
|
||||
| exception-xss.js:34:10:34:10 | e | exception-xss.js:35:18:35:18 | e |
|
||||
| exception-xss.js:46:3:46:19 | exceptional return of deep("bar" + foo) | exception-xss.js:47:10:47:10 | e |
|
||||
@@ -111,6 +147,7 @@ edges
|
||||
| exception-xss.js:90:10:90:10 | e | exception-xss.js:91:18:91:18 | e |
|
||||
| exception-xss.js:95:11:95:22 | [foo, "bar"] | exception-xss.js:96:10:96:10 | e |
|
||||
| exception-xss.js:95:12:95:14 | foo | exception-xss.js:95:11:95:22 | [foo, "bar"] |
|
||||
| exception-xss.js:95:12:95:14 | foo | exception-xss.js:96:10:96:10 | e |
|
||||
| exception-xss.js:96:10:96:10 | e | exception-xss.js:97:18:97:18 | e |
|
||||
| exception-xss.js:96:10:96:10 | e | exception-xss.js:97:18:97:18 | e |
|
||||
| exception-xss.js:102:12:102:14 | foo | exception-xss.js:106:10:106:10 | e |
|
||||
@@ -127,6 +164,30 @@ edges
|
||||
| exception-xss.js:128:11:128:52 | session ... ssion') | exception-xss.js:129:10:129:10 | e |
|
||||
| exception-xss.js:129:10:129:10 | e | exception-xss.js:130:18:130:18 | e |
|
||||
| exception-xss.js:129:10:129:10 | e | exception-xss.js:130:18:130:18 | e |
|
||||
| exception-xss.js:136:11:136:23 | req.params.id | exception-xss.js:136:27:136:31 | error |
|
||||
| exception-xss.js:136:11:136:23 | req.params.id | exception-xss.js:136:27:136:31 | error |
|
||||
| exception-xss.js:136:27:136:31 | error | exception-xss.js:138:19:138:23 | error |
|
||||
| exception-xss.js:136:27:136:31 | error | exception-xss.js:138:19:138:23 | error |
|
||||
| exception-xss.js:146:9:146:38 | foo | exception-xss.js:148:33:148:35 | foo |
|
||||
| exception-xss.js:146:9:146:38 | foo | exception-xss.js:153:9:153:11 | foo |
|
||||
| exception-xss.js:146:9:146:38 | foo | exception-xss.js:159:14:159:16 | foo |
|
||||
| exception-xss.js:146:9:146:38 | foo | exception-xss.js:174:31:174:33 | foo |
|
||||
| exception-xss.js:146:15:146:31 | document.location | exception-xss.js:146:15:146:38 | documen ... .search |
|
||||
| exception-xss.js:146:15:146:31 | document.location | exception-xss.js:146:15:146:38 | documen ... .search |
|
||||
| exception-xss.js:146:15:146:38 | documen ... .search | exception-xss.js:146:9:146:38 | foo |
|
||||
| exception-xss.js:148:33:148:35 | foo | exception-xss.js:148:55:148:55 | e |
|
||||
| exception-xss.js:148:55:148:55 | e | exception-xss.js:149:22:149:22 | e |
|
||||
| exception-xss.js:148:55:148:55 | e | exception-xss.js:149:22:149:22 | e |
|
||||
| exception-xss.js:153:9:153:11 | foo | exception-xss.js:154:13:154:13 | e |
|
||||
| exception-xss.js:154:13:154:13 | e | exception-xss.js:155:19:155:19 | e |
|
||||
| exception-xss.js:154:13:154:13 | e | exception-xss.js:155:19:155:19 | e |
|
||||
| exception-xss.js:159:14:159:16 | foo | exception-xss.js:160:13:160:13 | e |
|
||||
| exception-xss.js:160:13:160:13 | e | exception-xss.js:161:19:161:19 | e |
|
||||
| exception-xss.js:160:13:160:13 | e | exception-xss.js:161:19:161:19 | e |
|
||||
| exception-xss.js:174:25:174:43 | exceptional return of inner(foo, resolve) | exception-xss.js:174:53:174:53 | e |
|
||||
| exception-xss.js:174:31:174:33 | foo | exception-xss.js:174:25:174:43 | exceptional return of inner(foo, resolve) |
|
||||
| exception-xss.js:174:53:174:53 | e | exception-xss.js:175:22:175:22 | e |
|
||||
| exception-xss.js:174:53:174:53 | e | exception-xss.js:175:22:175:22 | e |
|
||||
| tst.js:298:9:298:16 | location | tst.js:299:10:299:10 | e |
|
||||
| tst.js:298:9:298:16 | location | tst.js:299:10:299:10 | e |
|
||||
| tst.js:299:10:299:10 | e | tst.js:300:20:300:20 | e |
|
||||
@@ -139,6 +200,7 @@ edges
|
||||
| exception-xss.js:11:18:11:18 | e | exception-xss.js:2:15:2:31 | document.location | exception-xss.js:11:18:11:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:15:2:31 | document.location | user-provided value |
|
||||
| exception-xss.js:17:18:17:18 | e | exception-xss.js:2:15:2:31 | document.location | exception-xss.js:17:18:17:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:15:2:31 | document.location | user-provided value |
|
||||
| exception-xss.js:23:18:23:18 | e | exception-xss.js:2:15:2:31 | document.location | exception-xss.js:23:18:23:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:15:2:31 | document.location | user-provided value |
|
||||
| exception-xss.js:29:18:29:18 | e | exception-xss.js:2:15:2:31 | document.location | exception-xss.js:29:18:29:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:15:2:31 | document.location | user-provided value |
|
||||
| exception-xss.js:35:18:35:18 | e | exception-xss.js:2:15:2:31 | document.location | exception-xss.js:35:18:35:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:15:2:31 | document.location | user-provided value |
|
||||
| exception-xss.js:48:18:48:18 | e | exception-xss.js:2:15:2:31 | document.location | exception-xss.js:48:18:48:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:15:2:31 | document.location | user-provided value |
|
||||
| exception-xss.js:83:18:83:18 | e | exception-xss.js:2:15:2:31 | document.location | exception-xss.js:83:18:83:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:15:2:31 | document.location | user-provided value |
|
||||
@@ -147,5 +209,10 @@ edges
|
||||
| exception-xss.js:107:18:107:18 | e | exception-xss.js:2:15:2:31 | document.location | exception-xss.js:107:18:107:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:15:2:31 | document.location | user-provided value |
|
||||
| exception-xss.js:119:14:119:30 | "Exception: " + e | exception-xss.js:117:13:117:25 | req.params.id | exception-xss.js:119:14:119:30 | "Exception: " + e | Cross-site scripting vulnerability due to $@. | exception-xss.js:117:13:117:25 | req.params.id | user-provided value |
|
||||
| exception-xss.js:130:18:130:18 | e | exception-xss.js:125:48:125:64 | document.location | exception-xss.js:130:18:130:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:125:48:125:64 | document.location | user-provided value |
|
||||
| exception-xss.js:138:19:138:23 | error | exception-xss.js:136:11:136:23 | req.params.id | exception-xss.js:138:19:138:23 | error | Cross-site scripting vulnerability due to $@. | exception-xss.js:136:11:136:23 | req.params.id | user-provided value |
|
||||
| exception-xss.js:149:22:149:22 | e | exception-xss.js:146:15:146:31 | document.location | exception-xss.js:149:22:149:22 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:146:15:146:31 | document.location | user-provided value |
|
||||
| exception-xss.js:155:19:155:19 | e | exception-xss.js:146:15:146:31 | document.location | exception-xss.js:155:19:155:19 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:146:15:146:31 | document.location | user-provided value |
|
||||
| exception-xss.js:161:19:161:19 | e | exception-xss.js:146:15:146:31 | document.location | exception-xss.js:161:19:161:19 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:146:15:146:31 | document.location | user-provided value |
|
||||
| exception-xss.js:175:22:175:22 | e | exception-xss.js:146:15:146:31 | document.location | exception-xss.js:175:22:175:22 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:146:15:146:31 | document.location | user-provided value |
|
||||
| tst.js:300:20:300:20 | e | tst.js:298:9:298:16 | location | tst.js:300:20:300:20 | e | Cross-site scripting vulnerability due to $@. | tst.js:298:9:298:16 | location | user-provided value |
|
||||
| tst.js:308:20:308:20 | e | tst.js:305:10:305:17 | location | tst.js:308:20:308:20 | e | Cross-site scripting vulnerability due to $@. | tst.js:305:10:305:17 | location | user-provided value |
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
try {
|
||||
unknown({prop: foo});
|
||||
} catch(e) {
|
||||
$('myId').html(e); // We don't flag this for now.
|
||||
$('myId').html(e); // NOT OK!
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -130,3 +130,48 @@ app.get('/user/:id', function(req, res) {
|
||||
$('myId').html(e); // NOT OK
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
app.get('/user/:id', function(req, res) {
|
||||
unknown(req.params.id, (error, res) => {
|
||||
if (error) {
|
||||
$('myId').html(error); // NOT OK
|
||||
return;
|
||||
}
|
||||
$('myId').html(res); // OK (for now?)
|
||||
});
|
||||
});
|
||||
|
||||
(function () {
|
||||
var foo = document.location.search;
|
||||
|
||||
new Promise(resolve => unknown(foo, resolve)).catch((e) => {
|
||||
$('myId').html(e); // NOT OK
|
||||
});
|
||||
|
||||
try {
|
||||
null[foo];
|
||||
} catch(e) {
|
||||
$('myId').html(e); // NOT OK
|
||||
}
|
||||
|
||||
try {
|
||||
unknown()[foo];
|
||||
} catch(e) {
|
||||
$('myId').html(e); // NOT OK
|
||||
}
|
||||
|
||||
try {
|
||||
"foo"[foo]
|
||||
} catch(e) {
|
||||
$('myId').html(e); // OK
|
||||
}
|
||||
|
||||
function inner(tainted, resolve) {
|
||||
unknown(tainted, resolve);
|
||||
}
|
||||
|
||||
new Promise(resolve => inner(foo, resolve)).catch((e) => {
|
||||
$('myId').html(e); // NOT OK
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user