Skip to content

Commit f89992e

Browse files
author
Max Schaefer
committed
Address more review feedback.
1 parent 921d8de commit f89992e

10 files changed

+81
-42
lines changed

javascript/ql/src/Security/CWE-078/CommandInjection.inc.qhelp

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,18 @@ allows the user to execute malicious code.</p>
1010
</overview>
1111

1212
<recommendation>
13-
<p>If possible, use hard-coded string literals to specify the command to run.
14-
Instead of interpreting the user input directly as a shell command, examine the
15-
user input and then choose among hard-coded string literals.</p>
13+
<p>If possible, use APIs that don't run shell commands, and accept command
14+
arguments as an array of strings rather than a single concatenated string. This
15+
is both safer and more portable.</p>
1616

17-
<p>If the applicable libraries or commands cannot be determined until runtime,
18-
then add code to verify that the user input string is safe before using it.</p>
19-
20-
<p>If possible, use APIs that don't run shell commands, and accept command arguments
21-
as an array of strings rather than a single concatenated string. This is both safer and more portable.</p>
22-
23-
<p>In the latter case, if you are given the arguments as a single string, note
24-
that it is not safe to simply split the string on whitespace, since an argument
25-
may contain quoted whitespace which would cause it to be split into multiple
26-
arguments. Instead, use a library such as <code>shell-quote</code> to parse the
27-
string into an array of arguments.</p>
17+
<p>If you are given the arguments as a single string, note that it is not safe
18+
to simply split the string on whitespace, since an argument may contain quoted
19+
whitespace which would cause it to be split into multiple arguments. Instead,
20+
use a library such as <code>shell-quote</code> to parse the string into an array
21+
of arguments.</p>
2822

23+
<p>If this approach is not viable, then add code to verify that the user input
24+
string is safe before using it.</p>
2925
</recommendation>
3026

3127
<example>
@@ -45,6 +41,12 @@ directory.</p>
4541
default:</p>
4642

4743
<sample src="examples/command-injection_fixed.js" />
44+
45+
<p>If you want to allow the user to specify other options to <code>wc</code>,
46+
you can use a library like <code>shell-quote</code> to parse the user input into
47+
an array of arguments without risking command injection:</p>
48+
49+
<sample src="examples/command-injection_shellquote.js" />
4850
</example>
4951

5052
<references>

javascript/ql/src/Security/CWE-078/IndirectCommandInjection.qhelp

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,38 +27,27 @@
2727

2828
<p>
2929

30-
If possible, use hard-coded string literals to specify the
31-
command to run or library to load. Instead of forwarding the
32-
command-line arguments to the process, examine the command-line
33-
arguments and then choose among hard-coded string literals.
30+
If possible, use APIs that don't run shell commands, and accept
31+
command arguments as an array of strings rather than a single
32+
concatenated string. This is both safer and more portable.
3433

3534
</p>
3635

3736
<p>
3837

39-
If the applicable libraries or commands cannot be determined
40-
at compile time, then add code to verify that each forwarded
41-
command-line argument is properly escaped before using it.
38+
If you are given the arguments as a single string, note that it is
39+
not safe to simply split the string on whitespace, since an argument
40+
may contain quoted whitespace which would cause it to be split into
41+
multiple arguments. Instead, use a library such as
42+
<code>shell-quote</code> to parse the string into an array of
43+
arguments.
4244

4345
</p>
4446

4547
<p>
4648

47-
If the forwarded command-line arguments are part of the
48-
arguments of the system command, prefer a library routine that handles
49-
the arguments as an array of strings rather than a single concatenated
50-
string. This prevents the unexpected evaluation of special characters.
51-
52-
</p>
53-
54-
<p>
55-
56-
In the latter case, if you are given the arguments as a single
57-
string, note that it is not safe to simply split the string on
58-
whitespace, since an argument may contain quoted whitespace which
59-
would cause it to be split into multiple arguments. Instead, use a
60-
library such as <code>shell-quote</code> to parse the string into an
61-
array of arguments.
49+
If this approach is not viable, then add code to verify that each
50+
forwarded command-line argument is properly escaped before using it.
6251

6352
</p>
6453

@@ -102,6 +91,17 @@
10291

10392
<sample src="examples/indirect-command-injection_fixed.js" />
10493

94+
<p>
95+
96+
If we want to allow the user to specify other options to
97+
<code>node</code>, we can use a library like
98+
<code>shell-quote</code> to parse the user input into an array of
99+
arguments without risking command injection:
100+
101+
</p>
102+
103+
<sample src="examples/indirect-command-injection_shellquote.js" />
104+
105105
</example>
106106

107107
<references>

javascript/ql/src/Security/CWE-078/UnsafeShellCommandConstruction.qhelp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@
7373

7474
<sample src="examples/unsafe-shell-command-construction_fixed.js" />
7575

76+
<p>
77+
78+
If you want to allow the user to specify other options to
79+
<code>wget</code> as a string, we can use a library like
80+
<code>shell-quote</code>
81+
to parse the user input into an array of arguments without risking
82+
command injection:
83+
84+
</p>
85+
86+
<sample src="examples/unsafe-shell-command-construction_shellquote.js" />
87+
7688
</example>
7789
<references>
7890

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
var cp = require("child_process"),
22
http = require('http'),
3-
path = require('path'),
43
url = require('url');
54

65
var server = http.createServer(function(req, res) {
7-
let file = path.basename(url.parse(req.url, true).query.path);
6+
let file = url.parse(req.url, true).query.path;
87

98
cp.execSync(`wc -l ${file}`); // BAD
109
});
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
var cp = require("child_process"),
22
http = require('http'),
3-
path = require('path'),
43
url = require('url');
54

65
var server = http.createServer(function(req, res) {
7-
let file = path.basename(url.parse(req.url, true).query.path);
6+
let file = url.parse(req.url, true).query.path;
87

98
cp.execFileSync('wc', ['-l', file]); // GOOD
109
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
var cp = require("child_process"),
2+
http = require('http'),
3+
url = require('url'),
4+
shellQuote = require('shell-quote');
5+
6+
var server = http.createServer(function(req, res) {
7+
let options = url.parse(req.url, true).query.options;
8+
9+
cp.execFileSync('wc', shellQuote.parse(options)); // GOOD
10+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
var cp = require("child_process"),
2+
shellQuote = require("shell-quote");
3+
4+
const args = process.argv.slice(2);
5+
let nodeOpts = '';
6+
if (args[0] === '--node-opts') {
7+
nodeOpts = args[1];
8+
args.splice(0, 2);
9+
}
10+
const script = path.join(__dirname, 'bin', 'main.js');
11+
cp.execFileSync('node', shellQuote.parse(nodeOpts).concat(script).concat(args)); // GOOD
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
var cp = require("child_process");
22

33
module.exports = function download(path, callback) {
4-
cp.execSync("wget " + path, callback);
4+
cp.exec("wget " + path, callback);
55
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
var cp = require("child_process");
22

33
module.exports = function download(path, callback) {
4-
cp.execFileSync("wget", [path], callback);
4+
cp.execFile("wget", [path], callback);
55
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
var cp = require("child_process"),
2+
shellQuote = require('shell-quote');
3+
4+
module.exports = function download(path, options, callback) {
5+
cp.execFile("wget", shellQuote.parse(options).concat(path), callback);
6+
}

0 commit comments

Comments
 (0)