Skip to content

Commit 2b3ab18

Browse files
authored
Merge pull request github#10077 from intrigus-lgtm/cpp/wexpand-commmand-injection
Add query for tainted `wordexp` calls.
2 parents 71135da + 894a0f1 commit 2b3ab18

File tree

6 files changed

+175
-0
lines changed

6 files changed

+175
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
int main(int argc, char** argv) {
3+
char *filePath = argv[2];
4+
5+
{
6+
// BAD: the user-controlled string is injected
7+
// directly into `wordexp` which performs command substitution
8+
9+
wordexp_t we;
10+
wordexp(filePath, &we, 0);
11+
}
12+
13+
{
14+
// GOOD: command substitution is disabled
15+
16+
wordexp_t we;
17+
wordexp(filePath, &we, WRDE_NOCMD);
18+
}
19+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>The code passes user input to <code>wordexp</code>. This leaves the code
7+
vulnerable to attack by command injection, because <code>wordexp</code> performs command substitution.
8+
Command substitution is a feature that replaces <code>$(command)</code> or <code>`command`</code> with the
9+
output of the given command, allowing the user to run arbitrary code on the system.
10+
</p>
11+
12+
</overview>
13+
<recommendation>
14+
15+
<p>When calling <code>wordexp</code>, pass the <code>WRDE_NOCMD</code> flag to prevent command substitution.</p>
16+
17+
</recommendation>
18+
<example>
19+
<p>The following example passes a user-supplied file path to <code>wordexp</code> in two ways. The
20+
first way uses <code>wordexp</code> with no specified flags. As such, it is vulnerable to command
21+
injection.
22+
The second way uses <code>wordexp</code> with the <code>WRDE_NOCMD</code> flag. As such, no command substitution
23+
is performed, making this safe from command injection.</p>
24+
<sample src="WordexpTainted.c" />
25+
26+
</example>
27+
<references>
28+
29+
<li>CERT C Coding Standard:
30+
<a href="https://www.securecoding.cert.org/confluence/display/c/STR02-C.+Sanitize+data+passed+to+complex+subsystems">STR02-C.
31+
Sanitize data passed to complex subsystems</a>.</li>
32+
<li>
33+
OWASP:
34+
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
35+
</li>
36+
37+
38+
<!-- LocalWords: CWE STR
39+
-->
40+
41+
</references>
42+
</qhelp>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @name Uncontrolled data used in `wordexp` command
3+
* @description Using user-supplied data in a `wordexp` command, without
4+
* disabling command substitution, can make code vulnerable
5+
* to command injection.
6+
* @kind path-problem
7+
* @problem.severity error
8+
* @precision high
9+
* @id cpp/wordexp-injection
10+
* @tags security
11+
* external/cwe/cwe-078
12+
*/
13+
14+
import cpp
15+
import semmle.code.cpp.ir.dataflow.TaintTracking
16+
import semmle.code.cpp.security.FlowSources
17+
import DataFlow::PathGraph
18+
19+
/**
20+
* The `wordexp` function, which can perform command substitution.
21+
*/
22+
private class WordexpFunction extends Function {
23+
WordexpFunction() { hasGlobalName("wordexp") }
24+
}
25+
26+
/**
27+
* Holds if `fc` disables command substitution by containing `WRDE_NOCMD` as a flag argument.
28+
*/
29+
private predicate isCommandSubstitutionDisabled(FunctionCall fc) {
30+
fc.getArgument(2).getValue().toInt().bitAnd(4) = 4
31+
/* 4 = WRDE_NOCMD. Check whether the flag is set. */
32+
}
33+
34+
/**
35+
* A configuration to track user-supplied data to the `wordexp` function.
36+
*/
37+
class WordexpTaintConfiguration extends TaintTracking::Configuration {
38+
WordexpTaintConfiguration() { this = "WordexpTaintConfiguration" }
39+
40+
override predicate isSource(DataFlow::Node source) { source instanceof FlowSource }
41+
42+
override predicate isSink(DataFlow::Node sink) {
43+
exists(FunctionCall fc | fc.getTarget() instanceof WordexpFunction |
44+
fc.getArgument(0) = sink.asExpr() and
45+
not isCommandSubstitutionDisabled(fc)
46+
)
47+
}
48+
49+
override predicate isSanitizer(DataFlow::Node node) {
50+
node.asExpr().getUnspecifiedType() instanceof IntegralType
51+
}
52+
}
53+
54+
from WordexpTaintConfiguration conf, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode
55+
where conf.hasFlowPath(sourceNode, sinkNode)
56+
select sinkNode.getNode(), sourceNode, sinkNode,
57+
"Using user-supplied data in a `wordexp` command, without disabling command substitution, can make code vulnerable to command injection."
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
edges
2+
| test.cpp:23:20:23:23 | argv | test.cpp:29:13:29:20 | (const char *)... |
3+
| test.cpp:23:20:23:23 | argv | test.cpp:29:13:29:20 | filePath |
4+
nodes
5+
| test.cpp:23:20:23:23 | argv | semmle.label | argv |
6+
| test.cpp:29:13:29:20 | (const char *)... | semmle.label | (const char *)... |
7+
| test.cpp:29:13:29:20 | filePath | semmle.label | filePath |
8+
subpaths
9+
#select
10+
| test.cpp:29:13:29:20 | (const char *)... | test.cpp:23:20:23:23 | argv | test.cpp:29:13:29:20 | (const char *)... | Using user-supplied data in a `wordexp` command, without disabling command substitution, can make code vulnerable to command injection. |
11+
| test.cpp:29:13:29:20 | filePath | test.cpp:23:20:23:23 | argv | test.cpp:29:13:29:20 | filePath | Using user-supplied data in a `wordexp` command, without disabling command substitution, can make code vulnerable to command injection. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-078/WordexpTainted.ql
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#ifdef _MSC_VER
2+
#define restrict __restrict
3+
#else
4+
#define restrict __restrict__
5+
#endif
6+
7+
typedef unsigned long size_t;
8+
9+
typedef struct {
10+
size_t we_wordc;
11+
char **we_wordv;
12+
size_t we_offs;
13+
} wordexp_t;
14+
15+
enum {
16+
WRDE_APPEND = (1 << 1),
17+
WRDE_NOCMD = (1 << 2)
18+
};
19+
20+
int wordexp(const char *restrict s, wordexp_t *restrict p, int flags);
21+
22+
int main(int argc, char** argv) {
23+
char *filePath = argv[2];
24+
25+
{
26+
// BAD: the user string is injected directly into `wordexp` which performs command substitution
27+
28+
wordexp_t we;
29+
wordexp(filePath, &we, 0);
30+
}
31+
32+
{
33+
// GOOD: command substitution is disabled
34+
35+
wordexp_t we;
36+
wordexp(filePath, &we, WRDE_NOCMD);
37+
}
38+
39+
{
40+
// GOOD: command substitution is disabled
41+
42+
wordexp_t we;
43+
wordexp(filePath, &we, WRDE_NOCMD | WRDE_APPEND);
44+
}
45+
}

0 commit comments

Comments
 (0)