mirror of
https://github.com/github/codeql.git
synced 2025-12-22 11:46:32 +01:00
160 lines
5.5 KiB
Plaintext
160 lines
5.5 KiB
Plaintext
/**
|
|
* Provides default sources, sinks and sanitizers for reasoning about
|
|
* shell command constructed from library input vulnerabilities, as
|
|
* well as extension points for adding your own.
|
|
*/
|
|
|
|
private import python
|
|
private import semmle.python.dataflow.new.DataFlow
|
|
private import semmle.python.dataflow.new.TaintTracking
|
|
private import CommandInjectionCustomizations::CommandInjection as CommandInjection
|
|
private import semmle.python.Concepts as Concepts
|
|
|
|
/**
|
|
* Module containing sources, sinks, and sanitizers for shell command constructed from library input.
|
|
*/
|
|
module UnsafeShellCommandConstruction {
|
|
/** A source for shell command constructed from library input vulnerabilities. */
|
|
abstract class Source extends DataFlow::Node { }
|
|
|
|
private import semmle.python.frameworks.Setuptools
|
|
|
|
/** An input parameter to a gem seen as a source. */
|
|
private class LibraryInputAsSource extends Source instanceof DataFlow::ParameterNode {
|
|
LibraryInputAsSource() {
|
|
this = Setuptools::getALibraryInput() and
|
|
not this.getParameter().getName().matches(["cmd%", "command%", "%_command", "%_cmd"])
|
|
}
|
|
}
|
|
|
|
/** A sink for shell command constructed from library input vulnerabilities. */
|
|
abstract class Sink extends DataFlow::Node {
|
|
Sink() { not this.asExpr() instanceof StrConst } // filter out string constants, makes testing easier
|
|
|
|
/** Gets a description of how the string in this sink was constructed. */
|
|
abstract string describe();
|
|
|
|
/** Gets the dataflow node where the string is constructed. */
|
|
DataFlow::Node getStringConstruction() { result = this }
|
|
|
|
/** Gets the dataflow node that executed the string as a shell command. */
|
|
abstract DataFlow::Node getCommandExecution();
|
|
}
|
|
|
|
/** Holds if the string constructed at `source` is executed at `shellExec` */
|
|
predicate isUsedAsShellCommand(DataFlow::Node source, Concepts::SystemCommandExecution shellExec) {
|
|
source = backtrackShellExec(TypeTracker::TypeBackTracker::end(), shellExec)
|
|
}
|
|
|
|
import semmle.python.dataflow.new.TypeTracker as TypeTracker
|
|
|
|
private DataFlow::LocalSourceNode backtrackShellExec(
|
|
TypeTracker::TypeBackTracker t, Concepts::SystemCommandExecution shellExec
|
|
) {
|
|
t.start() and
|
|
result = any(DataFlow::Node n | shellExec.isShellInterpreted(n)).getALocalSource()
|
|
or
|
|
exists(TypeTracker::TypeBackTracker t2 |
|
|
result = backtrackShellExec(t2, shellExec).backtrack(t2, t)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A string constructed from a string-literal (e.g. `f'foo {sink}'`),
|
|
* where the resulting string ends up being executed as a shell command.
|
|
*/
|
|
class StringInterpolationAsSink extends Sink {
|
|
Concepts::SystemCommandExecution s;
|
|
Fstring fstring;
|
|
|
|
StringInterpolationAsSink() {
|
|
isUsedAsShellCommand(DataFlow::exprNode(fstring), s) and
|
|
this.asExpr() = fstring.getASubExpression()
|
|
}
|
|
|
|
override string describe() { result = "f-string" }
|
|
|
|
override DataFlow::Node getCommandExecution() { result = s }
|
|
|
|
override DataFlow::Node getStringConstruction() { result.asExpr() = fstring }
|
|
}
|
|
|
|
/**
|
|
* A component of a string-concatenation (e.g. `"foo " + sink`),
|
|
* where the resulting string ends up being executed as a shell command.
|
|
*/
|
|
class StringConcatAsSink extends Sink {
|
|
Concepts::SystemCommandExecution s;
|
|
BinaryExpr add;
|
|
|
|
StringConcatAsSink() {
|
|
add.getOp() instanceof Add and
|
|
isUsedAsShellCommand(any(DataFlow::Node n | n.asExpr() = add), s) and
|
|
this.asExpr() = add.getASubExpression()
|
|
}
|
|
|
|
override DataFlow::Node getCommandExecution() { result = s }
|
|
|
|
override string describe() { result = "string concatenation" }
|
|
|
|
override DataFlow::Node getStringConstruction() { result.asExpr() = add }
|
|
}
|
|
|
|
/**
|
|
* A string constructed using a `" ".join(...)` call, where the resulting string ends up being executed as a shell command.
|
|
*/
|
|
class ArrayJoin extends Sink {
|
|
Concepts::SystemCommandExecution s;
|
|
DataFlow::MethodCallNode call;
|
|
|
|
ArrayJoin() {
|
|
call.getMethodName() = "join" and
|
|
unique( | | call.getArg(_)).asExpr().(Str).getText() = " " and
|
|
isUsedAsShellCommand(call, s) and
|
|
(
|
|
this = call.getArg(0) and
|
|
not call.getArg(0).asExpr() instanceof List
|
|
or
|
|
this.asExpr() = call.getArg(0).asExpr().(List).getASubExpression()
|
|
)
|
|
}
|
|
|
|
override string describe() { result = "array" }
|
|
|
|
override DataFlow::Node getCommandExecution() { result = s }
|
|
|
|
override DataFlow::Node getStringConstruction() { result = call }
|
|
}
|
|
|
|
/**
|
|
* A string constructed from a format call,
|
|
* where the resulting string ends up being executed as a shell command.
|
|
* Either a call to `.format(..)` or a string-interpolation with a `%` operator.
|
|
*/
|
|
class TaintedFormatStringAsSink extends Sink {
|
|
Concepts::SystemCommandExecution s;
|
|
DataFlow::Node formatCall;
|
|
|
|
TaintedFormatStringAsSink() {
|
|
(
|
|
formatCall.asExpr().(BinaryExpr).getOp() instanceof Mod and
|
|
this.asExpr() = formatCall.asExpr().(BinaryExpr).getASubExpression()
|
|
or
|
|
formatCall.(DataFlow::MethodCallNode).getMethodName() = "format" and
|
|
this =
|
|
[
|
|
formatCall.(DataFlow::MethodCallNode).getArg(_),
|
|
formatCall.(DataFlow::MethodCallNode).getObject()
|
|
]
|
|
) and
|
|
isUsedAsShellCommand(formatCall, s)
|
|
}
|
|
|
|
override string describe() { result = "formatted string" }
|
|
|
|
override DataFlow::Node getCommandExecution() { result = s }
|
|
|
|
override DataFlow::Node getStringConstruction() { result = formatCall }
|
|
}
|
|
}
|