Skip to content

Commit e9467fe

Browse files
authored
Merge pull request #14724 from egregius313/egregius313/java/environment-variable-injection
Java: Environment variable injection query
2 parents 18bd0d0 + 55da62e commit e9467fe

18 files changed

+230
-3
lines changed

java/ql/lib/ext/hudson.model.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ extensions:
3232
- ["hudson", "FilePath", True, "write", "(String,String)", "", "Argument[0]", "file-content-store", "manual"]
3333
- ["hudson", "Launcher$ProcStarter", False, "cmds", "", "", "Argument[0]", "command-injection", "manual"]
3434
- ["hudson", "Launcher$ProcStarter", False, "cmdAsSingleString", "", "", "Argument[0]", "command-injection", "manual"]
35+
- ["hudson", "Launcher$ProcStarter", False, "envs", "(String[])", "", "Argument[0]", "environment-injection", "manual"]
3536
- ["hudson", "Launcher", True, "launch", "", "", "Argument[0]", "command-injection", "manual"]
37+
- ["hudson", "Launcher", True, "decorateByEnv", "(String[])", "", "Argument[0]", "environment-injection", "manual"]
3638
- ["hudson", "Launcher", True, "launchChannel", "", "", "Argument[0]", "command-injection", "manual"]
39+
- ["hudson", "Launcher", True, "launchChannel", "", "", "Argument[3]", "environment-injection", "manual"]
3740
- ["hudson", "XmlFile", False, "XmlFile", "(XStream,File)", "", "Argument[1]", "path-injection", "ai-manual"]
3841
- addsTo:
3942
pack: codeql/java-all

