Skip to content

Commit 79839d2

Browse files
authored
Merge pull request github#5267 from erik-krogh/httpProxy
Approved by asgerf
2 parents b0fa8df + af7a188 commit 79839d2

File tree

7 files changed

+154
-1
lines changed

7 files changed

+154
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
lgtm,codescanning
2+
* Sources of user input and sinks for `js/request-forgery` in the http-proxy are now recognized.
3+
Affected packages are
4+
[http-proxy](https://www.npmjs.com/package/http-proxy)

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ import semmle.javascript.frameworks.LazyCache
9494
import semmle.javascript.frameworks.LodashUnderscore
9595
import semmle.javascript.frameworks.Logging
9696
import semmle.javascript.frameworks.HttpFrameworks
97+
import semmle.javascript.frameworks.HttpProxy
9798
import semmle.javascript.frameworks.Markdown
9899
import semmle.javascript.frameworks.NoSQL
99100
import semmle.javascript.frameworks.PkgCloud
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* Provides classes and predicates for working with the [http-proxy](https://www.npmjs.com/package/http-proxy) library.
3+
*/
4+
5+
import javascript
6+
7+
/**
8+
* Provides classes and predicates modelling the [http-proxy](https://www.npmjs.com/package/http-proxy) library.
9+
*/
10+
private module HttpProxy {
11+
/**
12+
* A call that creates a http proxy.
13+
*/
14+
class CreateServerCall extends API::CallNode, ClientRequest::Range {
15+
CreateServerCall() {
16+
this =
17+
API::moduleImport("http-proxy")
18+
.getMember(["createServer", "createProxyServer", "createProxy"])
19+
.getACall()
20+
}
21+
22+
override DataFlow::Node getUrl() { result = getParameter(0).getMember("target").getARhs() }
23+
24+
override DataFlow::Node getHost() {
25+
result = getParameter(0).getMember("target").getMember("host").getARhs()
26+
}
27+
28+
override DataFlow::Node getADataNode() { none() }
29+
}
30+
31+
/**
32+
* A call that proxies a request to some target.
33+
*/
34+
class ProxyCall extends API::CallNode, ClientRequest::Range {
35+
string method;
36+
37+
ProxyCall() {
38+
method = ["ws", "web"] and
39+
this = any(CreateServerCall server).getReturn().getMember(method).getACall()
40+
}
41+
42+
private API::Node getOptionsObject() {
43+
exists(int optionsIndex |
44+
method = "web" and optionsIndex = 2
45+
or
46+
method = "ws" and optionsIndex = 3
47+
|
48+
result = getParameter(optionsIndex)
49+
)
50+
}
51+
52+
override DataFlow::Node getUrl() { result = getOptionsObject().getMember("target").getARhs() }
53+
54+
override DataFlow::Node getHost() {
55+
result = getOptionsObject().getMember("target").getMember("host").getARhs()
56+
}
57+
58+
override DataFlow::Node getADataNode() { none() }
59+
}
60+
61+
/**
62+
* Holds if an event handler for `event` has a HTTP request parameter at `req` and a HTTP response parameter at `res`.
63+
*/
64+
predicate routeHandlingEventHandler(string event, int req, int res) {
65+
event = ["start", "end"] and req = 0 and res = 1
66+
or
67+
event = ["proxyReq", "proxyRes", "econnreset"] and req = 1 and res = 2
68+
or
69+
event = "proxyReqWs" and req = 1 and res = -10 // -10 for non-existent.
70+
}
71+
72+
/**
73+
* An http proxy event handler.
74+
*/
75+
class ProxyListenerCallback extends NodeJSLib::RouteHandler, DataFlow::FunctionNode {
76+
string event;
77+
API::CallNode call;
78+
79+
ProxyListenerCallback() {
80+
call = any(CreateServerCall server).getReturn().getMember(["on", "once"]).getACall() and
81+
call.getParameter(0).getARhs().mayHaveStringValue(event) and
82+
this = call.getParameter(1).getARhs().getAFunctionValue()
83+
}
84+
85+
override Parameter getRequestParameter() {
86+
exists(int req | routeHandlingEventHandler(event, req, _) |
87+
result = getFunction().getParameter(req)
88+
)
89+
}
90+
91+
override Parameter getResponseParameter() {
92+
exists(int res | routeHandlingEventHandler(event, _, res) |
93+
result = getFunction().getParameter(res)
94+
)
95+
}
96+
}
97+
}

javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ test_ClientRequest
7979
| tst.js:247:24:247:68 | request ... o.png') |
8080
| tst.js:249:1:251:2 | form.su ... e();\\n}) |
8181
| tst.js:257:1:262:2 | form.su ... rs()\\n}) |
82+
| tst.js:267:1:267:61 | httpPro ... 9000'}) |
83+
| tst.js:269:13:269:48 | httpPro ... ptions) |
84+
| tst.js:271:3:271:61 | proxy.w ... 080' }) |
85+
| tst.js:274:1:283:2 | httpPro ... true\\n}) |
8286
test_getADataNode
8387
| tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data |
8488
| tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 |
@@ -124,6 +128,7 @@ test_getHost
124128
| tst.js:93:5:93:35 | net.req ... host }) | tst.js:93:29:93:32 | host |
125129
| tst.js:219:5:219:41 | data.so ... Host"}) | tst.js:219:32:219:39 | "myHost" |
126130
| tst.js:257:1:262:2 | form.su ... rs()\\n}) | tst.js:259:11:259:23 | 'example.org' |
131+
| tst.js:274:1:283:2 | httpPro ... true\\n}) | tst.js:277:15:277:30 | 'my-domain-name' |
127132
test_getUrl
128133
| apollo.js:5:18:5:78 | new cre ... hql' }) | apollo.js:5:44:5:75 | 'https: ... raphql' |
129134
| apollo.js:10:1:10:54 | new Htt ... hql' }) | apollo.js:10:21:10:51 | 'http:/ ... raphql' |
@@ -210,6 +215,9 @@ test_getUrl
210215
| tst.js:247:24:247:68 | request ... o.png') | tst.js:247:32:247:67 | 'http:/ ... go.png' |
211216
| tst.js:249:1:251:2 | form.su ... e();\\n}) | tst.js:249:13:249:33 | 'http:/ ... e.org/' |
212217
| tst.js:257:1:262:2 | form.su ... rs()\\n}) | tst.js:257:13:262:1 | {\\n m ... ers()\\n} |
218+
| tst.js:267:1:267:61 | httpPro ... 9000'}) | tst.js:267:37:267:59 | 'http:/ ... t:9000' |
219+
| tst.js:271:3:271:61 | proxy.w ... 080' }) | tst.js:271:33:271:58 | 'http:/ ... m:8080' |
220+
| tst.js:274:1:283:2 | httpPro ... true\\n}) | tst.js:275:13:281:5 | {\\n ... ,\\n } |
213221
test_getAResponseDataNode
214222
| tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true |
215223
| tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true |

javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,25 @@ form.submit({
259259
host: 'example.org',
260260
path: '/upload',
261261
headers: form.getHeaders()
262-
});
262+
});
263+
264+
var httpProxy = require('http-proxy');
265+
var http = require("http");
266+
267+
httpProxy.createProxyServer({target:'http://localhost:9000'}).listen(8000);
268+
269+
var proxy = httpProxy.createProxyServer(options);
270+
http.createServer(function(req, res) {
271+
proxy.web(req, res, { target: 'http://mytarget.com:8080' });
272+
});
273+
274+
httpProxy.createProxyServer({
275+
target: {
276+
protocol: 'https:',
277+
host: 'my-domain-name',
278+
port: 443,
279+
pfx: fs.readFileSync('path/to/certificate.p12'),
280+
passphrase: 'password',
281+
},
282+
changeOrigin: true
283+
}).listen(8000);

javascript/ql/test/library-tests/frameworks/NodeJSLib/src/http.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,14 @@ http.createServer(function (req, res) {
7373
req.on("data", chunk => { // RemoteFlowSource
7474
res.send(chunk);
7575
})
76+
});
77+
78+
var httpProxy = require('http-proxy');
79+
var proxy = httpProxy.createProxyServer({});
80+
81+
proxy.on('proxyReq', function(proxyReq, req, res, options) {
82+
req.on("data", chunk => { // RemoteFlowSource
83+
res.send(chunk);
84+
});
85+
res.end("bla");
7686
});

