Merge pull request #2220 from erik-krogh/processEnvTaint

Approved by esbena, max-schaefer
This commit is contained in:
semmle-qlci
2019-11-20 13:16:43 +00:00
committed by GitHub
7 changed files with 209 additions and 13 deletions

View File

@@ -659,6 +659,20 @@ module TaintTracking {
}
}
/**
* A taint step through the Node.JS function `util.inspect(..)`.
*/
class UtilInspectTaintStep extends AdditionalTaintStep, DataFlow::InvokeNode {
UtilInspectTaintStep() {
this = DataFlow::moduleImport("util").getAMemberCall("inspect")
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
succ = this and
this.getAnArgument() = pred
}
}
/**
* A conditional checking a tainted string against a regular expression, which is
* considered to be a sanitizer for all configurations.

View File

@@ -61,6 +61,15 @@ private module Console {
if name = "assert"
then result = getArgument([1 .. getNumArgument()])
else result = getAnArgument()
or
result = getASpreadArgument()
}
/**
* Gets the name of the console logging method, e.g. "log", "error", "assert", etc.
*/
string getName() {
result = name
}
}
}

View File

@@ -13,7 +13,7 @@ module CleartextLogging {
import CleartextLoggingCustomizations::CleartextLogging
/**
* A dataflow tracking configuration for clear-text logging of sensitive information.
* A taint tracking configuration for clear-text logging of sensitive information.
*
* This configuration identifies flows from `Source`s, which are sources of
* sensitive data, to `Sink`s, which is an abstract class representing all
@@ -21,27 +21,37 @@ module CleartextLogging {
* added either by extending the relevant class, or by subclassing this configuration itself,
* and amending the sources and sinks.
*/
class Configuration extends DataFlow::Configuration {
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "CleartextLogging" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel lbl) {
source.(Source).getLabel() = lbl
}
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel lbl) {
sink.(Sink).getLabel() = lbl
}
override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Barrier }
override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node trg) {
StringConcatenation::taintStep(src, trg)
or
exists(string name | name = "toString" or name = "valueOf" |
src.(DataFlow::SourceNode).getAMethodCall(name) = trg
)
or
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
succ.(DataFlow::PropRead).getBase() = pred
}
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
// A taint propagating data flow edge through objects: a tainted write taints the entire object.
exists(DataFlow::PropWrite write |
write.getRhs() = src and
trg.(DataFlow::SourceNode).flowsTo(write.getBase())
)
or
// Taint through the arguments object.
exists(DataFlow::CallNode call, Function f |
src = call.getAnArgument() and
f = call.getACallee() and
not call.isImprecise() and
trg.asExpr() = f.getArgumentsVariable().getAnAccess()
)
}
}
}

View File

@@ -15,17 +15,39 @@ module CleartextLogging {
abstract class Source extends DataFlow::Node {
/** Gets a string that describes the type of this data flow source. */
abstract string describe();
abstract DataFlow::FlowLabel getLabel();
}
/**
* A data flow sink for clear-text logging of sensitive information.
*/
abstract class Sink extends DataFlow::Node { }
abstract class Sink extends DataFlow::Node {
DataFlow::FlowLabel getLabel() {
result.isDataOrTaint()
}
}
/**
* A barrier for clear-text logging of sensitive information.
*/
abstract class Barrier extends DataFlow::Node { }
/**
* A call to `.replace()` that seems to mask sensitive information.
*/
class MaskingReplacer extends Barrier, DataFlow::MethodCallNode {
MaskingReplacer() {
this.getCalleeName() = "replace" and
exists(RegExpLiteral reg |
reg = this.getArgument(0).getALocalSource().asExpr() and
reg.isGlobal() and
any(RegExpDot term).getLiteral() = reg
)
and
exists(this.getArgument(1).getStringValue())
}
}
/**
* An argument to a logging mechanism.
@@ -107,6 +129,10 @@ module CleartextLogging {
}
override string describe() { result = "an access to " + name }
override DataFlow::FlowLabel getLabel() {
result.isData()
}
}
/** An access to a variable or property that might contain a password. */
@@ -131,6 +157,10 @@ module CleartextLogging {
}
override string describe() { result = "an access to " + name }
override DataFlow::FlowLabel getLabel() {
result.isData()
}
}
/** A call that might return a password. */
@@ -143,5 +173,33 @@ module CleartextLogging {
}
override string describe() { result = "a call to " + name }
override DataFlow::FlowLabel getLabel() {
result.isData()
}
}
/** An access to the sensitive object `process.env`. */
class ProcessEnvSource extends Source {
ProcessEnvSource() {
this = NodeJSLib::process().getAPropertyRead("env")
}
override string describe() { result = "process environment" }
override DataFlow::FlowLabel getLabel() {
result.isData() or
result instanceof PartiallySensitiveMap
}
}
/**
* A flow label describing a map that might contain sensitive information in some properties.
* Property reads on such maps where the property name is fixed is unlikely to leak sensitive information.
*/
class PartiallySensitiveMap extends DataFlow::FlowLabel {
PartiallySensitiveMap() {
this = "PartiallySensitiveMap"
}
}
}

