mirror of
https://github.com/github/codeql.git
synced 2026-04-19 22:14:01 +02:00
Switched to new dataflow and added a test (but it doesn't produce results yet)
This commit is contained in:
@@ -11,27 +11,16 @@
|
||||
* external/cwe/cwe-078
|
||||
*/
|
||||
|
||||
import DataFlow::PathGraph
|
||||
import CommandInjectionRuntimeExec
|
||||
import ExecUserFlow::PathGraph
|
||||
|
||||
class RemoteSource extends Source instanceof RemoteFlowSource {}
|
||||
class RemoteSource extends Source instanceof RemoteFlowSource { }
|
||||
|
||||
from
|
||||
DataFlow::PathNode source, DataFlow::PathNode sink, ExecTaintConfiguration2 conf,
|
||||
MethodAccess call, DataFlow::Node sourceCmd, DataFlow::Node sinkCmd,
|
||||
ExecTaintConfiguration confCmd
|
||||
ExecUserFlow::PathNode source, ExecUserFlow::PathNode sink,
|
||||
MethodAccess call, DataFlow::Node sourceCmd, DataFlow::Node sinkCmd
|
||||
where
|
||||
call.getMethod() instanceof RuntimeExecMethod and
|
||||
// this is a command-accepting call to exec, e.g. rt.exec(new String[]{"/bin/sh", ...})
|
||||
(
|
||||
confCmd.hasFlow(sourceCmd, sinkCmd) and
|
||||
sinkCmd.asExpr() = call.getArgument(0)
|
||||
) and
|
||||
// it is tainted by untrusted user input
|
||||
(
|
||||
conf.hasFlow(source.getNode(), sink.getNode()) and
|
||||
sink.getNode().asExpr() = call.getArgument(0)
|
||||
)
|
||||
callIsTaintedByUserInputAndDangerousCommand(call, source, sink, sourceCmd, sinkCmd)
|
||||
select sink, source, sink,
|
||||
"Call to dangerous java.lang.Runtime.exec() with command '$@' with arg from untrusted input '$@'",
|
||||
sourceCmd, sourceCmd.toString(), source.getNode(), source.toString()
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import java
|
||||
import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
|
||||
// a static string of an unsafe executable tainting arg 0 of Runtime.exec()
|
||||
class ExecTaintConfiguration extends TaintTracking::Configuration {
|
||||
deprecated class ExecTaintConfiguration extends TaintTracking::Configuration {
|
||||
ExecTaintConfiguration() { this = "ExecTaintConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
@@ -33,12 +32,41 @@ class ExecTaintConfiguration extends TaintTracking::Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
module ExecCmdFlowConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr() instanceof StringLiteral and
|
||||
source.asExpr().(StringLiteral).getValue() instanceof UnSafeExecutable
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
exists(RuntimeExecMethod method, MethodAccess call |
|
||||
call.getMethod() = method and
|
||||
sink.asExpr() = call.getArgument(0) and
|
||||
sink.asExpr().getType() instanceof Array
|
||||
)
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node.asExpr().getFile().isSourceFile() and
|
||||
(
|
||||
node instanceof AssignToNonZeroIndex or
|
||||
node instanceof ArrayInitAtNonZeroIndex or
|
||||
node instanceof StreamConcatAtNonZeroIndex or
|
||||
node.getType() instanceof PrimitiveType or
|
||||
node.getType() instanceof BoxedType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Tracks flow of unvalidated user input that is used in Runtime.Exec */
|
||||
module ExecCmdFlow = TaintTracking::Global<ExecCmdFlowConfig>;
|
||||
|
||||
abstract class Source extends DataFlow::Node {
|
||||
Source() { this = this }
|
||||
}
|
||||
|
||||
// taint flow from user data to args of the command
|
||||
class ExecTaintConfiguration2 extends TaintTracking::Configuration {
|
||||
deprecated class ExecTaintConfiguration2 extends TaintTracking::Configuration {
|
||||
ExecTaintConfiguration2() { this = "ExecTaintConfiguration2" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -60,6 +88,31 @@ class ExecTaintConfiguration2 extends TaintTracking::Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
module ExecUserFlowConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source instanceof Source
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
exists(RuntimeExecMethod method, MethodAccess call |
|
||||
call.getMethod() = method and
|
||||
sink.asExpr() = call.getArgument(_) and
|
||||
sink.asExpr().getType() instanceof Array
|
||||
)
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node.asExpr().getFile().isSourceFile() and
|
||||
(
|
||||
node.getType() instanceof PrimitiveType or
|
||||
node.getType() instanceof BoxedType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Tracks flow of unvalidated user input that is used in Runtime.Exec */
|
||||
module ExecUserFlow = TaintTracking::Global<ExecUserFlowConfig>;
|
||||
|
||||
// array[3] = node
|
||||
class AssignToNonZeroIndex extends DataFlow::Node {
|
||||
AssignToNonZeroIndex() {
|
||||
@@ -101,3 +154,17 @@ class UnSafeExecutable extends string {
|
||||
not this = "netsh.exe"
|
||||
}
|
||||
}
|
||||
|
||||
predicate callIsTaintedByUserInputAndDangerousCommand(MethodAccess call, ExecUserFlow::PathNode source, ExecUserFlow::PathNode sink, DataFlow::Node sourceCmd, DataFlow::Node sinkCmd) {
|
||||
call.getMethod() instanceof RuntimeExecMethod and
|
||||
// this is a command-accepting call to exec, e.g. rt.exec(new String[]{"/bin/sh", ...})
|
||||
(
|
||||
ExecCmdFlow::flow(sourceCmd, sinkCmd) and
|
||||
sinkCmd.asExpr() = call.getArgument(0)
|
||||
) and
|
||||
// it is tainted by untrusted user input
|
||||
(
|
||||
ExecUserFlow::flowPath(source, sink) and
|
||||
sink.getNode().asExpr() = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
@@ -12,27 +12,16 @@
|
||||
* external/cwe/cwe-078
|
||||
*/
|
||||
|
||||
import DataFlow::PathGraph
|
||||
import CommandInjectionRuntimeExec
|
||||
import ExecUserFlow::PathGraph
|
||||
|
||||
class LocalSource extends Source instanceof LocalUserInput {}
|
||||
class LocalSource extends Source instanceof LocalUserInput { }
|
||||
|
||||
from
|
||||
DataFlow::PathNode source, DataFlow::PathNode sink, ExecTaintConfiguration2 conf,
|
||||
MethodAccess call, DataFlow::Node sourceCmd, DataFlow::Node sinkCmd,
|
||||
ExecTaintConfiguration confCmd
|
||||
ExecUserFlow::PathNode source, ExecUserFlow::PathNode sink,
|
||||
MethodAccess call, DataFlow::Node sourceCmd, DataFlow::Node sinkCmd
|
||||
where
|
||||
call.getMethod() instanceof RuntimeExecMethod and
|
||||
// this is a command-accepting call to exec, e.g. rt.exec(new String[]{"/bin/sh", ...})
|
||||
(
|
||||
confCmd.hasFlow(sourceCmd, sinkCmd) and
|
||||
sinkCmd.asExpr() = call.getArgument(0)
|
||||
) and
|
||||
// it is tainted by untrusted user input
|
||||
(
|
||||
conf.hasFlow(source.getNode(), sink.getNode()) and
|
||||
sink.getNode().asExpr() = call.getArgument(0)
|
||||
)
|
||||
callIsTaintedByUserInputAndDangerousCommand(call, source, sink, sourceCmd, sinkCmd)
|
||||
select sink, source, sink,
|
||||
"Call to dangerous java.lang.Runtime.exec() with command '$@' with arg from untrusted input '$@'",
|
||||
sourceCmd, sourceCmd.toString(), source.getNode(), source.toString()
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* @name Command Injection into Runtime.exec() with dangerous command
|
||||
* @description Testing query. High sensitvity and precision version of java/command-line-injection, designed to find more cases of command injection in rare cases that the default query does not find
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.1
|
||||
* @precision high
|
||||
* @id java/command-line-injection-extra-test
|
||||
* @tags testing
|
||||
* test
|
||||
* experimental
|
||||
* security
|
||||
* external/cwe/cwe-078
|
||||
*/
|
||||
|
||||
import CommandInjectionRuntimeExec
|
||||
|
||||
class DataSource extends Source {
|
||||
DataSource() { this instanceof RemoteFlowSource or this instanceof LocalUserInput }
|
||||
}
|
||||
|
||||
from
|
||||
DataFlow::Node source, DataFlow::Node sink, ExecTaintConfiguration2 conf, MethodAccess call,
|
||||
int index, DataFlow::Node sourceCmd, DataFlow::Node sinkCmd, ExecTaintConfiguration confCmd
|
||||
where
|
||||
call.getMethod() instanceof RuntimeExecMethod and
|
||||
// this is a command-accepting call to exec, e.g. exec("/bin/sh", ...)
|
||||
(
|
||||
confCmd.hasFlow(sourceCmd, sinkCmd) and
|
||||
sinkCmd.asExpr() = call.getArgument(0)
|
||||
) and
|
||||
// it is tainted by untrusted user input
|
||||
(
|
||||
conf.hasFlow(source, sink) and
|
||||
sink.asExpr() = call.getArgument(index)
|
||||
)
|
||||
select sink,
|
||||
"Call to dangerous java.lang.Runtime.exec() with command '$@' with arg from untrusted input '$@'",
|
||||
sourceCmd, sourceCmd.toString(), source, source.toString()
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* @name Command Injection into Runtime.exec() with dangerous command
|
||||
* @description Testing query. High sensitvity and precision version of java/command-line-injection, designed to find more cases of command injection in rare cases that the default query does not find
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.1
|
||||
* @precision high
|
||||
* @id java/command-line-injection-extra-test-path
|
||||
* @tags testing
|
||||
* test
|
||||
* experimental
|
||||
* security
|
||||
* external/cwe/cwe-078
|
||||
*/
|
||||
|
||||
import DataFlow::PathGraph
|
||||
import CommandInjectionRuntimeExec
|
||||
|
||||
class DataSource extends Source {
|
||||
DataSource() { this instanceof RemoteFlowSource or this instanceof LocalUserInput }
|
||||
}
|
||||
|
||||
from
|
||||
DataFlow::PathNode source, DataFlow::PathNode sink, ExecTaintConfiguration2 conf,
|
||||
MethodAccess call, DataFlow::Node sourceCmd, DataFlow::Node sinkCmd,
|
||||
ExecTaintConfiguration confCmd
|
||||
where
|
||||
call.getMethod() instanceof RuntimeExecMethod and
|
||||
// this is a command-accepting call to exec, e.g. rt.exec(new String[]{"/bin/sh", ...})
|
||||
(
|
||||
confCmd.hasFlow(sourceCmd, sinkCmd) and
|
||||
sinkCmd.asExpr() = call.getArgument(0)
|
||||
) and
|
||||
// it is tainted by untrusted user input
|
||||
(
|
||||
conf.hasFlow(source.getNode(), sink.getNode()) and
|
||||
sink.getNode().asExpr() = call.getArgument(0)
|
||||
)
|
||||
select sink, source, sink,
|
||||
"Call to dangerous java.lang.Runtime.exec() with command '$@' with arg from untrusted input '$@'",
|
||||
sourceCmd, sourceCmd.toString(), source.getNode(), source.toString()
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-078/CommandInjectionRuntimeExecLocal.ql
|
||||
@@ -0,0 +1,42 @@
|
||||
/* Tests for command injection query
|
||||
*
|
||||
* This is suitable for testing static analysis tools, as long as they treat local input as an attack surface (which can be prone to false positives)
|
||||
*
|
||||
* (C) Copyright GitHub, 2023
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.stream.Stream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class RuntimeExecTest {
|
||||
public static void test(String[] args) {
|
||||
System.out.println("Command injection test");
|
||||
|
||||
try {
|
||||
// 1. array literal
|
||||
String[] commandArray1 = new String[]{"/bin/sh", args[2], args[3], args[4]};
|
||||
Runtime.getRuntime().exec(commandArray1);
|
||||
|
||||
// 2. array assignment after it is created
|
||||
String[] commandArray2 = new String[4];
|
||||
commandArray2[0] = "/bin/sh";
|
||||
commandArray2[1] = args[2];
|
||||
commandArray2[2] = args[3];
|
||||
commandArray2[3] = args[4];
|
||||
Runtime.getRuntime().exec(commandArray2);
|
||||
|
||||
// 3. Stream concatenation
|
||||
Runtime.getRuntime().exec(
|
||||
Stream.concat(
|
||||
Arrays.stream(new String[]{"/bin/sh"}),
|
||||
Arrays.stream(new String[]{args[2], args[3], args[4]})
|
||||
).toArray(String[]::new)
|
||||
);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user