Merge pull request #4862 from erik-krogh/shellSanitizer

Approved by esbena
This commit is contained in:
CodeQL CI
2021-01-06 11:16:12 -08:00
committed by GitHub
11 changed files with 80 additions and 66 deletions

View File

@@ -18,6 +18,7 @@ import javascript
import DataFlow
import PathGraph
import semmle.javascript.DynamicPropertyAccess
private import semmle.javascript.dataflow.InferredTypes
/**
* A call of form `x.split(".")` where `x` is a parameter.
@@ -394,34 +395,31 @@ class InstanceOfGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::Value
*/
class TypeofGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::ValueNode {
override EqualityTest astNode;
TypeofExpr typeof;
string typeofStr;
Expr operand;
TypeofTag tag;
TypeofGuard() {
typeof = astNode.getAnOperand() and
typeofStr = astNode.getAnOperand().getStringValue()
}
TypeofGuard() { TaintTracking::isTypeofGuard(astNode, operand, tag) }
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
e = typeof.getOperand() and
e = operand and
outcome = astNode.getPolarity() and
(
typeofStr = "object" and
tag = "object" and
label = "constructor"
or
typeofStr = "function" and
tag = "function" and
label = "__proto__"
)
or
e = typeof.getOperand() and
e = operand and
outcome = astNode.getPolarity().booleanNot() and
(
// If something is not an object, sanitize object, as both must end
// in non-function prototype object.
typeofStr = "object" and
tag = "object" and
label instanceof UnsafePropLabel
or
typeofStr = "function" and
tag = "function" and
label = "constructor"
)
}

View File

