Merge pull request #447 from esben-semmle/js/indirect-sanitization

Approved by asger-semmle
This commit is contained in:
semmle-qlci
2018-11-13 09:14:28 +00:00
committed by GitHub
7 changed files with 242 additions and 10 deletions

View File

@@ -15,6 +15,7 @@
import javascript
import semmle.javascript.dataflow.CallGraph
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
private import semmle.javascript.dataflow.InferredTypes
/**
@@ -809,6 +810,74 @@ module TaintTracking {
}
/**
* A function that returns the result of a sanitizer check.
*/
private class SanitizingFunction extends Function {
Parameter sanitizedParameter;
SanitizerGuardNode sanitizer;
boolean sanitizerOutcome;
SanitizingFunction() {
exists(Expr e |
exists(Expr returnExpr |
returnExpr = sanitizer.asExpr()
or
// ad hoc support for conjunctions:
returnExpr.(LogAndExpr).getAnOperand() = sanitizer.asExpr() and sanitizerOutcome = true
or
// ad hoc support for disjunctions:
returnExpr.(LogOrExpr).getAnOperand() = sanitizer.asExpr() and sanitizerOutcome = false
|
exists(SsaExplicitDefinition ssa |
ssa.getDef().getSource() = returnExpr and
ssa.getVariable().getAUse() = getAReturnedExpr()
)
or
returnExpr = getAReturnedExpr()
) and
DataFlow::parameterNode(sanitizedParameter).flowsToExpr(e) and
sanitizer.sanitizes(sanitizerOutcome, e)
) and
getNumParameter() = 1 and
sanitizedParameter = getParameter(0)
}
/**
* Holds if this function sanitizes argument `e` of call `call`, provided the call evaluates to `outcome`.
*/
predicate isSanitizingCall(DataFlow::CallNode call, Expr e, boolean outcome) {
exists(DataFlow::Node arg |
arg.asExpr() = e and
arg = call.getArgument(0) and
call.getNumArgument() = 1 and
FlowSteps::argumentPassing(call, arg, this, sanitizedParameter) and
outcome = sanitizerOutcome
)
}
/**
* Holds if this function applies to the flow in `cfg`.
*/
predicate appliesTo(Configuration cfg) {
cfg.isBarrierGuard(sanitizer)
}
}
/**
* A call that sanitizes an argument.
*/
private class AdditionalSanitizingCall extends AdditionalSanitizerGuardNode, DataFlow::CallNode {
SanitizingFunction f;
AdditionalSanitizingCall() { f.isSanitizingCall(this, _, _) }
override predicate sanitizes(boolean outcome, Expr e) { f.isSanitizingCall(this, e, outcome) }
override predicate appliesTo(Configuration cfg) { f.appliesTo(cfg) }
}
/**
* An equality test on `e.origin` or `e.source` where `e` is a `postMessage` event object,

View File

@@ -38,3 +38,25 @@
| tst.js:226:9:226:26 | -1 >= o.indexOf(v) | ExampleConfiguration | false | tst.js:226:25:226:25 | v |
| tst.js:236:9:236:24 | isWhitelisted(v) | ExampleConfiguration | true | tst.js:236:23:236:23 | v |
| tst.js:240:9:240:28 | config.allowValue(v) | ExampleConfiguration | true | tst.js:240:27:240:27 | v |
| tst.js:252:16:252:36 | whiteli ... ains(x) | ExampleConfiguration | true | tst.js:252:35:252:35 | x |
| tst.js:254:9:254:12 | f(v) | ExampleConfiguration | true | tst.js:254:11:254:11 | v |
| tst.js:261:25:261:45 | whiteli ... ains(y) | ExampleConfiguration | true | tst.js:261:44:261:44 | y |
| tst.js:264:9:264:12 | g(v) | ExampleConfiguration | true | tst.js:264:11:264:11 | v |
| tst.js:271:25:271:45 | whiteli ... ains(z) | ExampleConfiguration | true | tst.js:271:44:271:44 | z |
| tst.js:281:16:281:25 | x2 != null | ExampleConfiguration | false | tst.js:281:16:281:17 | x2 |
| tst.js:281:30:281:51 | whiteli ... ins(x2) | ExampleConfiguration | true | tst.js:281:49:281:50 | x2 |
| tst.js:283:9:283:13 | f2(v) | ExampleConfiguration | true | tst.js:283:12:283:12 | v |
| tst.js:290:16:290:25 | x3 == null | ExampleConfiguration | true | tst.js:290:16:290:17 | x3 |
| tst.js:290:30:290:51 | whiteli ... ins(x3) | ExampleConfiguration | true | tst.js:290:49:290:50 | x3 |
| tst.js:299:17:299:38 | whiteli ... ins(x4) | ExampleConfiguration | true | tst.js:299:36:299:37 | x4 |
| tst.js:308:18:308:39 | whiteli ... ins(x5) | ExampleConfiguration | true | tst.js:308:37:308:38 | x5 |
| tst.js:317:26:317:47 | whiteli ... ins(x6) | ExampleConfiguration | true | tst.js:317:45:317:46 | x6 |
| tst.js:327:25:327:34 | x7 != null | ExampleConfiguration | false | tst.js:327:25:327:26 | x7 |
| tst.js:327:39:327:60 | whiteli ... ins(x7) | ExampleConfiguration | true | tst.js:327:58:327:59 | x7 |
| tst.js:330:9:330:13 | f7(v) | ExampleConfiguration | true | tst.js:330:12:330:12 | v |
| tst.js:337:25:337:46 | whiteli ... ins(x8) | ExampleConfiguration | true | tst.js:337:44:337:45 | x8 |
| tst.js:338:16:338:25 | x8 != null | ExampleConfiguration | false | tst.js:338:16:338:17 | x8 |
| tst.js:347:29:347:50 | whiteli ... ins(x9) | ExampleConfiguration | true | tst.js:347:48:347:49 | x9 |
| tst.js:356:16:356:27 | x10 !== null | ExampleConfiguration | false | tst.js:356:16:356:18 | x10 |
| tst.js:356:32:356:48 | x10 !== undefined | ExampleConfiguration | false | tst.js:356:32:356:34 | x10 |
| tst.js:358:9:358:14 | f10(v) | ExampleConfiguration | false | tst.js:358:13:358:13 | v |

View File

@@ -36,3 +36,23 @@
| tst.js:227:14:227:14 | v | tst.js:199:13:199:20 | SOURCE() |
| tst.js:239:14:239:14 | v | tst.js:235:13:235:20 | SOURCE() |
| tst.js:243:14:243:14 | v | tst.js:235:13:235:20 | SOURCE() |
| tst.js:249:10:249:10 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:257:14:257:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:267:14:267:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:275:14:275:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:277:14:277:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:286:14:286:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:293:14:293:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:295:14:295:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:302:14:302:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:304:14:304:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:311:14:311:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:313:14:313:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:321:14:321:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:323:14:323:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:333:14:333:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:341:14:341:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:343:14:343:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:350:14:350:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:352:14:352:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:359:14:359:14 | v | tst.js:248:13:248:20 | SOURCE() |

View File

@@ -31,3 +31,9 @@
| tst.js:229:14:229:14 | v | ExampleConfiguration |
| tst.js:237:14:237:14 | v | ExampleConfiguration |
| tst.js:241:14:241:14 | v | ExampleConfiguration |
| tst.js:255:14:255:14 | v | ExampleConfiguration |
| tst.js:265:14:265:14 | v | ExampleConfiguration |
| tst.js:284:14:284:14 | v | ExampleConfiguration |
| tst.js:331:14:331:14 | v | ExampleConfiguration |
| tst.js:356:16:356:27 | x10 | ExampleConfiguration |
| tst.js:361:14:361:14 | v | ExampleConfiguration |

View File

@@ -243,3 +243,122 @@ function adhocWhitelisting() {
SINK(v);
}
function IndirectSanitizer () {
var v = SOURCE();
SINK(v);
function f(x) {
return whitelist.contains(x);
}
if (f(v)) {
SINK(v);
} else {
SINK(v);
}
function g(y) {
var sanitized = whitelist.contains(y);
return sanitized;
}
if (g(v)) {
SINK(v);
} else {
SINK(v);
}
function h(z) {
var sanitized = whitelist.contains(z);
return somethingElse();
}
if (h(v)) {
SINK(v);
} else {
SINK(v);
}
function f2(x2) {
return x2 != null && whitelist.contains(x2);
}
if (f2(v)) {
SINK(v);
} else {
SINK(v);
}
function f3(x3) {
return x3 == null || whitelist.contains(x3);
}
if (f3(v)) {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
} else {
SINK(v);
}
function f4(x4) {
return !whitelist.contains(x4);
}
if (f4(v)) {
SINK(v);
} else {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
}
function f5(x5) {
return !!whitelist.contains(x5);
}
if (f5(v)) {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
} else {
SINK(v);
}
function f6(x6) {
var sanitized = !whitelist.contains(x6);
return !sanitized;
}
if (f6(v)) {
SINK(v);
} else {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
}
function f7(x7) {
var sanitized = x7 != null && whitelist.contains(x7);
return sanitized;
}
if (f7(v)) {
SINK(v);
} else {
SINK(v);
}
function f8(x8) {
var sanitized = whitelist.contains(x8);
return x8 != null && sanitized;
}
if (f8(v)) {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
} else {
SINK(v);
}
function f9(x9) {
return unknown() && whitelist.contains(x9) && unknown();
}
if (f9(v)) {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
} else {
SINK(v);
}
function f10(x10) {
return x10 !== null || x10 !== undefined;
}
if (f10(v)) {
SINK(v);
} else {
SINK(v);
}
}

View File

@@ -2,12 +2,12 @@
| express.js:12:26:12:44 | req.param("target") | Untrusted URL redirection due to $@. | express.js:12:26:12:44 | req.param("target") | user-provided value |
| express.js:33:18:33:23 | target | Untrusted URL redirection due to $@. | express.js:27:16:27:34 | req.param("target") | user-provided value |
| express.js:35:16:35:21 | target | Untrusted URL redirection due to $@. | express.js:27:16:27:34 | req.param("target") | user-provided value |
| express.js:44:16:44:108 | (req.pa ... ntacts" | Untrusted URL redirection due to $@. | express.js:44:69:44:87 | req.param('action') | user-provided value |
| express.js:53:26:53:28 | url | Untrusted URL redirection due to $@. | express.js:48:16:48:28 | req.params[0] | user-provided value |
| express.js:78:16:78:43 | `${req. ... )}/foo` | Untrusted URL redirection due to $@. | express.js:78:19:78:37 | req.param("target") | user-provided value |
| express.js:94:18:94:23 | target | Untrusted URL redirection due to $@. | express.js:87:16:87:34 | req.param("target") | user-provided value |
| express.js:101:16:101:21 | target | Untrusted URL redirection due to $@. | express.js:87:16:87:34 | req.param("target") | user-provided value |
| express.js:122:16:122:72 | [req.qu ... oin('') | Untrusted URL redirection due to $@. | express.js:122:17:122:30 | req.query.page | user-provided value |
| express.js:40:16:40:108 | (req.pa ... ntacts" | Untrusted URL redirection due to $@. | express.js:40:69:40:87 | req.param('action') | user-provided value |
| express.js:49:26:49:28 | url | Untrusted URL redirection due to $@. | express.js:44:16:44:28 | req.params[0] | user-provided value |
| express.js:74:16:74:43 | `${req. ... )}/foo` | Untrusted URL redirection due to $@. | express.js:74:19:74:37 | req.param("target") | user-provided value |
| express.js:90:18:90:23 | target | Untrusted URL redirection due to $@. | express.js:83:16:83:34 | req.param("target") | user-provided value |
| express.js:97:16:97:21 | target | Untrusted URL redirection due to $@. | express.js:83:16:83:34 | req.param("target") | user-provided value |
| express.js:118:16:118:72 | [req.qu ... oin('') | Untrusted URL redirection due to $@. | express.js:118:17:118:30 | req.query.page | user-provided value |
| node.js:7:34:7:39 | target | Untrusted URL redirection due to $@. | node.js:6:26:6:32 | req.url | user-provided value |
| node.js:15:34:15:45 | '/' + target | Untrusted URL redirection due to $@. | node.js:11:26:11:32 | req.url | user-provided value |
| node.js:32:34:32:55 | target ... =" + me | Untrusted URL redirection due to $@. | node.js:29:26:29:32 | req.url | user-provided value |

View File

@@ -35,10 +35,6 @@ app.get('/some/path', function(req, res) {
res.redirect(target);
});
function isLocalURL(target) {
return new RegExp("^/(?![/\\])|^~/").exec(target);
}
app.get('/foo', function(req, res) {
// BAD: may be a global redirection
res.redirect((req.param('action') && req.param('action') != "") ? req.param('action') : "/google_contacts")