Improve command-injection example and provide a fixed version.

This commit is contained in:
Max Schaefer
2023-07-04 14:44:20 +01:00
parent 3cde59e409
commit 74af0b1f05
3 changed files with 43 additions and 23 deletions

View File

@@ -2,43 +2,52 @@
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Code that passes user input directly to
<code>require('child_process').exec</code>, or some other library
routine that executes a command, allows the user to execute malicious
code.</p>
<p>Code that passes untrusted user input directly to
<code>child_process.exec</code> or similar APIs that execute commands by
spawning a shell allows the user to execute malicious code.</p>
</overview>
<recommendation>
<p>If possible, use hard-coded string literals to specify the command to run.
Instead of interpreting the user input directly as a shell command, examine the
user input and then choose among hard-coded string literals.</p>
<p>If possible, use hard-coded string literals to specify the command to run
or library to load. Instead of passing the user input directly to the
process or library function, examine the user input and then choose
among hard-coded string literals.</p>
<p>If the applicable libraries or commands cannot be determined at
compile time, then add code to verify that the user input string is
safe before using it.</p>
<p>If the applicable libraries or commands cannot be determined until runtime,
then add code to verify that the user input string is safe before using it.</p>
<p>If possible, prefer APIs that run the commands directly rather than via a
shell, and that accept command arguments as an array of strings rather than a
single concatenated string. This is both safer and more portable.</p>
</recommendation>
<example>
<p>The following example shows code that takes a shell script that can be changed
maliciously by a user, and passes it straight to <code>child_process.exec</code>
without examining it first.</p>
<example>
<p>The following example shows code that extracts a filename from an HTTP query
parameter that may contain untrusted data, and then embeds it into a shell
command to count its lines without examining it first.</p>
<sample src="examples/command-injection.js" />
</example>
<references>
<p>A malicious user can exploit this code to execute arbitrary shell commands by
passing a filename like <code>foo.txt; rm -rf .</code>, which will first count
the lines in <code>foo.txt</code> and then delete all files in the current
directory.</p>
<p>To avoid this potentially catastrophic loophole, use an API like
<code>child_process.execFileSync</code> that does not spawn a shell by
default:</p>
<sample src="examples/command-injection_fixed.js" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
</li>
<!-- LocalWords: CWE untrusted unsanitized Runtime
-->
</references>
</qhelp>

View File

@@ -1,9 +1,10 @@
var cp = require("child_process"),
http = require('http'),
path = require('path'),
url = require('url');
var server = http.createServer(function(req, res) {
let cmd = url.parse(req.url, true).query.path;
let file = path.basename(url.parse(req.url, true).query.path);
cp.exec(cmd); // BAD
cp.execSync(`wc -l ${file}`); // BAD
});

View File

@@ -0,0 +1,10 @@
var cp = require("child_process"),
http = require('http'),
path = require('path'),
url = require('url');
var server = http.createServer(function(req, res) {
let file = path.basename(url.parse(req.url, true).query.path);
cp.execFileSync('wc', ['-l', file]); // GOOD
});