View File

@@ -89,6 +89,8 @@ nodes
| passwords.js:123:31:123:38 | password |
| passwords.js:123:31:123:48 | password.valueOf() |
| passwords.js:127:9:132:5 | config |
| passwords.js:127:9:132:5 | config |
| passwords.js:127:18:132:5 | {\\n ... )\\n } |
| passwords.js:127:18:132:5 | {\\n ... )\\n } |
| passwords.js:127:18:132:5 | {\\n ... )\\n } |
| passwords.js:130:12:130:19 | password |
@@ -97,10 +99,35 @@ nodes
| passwords.js:131:12:131:24 | getPassword() |
| passwords.js:135:17:135:22 | config |
| passwords.js:135:17:135:22 | config |
| passwords.js:135:17:135:22 | config |
| passwords.js:136:17:136:24 | config.x |
| passwords.js:136:17:136:24 | config.x |
| passwords.js:137:17:137:24 | config.y |
| passwords.js:137:17:137:24 | config.y |
| passwords.js:142:26:142:34 | arguments |
| passwords.js:142:26:142:34 | arguments |
| passwords.js:147:12:147:19 | password |
| passwords.js:147:12:147:19 | password |
| passwords.js:149:21:149:28 | config.x |
| passwords.js:150:21:150:31 | process.env |
| passwords.js:150:21:150:31 | process.env |
| passwords.js:152:9:152:63 | procdesc |
| passwords.js:152:20:152:44 | Util.in ... ss.env) |
| passwords.js:152:20:152:63 | Util.in ... /g, '') |
| passwords.js:152:33:152:43 | process.env |
| passwords.js:152:33:152:43 | process.env |
| passwords.js:154:21:154:28 | procdesc |
| passwords.js:156:17:156:27 | process.env |
| passwords.js:156:17:156:27 | process.env |
| passwords.js:156:17:156:27 | process.env |
| passwords.js:163:14:163:21 | password |
| passwords.js:163:14:163:21 | password |
| passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:164:14:164:21 | password |
| passwords.js:164:14:164:21 | password |
| passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords_in_browser1.js:2:13:2:20 | password |
| passwords_in_browser1.js:2:13:2:20 | password |
| passwords_in_browser1.js:2:13:2:20 | password |
@@ -199,6 +226,9 @@ edges
| passwords.js:123:31:123:48 | password.valueOf() | passwords.js:123:17:123:48 | name + ... lueOf() |
| passwords.js:127:9:132:5 | config | passwords.js:135:17:135:22 | config |
| passwords.js:127:9:132:5 | config | passwords.js:135:17:135:22 | config |
| passwords.js:127:9:132:5 | config | passwords.js:135:17:135:22 | config |
| passwords.js:127:9:132:5 | config | passwords.js:135:17:135:22 | config |
| passwords.js:127:18:132:5 | {\\n ... )\\n } | passwords.js:127:9:132:5 | config |
| passwords.js:127:18:132:5 | {\\n ... )\\n } | passwords.js:127:9:132:5 | config |
| passwords.js:127:18:132:5 | {\\n ... )\\n } | passwords.js:127:9:132:5 | config |
| passwords.js:130:12:130:19 | password | passwords.js:127:18:132:5 | {\\n ... )\\n } |
@@ -213,6 +243,30 @@ edges
| passwords.js:131:12:131:24 | getPassword() | passwords.js:137:17:137:24 | config.y |
| passwords.js:131:12:131:24 | getPassword() | passwords.js:137:17:137:24 | config.y |
| passwords.js:131:12:131:24 | getPassword() | passwords.js:137:17:137:24 | config.y |
| passwords.js:147:12:147:19 | password | passwords.js:149:21:149:28 | config.x |
| passwords.js:147:12:147:19 | password | passwords.js:149:21:149:28 | config.x |
| passwords.js:149:21:149:28 | config.x | passwords.js:142:26:142:34 | arguments |
| passwords.js:149:21:149:28 | config.x | passwords.js:142:26:142:34 | arguments |
| passwords.js:150:21:150:31 | process.env | passwords.js:142:26:142:34 | arguments |
| passwords.js:150:21:150:31 | process.env | passwords.js:142:26:142:34 | arguments |
| passwords.js:150:21:150:31 | process.env | passwords.js:142:26:142:34 | arguments |
| passwords.js:150:21:150:31 | process.env | passwords.js:142:26:142:34 | arguments |
| passwords.js:152:9:152:63 | procdesc | passwords.js:154:21:154:28 | procdesc |
| passwords.js:152:20:152:44 | Util.in ... ss.env) | passwords.js:152:20:152:63 | Util.in ... /g, '') |
| passwords.js:152:20:152:63 | Util.in ... /g, '') | passwords.js:152:9:152:63 | procdesc |
| passwords.js:152:33:152:43 | process.env | passwords.js:152:20:152:44 | Util.in ... ss.env) |
| passwords.js:152:33:152:43 | process.env | passwords.js:152:20:152:44 | Util.in ... ss.env) |
| passwords.js:154:21:154:28 | procdesc | passwords.js:142:26:142:34 | arguments |
| passwords.js:154:21:154:28 | procdesc | passwords.js:142:26:142:34 | arguments |
| passwords.js:156:17:156:27 | process.env | passwords.js:156:17:156:27 | process.env |
| passwords.js:163:14:163:21 | password | passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:163:14:163:21 | password | passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:163:14:163:21 | password | passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:163:14:163:21 | password | passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:164:14:164:21 | password | passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords.js:164:14:164:21 | password | passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords.js:164:14:164:21 | password | passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords.js:164:14:164:21 | password | passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords_in_browser1.js:2:13:2:20 | password | passwords_in_browser1.js:2:13:2:20 | password |
| passwords_in_browser2.js:2:13:2:20 | password | passwords_in_browser2.js:2:13:2:20 | password |
| passwords_in_server_1.js:6:13:6:20 | password | passwords_in_server_1.js:6:13:6:20 | password |
@@ -250,6 +304,12 @@ edges
| passwords.js:135:17:135:22 | config | passwords.js:131:12:131:24 | getPassword() | passwords.js:135:17:135:22 | config | Sensitive data returned by $@ is logged here. | passwords.js:131:12:131:24 | getPassword() | a call to getPassword |
| passwords.js:136:17:136:24 | config.x | passwords.js:130:12:130:19 | password | passwords.js:136:17:136:24 | config.x | Sensitive data returned by $@ is logged here. | passwords.js:130:12:130:19 | password | an access to password |
| passwords.js:137:17:137:24 | config.y | passwords.js:131:12:131:24 | getPassword() | passwords.js:137:17:137:24 | config.y | Sensitive data returned by $@ is logged here. | passwords.js:131:12:131:24 | getPassword() | a call to getPassword |
| passwords.js:142:26:142:34 | arguments | passwords.js:147:12:147:19 | password | passwords.js:142:26:142:34 | arguments | Sensitive data returned by $@ is logged here. | passwords.js:147:12:147:19 | password | an access to password |
| passwords.js:142:26:142:34 | arguments | passwords.js:150:21:150:31 | process.env | passwords.js:142:26:142:34 | arguments | Sensitive data returned by $@ is logged here. | passwords.js:150:21:150:31 | process.env | process environment |
| passwords.js:142:26:142:34 | arguments | passwords.js:152:33:152:43 | process.env | passwords.js:142:26:142:34 | arguments | Sensitive data returned by $@ is logged here. | passwords.js:152:33:152:43 | process.env | process environment |
| passwords.js:156:17:156:27 | process.env | passwords.js:156:17:156:27 | process.env | passwords.js:156:17:156:27 | process.env | Sensitive data returned by $@ is logged here. | passwords.js:156:17:156:27 | process.env | process environment |
| passwords.js:163:14:163:41 | passwor ... g, "*") | passwords.js:163:14:163:21 | password | passwords.js:163:14:163:41 | passwor ... g, "*") | Sensitive data returned by $@ is logged here. | passwords.js:163:14:163:21 | password | an access to password |
| passwords.js:164:14:164:42 | passwor ... g, "*") | passwords.js:164:14:164:21 | password | passwords.js:164:14:164:42 | passwor ... g, "*") | Sensitive data returned by $@ is logged here. | passwords.js:164:14:164:21 | password | an access to password |
| passwords_in_server_1.js:6:13:6:20 | password | passwords_in_server_1.js:6:13:6:20 | password | passwords_in_server_1.js:6:13:6:20 | password | Sensitive data returned by $@ is logged here. | passwords_in_server_1.js:6:13:6:20 | password | an access to password |
| passwords_in_server_2.js:3:13:3:20 | password | passwords_in_server_2.js:3:13:3:20 | password | passwords_in_server_2.js:3:13:3:20 | password | Sensitive data returned by $@ is logged here. | passwords_in_server_2.js:3:13:3:20 | password | an access to password |
| passwords_in_server_3.js:2:13:2:20 | password | passwords_in_server_3.js:2:13:2:20 | password | passwords_in_server_3.js:2:13:2:20 | password | Sensitive data returned by $@ is logged here. | passwords_in_server_3.js:2:13:2:20 | password | an access to password |

View File

@@ -137,3 +137,29 @@
console.log(config.y); // NOT OK
console.log(config[x]); // OK (probably)
});
function indirectLogCall() {
console.log.apply(this, arguments);
}
var Util = require('util');
(function() {
var config = {
x: password
};
indirectLogCall(config.x); // NOT OK
indirectLogCall(process.env); // NOT OK
var procdesc = Util.inspect(process.env).replace(/\n/g, '')
indirectLogCall(procdesc); // NOT OK
console.log(process.env); // NOT OK
console.log(process.env.PATH); // OK.
console.log(process.env["foo" + "bar"]); // OK.
});
(function () {
console.log(password.replace(/./g, "*")); // OK!
console.log(password.replace(/\./g, "*")); // NOT OK!
console.log(password.replace(/foo/g, "*")); // NOT OK!
})();