Merge pull request #4231 from erik-krogh/CVE767

Approved by asgerf
This commit is contained in:
CodeQL CI
2020-09-15 03:47:40 -07:00
committed by GitHub
14 changed files with 199 additions and 37 deletions

View File

@@ -3,6 +3,7 @@
## General improvements ## General improvements
* Support for the following frameworks and libraries has been improved: * Support for the following frameworks and libraries has been improved:
- [bluebird](https://www.npmjs.com/package/bluebird)
- [fast-json-stable-stringify](https://www.npmjs.com/package/fast-json-stable-stringify) - [fast-json-stable-stringify](https://www.npmjs.com/package/fast-json-stable-stringify)
- [fast-safe-stringify](https://www.npmjs.com/package/fast-safe-stringify) - [fast-safe-stringify](https://www.npmjs.com/package/fast-safe-stringify)
- [javascript-stringify](https://www.npmjs.com/package/javascript-stringify) - [javascript-stringify](https://www.npmjs.com/package/javascript-stringify)
@@ -10,9 +11,11 @@
- [json-stable-stringify](https://www.npmjs.com/package/json-stable-stringify) - [json-stable-stringify](https://www.npmjs.com/package/json-stable-stringify)
- [json-stringify-safe](https://www.npmjs.com/package/json-stringify-safe) - [json-stringify-safe](https://www.npmjs.com/package/json-stringify-safe)
- [json3](https://www.npmjs.com/package/json3) - [json3](https://www.npmjs.com/package/json3)
- [lodash](https://www.npmjs.com/package/lodash)
- [object-inspect](https://www.npmjs.com/package/object-inspect) - [object-inspect](https://www.npmjs.com/package/object-inspect)
- [pretty-format](https://www.npmjs.com/package/pretty-format) - [pretty-format](https://www.npmjs.com/package/pretty-format)
- [stringify-object](https://www.npmjs.com/package/stringify-object) - [stringify-object](https://www.npmjs.com/package/stringify-object)
- [underscore](https://www.npmjs.com/package/underscore)
* Analyzing files with the ".cjs" extension is now supported. * Analyzing files with the ".cjs" extension is now supported.

View File

@@ -24,13 +24,11 @@ module ArrayTaintTracking {
predicate arrayFunctionTaintStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode call) { predicate arrayFunctionTaintStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode call) {
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are // `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
// `elt` and `ary`; similar for `forEach` // `elt` and `ary`; similar for `forEach`
exists(string name, Function f, int i | exists(Function f |
(name = "map" or name = "forEach") and
(i = 0 or i = 2) and
call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and
call.(DataFlow::MethodCallNode).getMethodName() = name and call.(DataFlow::MethodCallNode).getMethodName() = ["map", "forEach"] and
pred = call.getReceiver() and pred = call.getReceiver() and
succ = DataFlow::parameterNode(f.getParameter(i)) succ = DataFlow::parameterNode(f.getParameter([0, 2]))
) )
or or
// `array.map` with tainted return value in callback // `array.map` with tainted return value in callback
@@ -47,41 +45,22 @@ module ArrayTaintTracking {
succ = call succ = call
or or
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`. // `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
exists(string name | pred = call.getAnArgument() and
name = "push" or succ.(DataFlow::SourceNode).getAMethodCall(["push", "unshift"]) = call
name = "unshift"
|
pred = call.getAnArgument() and
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
)
or or
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`. // `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
exists(string name | pred = call.getASpreadArgument() and
name = "push" or // Make sure we handle reflective calls
name = "unshift" succ = call.getReceiver().getALocalSource() and
| call.getCalleeName() = ["push", "unshift"]
pred = call.getASpreadArgument() and
// Make sure we handle reflective calls
succ = call.getReceiver().getALocalSource() and
call.getCalleeName() = name
)
or or
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`. // `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
exists(string name | name = "splice" | pred = call.getArgument(2) and
pred = call.getArgument(2) and succ.(DataFlow::SourceNode).getAMethodCall("splice") = call
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
)
or or
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`. // `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
exists(string name | call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice"]) and
name = "pop" or succ = call
name = "shift" or
name = "slice" or
name = "splice"
|
call.(DataFlow::MethodCallNode).calls(pred, name) and
succ = call
)
or or
// `e = Array.from(x)`: if `x` is tainted, then so is `e`. // `e = Array.from(x)`: if `x` is tainted, then so is `e`.
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and

View File

@@ -440,6 +440,18 @@ predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
pred.getEnclosingExpr() = await.getOperand() and pred.getEnclosingExpr() = await.getOperand() and
succ.getEnclosingExpr() = await succ.getEnclosingExpr() = await
) )
or
exists(DataFlow::CallNode mapSeries |
mapSeries = DataFlow::moduleMember("bluebird", "mapSeries").getACall()
|
// from `xs` to `x` in `require("bluebird").mapSeries(xs, (x) => {...})`.
pred = mapSeries.getArgument(0) and
succ = mapSeries.getABoundCallbackParameter(1, 0)
or
// from `y` to `require("bluebird").mapSeries(x, x => y)`.
pred = mapSeries.getCallback(1).getAReturn() and
succ = mapSeries
)
} }
/** /**

View File

@@ -389,6 +389,53 @@ module ClientRequest {
} }
} }
/**
* Gets an instantiation `socket` of `require("net").Socket` type tracked using `t`.
*/
private DataFlow::SourceNode netSocketInstantiation(
DataFlow::TypeTracker t, DataFlow::NewNode socket
) {
t.start() and
socket = DataFlow::moduleMember("net", "Socket").getAnInstantiation() and
result = socket
or
exists(DataFlow::TypeTracker t2 | result = netSocketInstantiation(t2, socket).track(t2, t))
}
/**
* A model of a request made using `(new require("net").Socket()).connect(args);`.
*/
class NetSocketRequest extends ClientRequest::Range {
DataFlow::NewNode socket;
NetSocketRequest() {
this = netSocketInstantiation(DataFlow::TypeTracker::end(), socket).getAMethodCall("connect")
}
override DataFlow::Node getUrl() {
result = getArgument([0, 1]) // there are multiple overrides of `connect`, and the URL can be in the first or second argument.
}
override DataFlow::Node getHost() { result = getOptionArgument(0, "host") }
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
responseType = "text" and
promise = false and
exists(DataFlow::CallNode call |
call = netSocketInstantiation(DataFlow::TypeTracker::end(), socket).getAMemberCall("on") and
call.getArgument(0).mayHaveStringValue("data") and
result = call.getABoundCallbackParameter(1, 0)
)
}
override DataFlow::Node getADataNode() {
exists(DataFlow::CallNode call |
call = netSocketInstantiation(DataFlow::TypeTracker::end(), socket).getAMemberCall("write") and
result = call.getArgument(0)
)
}
}
/** /**
* A model of a URL request made using the `superagent` library. * A model of a URL request made using the `superagent` library.
*/ */

View File

@@ -394,6 +394,52 @@ module LodashUnderscore {
succ = this.getExceptionalReturn() succ = this.getExceptionalReturn()
} }
} }
/**
* Holds if there is a taint-step involving a (non-function) underscore method from `pred` to `succ`.
*/
private predicate underscoreTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(string name, DataFlow::CallNode call |
call = any(Member member | member.getName() = name).getACall()
|
name =
["find", "filter", "findWhere", "where", "reject", "pluck", "max", "min", "sortBy",
"shuffle", "sample", "toArray", "partition", "compact", "first", "initial", "last",
"rest", "flatten", "without", "difference", "uniq", "unique", "unzip", "transpose",
"object", "chunk", "values", "mapObject", "pick", "omit", "defaults", "clone", "tap",
"identity"] and
pred = call.getArgument(0) and
succ = call
or
name = ["union", "zip"] and
pred = call.getAnArgument() and
succ = call
or
name =
["each", "map", "every", "some", "max", "min", "sortBy", "partition", "mapObject", "tap"] and
pred = call.getArgument(0) and
succ = call.getABoundCallbackParameter(1, 0)
or
name = ["reduce", "reduceRight"] and
pred = call.getArgument(0) and
succ = call.getABoundCallbackParameter(1, 1)
or
name = ["map", "reduce", "reduceRight"] and
pred = call.getCallback(1).getAReturn() and
succ = call
)
}
/**
* A model for taint-steps involving (non-function) underscore methods.
*/
private class UnderscoreTaintStep extends TaintTracking::AdditionalTaintStep {
UnderscoreTaintStep() { underscoreTaintStep(this, _) }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
underscoreTaintStep(pred, succ) and pred = this
}
}
} }
/** /**

View File

@@ -1068,8 +1068,10 @@ module NodeJSLib {
/** /**
* An instance of net.createServer(), which creates a new TCP/IPC server. * An instance of net.createServer(), which creates a new TCP/IPC server.
*/ */
private class NodeJSNetServer extends DataFlow::SourceNode { class NodeJSNetServer extends DataFlow::InvokeNode {
NodeJSNetServer() { this = DataFlow::moduleMember("net", "createServer").getAnInvocation() } NodeJSNetServer() {
this = DataFlow::moduleMember(["net", "tls"], "createServer").getAnInvocation()
}
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) { private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and result = this t.start() and result = this
@@ -1096,6 +1098,8 @@ module NodeJSLib {
| |
this = call.getCallback(1).getParameter(0) this = call.getCallback(1).getParameter(0)
) )
or
this = server.getCallback([0, 1]).getParameter(0)
} }
DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) } DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) }