@@ -328,12 +328,7 @@ module DefensiveExpressionTest {
Expr operand;
TypeofTag tag;
TypeofTest() {
exists(Expr op1, Expr op2 | hasOperands(op1, op2) |
operand = op1.(TypeofExpr).getOperand() and
op2.mayHaveStringValue(tag)
)
}
TypeofTest() { TaintTracking::isTypeofGuard(this, operand, tag) }
boolean getTheTestResult() {
exists(boolean testResult |

View File

@@ -899,12 +899,7 @@ module TaintTracking {
Expr x;
override EqualityTest astNode;
TypeOfUndefinedSanitizer() {
exists(StringLiteral str, TypeofExpr typeof | astNode.hasOperands(str, typeof) |
str.getValue() = "undefined" and
typeof.getOperand() = x
)
}
TypeOfUndefinedSanitizer() { isTypeofGuard(astNode, x, "undefined") }
override predicate sanitizes(boolean outcome, Expr e) {
outcome = astNode.getPolarity() and
@@ -914,6 +909,18 @@ module TaintTracking {
override predicate appliesTo(Configuration cfg) { any() }
}
/**
* Holds if `test` is a guard that checks if `operand` is typeof `tag`.
*
* See `TypeOfUndefinedSanitizer` for example usage.
*/
predicate isTypeofGuard(EqualityTest test, Expr operand, TypeofTag tag) {
exists(Expr str, TypeofExpr typeof | test.hasOperands(str, typeof) |
str.mayHaveStringValue(tag) and
typeof.getOperand() = operand
)
}
/** DEPRECATED. This class has been renamed to `MembershipTestSanitizer`. */
deprecated class StringInclusionSanitizer = MembershipTestSanitizer;

View File

@@ -14,6 +14,7 @@
*/
import javascript
private import semmle.javascript.dataflow.InferredTypes
module TaintedObject {
private import DataFlow
@@ -98,25 +99,24 @@ module TaintedObject {
*/
private class TypeTestGuard extends SanitizerGuard, ValueNode {
override EqualityTest astNode;
TypeofExpr typeof;
Expr operand;
boolean polarity;
TypeTestGuard() {
astNode.getAnOperand() = typeof and
(
exists(TypeofTag tag | TaintTracking::isTypeofGuard(astNode, operand, tag) |
// typeof x === "object" sanitizes `x` when it evaluates to false
astNode.getAnOperand().getStringValue() = "object" and
tag = "object" and
polarity = astNode.getPolarity().booleanNot()
or
// typeof x === "string" sanitizes `x` when it evaluates to true
astNode.getAnOperand().getStringValue() != "object" and
tag != "object" and
polarity = astNode.getPolarity()
)
}
override predicate sanitizes(boolean outcome, Expr e, FlowLabel label) {
polarity = outcome and
e = typeof.getOperand() and
e = operand and
label = label()
}
}

View File

@@ -9,6 +9,7 @@
private import javascript
private import semmle.javascript.DynamicPropertyAccess
private import semmle.javascript.dataflow.InferredTypes
/**
* Provides a taint tracking configuration for reasoning about
@@ -164,22 +165,18 @@ module PrototypePollutingAssignment {
private class TypeofCheck extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::ValueNode {
override EqualityTest astNode;
Expr operand;
string value;
boolean polarity;
TypeofCheck() {
exists(TypeofExpr typeof, Expr str |
astNode.hasOperands(typeof, str) and
typeof.getOperand() = operand and
str.getStringValue() = value
exists(TypeofTag value | TaintTracking::isTypeofGuard(astNode, operand, value) |
value = "object" and polarity = astNode.getPolarity().booleanNot()
or
value != "object" and polarity = astNode.getPolarity()
)
}
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
(
value = "object" and outcome = astNode.getPolarity().booleanNot()
or
value != "object" and outcome = astNode.getPolarity()
) and
polarity = outcome and
e = operand and
label instanceof ObjectPrototype
}

View File

@@ -134,10 +134,7 @@ module UnsafeJQueryPlugin {
SyntacticConstants::isUndefined(undef)
)
or
exists(Expr op1, Expr op2 | test.hasOperands(op1, op2) |
read.asExpr() = op1.(TypeofExpr).getOperand() and
op2.mayHaveStringValue(any(InferredType t | t = TTUndefined()).getTypeofTag())
)
TaintTracking::isTypeofGuard(test, read.asExpr(), "undefined")
)
or
polarity = true and

View File

@@ -7,6 +7,7 @@
import javascript
private import semmle.javascript.security.dataflow.RemoteFlowSources
private import semmle.javascript.PackageExports as Exports
private import semmle.javascript.dataflow.InferredTypes
/**
* Module containing sources, sinks, and sanitizers for shell command constructed from library input.
@@ -191,4 +192,20 @@ module UnsafeShellCommandConstruction {
)
}
}
/**
* A guard of the form `typeof x === "<T>"`, where <T> is "number", or "boolean",
* which sanitizes `x` in its "then" branch.
*/
class TypeOfSanitizer extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode {
Expr x;
override EqualityTest astNode;
TypeOfSanitizer() { TaintTracking::isTypeofGuard(astNode, x, ["number", "boolean"]) }
override predicate sanitizes(boolean outcome, Expr e) {
outcome = astNode.getPolarity() and
e = x
}
}
}

View File

@@ -98,16 +98,13 @@ module UnvalidatedDynamicMethodCall {
*/
class FunctionCheck extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::ValueNode {
override EqualityTest astNode;
TypeofExpr t;
Expr operand;
FunctionCheck() {
astNode.getAnOperand().getStringValue() = "function" and
astNode.getAnOperand().getUnderlyingValue() = t
}
FunctionCheck() { TaintTracking::isTypeofGuard(astNode, operand, "function") }
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
outcome = astNode.getPolarity() and
e = t.getOperand().getUnderlyingValue() and
e = operand and
label instanceof MaybeNonFunction
}
}

View File

@@ -4,6 +4,7 @@
*/
import javascript
private import semmle.javascript.dataflow.InferredTypes
/**
* Classes and predicates for the XSS through DOM query.
@@ -103,25 +104,24 @@ module XssThroughDom {
*/
class TypeTestGuard extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode {
override EqualityTest astNode;
TypeofExpr typeof;
Expr operand;
boolean polarity;
TypeTestGuard() {
astNode.getAnOperand() = typeof and
(
exists(TypeofTag tag | TaintTracking::isTypeofGuard(astNode, operand, tag) |
// typeof x === "string" sanitizes `x` when it evaluates to false
astNode.getAnOperand().getStringValue() = "string" and
tag = "string" and
polarity = astNode.getPolarity().booleanNot()
or
// typeof x === "object" sanitizes `x` when it evaluates to true
astNode.getAnOperand().getStringValue() != "string" and
tag != "string" and
polarity = astNode.getPolarity()
)
}
override predicate sanitizes(boolean outcome, Expr e) {
polarity = outcome and
e = typeof.getOperand()
e = operand
}
}
}

View File

@@ -191,10 +191,10 @@ nodes
| lib/lib.js:340:22:340:26 | id(n) |
| lib/lib.js:340:22:340:26 | id(n) |
| lib/lib.js:340:25:340:25 | n |
| lib/lib.js:343:29:343:34 | unsafe |
| lib/lib.js:343:29:343:34 | unsafe |
| lib/lib.js:345:22:345:27 | unsafe |
| lib/lib.js:345:22:345:27 | unsafe |
| lib/lib.js:349:29:349:34 | unsafe |
| lib/lib.js:349:29:349:34 | unsafe |
| lib/lib.js:351:22:351:27 | unsafe |
| lib/lib.js:351:22:351:27 | unsafe |
edges
| lib/lib2.js:3:28:3:31 | name | lib/lib2.js:4:22:4:25 | name |
| lib/lib2.js:3:28:3:31 | name | lib/lib2.js:4:22:4:25 | name |
@@ -421,10 +421,10 @@ edges
| lib/lib.js:339:39:339:39 | n | lib/lib.js:340:25:340:25 | n |
| lib/lib.js:340:25:340:25 | n | lib/lib.js:340:22:340:26 | id(n) |
| lib/lib.js:340:25:340:25 | n | lib/lib.js:340:22:340:26 | id(n) |
| lib/lib.js:343:29:343:34 | unsafe | lib/lib.js:345:22:345:27 | unsafe |
| lib/lib.js:343:29:343:34 | unsafe | lib/lib.js:345:22:345:27 | unsafe |
| lib/lib.js:343:29:343:34 | unsafe | lib/lib.js:345:22:345:27 | unsafe |
| lib/lib.js:343:29:343:34 | unsafe | lib/lib.js:345:22:345:27 | unsafe |
| lib/lib.js:349:29:349:34 | unsafe | lib/lib.js:351:22:351:27 | unsafe |
| lib/lib.js:349:29:349:34 | unsafe | lib/lib.js:351:22:351:27 | unsafe |
| lib/lib.js:349:29:349:34 | unsafe | lib/lib.js:351:22:351:27 | unsafe |
| lib/lib.js:349:29:349:34 | unsafe | lib/lib.js:351:22:351:27 | unsafe |
#select
| lib/lib2.js:4:10:4:25 | "rm -rf " + name | lib/lib2.js:3:28:3:31 | name | lib/lib2.js:4:22:4:25 | name | $@ based on library input is later used in $@. | lib/lib2.js:4:10:4:25 | "rm -rf " + name | String concatenation | lib/lib2.js:4:2:4:26 | cp.exec ... + name) | shell command |
| lib/lib2.js:8:10:8:25 | "rm -rf " + name | lib/lib2.js:7:32:7:35 | name | lib/lib2.js:8:22:8:25 | name | $@ based on library input is later used in $@. | lib/lib2.js:8:10:8:25 | "rm -rf " + name | String concatenation | lib/lib2.js:8:2:8:26 | cp.exec ... + name) | shell command |
@@ -480,4 +480,4 @@ edges
| lib/lib.js:320:11:320:26 | "rm -rf " + name | lib/lib.js:314:40:314:43 | name | lib/lib.js:320:23:320:26 | name | $@ based on library input is later used in $@. | lib/lib.js:320:11:320:26 | "rm -rf " + name | String concatenation | lib/lib.js:320:3:320:27 | cp.exec ... + name) | shell command |
| lib/lib.js:325:12:325:51 | "MyWind ... " + arg | lib/lib.js:324:40:324:42 | arg | lib/lib.js:325:49:325:51 | arg | $@ based on library input is later used in $@. | lib/lib.js:325:12:325:51 | "MyWind ... " + arg | String concatenation | lib/lib.js:326:2:326:13 | cp.exec(cmd) | shell command |
| lib/lib.js:340:10:340:26 | "rm -rf " + id(n) | lib/lib.js:339:39:339:39 | n | lib/lib.js:340:22:340:26 | id(n) | $@ based on library input is later used in $@. | lib/lib.js:340:10:340:26 | "rm -rf " + id(n) | String concatenation | lib/lib.js:340:2:340:27 | cp.exec ... id(n)) | shell command |
| lib/lib.js:345:10:345:27 | "rm -rf " + unsafe | lib/lib.js:343:29:343:34 | unsafe | lib/lib.js:345:22:345:27 | unsafe | $@ based on library input is later used in $@. | lib/lib.js:345:10:345:27 | "rm -rf " + unsafe | String concatenation | lib/lib.js:345:2:345:28 | cp.exec ... unsafe) | shell command |
| lib/lib.js:351:10:351:27 | "rm -rf " + unsafe | lib/lib.js:349:29:349:34 | unsafe | lib/lib.js:351:22:351:27 | unsafe | $@ based on library input is later used in $@. | lib/lib.js:351:10:351:27 | "rm -rf " + unsafe | String concatenation | lib/lib.js:351:2:351:28 | cp.exec ... unsafe) | shell command |

View File

@@ -340,6 +340,12 @@ module.exports.problematic = function(n) {
cp.exec("rm -rf " + id(n)); // NOT OK
};
module.exports.typeofNumber = function(n) {
if (typeof n === "number") {
cp.exec("rm -rf " + n); // OK
}
};
function boundProblem(safe, unsafe) {
cp.exec("rm -rf " + safe); // OK
cp.exec("rm -rf " + unsafe); // NOT OK
@@ -349,4 +355,4 @@ Object.defineProperty(module.exports, "boundProblem", {
get: function () {
return boundProblem.bind(this, "safe");
}
});
});