java/ql/lib/ext/java.lang.model.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ extensions:
2222
- ["java.lang", "Runtime", True, "exec", "(String,String[])", "", "Argument[0]", "command-injection", "ai-manual"]
2323
- ["java.lang", "Runtime", True, "exec", "(String,String[],File)", "", "Argument[0]", "command-injection", "ai-manual"]
2424
- ["java.lang", "Runtime", True, "exec", "(String,String[],File)", "", "Argument[2]", "command-injection", "ai-manual"]
25+
# All implementations of `java.lang.Runtime::exec` take the environment variables as their second argument.
26+
- ["java.lang", "Runtime", True, "exec", "", "", "Argument[1]", "environment-injection", "manual"]
2527
# These are potential vulnerabilities, but not for command-injection. No query for this kind of vulnerability currently exists.
2628
# - ["java.lang", "Runtime", False, "load", "(String)", "", "Argument[0]", "command-injection", "ai-manual"]
2729
# - ["java.lang", "Runtime", False, "loadLibrary", "(String)", "", "Argument[0]", "command-injection", "ai-manual"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/java-all
4+
extensible: sinkModel
5+
data:
6+
- ["org.apache.commons.exec.environment", "EnvironmentUtils", True, "addVariableToEnvironment", "(Map,String)", "", "Argument[0]", "environment-injection", "manual"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/java-all
4+
extensible: sinkModel
5+
data:
6+
- ["org.apache.commons.exec.launcher", "CommandLauncher", True, "exec", "", "", "Argument[1]", "environment-injection", "manual"]

java/ql/lib/ext/org.apache.commons.exec.model.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ extensions:
99
- ["org.apache.commons.exec", "CommandLine", True, "addArguments", "(String,boolean)", "", "Argument[0]", "command-injection", "manual"]
1010
- ["org.apache.commons.exec", "CommandLine", True, "addArguments", "(String[])", "", "Argument[0]", "command-injection", "manual"]
1111
- ["org.apache.commons.exec", "CommandLine", True, "addArguments", "(String[],boolean)", "", "Argument[0]", "command-injection", "manual"]
12+
- ["org.apache.commons.exec", "Executor", True, "execute", "(CommandLine,Map)", "", "Argument[1]", "environment-injection", "manual"]
13+
- ["org.apache.commons.exec", "Executor", True, "execute", "(CommandLine,Map,ExecuteResultHandler)", "", "Argument[1]", "environment-injection", "manual"]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/** Modules to reason about the tainting of environment variables */
2+
3+
private import semmle.code.java.dataflow.ExternalFlow
4+
private import semmle.code.java.dataflow.FlowSources
5+
private import semmle.code.java.Maps
6+
private import semmle.code.java.JDK
7+
8+
private module ProcessBuilderEnvironmentConfig implements DataFlow::ConfigSig {
9+
predicate isSource(DataFlow::Node source) {
10+
exists(MethodCall mc | mc = source.asExpr() |
11+
mc.getMethod().hasQualifiedName("java.lang", "ProcessBuilder", "environment")
12+
)
13+
}
14+
15+
predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(MapMutation mm).getQualifier() }
16+
}
17+
18+
private module ProcessBuilderEnvironmentFlow = DataFlow::Global<ProcessBuilderEnvironmentConfig>;
19+
20+
/**
21+
* A node that acts as a sanitizer in configurations related to environment variable injection.
22+
*/
23+
abstract class ExecTaintedEnvironmentSanitizer extends DataFlow::Node { }
24+
25+
/**
26+
* A taint-tracking configuration that tracks flow from unvalidated data to an environment variable for a subprocess.
27+
*/
28+
module ExecTaintedEnvironmentConfig implements DataFlow::ConfigSig {
29+
predicate isSource(DataFlow::Node source) { source instanceof ThreatModelFlowSource }
30+
31+
predicate isBarrier(DataFlow::Node barrier) { barrier instanceof ExecTaintedEnvironmentSanitizer }
32+
33+
predicate isSink(DataFlow::Node sink) {
34+
sinkNode(sink, "environment-injection")
35+
or
36+
// sink is a key or value added to a `ProcessBuilder::environment` map.
37+
exists(MapMutation mm | mm.getAnArgument() = sink.asExpr() |
38+
ProcessBuilderEnvironmentFlow::flowToExpr(mm.getQualifier())
39+
)
40+
}
41+
}
42+
43+
/**
44+
* Taint-tracking flow for unvalidated data to an environment variable for a subprocess.
45+
*/
46+
module ExecTaintedEnvironmentFlow = TaintTracking::Global<ExecTaintedEnvironmentConfig>;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>Passing unvalidated user input into the environment variables of a subprocess can allow an attacker to execute malicious code.</p>
7+
</overview>
8+
9+
<recommendation>
10+
<p>If possible, use hard-coded string literals to specify the environment variable or its value.
11+
Instead of passing the user input directly to the
12+
process or library function, examine the user input and then choose
13+
among hard-coded string literals.</p>
14+
15+
<p>If the applicable environment variables cannot be determined at
16+
compile time, then add code to verify that the user input string is
17+
safe before using it.</p>
18+
</recommendation>
19+
20+
<example>
21+
<p>In the following (BAD) example, the environment variable <code>PATH</code> is set to the value of the user input <code>path</code> without validation.</p>
22+
23+
<sample src="ExecTaintedEnvironmentValue.java" />
24+
25+
<p>In the following (BAD) example, an environment variable is set with a name that is derived from the user input <code>var</code> without validation.</p>
26+
27+
<sample src="ExecTaintedEnvironmentName.java" />
28+
<p>In the following (GOOD) example, the user's input is validated before being used to set the environment variable.</p>
29+
30+
<sample src="ExecTaintedEnvironmentValidated.java" />
31+
32+
<p>In the following (GOOD) example, the user's input is checked and used to determine an environment variable to add.</p>
33+
34+
<sample src="ExecTaintedEnvironmentChecked.java" />
35+
</example>
36+
<references>
37+
<li>
38+
The Java Tutorials: <a href="https://docs.oracle.com/javase/tutorial/essential/environment/env.html">Environment Variables</a>.
39+
</li>
40+
<li>
41+
OWASP: <a href="https://owasp.org/www-community/attacks/Command_Injection">Command injection</a>.
42+
</li>
43+
</references>
44+
</qhelp>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @name Building a command with an injected environment variable
3+
* @description Passing environment variables containing externally controlled
4+
* strings to a command line is vulnerable to malicious changes to the
5+
* environment of a subprocess.
6+
* @problem.severity error
7+
* @kind path-problem
8+
* @security-severity 9.8
9+
* @precision medium
10+
* @id java/exec-tainted-environment
11+
* @tags security
12+
* external/cwe/cwe-078
13+
* external/cwe/cwe-088
14+
* external/cwe/cwe-454
15+
*/
16+
17+
import java
18+
import semmle.code.java.security.TaintedEnvironmentVariableQuery
19+
import ExecTaintedEnvironmentFlow::PathGraph
20+
21+
from ExecTaintedEnvironmentFlow::PathNode source, ExecTaintedEnvironmentFlow::PathNode sink
22+
where ExecTaintedEnvironmentFlow::flowPath(source, sink)
23+
select sink.getNode(), source, sink,
24+
"This command will be execute with a tainted environment variable."
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Map<String, String> env = builder.environment();
2+
String debug = request.getParameter("debug");
3+
4+
// GOOD: Checking the value and not tainting the variable added to the environment
5+
if (debug != null) {
6+
env.put("PYTHONDEBUG", "1");
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
public void doGet(HttpServletRequest request, HttpServletResponse response) {
2+
String attr = request.getParameter("attribute");
3+
String value = request.getParameter("value");
4+
5+
Map<String, String> env = processBuilder.environment();
6+
// BAD: attr and value are tainted and being added to the environment
7+
env.put(attr, value);
8+
9+
processBuilder.start();
10+
}

0 commit comments

Comments
 (0)