Merge branch 'main' into starcke/automodel-pack

This commit is contained in:
Anders Starcke Henriksen
2023-08-08 15:02:33 +02:00
252 changed files with 13920 additions and 2899 deletions

View File

@@ -552,7 +552,7 @@ open class KotlinFileExtractor(
logger.warnElement("Expected annotation property to define a getter", prop)
} else {
val getterId = useFunction<DbMethod>(getter)
val exprId = extractAnnotationValueExpression(v, id, i, "{${getterId}}", getter.returnType, extractEnumTypeAccesses)
val exprId = extractAnnotationValueExpression(v, id, i, "{$getterId}", getter.returnType, extractEnumTypeAccesses)
if (exprId != null) {
tw.writeAnnotValue(id, getterId, exprId)
}
@@ -587,7 +587,7 @@ open class KotlinFileExtractor(
extractAnnotation(v, parent, idx, extractEnumTypeAccesses, contextLabel)
}
is IrVararg -> {
tw.getLabelFor<DbArrayinit>("@\"annotationarray;{${parent}};$contextLabel\"").also { arrayId ->
tw.getLabelFor<DbArrayinit>("@\"annotationarray;{$parent};$contextLabel\"").also { arrayId ->
// Use the context type (i.e., the type the annotation expects, not the actual type of the array)
// because the Java extractor fills in array types using the same technique. These should only
// differ for generic annotations.
@@ -1193,7 +1193,7 @@ open class KotlinFileExtractor(
// n + o'th parameter, where `o` is the parameter offset caused by adding any dispatch receiver to the parameter list.
// Note we don't need to add the extension receiver here because `useValueParameter` always assumes an extension receiver
// will be prepended if one exists.
val realFunctionId = useFunction<DbCallable>(f)
val realFunctionId = useFunction<DbCallable>(f, parentId, null)
DeclarationStackAdjuster(f, OverriddenFunctionAttributes(id, id, locId, nonSyntheticParams, typeParameters = listOf(), isStatic = true)).use {
val realParamsVarId = getValueParameterLabel(id, parameterTypes.size - 2)
val intType = pluginContext.irBuiltIns.intType

View File

@@ -612,7 +612,7 @@ open class KotlinUsesExtractor(
val componentTypeLabel = recInfo.componentTypeResults.javaResult.id
val dimensions = recInfo.dimensions + 1
val id = tw.getLabelFor<DbArray>("@\"array;$dimensions;{${elementTypeLabel}}\"") {
val id = tw.getLabelFor<DbArray>("@\"array;$dimensions;{$elementTypeLabel}\"") {
tw.writeArrays(
it,
javaShortName,
@@ -1141,7 +1141,7 @@ open class KotlinUsesExtractor(
// method (and presumably that disambiguation is never needed when the method belongs to a parameterized
// instance of a generic class), but as of now I don't know when the raw method would be referred to.
val typeArgSuffix = if (functionTypeParameters.isNotEmpty() && classTypeArgsIncludingOuterClasses.isNullOrEmpty()) "<${functionTypeParameters.size}>" else "";
return "@\"$prefix;{$parentId}.$name($paramTypeIds){$returnTypeId}${typeArgSuffix}\""
return "@\"$prefix;{$parentId}.$name($paramTypeIds){$returnTypeId}$typeArgSuffix\""
}
val javaLangClass by lazy { referenceExternalClass("java.lang.Class") }
@@ -1672,7 +1672,7 @@ open class KotlinUsesExtractor(
// clashing trap labels. These are always private, so we can just make up a label without
// worrying about their names as seen from Java.
val extensionPropertyDiscriminator = getExtensionReceiverType(f)?.let { "extension;${useType(it).javaResult.id}" } ?: ""
return "@\"field;{$parentId};${extensionPropertyDiscriminator}${f.name.asString()}\""
return "@\"field;{$parentId};$extensionPropertyDiscriminator${f.name.asString()}\""
}
fun useField(f: IrField): Label<out DbField> =

View File

@@ -66,6 +66,7 @@ where
// modeled in a MaD model, then it doesn't belong to any additional sink types, and we don't need to reexamine it.
not CharacteristicsImpl::isSink(endpoint, _, _) and
meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input) and
includeAutomodelCandidate(package, type, name, signature) and
// The message is the concatenation of all sink types for which this endpoint is known neither to be a sink nor to be
// a non-sink, and we surface only endpoints that have at least one such sink type.
message =

View File

@@ -30,6 +30,7 @@ where
// modeled in a MaD model, then it doesn't belong to any additional sink types, and we don't need to reexamine it.
not CharacteristicsImpl::isSink(endpoint, _, _) and
meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input, parameterName) and
includeAutomodelCandidate(package, type, name, signature) and
// The message is the concatenation of all sink types for which this endpoint is known neither to be a sink nor to be
// a non-sink, and we surface only endpoints that have at least one such sink type.
message =

View File

@@ -66,3 +66,24 @@ boolean considerSubtypes(Callable callable) {
then result = false
else result = true
}
/**
* Holds if the given package, type, name and signature is a candidate for automodeling.
*
* This predicate is extensible, so that different endpoints can be selected at runtime.
*/
extensible predicate automodelCandidateFilter(
string package, string type, string name, string signature
);
/**
* Holds if the given package, type, name and signature is a candidate for automodeling.
*
* This relies on an extensible predicate, and if that is not supplied then
* all endpoints are considered candidates.
*/
bindingset[package, type, name, signature]
predicate includeAutomodelCandidate(string package, string type, string name, string signature) {
not automodelCandidateFilter(_, _, _, _) or
automodelCandidateFilter(package, type, name, signature)
}

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Fixed a typo in the `StdlibRandomSource` class in `RandomDataSource.qll`, which caused the class to improperly model calls to the `nextBytes` method. Queries relying on `StdlibRandomSource` may see an increase in results.

View File

@@ -17,11 +17,11 @@ extensions:
- ["java.nio.file", "Files", False, "createTempFile", "(Path,String,String,FileAttribute[])", "", "Argument[0]", "path-injection", "manual"]
- ["java.nio.file", "Files", False, "delete", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "deleteIfExists", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "deleteIfExists", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "getFileStore", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"] # the FileStore class is unlikely to be used for later sanitization
- ["java.nio.file", "Files", False, "lines", "(Path,Charset)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "lines", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "move", "", "", "Argument[1]", "path-injection", "manual"]
- ["java.nio.file", "Files", False, "move", "(Path,Path,CopyOption[])", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "newBufferedReader", "(Path,Charset)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "newBufferedReader", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "newBufferedWriter", "", "", "Argument[0]", "path-injection", "manual"]
@@ -37,11 +37,6 @@ extensions:
- ["java.nio.file", "Files", False, "write", "", "", "Argument[1]", "file-content-store", "manual"]
- ["java.nio.file", "Files", False, "writeString", "", "", "Argument[0]", "path-injection", "manual"]
- ["java.nio.file", "Files", False, "writeString", "", "", "Argument[1]", "file-content-store", "manual"]
- ["java.nio.file", "Files", True, "move", "(Path,Path,CopyOption[])", "", "Argument[1]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", True, "move", "(Path,Path,CopyOption[])", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", True, "delete", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", True, "newInputStream", "(Path,OpenOption[])", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", True, "newOutputStream", "(Path,OpenOption[])", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "FileSystem", False, "getPath", "", "", "Argument[0..1]", "path-injection", "manual"] # old PathCreation
- ["java.nio.file", "FileSystems", False, "newFileSystem", "(URI,Map)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "FileSystems", False, "newFileSystem", "(URI,Map)", "", "Argument[0]", "request-forgery", "ai-manual"]

View File

@@ -0,0 +1,7 @@
extensions:
- addsTo:
pack: codeql/java-all
extensible: supportedThreatModels
data:
- ["default"] # The "default" threat model is always included.

View File

@@ -0,0 +1,23 @@
extensions:
- addsTo:
pack: codeql/java-all
extensible: threatModelGrouping
data:
# Default threat model
- ["remote", "default"]
- ["uri-path", "default"]
# Android threat models
- ["android-external-storage-dir", "android"]
- ["contentprovider", "android"]
# Remote threat models
- ["request", "remote"]
- ["response", "remote"]
# Local threat models
- ["database", "local"]
- ["cli", "local"]
- ["environment", "local"]
- ["file", "local"]

View File

@@ -16,4 +16,5 @@ dataExtensions:
- ext/*.model.yml
- ext/generated/*.model.yml
- ext/experimental/*.model.yml
- ext/threatmodels/*.model.yml
warnOnImplicitThis: true

View File

@@ -0,0 +1,31 @@
/**
* INTERNAL use only. This is an experimental API subject to change without notice.
*
* This module provides extensible predicates for configuring which kinds of MaD models
* are applicable to generic queries.
*/
private import ExternalFlowExtensions
/**
* Holds if the specified kind of source model is supported for the current query.
*/
extensible private predicate supportedThreatModels(string kind);
/**
* Holds if the specified kind of source model is containted within the specified group.
*/
extensible private predicate threatModelGrouping(string kind, string group);
/**
* Gets the threat models that are direct descendants of the specified kind/group.
*/
private string getChildThreatModel(string group) { threatModelGrouping(result, group) }
/**
* Holds if the source model kind `kind` is relevant for generic queries
* under the current threat model configuration.
*/
predicate sourceModelKindConfig(string kind) {
exists(string group | supportedThreatModels(group) and kind = getChildThreatModel*(group))
}

View File

@@ -1,3 +1,3 @@
private import DataFlowImplSpecific
private import codeql.dataflow.DataFlowImpl
private import codeql.dataflow.internal.DataFlowImpl
import MakeImpl<JavaDataFlow>

View File

@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or

View File

@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or

View File

@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or

View File

@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or

View File

@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or

View File

@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or

View File

@@ -1,3 +1,3 @@
private import DataFlowImplSpecific
private import codeql.dataflow.DataFlowImplCommon
private import codeql.dataflow.internal.DataFlowImplCommon
import MakeImplCommon<JavaDataFlow>

View File

@@ -2,7 +2,7 @@
* Provides Java-specific definitions for use in the data flow library.
*/
private import codeql.dataflow.DataFlowParameter
private import codeql.dataflow.DataFlow
module Private {
import DataFlowPrivate
@@ -13,7 +13,7 @@ module Public {
import DataFlowUtil
}
module JavaDataFlow implements DataFlowParameter {
module JavaDataFlow implements InputSig {
import Private
import Public

View File

@@ -24,6 +24,7 @@ private module AddTaintDefaults<DataFlowInternal::FullStateConfigSig Config> imp
Config::allowImplicitRead(node, c)
or
(
Config::isSink(node) or
Config::isSink(node, _) or
Config::isAdditionalFlowStep(node, _) or
Config::isAdditionalFlowStep(node, _, _, _)

View File

@@ -103,7 +103,7 @@ class StdlibRandomSource extends RandomDataSource {
}
override Expr getOutput() {
if m.hasName("getBytes") then result = this.getArgument(0) else result = this
if m.hasName("nextBytes") then result = this.getArgument(0) else result = this
}
}

View File

@@ -0,0 +1,5 @@
extensions:
- addsTo:
pack: codeql/java-queries
extensible: automodelCandidateFilter
data: []

View File

@@ -0,0 +1,9 @@
class Test {
public static void main(String[] args) {
String script = System.getenv("SCRIPTNAME");
if (script != null) {
// BAD: The script to be executed by /bin/sh is controlled by the user.
Runtime.getRuntime().exec(new String[]{"/bin/sh", script});
}
}
}

View File

@@ -0,0 +1,41 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Code that passes remote user input to an arugment of a call of <code>Runtime.exec</code> that
executes a scripting executable will allow the user to execute malicious code.</p>
</overview>
<recommendation>
<p>If possible, use hard-coded string literals to specify the command or script 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>
</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 the array going into <code>Runtime.exec</code>
without examining it first.</p>
<sample src="CommandInjectionRuntimeExec.java" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
</li>
<li>SEI CERT Oracle Coding Standard for Java:
<a href="https://wiki.sei.cmu.edu/confluence/display/java/IDS07-J.+Sanitize+untrusted+data+passed+to+the+Runtime.exec()+method">IDS07-J. Sanitize untrusted data passed to the Runtime.exec() method</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,25 @@
/**
* @name Command Injection into Runtime.exec() with dangerous command
* @description 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
* @tags security
* experimental
* external/cwe/cwe-078
*/
import CommandInjectionRuntimeExec
import ExecUserFlow::PathGraph
class RemoteSource extends Source instanceof RemoteFlowSource { }
from
ExecUserFlow::PathNode source, ExecUserFlow::PathNode sink, DataFlow::Node sourceCmd,
DataFlow::Node sinkCmd
where callIsTaintedByUserInputAndDangerousCommand(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()

View File

@@ -0,0 +1,108 @@
import java
import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
module ExecCmdFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr().(CompileTimeConstantExpr).getStringValue() instanceof UnSafeExecutable
}
predicate isSink(DataFlow::Node sink) {
exists(MethodAccess call |
call.getMethod() instanceof RuntimeExecMethod and
sink.asExpr() = call.getArgument(0) and
sink.asExpr().getType() instanceof Array
)
}
predicate isBarrier(DataFlow::Node node) {
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 { }
module ExecUserFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof Source }
predicate isSink(DataFlow::Node sink) {
exists(MethodAccess call |
call.getMethod() instanceof RuntimeExecMethod and
sink.asExpr() = call.getArgument(_) and
sink.asExpr().getType() instanceof Array
)
}
predicate isBarrier(DataFlow::Node node) {
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() {
exists(AssignExpr assign, ArrayAccess access |
assign.getDest() = access and
access.getIndexExpr().(IntegerLiteral).getValue().toInt() != 0 and
assign.getSource() = this.asExpr()
)
}
}
// String[] array = {"a", "b, "c"};
class ArrayInitAtNonZeroIndex extends DataFlow::Node {
ArrayInitAtNonZeroIndex() {
exists(ArrayInit init, int index |
init.getInit(index) = this.asExpr() and
index != 0
)
}
}
// Stream.concat(Arrays.stream(array_1), Arrays.stream(array_2))
class StreamConcatAtNonZeroIndex extends DataFlow::Node {
StreamConcatAtNonZeroIndex() {
exists(MethodAccess call, int index |
call.getMethod().getQualifiedName() = "java.util.stream.Stream.concat" and
call.getArgument(index) = this.asExpr() and
index != 0
)
}
}
// list of executables that execute their arguments
// TODO: extend with data extensions
class UnSafeExecutable extends string {
bindingset[this]
UnSafeExecutable() {
this.regexpMatch("^(|.*/)([a-z]*sh|javac?|python.*|perl|[Pp]ower[Ss]hell|php|node|deno|bun|ruby|osascript|cmd|Rscript|groovy)(\\.exe)?$") and
not this = "netsh.exe"
}
}
predicate callIsTaintedByUserInputAndDangerousCommand(
ExecUserFlow::PathNode source, ExecUserFlow::PathNode sink, DataFlow::Node sourceCmd,
DataFlow::Node sinkCmd
) {
exists(MethodAccess call |
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)
)
}

View File

@@ -0,0 +1,41 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Code that passes local user input to an arugment of a call of <code>Runtime.exec</code> that
executes a scripting executable will allow the user to execute malicious code.</p>
</overview>
<recommendation>
<p>If possible, use hard-coded string literals to specify the command or script 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>
</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 the array going into <code>Runtime.exec</code>
without examining it first.</p>
<sample src="CommandInjectionRuntimeExec.java" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
</li>
<li>SEI CERT Oracle Coding Standard for Java:
<a href="https://wiki.sei.cmu.edu/confluence/display/java/IDS07-J.+Sanitize+untrusted+data+passed+to+the+Runtime.exec()+method">IDS07-J. Sanitize untrusted data passed to the Runtime.exec() method</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,26 @@
/**
* @name Command Injection into Runtime.exec() with dangerous command
* @description 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-local
* @tags security
* experimental
* local
* external/cwe/cwe-078
*/
import CommandInjectionRuntimeExec
import ExecUserFlow::PathGraph
class LocalSource extends Source instanceof LocalUserInput { }
from
ExecUserFlow::PathNode source, ExecUserFlow::PathNode sink, DataFlow::Node sourceCmd,
DataFlow::Node sinkCmd
where callIsTaintedByUserInputAndDangerousCommand(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()

View File

@@ -12,4 +12,5 @@ dependencies:
codeql/util: ${workspace}
dataExtensions:
- Telemetry/ExtractorInformation.yml
- Telemetry/AutomodelCandidateFilter.yml
warnOnImplicitThis: true

View File

@@ -0,0 +1,41 @@
edges
| RuntimeExecTest.java:17:25:17:51 | getenv(...) : String | RuntimeExecTest.java:22:67:22:72 | script : String |
| RuntimeExecTest.java:17:25:17:51 | getenv(...) : String | RuntimeExecTest.java:25:66:25:71 | script : String |
| RuntimeExecTest.java:17:25:17:51 | getenv(...) : String | RuntimeExecTest.java:31:36:31:41 | script : String |
| RuntimeExecTest.java:17:25:17:51 | getenv(...) : String | RuntimeExecTest.java:38:52:38:57 | script : String |
| RuntimeExecTest.java:22:43:22:73 | {...} : String[] [[]] : String | RuntimeExecTest.java:22:43:22:73 | new String[] |
| RuntimeExecTest.java:22:67:22:72 | script : String | RuntimeExecTest.java:22:43:22:73 | {...} : String[] [[]] : String |
| RuntimeExecTest.java:25:42:25:72 | {...} : String[] [[]] : String | RuntimeExecTest.java:26:43:26:55 | commandArray1 |
| RuntimeExecTest.java:25:66:25:71 | script : String | RuntimeExecTest.java:25:42:25:72 | {...} : String[] [[]] : String |
| RuntimeExecTest.java:31:17:31:29 | commandArray2 [post update] : String[] [[]] : String | RuntimeExecTest.java:32:43:32:55 | commandArray2 |
| RuntimeExecTest.java:31:36:31:41 | script : String | RuntimeExecTest.java:31:17:31:29 | commandArray2 [post update] : String[] [[]] : String |
| RuntimeExecTest.java:36:21:39:21 | concat(...) : Stream [<element>] : String | RuntimeExecTest.java:36:21:39:44 | toArray(...) : String[] [[]] : String |
| RuntimeExecTest.java:36:21:39:44 | toArray(...) : String[] [[]] : String | RuntimeExecTest.java:36:21:39:44 | toArray(...) |
| RuntimeExecTest.java:38:25:38:59 | stream(...) : Stream [<element>] : String | RuntimeExecTest.java:36:21:39:21 | concat(...) : Stream [<element>] : String |
| RuntimeExecTest.java:38:39:38:58 | new String[] : String[] [[]] : String | RuntimeExecTest.java:38:25:38:59 | stream(...) : Stream [<element>] : String |
| RuntimeExecTest.java:38:39:38:58 | {...} : String[] [[]] : String | RuntimeExecTest.java:38:39:38:58 | new String[] : String[] [[]] : String |
| RuntimeExecTest.java:38:52:38:57 | script : String | RuntimeExecTest.java:38:39:38:58 | {...} : String[] [[]] : String |
nodes
| RuntimeExecTest.java:17:25:17:51 | getenv(...) : String | semmle.label | getenv(...) : String |
| RuntimeExecTest.java:22:43:22:73 | new String[] | semmle.label | new String[] |
| RuntimeExecTest.java:22:43:22:73 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String |
| RuntimeExecTest.java:22:67:22:72 | script : String | semmle.label | script : String |
| RuntimeExecTest.java:25:42:25:72 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String |
| RuntimeExecTest.java:25:66:25:71 | script : String | semmle.label | script : String |
| RuntimeExecTest.java:26:43:26:55 | commandArray1 | semmle.label | commandArray1 |
| RuntimeExecTest.java:31:17:31:29 | commandArray2 [post update] : String[] [[]] : String | semmle.label | commandArray2 [post update] : String[] [[]] : String |
| RuntimeExecTest.java:31:36:31:41 | script : String | semmle.label | script : String |
| RuntimeExecTest.java:32:43:32:55 | commandArray2 | semmle.label | commandArray2 |
| RuntimeExecTest.java:36:21:39:21 | concat(...) : Stream [<element>] : String | semmle.label | concat(...) : Stream [<element>] : String |
| RuntimeExecTest.java:36:21:39:44 | toArray(...) | semmle.label | toArray(...) |
| RuntimeExecTest.java:36:21:39:44 | toArray(...) : String[] [[]] : String | semmle.label | toArray(...) : String[] [[]] : String |
| RuntimeExecTest.java:38:25:38:59 | stream(...) : Stream [<element>] : String | semmle.label | stream(...) : Stream [<element>] : String |
| RuntimeExecTest.java:38:39:38:58 | new String[] : String[] [[]] : String | semmle.label | new String[] : String[] [[]] : String |
| RuntimeExecTest.java:38:39:38:58 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String |
| RuntimeExecTest.java:38:52:38:57 | script : String | semmle.label | script : String |
subpaths
#select
| RuntimeExecTest.java:22:43:22:73 | new String[] | RuntimeExecTest.java:17:25:17:51 | getenv(...) : String | RuntimeExecTest.java:22:43:22:73 | new String[] | Call to dangerous java.lang.Runtime.exec() with command '$@' with arg from untrusted input '$@' | RuntimeExecTest.java:22:56:22:64 | "/bin/sh" | "/bin/sh" | RuntimeExecTest.java:17:25:17:51 | getenv(...) | getenv(...) : String |
| RuntimeExecTest.java:26:43:26:55 | commandArray1 | RuntimeExecTest.java:17:25:17:51 | getenv(...) : String | RuntimeExecTest.java:26:43:26:55 | commandArray1 | Call to dangerous java.lang.Runtime.exec() with command '$@' with arg from untrusted input '$@' | RuntimeExecTest.java:25:55:25:63 | "/bin/sh" | "/bin/sh" | RuntimeExecTest.java:17:25:17:51 | getenv(...) | getenv(...) : String |
| RuntimeExecTest.java:32:43:32:55 | commandArray2 | RuntimeExecTest.java:17:25:17:51 | getenv(...) : String | RuntimeExecTest.java:32:43:32:55 | commandArray2 | Call to dangerous java.lang.Runtime.exec() with command '$@' with arg from untrusted input '$@' | RuntimeExecTest.java:30:36:30:44 | "/bin/sh" | "/bin/sh" | RuntimeExecTest.java:17:25:17:51 | getenv(...) | getenv(...) : String |
| RuntimeExecTest.java:36:21:39:44 | toArray(...) | RuntimeExecTest.java:17:25:17:51 | getenv(...) : String | RuntimeExecTest.java:36:21:39:44 | toArray(...) | Call to dangerous java.lang.Runtime.exec() with command '$@' with arg from untrusted input '$@' | RuntimeExecTest.java:37:52:37:60 | "/bin/sh" | "/bin/sh" | RuntimeExecTest.java:17:25:17:51 | getenv(...) | getenv(...) : String |

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-078/CommandInjectionRuntimeExecLocal.ql

View File

@@ -0,0 +1,47 @@
/* 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() {
System.out.println("Command injection test");
String script = System.getenv("SCRIPTNAME");
if (script != null) {
try {
// 1. array literal in the args
Runtime.getRuntime().exec(new String[]{"/bin/sh", script});
// 2. array literal with dataflow
String[] commandArray1 = new String[]{"/bin/sh", script};
Runtime.getRuntime().exec(commandArray1);
// 3. array assignment after it is created
String[] commandArray2 = new String[4];
commandArray2[0] = "/bin/sh";
commandArray2[1] = script;
Runtime.getRuntime().exec(commandArray2);
// 4. Stream concatenation
Runtime.getRuntime().exec(
Stream.concat(
Arrays.stream(new String[]{"/bin/sh"}),
Arrays.stream(new String[]{script})
).toArray(String[]::new)
);
} catch (Exception e) {
System.err.println("ERROR: " + e.getMessage());
}
}
}
}

View File

@@ -0,0 +1 @@
class Empty { }

View File

@@ -0,0 +1,5 @@
| default |
| remote |
| request |
| response |
| uri-path |

View File

@@ -0,0 +1,5 @@
import semmle.code.java.dataflow.ExternalFlowConfiguration as ExternalFlowConfiguration
query predicate supportedThreatModels(string kind) {
ExternalFlowConfiguration::sourceModelKindConfig(kind)
}

View File

@@ -0,0 +1,10 @@
| cli |
| database |
| default |
| environment |
| file |
| local |
| remote |
| request |
| response |
| uri-path |

View File

@@ -0,0 +1,7 @@
extensions:
- addsTo:
pack: codeql/java-all
extensible: supportedThreatModels
data:
- ["local"] # Add the "local" group threat model.

View File

@@ -0,0 +1,5 @@
import semmle.code.java.dataflow.ExternalFlowConfiguration as ExternalFlowConfiguration
query predicate supportedThreatModels(string kind) {
ExternalFlowConfiguration::sourceModelKindConfig(kind)
}