mirror of
https://github.com/github/codeql.git
synced 2026-04-25 08:45:14 +02:00
Python: initial support for CMDi via asyncio
This commit is contained in:
@@ -4396,6 +4396,145 @@ private module StdlibPrivate {
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// asyncio
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `asyncio` module. */
|
||||
API::Node asyncio() { result = API::moduleImport("asyncio") }
|
||||
|
||||
/** Provides models for the `asyncio` module. */
|
||||
module AsyncIO {
|
||||
/**
|
||||
* A call to the `asyncio.create_subprocess_exec` function (also accessible via the `subprocess` module of `asyncio`)
|
||||
*
|
||||
* See https://docs.python.org/3/library/asyncio-subprocess.html#creating-subprocesses
|
||||
*/
|
||||
private class CreateSubprocessExec extends SystemCommandExecution::Range,
|
||||
FileSystemAccess::Range, DataFlow::CallCfgNode
|
||||
{
|
||||
CreateSubprocessExec() {
|
||||
exists(string name |
|
||||
name = "create_subprocess_exec" and
|
||||
(
|
||||
this = asyncio().getMember(name).getACall()
|
||||
or
|
||||
this = asyncio().getMember("subprocess").getMember(name).getACall()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result = this.getArg(0)
|
||||
or
|
||||
result = this.getArgByName("program")
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
none() // this is a safe API.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `asyncio.create_subprocess_shell` function (also in the `subprocess` module of `asyncio`)
|
||||
*
|
||||
* See https://docs.python.org/3/library/asyncio-subprocess.html#creating-subprocesses
|
||||
*/
|
||||
private class CreateSubprocessShell extends SystemCommandExecution::Range,
|
||||
FileSystemAccess::Range, DataFlow::CallCfgNode
|
||||
{
|
||||
CreateSubprocessShell() {
|
||||
exists(string name |
|
||||
name = "create_subprocess_shell" and
|
||||
(
|
||||
this = asyncio().getMember(name).getACall()
|
||||
or
|
||||
this = asyncio().getMember("subprocess").getMember(name).getACall()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result = this.getArg(0)
|
||||
or
|
||||
result = this.getArgByName("cmd")
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A source for an event loop (an object with basetype `AbstractEventLoop`).
|
||||
*
|
||||
* See https://docs.python.org/3/library/asyncio-eventloop.html
|
||||
*/
|
||||
private class EventLoopSource extends DataFlow::LocalSourceNode, DataFlow::CallCfgNode {
|
||||
EventLoopSource() {
|
||||
this = asyncio().getMember("get_running_loop").getACall()
|
||||
or
|
||||
this = asyncio().getMember("get_event_loop").getACall() // deprecated in Python 3.10.0 and later
|
||||
or
|
||||
this = asyncio().getMember("new_event_loop").getACall()
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a reference to an event loop instance. */
|
||||
private DataFlow::TypeTrackingNode eventLoopInstance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof EventLoopSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = eventLoopInstance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an event loop instance. */
|
||||
DataFlow::Node eventLoopInstance() {
|
||||
eventLoopInstance(DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `subprocess_exec` on an event loop instance.
|
||||
*
|
||||
* See https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.subprocess_exec
|
||||
*/
|
||||
private class EventLoopSubprocessExec extends DataFlow::MethodCallNode,
|
||||
SystemCommandExecution::Range, FileSystemAccess::Range
|
||||
{
|
||||
EventLoopSubprocessExec() { this.calls(eventLoopInstance(), "subprocess_exec") }
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getArg(1) }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
none() // this is a safe API.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `subprocess_shell` on an event loop instance.
|
||||
*
|
||||
* See https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.subprocess_shell
|
||||
*/
|
||||
private class EventLoopSubprocessShell extends DataFlow::MethodCallNode,
|
||||
SystemCommandExecution::Range, FileSystemAccess::Range
|
||||
{
|
||||
EventLoopSubprocessShell() { this.calls(eventLoopInstance(), "subprocess_shell") }
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result = this.getArg(1)
|
||||
or
|
||||
result = this.getArgByName("cmd")
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -8,6 +8,12 @@ edges
|
||||
| command_injection.py:5:26:5:32 | GSSA Variable request | command_injection.py:54:15:54:21 | ControlFlowNode for request |
|
||||
| command_injection.py:5:26:5:32 | GSSA Variable request | command_injection.py:71:12:71:18 | ControlFlowNode for request |
|
||||
| command_injection.py:5:26:5:32 | GSSA Variable request | command_injection.py:78:12:78:18 | ControlFlowNode for request |
|
||||
| command_injection.py:5:26:5:32 | GSSA Variable request | command_injection.py:87:13:87:19 | ControlFlowNode for request |
|
||||
| command_injection.py:5:26:5:32 | GSSA Variable request | command_injection.py:92:13:92:19 | ControlFlowNode for request |
|
||||
| command_injection.py:5:26:5:32 | GSSA Variable request | command_injection.py:102:13:102:19 | ControlFlowNode for request |
|
||||
| command_injection.py:5:26:5:32 | GSSA Variable request | command_injection.py:112:13:112:19 | ControlFlowNode for request |
|
||||
| command_injection.py:5:26:5:32 | GSSA Variable request | command_injection.py:117:13:117:19 | ControlFlowNode for request |
|
||||
| command_injection.py:5:26:5:32 | GSSA Variable request | command_injection.py:122:13:122:19 | ControlFlowNode for request |
|
||||
| command_injection.py:11:5:11:9 | SSA variable files | command_injection.py:13:15:13:27 | ControlFlowNode for BinaryExpr |
|
||||
| command_injection.py:11:13:11:19 | ControlFlowNode for request | command_injection.py:11:13:11:24 | ControlFlowNode for Attribute |
|
||||
| command_injection.py:11:13:11:24 | ControlFlowNode for Attribute | command_injection.py:11:13:11:41 | ControlFlowNode for Attribute() |
|
||||
@@ -45,6 +51,30 @@ edges
|
||||
| command_injection.py:78:12:78:18 | ControlFlowNode for request | command_injection.py:78:12:78:23 | ControlFlowNode for Attribute |
|
||||
| command_injection.py:78:12:78:23 | ControlFlowNode for Attribute | command_injection.py:78:12:78:39 | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:78:12:78:39 | ControlFlowNode for Attribute() | command_injection.py:78:5:78:8 | SSA variable path |
|
||||
| command_injection.py:87:5:87:9 | SSA variable files | command_injection.py:88:48:88:52 | ControlFlowNode for files |
|
||||
| command_injection.py:87:13:87:19 | ControlFlowNode for request | command_injection.py:87:13:87:24 | ControlFlowNode for Attribute |
|
||||
| command_injection.py:87:13:87:24 | ControlFlowNode for Attribute | command_injection.py:87:13:87:41 | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:87:13:87:41 | ControlFlowNode for Attribute() | command_injection.py:87:5:87:9 | SSA variable files |
|
||||
| command_injection.py:92:5:92:9 | SSA variable files | command_injection.py:93:51:93:55 | ControlFlowNode for files |
|
||||
| command_injection.py:92:13:92:19 | ControlFlowNode for request | command_injection.py:92:13:92:24 | ControlFlowNode for Attribute |
|
||||
| command_injection.py:92:13:92:24 | ControlFlowNode for Attribute | command_injection.py:92:13:92:41 | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:92:13:92:41 | ControlFlowNode for Attribute() | command_injection.py:92:5:92:9 | SSA variable files |
|
||||
| command_injection.py:102:5:102:9 | SSA variable files | command_injection.py:106:82:106:86 | ControlFlowNode for files |
|
||||
| command_injection.py:102:13:102:19 | ControlFlowNode for request | command_injection.py:102:13:102:24 | ControlFlowNode for Attribute |
|
||||
| command_injection.py:102:13:102:24 | ControlFlowNode for Attribute | command_injection.py:102:13:102:41 | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:102:13:102:41 | ControlFlowNode for Attribute() | command_injection.py:102:5:102:9 | SSA variable files |
|
||||
| command_injection.py:112:5:112:9 | SSA variable files | command_injection.py:113:49:113:53 | ControlFlowNode for files |
|
||||
| command_injection.py:112:13:112:19 | ControlFlowNode for request | command_injection.py:112:13:112:24 | ControlFlowNode for Attribute |
|
||||
| command_injection.py:112:13:112:24 | ControlFlowNode for Attribute | command_injection.py:112:13:112:41 | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:112:13:112:41 | ControlFlowNode for Attribute() | command_injection.py:112:5:112:9 | SSA variable files |
|
||||
| command_injection.py:117:5:117:9 | SSA variable files | command_injection.py:118:52:118:56 | ControlFlowNode for files |
|
||||
| command_injection.py:117:13:117:19 | ControlFlowNode for request | command_injection.py:117:13:117:24 | ControlFlowNode for Attribute |
|
||||
| command_injection.py:117:13:117:24 | ControlFlowNode for Attribute | command_injection.py:117:13:117:41 | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:117:13:117:41 | ControlFlowNode for Attribute() | command_injection.py:117:5:117:9 | SSA variable files |
|
||||
| command_injection.py:122:5:122:9 | SSA variable files | command_injection.py:125:83:125:87 | ControlFlowNode for files |
|
||||
| command_injection.py:122:13:122:19 | ControlFlowNode for request | command_injection.py:122:13:122:24 | ControlFlowNode for Attribute |
|
||||
| command_injection.py:122:13:122:24 | ControlFlowNode for Attribute | command_injection.py:122:13:122:41 | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:122:13:122:41 | ControlFlowNode for Attribute() | command_injection.py:122:5:122:9 | SSA variable files |
|
||||
nodes
|
||||
| command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
|
||||
| command_injection.py:5:26:5:32 | GSSA Variable request | semmle.label | GSSA Variable request |
|
||||
@@ -93,6 +123,36 @@ nodes
|
||||
| command_injection.py:78:12:78:23 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:78:12:78:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:80:19:80:30 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
|
||||
| command_injection.py:87:5:87:9 | SSA variable files | semmle.label | SSA variable files |
|
||||
| command_injection.py:87:13:87:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| command_injection.py:87:13:87:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:87:13:87:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:88:48:88:52 | ControlFlowNode for files | semmle.label | ControlFlowNode for files |
|
||||
| command_injection.py:92:5:92:9 | SSA variable files | semmle.label | SSA variable files |
|
||||
| command_injection.py:92:13:92:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| command_injection.py:92:13:92:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:92:13:92:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:93:51:93:55 | ControlFlowNode for files | semmle.label | ControlFlowNode for files |
|
||||
| command_injection.py:102:5:102:9 | SSA variable files | semmle.label | SSA variable files |
|
||||
| command_injection.py:102:13:102:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| command_injection.py:102:13:102:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:102:13:102:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:106:82:106:86 | ControlFlowNode for files | semmle.label | ControlFlowNode for files |
|
||||
| command_injection.py:112:5:112:9 | SSA variable files | semmle.label | SSA variable files |
|
||||
| command_injection.py:112:13:112:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| command_injection.py:112:13:112:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:112:13:112:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:113:49:113:53 | ControlFlowNode for files | semmle.label | ControlFlowNode for files |
|
||||
| command_injection.py:117:5:117:9 | SSA variable files | semmle.label | SSA variable files |
|
||||
| command_injection.py:117:13:117:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| command_injection.py:117:13:117:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:117:13:117:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:118:52:118:56 | ControlFlowNode for files | semmle.label | ControlFlowNode for files |
|
||||
| command_injection.py:122:5:122:9 | SSA variable files | semmle.label | SSA variable files |
|
||||
| command_injection.py:122:13:122:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| command_injection.py:122:13:122:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:122:13:122:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| command_injection.py:125:83:125:87 | ControlFlowNode for files | semmle.label | ControlFlowNode for files |
|
||||
subpaths
|
||||
#select
|
||||
| command_injection.py:13:15:13:27 | ControlFlowNode for BinaryExpr | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | command_injection.py:13:15:13:27 | ControlFlowNode for BinaryExpr | This command line depends on a $@. | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
@@ -108,3 +168,9 @@ subpaths
|
||||
| command_injection.py:59:20:59:26 | ControlFlowNode for command | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | command_injection.py:59:20:59:26 | ControlFlowNode for command | This command line depends on a $@. | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| command_injection.py:73:19:73:30 | ControlFlowNode for BinaryExpr | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | command_injection.py:73:19:73:30 | ControlFlowNode for BinaryExpr | This command line depends on a $@. | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| command_injection.py:80:19:80:30 | ControlFlowNode for BinaryExpr | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | command_injection.py:80:19:80:30 | ControlFlowNode for BinaryExpr | This command line depends on a $@. | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| command_injection.py:88:48:88:52 | ControlFlowNode for files | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | command_injection.py:88:48:88:52 | ControlFlowNode for files | This command line depends on a $@. | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| command_injection.py:93:51:93:55 | ControlFlowNode for files | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | command_injection.py:93:51:93:55 | ControlFlowNode for files | This command line depends on a $@. | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| command_injection.py:106:82:106:86 | ControlFlowNode for files | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | command_injection.py:106:82:106:86 | ControlFlowNode for files | This command line depends on a $@. | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| command_injection.py:113:49:113:53 | ControlFlowNode for files | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | command_injection.py:113:49:113:53 | ControlFlowNode for files | This command line depends on a $@. | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| command_injection.py:118:52:118:56 | ControlFlowNode for files | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | command_injection.py:118:52:118:56 | ControlFlowNode for files | This command line depends on a $@. | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| command_injection.py:125:83:125:87 | ControlFlowNode for files | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | command_injection.py:125:83:125:87 | ControlFlowNode for files | This command line depends on a $@. | command_injection.py:5:26:5:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
missingAnnotationOnSink
|
||||
failures
|
||||
missingAnnotationOnSink
|
||||
testFailures
|
||||
|
||||
@@ -78,3 +78,50 @@ def restricted_characters():
|
||||
path = request.args.get('path', '')
|
||||
if re.match(r'^[a-zA-Z0-9_-]+$', path):
|
||||
os.system("ls " + path) # $SPURIOUS: result=BAD
|
||||
|
||||
import asyncio
|
||||
from asyncio import subprocess
|
||||
|
||||
@app.route("/asyncio-exec1")
|
||||
def asyncio_exec_command_injection1():
|
||||
files = request.args.get('files', '')
|
||||
asyncio.run(asyncio.create_subprocess_exec(files)) # $result=BAD
|
||||
|
||||
@app.route("/asyncio-exec2")
|
||||
def asyncio_exec_command_injection2():
|
||||
files = request.args.get('files', '')
|
||||
asyncio.run(subprocess.create_subprocess_exec(files)) # $result=BAD
|
||||
|
||||
@app.route("/asyncio-exec-args")
|
||||
def asyncio_exec_arg_injection():
|
||||
files = request.args.get('files', '')
|
||||
asyncio.run(asyncio.create_subprocess_exec("ls", files)) # $result=OK - only an argument injection, not a command injection
|
||||
|
||||
@app.route("/asyncio-eventloop-command1")
|
||||
def asyncio_eventloop_exec_command_injection1():
|
||||
files = request.args.get('files', '')
|
||||
args = ["-a", "-l"]
|
||||
loop = asyncio.new_event_loop()
|
||||
try:
|
||||
loop.run_until_complete(loop.subprocess_exec(asyncio.SubprocessProtocol, files, *args)) # $result=BAD
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
@app.route("/asyncio-shell1")
|
||||
def asyncio_shell_command_injection1():
|
||||
files = request.args.get('files', '')
|
||||
asyncio.run(asyncio.create_subprocess_shell(files)) # $result=BAD
|
||||
|
||||
@app.route("/asyncio-shell2")
|
||||
def asyncio_shell_command_injection1():
|
||||
files = request.args.get('files', '')
|
||||
asyncio.run(subprocess.create_subprocess_shell(files)) # $result=BAD
|
||||
|
||||
@app.route("/asyncio-eventloop-shell1")
|
||||
def asyncio_eventloop_shell_command_injection1():
|
||||
files = request.args.get('files', '')
|
||||
loop = asyncio.new_event_loop()
|
||||
try:
|
||||
loop.run_until_complete(loop.subprocess_shell(asyncio.SubprocessProtocol, files)) # $result=BAD
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
Reference in New Issue
Block a user