Skip to content

Commit 2c5ce32

Browse files
authored
Merge pull request github#14846 from github/max-schaefer/js/path-injection
Update qhelp for js/path-injection.
2 parents 08383ea + dfffa1e commit 2c5ce32

File tree

6 files changed

+253
-30
lines changed

6 files changed

+253
-30
lines changed

javascript/ql/src/Security/CWE-022/TaintedPath.qhelp

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,47 @@ attacker being able to influence behavior by modifying unexpected files.
1313

1414
<recommendation>
1515
<p>
16-
Validate user input before using it to construct a file path, either using an off-the-shelf library
17-
like the <code>sanitize-filename</code> npm package, or by performing custom validation.
16+
Validate user input before using it to construct a file path.
1817
</p>
1918

2019
<p>
21-
Ideally, follow these rules:
20+
The validation method you should use depends on whether you want to allow the user to specify complex paths with multiple components that may span multiple folders, or only simple filenames without a path component.
2221
</p>
2322

24-
<ul>
25-
<li>Do not allow more than a single "." character.</li>
26-
<li>Do not allow directory separators such as "/" or "\" (depending on the file system).</li>
27-
<li>Do not rely on simply replacing problematic sequences such as "../". For example, after
28-
applying this filter to ".../...//", the resulting string would still be "../".</li>
29-
<li>Use a whitelist of known good patterns.</li>
30-
</ul>
23+
<p>
24+
In the former case, a common strategy is to make sure that the constructed file path is contained within a safe root folder.
25+
First, normalize the path using <code>path.resolve</code> or <code>fs.realpathSync</code> to remove any ".." segments.
26+
You should always normalize the file path since an unnormalized path that starts with the root folder can still be used to access files outside the root folder.
27+
Then, after you have normalized the path, check that the path starts with the root folder.
28+
</p>
29+
30+
<p>
31+
In the latter case, you can use a library like the <code>sanitize-filename</code> npm package to eliminate any special characters from the file path.
32+
Note that it is <i>not</i> sufficient to only remove "../" sequences: for example, applying this filter to ".../...//" would still result in the string "../".
33+
</p>
34+
35+
<p>
36+
Finally, the simplest (but most restrictive) option is to use an allow list of safe patterns and make sure that the user input matches one of these patterns.
37+
</p>
3138
</recommendation>
3239

3340
<example>
3441
<p>
35-
In the first example, a file name is read from an HTTP request and then used to access a file.
36-
However, a malicious user could enter a file name which is an absolute path, such as
37-
<code>"/etc/passwd"</code>.
42+
In the first (bad) example, the code reads the file name from an HTTP request, then accesses that file within a root folder.
43+
A malicious user could enter a file name containing "../" segments to navigate outside the root folder and access sensitive files.
3844
</p>
3945

46+
<sample src="examples/TaintedPath.js" />
47+
4048
<p>
41-
In the second example, it appears that the user is restricted to opening a file within the
42-
<code>"user"</code> home directory. However, a malicious user could enter a file name containing
43-
special characters. For example, the string <code>"../../etc/passwd"</code> will result in the code
44-
reading the file located at <code>"/home/user/../../etc/passwd"</code>, which is the system's
45-
password file. This file would then be sent back to the user, giving them access to all the
46-
system's passwords.
49+
The second (good) example shows how to avoid access to sensitive files by sanitizing the file path.
50+
First, the code resolves the file name relative to a root folder, normalizing the path and removing any "../" segments in the process.
51+
Then, the code calls <code>fs.realpathSync</code> to resolve any symbolic links in the path.
52+
Finally, the code checks that the normalized path starts with the path of the root folder, ensuring the file is contained within the root folder.
4753
</p>
4854

49-
<sample src="examples/TaintedPath.js" />
55+
<sample src="examples/TaintedPathGood.js" />
56+
5057
</example>
5158

5259
<references>
Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
var fs = require('fs'),
2-
http = require('http'),
3-
url = require('url');
1+
const fs = require('fs'),
2+
http = require('http'),
3+
url = require('url');
44

5-
var server = http.createServer(function(req, res) {
6-
let path = url.parse(req.url, true).query.path;
5+
const ROOT = "/var/www/";
76

8-
// BAD: This could read any file on the file system
9-
res.write(fs.readFileSync(path));
7+
var server = http.createServer(function(req, res) {
8+
let filePath = url.parse(req.url, true).query.path;
109

11-
// BAD: This could still read any file on the file system
12-
res.write(fs.readFileSync("/home/user/" + path));
13-
});
10+
// BAD: This function uses unsanitized input that can read any file on the file system.
11+
res.write(fs.readFileSync(ROOT + filePath, 'utf8'));
12+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const fs = require('fs'),
2+
http = require('http'),
3+
path = require('path'),
4+
url = require('url');
5+
6+
const ROOT = "/var/www/";
7+
8+
var server = http.createServer(function(req, res) {
9+
let filePath = url.parse(req.url, true).query.path;
10+
11+
// GOOD: Verify that the file path is under the root directory
12+
filePath = fs.realpathSync(path.resolve(ROOT, filePath));
13+
if (!filePath.startsWith(ROOT)) {
14+
res.statusCode = 403;
15+
res.end();
16+
return;
17+
}
18+
res.write(fs.readFileSync(filePath, 'utf8'));
19+
});

0 commit comments

Comments
 (0)