Skip to content

Commit b6b8387

Browse files
authored
Merge pull request github#3704 from asger-semmle/js/cve-serve
Approved by esbena
2 parents d844e00 + 315f338 commit b6b8387

File tree

8 files changed

+191
-5
lines changed

8 files changed

+191
-5
lines changed

change-notes/1.25/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- [jGrowl](https://github.com/stanlemon/jGrowl)
1212
- [jQuery](https://jquery.com/)
1313
- [marsdb](https://www.npmjs.com/package/marsdb)
14+
- [micro](https://www.npmjs.com/package/micro/)
1415
- [minimongo](https://www.npmjs.com/package/minimongo/)
1516
- [mssql](https://www.npmjs.com/package/mssql)
1617
- [mysql](https://www.npmjs.com/package/mysql)

javascript/ql/src/semmle/javascript/Promises.qll

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,22 @@ module Bluebird {
576576

577577
override DataFlow::Node getArrayNode() { result = getArgument(0) }
578578
}
579+
580+
/**
581+
* An async function created using a call to `bluebird.coroutine`.
582+
*/
583+
class BluebirdCoroutineDefinition extends DataFlow::CallNode {
584+
BluebirdCoroutineDefinition() { this = bluebird().getAMemberCall("coroutine") }
585+
}
586+
587+
private class BluebirdCoroutineDefinitionAsPartialInvoke extends DataFlow::PartialInvokeNode::Range,
588+
BluebirdCoroutineDefinition {
589+
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
590+
boundArgs = 0 and
591+
callback = getArgument(0) and
592+
result = this
593+
}
594+
}
579595
}
580596

581597
/**

javascript/ql/src/semmle/javascript/frameworks/HttpFrameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import semmle.javascript.frameworks.Express
22
import semmle.javascript.frameworks.Hapi
33
import semmle.javascript.frameworks.Koa
44
import semmle.javascript.frameworks.NodeJSLib
5+
import semmle.javascript.frameworks.Micro
56
import semmle.javascript.frameworks.Restify
67
import semmle.javascript.frameworks.Connect
78
import semmle.javascript.frameworks.Fastify
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/**
2+
* Provides a model of the `micro` NPM package.
3+
*/
4+
5+
private import javascript
6+
7+
private module Micro {
8+
private import DataFlow
9+
10+
/**
11+
* A node that should be interpreted as a route handler, to use as starting
12+
* point for back-tracking.
13+
*/
14+
Node microRouteHandlerSink() {
15+
result = moduleMember("micro", "run").getACall().getLastArgument()
16+
or
17+
result = moduleImport("micro").getACall().getArgument(0)
18+
}
19+
20+
/** Gets a data flow node interpreted as a route handler. */
21+
private DataFlow::SourceNode microRouteHandler(DataFlow::TypeBackTracker t) {
22+
t.start() and
23+
result = microRouteHandlerSink().getALocalSource()
24+
or
25+
exists(DataFlow::TypeBackTracker t2 | result = microRouteHandler(t2).backtrack(t2, t))
26+
or
27+
exists(DataFlow::CallNode transformer |
28+
transformer = moduleImport("micro-compress").getACall()
29+
or
30+
transformer instanceof Bluebird::BluebirdCoroutineDefinition
31+
|
32+
microRouteHandler(t.continue()) = transformer and
33+
result = transformer.getArgument(0).getALocalSource()
34+
)
35+
}
36+
37+
/** Gets a data flow node interpreted as a route handler. */
38+
DataFlow::SourceNode microRouteHandler() {
39+
result = microRouteHandler(DataFlow::TypeBackTracker::end())
40+
}
41+
42+
/**
43+
* A function passed to `micro` or `micro.run`.
44+
*/
45+
class MicroRouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::FunctionNode {
46+
MicroRouteHandler() { this = microRouteHandler().getAFunctionValue() }
47+
}
48+
49+
class MicroRequestSource extends HTTP::Servers::RequestSource {
50+
MicroRouteHandler h;
51+
52+
MicroRequestSource() { this = h.getParameter(0) }
53+
54+
override HTTP::RouteHandler getRouteHandler() { result = h }
55+
}
56+
57+
class MicroResponseSource extends HTTP::Servers::ResponseSource {
58+
MicroRouteHandler h;
59+
60+
MicroResponseSource() { this = h.getParameter(1) }
61+
62+
override HTTP::RouteHandler getRouteHandler() { result = h }
63+
}
64+
65+
class MicroRequestExpr extends NodeJSLib::RequestExpr {
66+
override MicroRequestSource src;
67+
}
68+
69+
class MicroReseponseExpr extends NodeJSLib::ResponseExpr {
70+
override MicroResponseSource src;
71+
}
72+
73+
private HTTP::RouteHandler getRouteHandlerFromReqRes(DataFlow::Node node) {
74+
exists(HTTP::Servers::RequestSource src |
75+
src.ref().flowsTo(node) and
76+
result = src.getRouteHandler()
77+
)
78+
or
79+
exists(HTTP::Servers::ResponseSource src |
80+
src.ref().flowsTo(node) and
81+
result = src.getRouteHandler()
82+
)
83+
}
84+
85+
class MicroBodyParserCall extends HTTP::RequestInputAccess, DataFlow::CallNode {
86+
string name;
87+
88+
MicroBodyParserCall() {
89+
name = ["buffer", "text", "json"] and
90+
this = moduleMember("micro", name).getACall()
91+
}
92+
93+
override string getKind() { result = "body" }
94+
95+
override HTTP::RouteHandler getRouteHandler() {
96+
result = getRouteHandlerFromReqRes(getArgument(0))
97+
}
98+
99+
override predicate isUserControlledObject() { name = "json" }
100+
}
101+
102+
class MicroSendArgument extends HTTP::ResponseSendArgument {
103+
CallNode send;
104+
105+
MicroSendArgument() {
106+
send = moduleMember("micro", ["send", "sendError"]).getACall() and
107+
this = send.getLastArgument().asExpr()
108+
}
109+
110+
override HTTP::RouteHandler getRouteHandler() {
111+
result = getRouteHandlerFromReqRes(send.getArgument([0, 1]))
112+
}
113+
}
114+
}

javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,10 @@ module TaintedPath {
212212
DataFlow::Node output;
213213

214214
PreservingPathCall() {
215-
exists(string name | name = "dirname" or name = "toNamespacedPath" |
216-
this = NodeJSLib::Path::moduleMember(name).getACall() and
217-
input = getAnArgument() and
218-
output = this
219-
)
215+
this =
216+
NodeJSLib::Path::moduleMember(["dirname", "toNamespacedPath", "parse", "format"]).getACall() and
217+
input = getAnArgument() and
218+
output = this
220219
or
221220
// non-global replace or replace of something other than /\.\./g, /[/]/g, or /[\.]/g.
222221
this.getCalleeName() = "replace" and
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
routeHandler
2+
| tst.js:5:7:10:1 | async ( ... llo";\\n} |
3+
| tst.js:12:1:15:1 | functio ... nse";\\n} |
4+
requestSource
5+
| tst.js:5:14:5:16 | req |
6+
| tst.js:12:26:12:28 | req |
7+
responseSource
8+
| tst.js:5:19:5:21 | res |
9+
| tst.js:12:31:12:33 | res |
10+
requestInputAccess
11+
| body | tst.js:7:5:7:19 | micro.json(req) |
12+
| header | tst.js:6:5:6:31 | req.hea ... -type'] |
13+
| header | tst.js:13:5:13:31 | req.hea ... -type'] |
14+
userControlledObject
15+
| tst.js:7:5:7:19 | micro.json(req) |
16+
responseSendArgument
17+
| tst.js:8:31:8:36 | "data" |
18+
responseSendArgumentHandler
19+
| tst.js:5:7:10:1 | async ( ... llo";\\n} | tst.js:8:31:8:36 | "data" |
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import javascript
2+
3+
query HTTP::RouteHandler routeHandler() { any() }
4+
5+
query HTTP::Servers::RequestSource requestSource() { any() }
6+
7+
query HTTP::Servers::ResponseSource responseSource() { any() }
8+
9+
query HTTP::RequestInputAccess requestInputAccess(string kind) { kind = result.getKind() }
10+
11+
query HTTP::RequestInputAccess userControlledObject() { result.isUserControlledObject() }
12+
13+
query HTTP::ResponseSendArgument responseSendArgument() { any() }
14+
15+
query HTTP::ResponseSendArgument responseSendArgumentHandler(HTTP::RouteHandler h) {
16+
h = result.getRouteHandler()
17+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const micro = require('micro')
2+
const bluebird = require('bluebird');
3+
const compress = require('micro-compress');
4+
5+
micro(async (req, res) => {
6+
req.headers['content-type'];
7+
micro.json(req);
8+
micro.sendError(req, res, "data");
9+
return "Hello";
10+
})
11+
12+
function* wrappedHandler(req, res) {
13+
req.headers['content-type'];
14+
yield "Response";
15+
}
16+
17+
let handler = bluebird.coroutine(wrappedHandler);
18+
19+
micro(compress(handler));

0 commit comments

Comments
 (0)