mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
@@ -3,6 +3,7 @@
|
||||
## General improvements
|
||||
|
||||
* 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-safe-stringify](https://www.npmjs.com/package/fast-safe-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-stringify-safe](https://www.npmjs.com/package/json-stringify-safe)
|
||||
- [json3](https://www.npmjs.com/package/json3)
|
||||
- [lodash](https://www.npmjs.com/package/lodash)
|
||||
- [object-inspect](https://www.npmjs.com/package/object-inspect)
|
||||
- [pretty-format](https://www.npmjs.com/package/pretty-format)
|
||||
- [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.
|
||||
|
||||
|
||||
@@ -24,13 +24,11 @@ module ArrayTaintTracking {
|
||||
predicate arrayFunctionTaintStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode call) {
|
||||
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
|
||||
// `elt` and `ary`; similar for `forEach`
|
||||
exists(string name, Function f, int i |
|
||||
(name = "map" or name = "forEach") and
|
||||
(i = 0 or i = 2) and
|
||||
exists(Function f |
|
||||
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
|
||||
succ = DataFlow::parameterNode(f.getParameter(i))
|
||||
succ = DataFlow::parameterNode(f.getParameter([0, 2]))
|
||||
)
|
||||
or
|
||||
// `array.map` with tainted return value in callback
|
||||
@@ -47,41 +45,22 @@ module ArrayTaintTracking {
|
||||
succ = call
|
||||
or
|
||||
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
|
||||
exists(string name |
|
||||
name = "push" or
|
||||
name = "unshift"
|
||||
|
|
||||
pred = call.getAnArgument() and
|
||||
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
|
||||
)
|
||||
succ.(DataFlow::SourceNode).getAMethodCall(["push", "unshift"]) = call
|
||||
or
|
||||
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
|
||||
exists(string name |
|
||||
name = "push" or
|
||||
name = "unshift"
|
||||
|
|
||||
pred = call.getASpreadArgument() and
|
||||
// Make sure we handle reflective calls
|
||||
succ = call.getReceiver().getALocalSource() and
|
||||
call.getCalleeName() = name
|
||||
)
|
||||
call.getCalleeName() = ["push", "unshift"]
|
||||
or
|
||||
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
|
||||
exists(string name | name = "splice" |
|
||||
pred = call.getArgument(2) and
|
||||
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
|
||||
)
|
||||
succ.(DataFlow::SourceNode).getAMethodCall("splice") = call
|
||||
or
|
||||
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
|
||||
exists(string name |
|
||||
name = "pop" or
|
||||
name = "shift" or
|
||||
name = "slice" or
|
||||
name = "splice"
|
||||
|
|
||||
call.(DataFlow::MethodCallNode).calls(pred, name) and
|
||||
call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice"]) and
|
||||
succ = call
|
||||
)
|
||||
or
|
||||
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
|
||||
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and
|
||||
|
||||
@@ -440,6 +440,18 @@ predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred.getEnclosingExpr() = await.getOperand() and
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -394,6 +394,52 @@ module LodashUnderscore {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1068,8 +1068,10 @@ module NodeJSLib {
|
||||
/**
|
||||
* An instance of net.createServer(), which creates a new TCP/IPC server.
|
||||
*/
|
||||
private class NodeJSNetServer extends DataFlow::SourceNode {
|
||||
NodeJSNetServer() { this = DataFlow::moduleMember("net", "createServer").getAnInvocation() }
|
||||
class NodeJSNetServer extends DataFlow::InvokeNode {
|
||||
NodeJSNetServer() {
|
||||
this = DataFlow::moduleMember(["net", "tls"], "createServer").getAnInvocation()
|
||||
}
|
||||
|
||||
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
|
||||
t.start() and result = this
|
||||
@@ -1096,6 +1098,8 @@ module NodeJSLib {
|
||||
|
|
||||
this = call.getCallback(1).getParameter(0)
|
||||
)
|
||||
or
|
||||
this = server.getCallback([0, 1]).getParameter(0)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) }
|
||||
|
||||
@@ -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:68:16:68:22 | inner() |
|
||||
| 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) |
|
||||
|
||||
@@ -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
|
||||
})();
|
||||
@@ -154,4 +154,15 @@
|
||||
} catch (e) {
|
||||
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)
|
||||
})
|
||||
@@ -237,6 +237,8 @@ flow
|
||||
exclusiveTaintFlow
|
||||
| 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: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 |
|
||||
typetrack
|
||||
| flow2.js:4:2:4:31 | Promise ... lean"]) | flow2.js:4:14:4:30 | [source, "clean"] | copy $PromiseResolveField$ |
|
||||
|
||||
@@ -66,6 +66,7 @@ test_ClientRequest
|
||||
| tst.js:200:2:200:21 | $.get("example.php") |
|
||||
| tst.js:202:5:208:7 | $.ajax( ... }}) |
|
||||
| tst.js:210:2:210:21 | $.get("example.php") |
|
||||
| tst.js:219:5:219:41 | data.so ... Host"}) |
|
||||
test_getADataNode
|
||||
| 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 |
|
||||
@@ -95,11 +96,13 @@ test_getADataNode
|
||||
| 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: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
|
||||
| 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: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:219:5:219:41 | data.so ... Host"}) | tst.js:219:32:219:39 | "myHost" |
|
||||
test_getUrl
|
||||
| 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 |
|
||||
@@ -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: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:219:5:219:41 | data.so ... Host"}) | tst.js:219:25:219:40 | {host: "myHost"} |
|
||||
test_getAResponseDataNode
|
||||
| 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 |
|
||||
@@ -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: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:219:5:219:41 | data.so ... Host"}) | tst.js:221:29:221:32 | data | text | false |
|
||||
|
||||
@@ -209,3 +209,16 @@ import {ClientRequest, net} from 'electron';
|
||||
|
||||
$.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");
|
||||
})();
|
||||
@@ -2,3 +2,14 @@ var https = require('https');
|
||||
https.createServer(function (req, res) {});
|
||||
https.createServer(o, function (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) => {})
|
||||
});
|
||||
@@ -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:51:14:51:25 | 'more stuff' |
|
||||
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:8:3:8:20 | req.headers.cookie |
|
||||
| src/http.js:9:3:9:17 | req.headers.foo |
|
||||
|
||||
Reference in New Issue
Block a user