View File

@@ -86,3 +86,9 @@
| tst.js:2:17:2:22 | "src1" | tst.js:61:16:61:18 | o.r | | tst.js:2:17:2:22 | "src1" | tst.js:61:16:61:18 | o.r |
| tst.js:2:17:2:22 | "src1" | tst.js:68:16:68:22 | inner() | | tst.js:2:17:2:22 | "src1" | tst.js:68:16:68:22 | inner() |
| tst.js:2:17:2:22 | "src1" | tst.js:80:16:80:22 | outer() | | tst.js:2:17:2:22 | "src1" | tst.js:80:16:80:22 | outer() |
| underscore.js:2:17:2:22 | "src1" | underscore.js:3:15:3:28 | _.max(source1) |
| underscore.js:5:17:5:22 | "src2" | underscore.js:6:15:6:34 | _.union([], source2) |
| underscore.js:5:17:5:22 | "src2" | underscore.js:7:15:7:32 | _.zip(source2, []) |
| underscore.js:9:17:9:22 | "src3" | underscore.js:11:17:11:17 | x |
| underscore.js:14:17:14:22 | "src4" | underscore.js:16:17:16:17 | e |
| underscore.js:19:17:19:22 | "src5" | underscore.js:20:15:20:44 | _.map([ ... ource5) |

View File

@@ -0,0 +1,21 @@
(function() {
var source1 = "src1";
var sink1 = _.max(source1); // NOT OK
var source2 = "src2";
var sink2 = _.union([], source2); // NOT OK
var sink3 = _.zip(source2, []); // NOT OK
var source3 = "src3";
_.map(source3, (x) => {
let sink4 = x; // NOT OK
});
var source4 = "src4";
_.reduce(source4, (acc, e) => {
let sink5 = e; // NOT OK
});
var source5 = "src5";
var sink6 = _.map([1,2,3], (x) => source5); // NOT OK
})();

View File

@@ -154,4 +154,15 @@
} catch (e) { } catch (e) {
sink(e); // NOT OK sink(e); // NOT OK
} }
})(); })();
(function () {
var source = "source";
var bluebird = require("bluebird");
bluebird.mapSeries(source, x => sink(x)); // NOT OK (for taint-tracking configs)
const foo = bluebird.mapSeries(source, x => x);
sink(foo); // NOT OK (for taint-tracking configs)
})

