Skip to content

Commit 74af0b1

Browse files
author
Max Schaefer
committed
Improve command-injection example and provide a fixed version.
1 parent 3cde59e commit 74af0b1

File tree

3 files changed

+42
-22
lines changed

3 files changed

+42
-22
lines changed

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

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,52 @@
22
"-//Semmle//qhelp//EN"
33
"qhelp.dtd">
44
<qhelp>
5-
<overview>
6-
<p>Code that passes user input directly to
7-
<code>require('child_process').exec</code>, or some other library
8-
routine that executes a command, allows the user to execute malicious
9-
code.</p>
105

6+
<overview>
7+
<p>Code that passes untrusted user input directly to
8+
<code>child_process.exec</code> or similar APIs that execute commands by
9+
spawning a shell allows the user to execute malicious code.</p>
1110
</overview>
12-
<recommendation>
1311

14-
<p>If possible, use hard-coded string literals to specify the command to run
15-
or library to load. Instead of passing the user input directly to the
16-
process or library function, examine the user input and then choose
17-
among hard-coded string literals.</p>
12+
<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>
1816

19-
<p>If the applicable libraries or commands cannot be determined at
20-
compile time, then add code to verify that the user input string is
21-
safe before using it.</p>
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>
2219

20+
<p>If possible, prefer APIs that run the commands directly rather than via a
21+
shell, and that accept command arguments as an array of strings rather than a
22+
single concatenated string. This is both safer and more portable.</p>
2323
</recommendation>
24-
<example>
2524

26-
<p>The following example shows code that takes a shell script that can be changed
27-
maliciously by a user, and passes it straight to <code>child_process.exec</code>
28-
without examining it first.</p>
25+
<example>
26+
<p>The following example shows code that extracts a filename from an HTTP query
27+
parameter that may contain untrusted data, and then embeds it into a shell
28+
command to count its lines without examining it first.</p>
2929

3030
<sample src="examples/command-injection.js" />
3131

32+
<p>A malicious user can exploit this code to execute arbitrary shell commands by
33+
passing a filename like <code>foo.txt; rm -rf .</code>, which will first count
34+
the lines in <code>foo.txt</code> and then delete all files in the current
35+
directory.</p>
36+
37+
<p>To avoid this potentially catastrophic loophole, use an API like
38+
<code>child_process.execFileSync</code> that does not spawn a shell by
39+
default:</p>
40+
41+
<sample src="examples/command-injection_fixed.js" />
3242
</example>
33-
<references>
3443

44+
<references>
3545
<li>
3646
OWASP:
3747
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
3848
</li>
39-
4049
<!-- LocalWords: CWE untrusted unsanitized Runtime
4150
-->
42-
4351
</references>
52+
4453
</qhelp>
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
var cp = require("child_process"),
22
http = require('http'),
3+
path = require('path'),
34
url = require('url');
45

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

8-
cp.exec(cmd); // BAD
9+
cp.execSync(`wc -l ${file}`); // BAD
910
});
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+
path = require('path'),
4+
url = require('url');
5+
6+
var server = http.createServer(function(req, res) {
7+
let file = path.basename(url.parse(req.url, true).query.path);
8+
9+
cp.execFileSync('wc', ['-l', file]); // GOOD
10+
});

0 commit comments

Comments
 (0)