mirror of
https://github.com/github/codeql.git
synced 2026-04-25 08:45:14 +02:00
Merge branch 'main' into starcke/automodel-pack
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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> =
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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"]
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
extensions:
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/java-all
|
||||
extensible: supportedThreatModels
|
||||
data:
|
||||
- ["default"] # The "default" threat model is always included.
|
||||
23
java/ql/lib/ext/threatmodels/threat-model-grouping.model.yml
Normal file
23
java/ql/lib/ext/threatmodels/threat-model-grouping.model.yml
Normal 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"]
|
||||
@@ -16,4 +16,5 @@ dataExtensions:
|
||||
- ext/*.model.yml
|
||||
- ext/generated/*.model.yml
|
||||
- ext/experimental/*.model.yml
|
||||
- ext/threatmodels/*.model.yml
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
private import DataFlowImplSpecific
|
||||
private import codeql.dataflow.DataFlowImpl
|
||||
private import codeql.dataflow.internal.DataFlowImpl
|
||||
import MakeImpl<JavaDataFlow>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
private import DataFlowImplSpecific
|
||||
private import codeql.dataflow.DataFlowImplCommon
|
||||
private import codeql.dataflow.internal.DataFlowImplCommon
|
||||
import MakeImplCommon<JavaDataFlow>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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, _, _, _)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
java/ql/src/Telemetry/AutomodelCandidateFilter.yml
Normal file
5
java/ql/src/Telemetry/AutomodelCandidateFilter.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/java-queries
|
||||
extensible: automodelCandidateFilter
|
||||
data: []
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -12,4 +12,5 @@ dependencies:
|
||||
codeql/util: ${workspace}
|
||||
dataExtensions:
|
||||
- Telemetry/ExtractorInformation.yml
|
||||
- Telemetry/AutomodelCandidateFilter.yml
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-078/CommandInjectionRuntimeExecLocal.ql
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
class Empty { }
|
||||
@@ -0,0 +1,5 @@
|
||||
| default |
|
||||
| remote |
|
||||
| request |
|
||||
| response |
|
||||
| uri-path |
|
||||
@@ -0,0 +1,5 @@
|
||||
import semmle.code.java.dataflow.ExternalFlowConfiguration as ExternalFlowConfiguration
|
||||
|
||||
query predicate supportedThreatModels(string kind) {
|
||||
ExternalFlowConfiguration::sourceModelKindConfig(kind)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
| cli |
|
||||
| database |
|
||||
| default |
|
||||
| environment |
|
||||
| file |
|
||||
| local |
|
||||
| remote |
|
||||
| request |
|
||||
| response |
|
||||
| uri-path |
|
||||
@@ -0,0 +1,7 @@
|
||||
extensions:
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/java-all
|
||||
extensible: supportedThreatModels
|
||||
data:
|
||||
- ["local"] # Add the "local" group threat model.
|
||||
@@ -0,0 +1,5 @@
|
||||
import semmle.code.java.dataflow.ExternalFlowConfiguration as ExternalFlowConfiguration
|
||||
|
||||
query predicate supportedThreatModels(string kind) {
|
||||
ExternalFlowConfiguration::sourceModelKindConfig(kind)
|
||||
}
|
||||
Reference in New Issue
Block a user