View File

@@ -237,6 +237,8 @@ flow
exclusiveTaintFlow exclusiveTaintFlow
| flow2.js:2:15:2:22 | "source" | flow2.js:20:7:20:14 | tainted3 | | flow2.js:2:15:2:22 | "source" | flow2.js:20:7:20:14 | tainted3 |
| flow.js:136:15:136:22 | "source" | flow.js:141:7:141:13 | async() | | flow.js:136:15:136:22 | "source" | flow.js:141:7:141:13 | async() |
| flow.js:160:15:160:22 | "source" | flow.js:164:39:164:39 | x |
| flow.js:160:15:160:22 | "source" | flow.js:167:7:167:9 | foo |
| interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | | interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error |
typetrack typetrack
| flow2.js:4:2:4:31 | Promise ... lean"]) | flow2.js:4:14:4:30 | [source, "clean"] | copy $PromiseResolveField$ | | flow2.js:4:2:4:31 | Promise ... lean"]) | flow2.js:4:14:4:30 | [source, "clean"] | copy $PromiseResolveField$ |

View File

@@ -66,6 +66,7 @@ test_ClientRequest
| tst.js:200:2:200:21 | $.get("example.php") | | tst.js:200:2:200:21 | $.get("example.php") |
| tst.js:202:5:208:7 | $.ajax( ... }}) | | tst.js:202:5:208:7 | $.ajax( ... }}) |
| tst.js:210:2:210:21 | $.get("example.php") | | tst.js:210:2:210:21 | $.get("example.php") |
| tst.js:219:5:219:41 | data.so ... Host"}) |
test_getADataNode test_getADataNode
| tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data | | tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data |
| tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 | | tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 |
@@ -95,11 +96,13 @@ test_getADataNode
| tst.js:179:2:179:60 | $.getJS ... a ) {}) | tst.js:179:31:179:38 | "MyData" | | tst.js:179:2:179:60 | $.getJS ... a ) {}) | tst.js:179:31:179:38 | "MyData" |
| tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:28:183:37 | "PostData" | | tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:28:183:37 | "PostData" |
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:190:11:190:20 | "AjaxData" | | tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:190:11:190:20 | "AjaxData" |
| tst.js:219:5:219:41 | data.so ... Host"}) | tst.js:223:23:223:30 | "foobar" |
test_getHost test_getHost
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host | | tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host |
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host | | tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host |
| tst.js:91:5:91:34 | got(rel ... host}) | tst.js:91:29:91:32 | host | | tst.js:91:5:91:34 | got(rel ... host}) | tst.js:91:29:91:32 | host |
| tst.js:93:5:93:35 | net.req ... host }) | tst.js:93:29:93:32 | host | | tst.js:93:5:93:35 | net.req ... host }) | tst.js:93:29:93:32 | host |
| tst.js:219:5:219:41 | data.so ... Host"}) | tst.js:219:32:219:39 | "myHost" |
test_getUrl test_getUrl
| tst.js:11:5:11:16 | request(url) | tst.js:11:13:11:15 | url | | tst.js:11:5:11:16 | request(url) | tst.js:11:13:11:15 | url |
| tst.js:13:5:13:20 | request.get(url) | tst.js:13:17:13:19 | url | | tst.js:13:5:13:20 | request.get(url) | tst.js:13:17:13:19 | url |
@@ -173,6 +176,7 @@ test_getUrl
| tst.js:200:2:200:21 | $.get("example.php") | tst.js:200:8:200:20 | "example.php" | | tst.js:200:2:200:21 | $.get("example.php") | tst.js:200:8:200:20 | "example.php" |
| tst.js:202:5:208:7 | $.ajax( ... }}) | tst.js:203:10:203:22 | "example.php" | | tst.js:202:5:208:7 | $.ajax( ... }}) | tst.js:203:10:203:22 | "example.php" |
| tst.js:210:2:210:21 | $.get("example.php") | tst.js:210:8:210:20 | "example.php" | | tst.js:210:2:210:21 | $.get("example.php") | tst.js:210:8:210:20 | "example.php" |
| tst.js:219:5:219:41 | data.so ... Host"}) | tst.js:219:25:219:40 | {host: "myHost"} |
test_getAResponseDataNode test_getAResponseDataNode
| tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true | | tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true |
| tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true | | tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true |
@@ -233,3 +237,4 @@ test_getAResponseDataNode
| tst.js:200:2:200:21 | $.get("example.php") | tst.js:200:37:200:44 | response | | false | | tst.js:200:2:200:21 | $.get("example.php") | tst.js:200:37:200:44 | response | | false |
| tst.js:202:5:208:7 | $.ajax( ... }}) | tst.js:207:21:207:36 | err.responseText | json | false | | tst.js:202:5:208:7 | $.ajax( ... }}) | tst.js:207:21:207:36 | err.responseText | json | false |
| tst.js:210:2:210:21 | $.get("example.php") | tst.js:210:55:210:70 | xhr.responseText | | false | | tst.js:210:2:210:21 | $.get("example.php") | tst.js:210:55:210:70 | xhr.responseText | | false |
| tst.js:219:5:219:41 | data.so ... Host"}) | tst.js:221:29:221:32 | data | text | false |