javascript/ql/test/library-tests/frameworks/NodeJSLib/tests.expected

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ test_ResponseExpr
5656
| src/http.js:68:17:68:19 | res | src/http.js:68:12:68:27 | (req,res) => f() |
5757
| src/http.js:72:34:72:36 | res | src/http.js:72:19:76:1 | functio ... \\n })\\n} |
5858
| src/http.js:74:5:74:7 | res | src/http.js:72:19:76:1 | functio ... \\n })\\n} |
59+
| src/http.js:81:46:81:48 | res | src/http.js:81:22:86:1 | functio ... la");\\n} |
60+
| src/http.js:83:5:83:7 | res | src/http.js:81:22:86:1 | functio ... la");\\n} |
61+
| src/http.js:85:3:85:5 | res | src/http.js:81:22:86:1 | functio ... la");\\n} |
5962
| src/https.js:4:47:4:49 | res | src/https.js:4:33:10:1 | functio ... .foo;\\n} |
6063
| src/https.js:7:3:7:5 | res | src/https.js:4:33:10:1 | functio ... .foo;\\n} |
6164
| src/https.js:12:34:12:36 | res | src/https.js:12:20:16:1 | functio ... ar");\\n} |
@@ -150,6 +153,9 @@ test_RouteHandler_getAResponseExpr
150153
| src/http.js:68:12:68:27 | (req,res) => f() | src/http.js:68:17:68:19 | res |
151154
| src/http.js:72:19:76:1 | functio ... \\n })\\n} | src/http.js:72:34:72:36 | res |
152155
| src/http.js:72:19:76:1 | functio ... \\n })\\n} | src/http.js:74:5:74:7 | res |
156+
| src/http.js:81:22:86:1 | functio ... la");\\n} | src/http.js:81:46:81:48 | res |
157+
| src/http.js:81:22:86:1 | functio ... la");\\n} | src/http.js:83:5:83:7 | res |
158+
| src/http.js:81:22:86:1 | functio ... la");\\n} | src/http.js:85:3:85:5 | res |
153159
| src/https.js:4:33:10:1 | functio ... .foo;\\n} | src/https.js:4:47:4:49 | res |
154160
| src/https.js:4:33:10:1 | functio ... .foo;\\n} | src/https.js:7:3:7:5 | res |
155161
| src/https.js:12:20:16:1 | functio ... ar");\\n} | src/https.js:12:34:12:36 | res |
@@ -189,6 +195,7 @@ test_ResponseSendArgument
189195
| src/http.js:14:13:14:17 | "foo" | src/http.js:12:19:16:1 | functio ... ar");\\n} |
190196
| src/http.js:15:11:15:15 | "bar" | src/http.js:12:19:16:1 | functio ... ar");\\n} |
191197
| src/http.js:64:11:64:16 | "bar2" | src/http.js:62:19:65:1 | functio ... r2");\\n} |
198+
| src/http.js:85:11:85:15 | "bla" | src/http.js:81:22:86:1 | functio ... la");\\n} |
192199
| src/https.js:14:13:14:17 | "foo" | src/https.js:12:20:16:1 | functio ... ar");\\n} |
193200
| src/https.js:15:11:15:15 | "bar" | src/https.js:12:20:16:1 | functio ... ar");\\n} |
194201
| src/indirect.js:26:13:26:17 | "foo" | src/indirect.js:25:24:27:3 | (req, r ... ");\\n } |
@@ -235,6 +242,7 @@ test_RemoteFlowSources
235242
| src/http.js:40:23:40:30 | authInfo |
236243
| src/http.js:45:23:45:27 | error |
237244
| src/http.js:73:18:73:22 | chunk |
245+
| src/http.js:82:18:82:22 | chunk |
238246
| src/https.js:6:26:6:32 | req.url |
239247
| src/https.js:8:3:8:20 | req.headers.cookie |
240248
| src/https.js:9:3:9:17 | req.headers.foo |
@@ -273,6 +281,8 @@ test_RequestExpr
273281
| src/http.js:68:13:68:15 | req | src/http.js:68:12:68:27 | (req,res) => f() |
274282
| src/http.js:72:29:72:31 | req | src/http.js:72:19:76:1 | functio ... \\n })\\n} |
275283
| src/http.js:73:3:73:5 | req | src/http.js:72:19:76:1 | functio ... \\n })\\n} |
284+
| src/http.js:81:41:81:43 | req | src/http.js:81:22:86:1 | functio ... la");\\n} |
285+
| src/http.js:82:3:82:5 | req | src/http.js:81:22:86:1 | functio ... la");\\n} |
276286
| src/https.js:4:42:4:44 | req | src/https.js:4:33:10:1 | functio ... .foo;\\n} |
277287
| src/https.js:6:26:6:28 | req | src/https.js:4:33:10:1 | functio ... .foo;\\n} |
278288
| src/https.js:8:3:8:5 | req | src/https.js:4:33:10:1 | functio ... .foo;\\n} |
@@ -312,6 +322,8 @@ test_RouteHandler_getARequestExpr
312322
| src/http.js:68:12:68:27 | (req,res) => f() | src/http.js:68:13:68:15 | req |
313323
| src/http.js:72:19:76:1 | functio ... \\n })\\n} | src/http.js:72:29:72:31 | req |
314324
| src/http.js:72:19:76:1 | functio ... \\n })\\n} | src/http.js:73:3:73:5 | req |
325+
| src/http.js:81:22:86:1 | functio ... la");\\n} | src/http.js:81:41:81:43 | req |
326+
| src/http.js:81:22:86:1 | functio ... la");\\n} | src/http.js:82:3:82:5 | req |
315327
| src/https.js:4:33:10:1 | functio ... .foo;\\n} | src/https.js:4:42:4:44 | req |
316328
| src/https.js:4:33:10:1 | functio ... .foo;\\n} | src/https.js:6:26:6:28 | req |
317329
| src/https.js:4:33:10:1 | functio ... .foo;\\n} | src/https.js:8:3:8:5 | req |

0 commit comments

Comments
 (0)