Skip to content

Commit e722e32

Browse files
authored
Merge pull request github#13771 from github/max-schaefer/server-side-url-redirect-help
JavaScript: Improve query help for `js/server-side-unvalidated-url-redirection`.
2 parents 62b4179 + a9e8167 commit e722e32

File tree

8 files changed

+95
-5
lines changed

8 files changed

+95
-5
lines changed

javascript/ql/src/Security/CWE-601/ServerSideUrlRedirect.qhelp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ To guard against untrusted URL redirection, it is advisable to avoid putting use
1616
directly into a redirect URL. Instead, maintain a list of authorized
1717
redirects on the server; then choose from that list based on the user input provided.
1818
</p>
19+
<p>
20+
If this is not possible, then the user input should be validated in some other way,
21+
for example, by verifying that the target URL is on the same host as the current page.
22+
</p>
1923
</recommendation>
2024

2125
<example>
@@ -32,6 +36,21 @@ before doing the redirection:
3236
</p>
3337

3438
<sample src="examples/ServerSideUrlRedirectGood.js"/>
39+
40+
<p>
41+
Alternatively, we can check that the target URL does not redirect to a different host
42+
by parsing it relative to a base URL with a known host and verifying that the host
43+
stays the same:
44+
</p>
45+
46+
<sample src="examples/ServerSideUrlRedirectGood2.js"/>
47+
48+
<p>
49+
Note that as written, the above code will allow redirects to URLs on <code>example.com</code>,
50+
which is harmless but perhaps not intended. You can substitute your own domain (if known) for
51+
<code>example.com</code> to prevent this.
52+
</p>
53+
3554
</example>
3655

3756
<references>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const app = require("express")();
22

3-
app.get('/some/path', function(req, res) {
3+
app.get("/redirect", function (req, res) {
44
// BAD: a request parameter is incorporated without validation into a URL redirect
5-
res.redirect(req.param("target"));
5+
res.redirect(req.query["target"]);
66
});

javascript/ql/src/Security/CWE-601/examples/ServerSideUrlRedirectGood.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ const app = require("express")();
22

33
const VALID_REDIRECT = "http://cwe.mitre.org/data/definitions/601.html";
44

5-
app.get('/some/path', function(req, res) {
5+
app.get("/redirect", function (req, res) {
66
// GOOD: the request parameter is validated against a known fixed string
7-
let target = req.param("target");
8-
if (VALID_REDIRECT === target)
7+
let target = req.query["target"];
8+
if (VALID_REDIRECT === target) {
99
res.redirect(target);
10+
} else {
11+
res.redirect("/");
12+
}
1013
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const app = require("express")();
2+
3+
function isLocalUrl(path) {
4+
try {
5+
return (
6+
// TODO: consider substituting your own domain for example.com
7+
new URL(path, "https://example.com").origin === "https://example.com"
8+
);
9+
} catch (e) {
10+
return false;
11+
}
12+
}
13+
14+
app.get("/redirect", function (req, res) {
15+
// GOOD: check that we don't redirect to a different host
16+
let target = req.query["target"];
17+
if (isLocalUrl(target)) {
18+
res.redirect(target);
19+
} else {
20+
res.redirect("/");
21+
}
22+
});

javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
nodes
2+
| ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] |
3+
| ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] |
4+
| ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] |
25
| express.js:7:16:7:34 | req.param("target") |
36
| express.js:7:16:7:34 | req.param("target") |
47
| express.js:7:16:7:34 | req.param("target") |
@@ -114,6 +117,7 @@ nodes
114117
| react-native.js:9:26:9:32 | tainted |
115118
| react-native.js:9:26:9:32 | tainted |
116119
edges
120+
| ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] | ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] |
117121
| express.js:7:16:7:34 | req.param("target") | express.js:7:16:7:34 | req.param("target") |
118122
| express.js:12:26:12:44 | req.param("target") | express.js:12:26:12:44 | req.param("target") |
119123
| express.js:27:7:27:34 | target | express.js:33:18:33:23 | target |
@@ -211,6 +215,7 @@ edges
211215
| react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted |
212216
| react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted |
213217
#select
218+
| ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] | ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] | ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] | Untrusted URL redirection depends on a $@. | ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] | user-provided value |
214219
| express.js:7:16:7:34 | req.param("target") | express.js:7:16:7:34 | req.param("target") | express.js:7:16:7:34 | req.param("target") | Untrusted URL redirection depends on a $@. | express.js:7:16:7:34 | req.param("target") | user-provided value |
215220
| express.js:12:26:12:44 | req.param("target") | express.js:12:26:12:44 | req.param("target") | express.js:12:26:12:44 | req.param("target") | Untrusted URL redirection depends on a $@. | express.js:12:26:12:44 | req.param("target") | user-provided value |
216221
| express.js:33:18:33:23 | target | express.js:27:16:27:34 | req.param("target") | express.js:33:18:33:23 | target | Untrusted URL redirection depends on a $@. | express.js:27:16:27:34 | req.param("target") | user-provided value |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const app = require("express")();
2+
3+
app.get("/redirect", function (req, res) {
4+
// BAD: a request parameter is incorporated without validation into a URL redirect
5+
res.redirect(req.query["target"]);
6+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const app = require("express")();
2+
3+
const VALID_REDIRECT = "http://cwe.mitre.org/data/definitions/601.html";
4+
5+
app.get("/redirect", function (req, res) {
6+
// GOOD: the request parameter is validated against a known fixed string
7+
let target = req.query["target"];
8+
if (VALID_REDIRECT === target) {
9+
res.redirect(target);
10+
} else {
11+
res.redirect("/");
12+
}
13+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const app = require("express")();
2+
3+
function isLocalUrl(path) {
4+
try {
5+
return (
6+
// TODO: consider substituting your own domain for example.com
7+
new URL(path, "https://example.com").origin === "https://example.com"
8+
);
9+
} catch (e) {
10+
return false;
11+
}
12+
}
13+
14+
app.get("/redirect", function (req, res) {
15+
// GOOD: check that we don't redirect to a different host
16+
let target = req.query["target"];
17+
if (isLocalUrl(target)) {
18+
res.redirect(target);
19+
} else {
20+
res.redirect("/");
21+
}
22+
});

0 commit comments

Comments
 (0)