View File

@@ -209,3 +209,16 @@ import {ClientRequest, net} from 'electron';
$.get("example.php").fail(function(xhr) {console.log(xhr.responseText)}); $.get("example.php").fail(function(xhr) {console.log(xhr.responseText)});
}); });
const net = require("net");
(function () {
var data = {
socket: new net.Socket()
}
data.socket.connect({host: "myHost"});
data.socket.on("data", (data) => {});
data.socket.write("foobar");
})();

View File

@@ -2,3 +2,14 @@ var https = require('https');
https.createServer(function (req, res) {}); https.createServer(function (req, res) {});
https.createServer(o, function (req, res) {}); https.createServer(o, function (req, res) {});
require('http2').createServer((req, res) => {}); require('http2').createServer((req, res) => {});
require("tls").createServer((socket) => {
socket.on("data", (data) => {})
});
const net = require('net');
const tls = require('tls');
const server = (isSecure ? tls : net).createServer(options, (socket) => {
socket.on("data", (data) => {})
});

View File

@@ -163,6 +163,8 @@ test_ClientRequest_getADataNode
| src/http.js:27:16:27:73 | http.re ... POST'}) | src/http.js:50:16:50:22 | 'stuff' | | src/http.js:27:16:27:73 | http.re ... POST'}) | src/http.js:50:16:50:22 | 'stuff' |
| src/http.js:27:16:27:73 | http.re ... POST'}) | src/http.js:51:14:51:25 | 'more stuff' | | src/http.js:27:16:27:73 | http.re ... POST'}) | src/http.js:51:14:51:25 | 'more stuff' |
test_RemoteFlowSources test_RemoteFlowSources
| createServer.js:7:24:7:27 | data |
| createServer.js:14:24:14:27 | data |
| src/http.js:6:26:6:32 | req.url | | src/http.js:6:26:6:32 | req.url |
| src/http.js:8:3:8:20 | req.headers.cookie | | src/http.js:8:3:8:20 | req.headers.cookie |
| src/http.js:9:3:9:17 | req.headers.foo | | src/http.js:9:3:9:17 | req.headers.foo |