mirror of
https://github.com/github/codeql.git
synced 2025-12-18 01:33:15 +01:00
Merge remote-tracking branch 'upstream/main' into 'rc/3.14'
This commit is contained in:
@@ -12,6 +12,29 @@
|
||||
|
||||
import python
|
||||
import Variables.Definition
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
private predicate is_pytest_fixture(Import imp, Variable name) {
|
||||
exists(Alias a, API::Node pytest_fixture, API::Node decorator |
|
||||
pytest_fixture = API::moduleImport("pytest").getMember("fixture") and
|
||||
// The additional `.getReturn()` is to account for the difference between
|
||||
// ```
|
||||
// @pytest.fixture
|
||||
// def foo():
|
||||
// ...
|
||||
// ```
|
||||
// and
|
||||
// ```
|
||||
// @pytest.fixture(some, args, here)
|
||||
// def foo():
|
||||
// ...
|
||||
// ```
|
||||
decorator in [pytest_fixture, pytest_fixture.getReturn()] and
|
||||
a = imp.getAName() and
|
||||
a.getAsname().(Name).getVariable() = name and
|
||||
a.getValue() = decorator.getReturn().getAValueReachableFromSource().asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
predicate global_name_used(Module m, string name) {
|
||||
exists(Name u, GlobalVariable v |
|
||||
@@ -117,6 +140,7 @@ predicate unused_import(Import imp, Variable name) {
|
||||
not all_not_understood(imp.getEnclosingModule()) and
|
||||
not imported_module_used_in_doctest(imp) and
|
||||
not imported_alias_used_in_typehint(imp, name) and
|
||||
not is_pytest_fixture(imp, name) and
|
||||
// Only consider import statements that actually point-to something (possibly an unknown module).
|
||||
// If this is not the case, it's likely that the import statement never gets executed.
|
||||
imp.getAName().getValue().pointsTo(_)
|
||||
|
||||
4
python/ql/src/change-notes/2024-06-04-ssrf-sanitizers.md
Normal file
4
python/ql/src/change-notes/2024-06-04-ssrf-sanitizers.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Additional sanitizers have been added to the `py/full-ssrf` and `py/partial-ssrf` queries for methods that verify a string contains only a certain set of characters, such as `.isalnum()` as well as regular expression tests.
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Processing an unvalidated user input can allow an attacker to inject arbitrary command in your local and remote servers when creating a ssh connection.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
This vulnerability can be prevented by not allowing untrusted user input to be passed as ProxyCommand or exec_command.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the example below, the ProxyCommand and exec_command are controlled by the user and hence leads to a vulnerability.</p>
|
||||
<sample src="paramikoBad.py" />
|
||||
</example>
|
||||
</qhelp>
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* @name RCE with user provided command with paramiko ssh client
|
||||
* @description user provided command can lead to execute code on a external server that can be belong to other users or admins
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 9.3
|
||||
* @precision high
|
||||
* @id py/paramiko-command-injection
|
||||
* @tags security
|
||||
* experimental
|
||||
* external/cwe/cwe-074
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
private API::Node paramikoClient() {
|
||||
result = API::moduleImport("paramiko").getMember("SSHClient").getReturn()
|
||||
}
|
||||
|
||||
private module ParamikoConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
/**
|
||||
* exec_command of `paramiko.SSHClient` class execute command on ssh target server
|
||||
* the `paramiko.ProxyCommand` is equivalent of `ssh -o ProxyCommand="CMD"`
|
||||
* and it run CMD on current system that running the ssh command
|
||||
* the Sink related to proxy command is the `connect` method of `paramiko.SSHClient` class
|
||||
*/
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = paramikoClient().getMember("exec_command").getACall().getParameter(0, "command").asSink()
|
||||
or
|
||||
sink = paramikoClient().getMember("connect").getACall().getParameter(11, "sock").asSink()
|
||||
}
|
||||
|
||||
/**
|
||||
* this additional taint step help taint tracking to find the vulnerable `connect` method of `paramiko.SSHClient` class
|
||||
*/
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(API::CallNode call |
|
||||
call = API::moduleImport("paramiko").getMember("ProxyCommand").getACall() and
|
||||
nodeFrom = call.getParameter(0, "command_line").asSink() and
|
||||
nodeTo = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "paramiko command injection" vulnerabilities. */
|
||||
module ParamikoFlow = TaintTracking::Global<ParamikoConfig>;
|
||||
|
||||
import ParamikoFlow::PathGraph
|
||||
|
||||
from ParamikoFlow::PathNode source, ParamikoFlow::PathNode sink
|
||||
where ParamikoFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This code execution depends on a $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Allowing users to execute arbitrary commands using an SSH connection on a remote server can lead to security issues unless you implement proper authorization.
|
||||
</p>
|
||||
<p>
|
||||
Assume that you connect to a remote system via SSH connection from your main or local server that accepts user-controlled data and has interaction with users that you don't trust, passing these data to SSH API as a part of a command that will be executed on a secondary remote server can lead to security issues. You should consider proper authorization rules very carefully.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
This vulnerability can be prevented by implementing proper authorization rules for untrusted user input that can be passed to your secondary servers.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the example below, the exec_command is controlled by the user and hence leads to a vulnerability.</p>
|
||||
<sample src="paramikoBad.py" />
|
||||
<p>In the example below, the exec_command is controlled by the an Authorized user and hence it is safe.</p>
|
||||
<sample src="paramikoGood.py" />
|
||||
</example>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @name Command execution on a secondary remote server
|
||||
* @description user provided command can lead to execute code on a external server that can be belong to other users or admins
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 9.3
|
||||
* @precision high
|
||||
* @id py/paramiko-command-injection
|
||||
* @tags security
|
||||
* experimental
|
||||
* external/cwe/cwe-074
|
||||
*/
|
||||
|
||||
import python
|
||||
import experimental.semmle.python.security.RemoteCommandExecution
|
||||
import RemoteCommandExecutionFlow::PathGraph
|
||||
|
||||
from RemoteCommandExecutionFlow::PathNode source, RemoteCommandExecutionFlow::PathNode sink
|
||||
where RemoteCommandExecutionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This code execution depends on a $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from flask import request, Flask
|
||||
import paramiko
|
||||
from paramiko import SSHClient
|
||||
|
||||
app = Flask(__name__)
|
||||
paramiko_ssh_client = SSHClient()
|
||||
paramiko_ssh_client.load_system_host_keys()
|
||||
paramiko_ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
paramiko_ssh_client.connect(hostname="127.0.0.1", port="22", username="ssh_user_name", pkey="k", timeout=11, banner_timeout=200)
|
||||
|
||||
|
||||
@app.route('/external_exec_command_1')
|
||||
def withoutAuthorization():
|
||||
user_cmd = request.args.get('command')
|
||||
stdin, stdout, stderr = paramiko_ssh_client.exec_command(user_cmd)
|
||||
return stdout
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.debug = False
|
||||
app.run()
|
||||
|
||||
@@ -12,23 +12,14 @@ paramiko_ssh_client.connect(hostname="127.0.0.1", port="22", username="ssh_user_
|
||||
|
||||
|
||||
@app.route('/external_exec_command_1')
|
||||
def bad1():
|
||||
def withAuthorization():
|
||||
user_cmd = request.args.get('command')
|
||||
auth_jwt = request.args.get('Auth')
|
||||
# validating jwt token first
|
||||
# .... then continue to run the command
|
||||
stdin, stdout, stderr = paramiko_ssh_client.exec_command(user_cmd)
|
||||
return stdout
|
||||
|
||||
@app.route('/external_exec_command_2')
|
||||
def bad2():
|
||||
user_cmd = request.args.get('command')
|
||||
stdin, stdout, stderr = paramiko_ssh_client.exec_command(command=user_cmd)
|
||||
return stdout
|
||||
|
||||
|
||||
@app.route('/proxycommand')
|
||||
def bad2():
|
||||
user_cmd = request.args.get('command')
|
||||
stdin, stdout, stderr = paramiko_ssh_client.connect('hostname', username='user',password='yourpassword',sock=paramiko.ProxyCommand(user_cmd))
|
||||
return stdout
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.debug = False
|
||||
@@ -15,6 +15,39 @@ private import semmle.python.dataflow.new.TaintTracking
|
||||
private import experimental.semmle.python.Frameworks
|
||||
private import semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* A data-flow node that executes an operating system command,
|
||||
* on a remote server likely by SSH connections.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `RemoteCommandExecution::Range` instead.
|
||||
*/
|
||||
class RemoteCommandExecution extends DataFlow::Node instanceof RemoteCommandExecution::Range {
|
||||
/** Holds if a shell interprets `arg`. */
|
||||
predicate isShellInterpreted(DataFlow::Node arg) { super.isShellInterpreted(arg) }
|
||||
|
||||
/** Gets the argument that specifies the command to be executed. */
|
||||
DataFlow::Node getCommand() { result = super.getCommand() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling new remote server command execution APIs. */
|
||||
module RemoteCommandExecution {
|
||||
/**
|
||||
* A data-flow node that executes an operating system command,
|
||||
* on a remote server likely by SSH connections.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `RemoteCommandExecution` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the command to be executed. */
|
||||
abstract DataFlow::Node getCommand();
|
||||
|
||||
/** Holds if a shell interprets `arg`. */
|
||||
predicate isShellInterpreted(DataFlow::Node arg) { none() }
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides classes for modeling copying file related APIs. */
|
||||
module CopyFile {
|
||||
/**
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
* Helper file that imports all framework modeling.
|
||||
*/
|
||||
|
||||
private import experimental.semmle.python.frameworks.AsyncSsh
|
||||
private import experimental.semmle.python.frameworks.Stdlib
|
||||
private import experimental.semmle.python.frameworks.Flask
|
||||
private import experimental.semmle.python.frameworks.Django
|
||||
private import experimental.semmle.python.frameworks.LDAP
|
||||
private import experimental.semmle.python.frameworks.Netmiko
|
||||
private import experimental.semmle.python.frameworks.Paramiko
|
||||
private import experimental.semmle.python.frameworks.Pexpect
|
||||
private import experimental.semmle.python.frameworks.Scrapli
|
||||
private import experimental.semmle.python.frameworks.Twisted
|
||||
private import experimental.semmle.python.frameworks.JWT
|
||||
private import experimental.semmle.python.frameworks.Csv
|
||||
private import experimental.semmle.python.libraries.PyJWT
|
||||
@@ -14,5 +20,6 @@ private import experimental.semmle.python.libraries.Authlib
|
||||
private import experimental.semmle.python.libraries.PythonJose
|
||||
private import experimental.semmle.python.frameworks.CopyFile
|
||||
private import experimental.semmle.python.frameworks.Sendgrid
|
||||
private import experimental.semmle.python.frameworks.Ssh2
|
||||
private import experimental.semmle.python.libraries.FlaskMail
|
||||
private import experimental.semmle.python.libraries.SmtpLib
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `asyncssh` PyPI package.
|
||||
* See https://pypi.org/project/asyncssh/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
import experimental.semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides models for the `asyncssh` PyPI package.
|
||||
* See https://pypi.org/project/asyncssh/.
|
||||
*/
|
||||
private module Asyncssh {
|
||||
/**
|
||||
* Gets `asyncssh` package.
|
||||
*/
|
||||
private API::Node asyncssh() { result = API::moduleImport("asyncssh") }
|
||||
|
||||
/**
|
||||
* A `run` method responsible for executing commands on remote secondary servers.
|
||||
*/
|
||||
class AsyncsshRun extends RemoteCommandExecution::Range, API::CallNode {
|
||||
AsyncsshRun() { this = asyncssh().getMember("connect").getReturn().getMember("run").getACall() }
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `netmiko` PyPI package.
|
||||
* See https://pypi.org/project/netmiko/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
import experimental.semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides models for the `netmiko` PyPI package.
|
||||
* See https://pypi.org/project/netmiko/.
|
||||
*/
|
||||
private module Netmiko {
|
||||
/**
|
||||
* Gets `netmiko` package.
|
||||
*/
|
||||
private API::Node netmiko() { result = API::moduleImport("netmiko") }
|
||||
|
||||
/**
|
||||
* Gets `netmiko.ConnectHandler` return value.
|
||||
*/
|
||||
private API::Node netmikoConnectHandler() {
|
||||
result = netmiko().getMember("ConnectHandler").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* The `send_*` methods responsible for executing commands on remote secondary servers.
|
||||
*/
|
||||
class NetmikoSendCommand extends RemoteCommandExecution::Range, API::CallNode {
|
||||
boolean isMultiline;
|
||||
|
||||
NetmikoSendCommand() {
|
||||
this =
|
||||
netmikoConnectHandler()
|
||||
.getMember(["send_command", "send_command_expect", "send_command_timing"])
|
||||
.getACall() and
|
||||
isMultiline = false
|
||||
or
|
||||
this =
|
||||
netmikoConnectHandler().getMember(["send_multiline", "send_multiline_timing"]).getACall() and
|
||||
isMultiline = true
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result = this.getParameter(0, "command_string").asSink() and
|
||||
isMultiline = false
|
||||
or
|
||||
result = this.getParameter(0, "commands").asSink() and
|
||||
isMultiline = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `paramiko` PyPI package.
|
||||
* See https://pypi.org/project/paramiko/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
import experimental.semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides models for the `paramiko` PyPI package.
|
||||
* See https://pypi.org/project/paramiko/.
|
||||
*/
|
||||
private module Paramiko {
|
||||
/**
|
||||
* Gets `paramiko` package.
|
||||
*/
|
||||
private API::Node paramiko() { result = API::moduleImport("paramiko") }
|
||||
|
||||
/**
|
||||
* Gets `paramiko.SSHClient` return value.
|
||||
*/
|
||||
private API::Node paramikoClient() { result = paramiko().getMember("SSHClient").getReturn() }
|
||||
|
||||
/**
|
||||
* The `exec_command` of `paramiko.SSHClient` class execute command on ssh target server
|
||||
*/
|
||||
class ParamikoExecCommand extends RemoteCommandExecution::Range, API::CallNode {
|
||||
ParamikoExecCommand() { this = paramikoClient().getMember("exec_command").getACall() }
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `pexpect` PyPI package.
|
||||
* See https://pypi.org/project/pexpect/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.ApiGraphs
|
||||
import experimental.semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides models for the `pexpect` PyPI package.
|
||||
* See https://pypi.org/project/pexpect/.
|
||||
*/
|
||||
private module Pexpect {
|
||||
/**
|
||||
* The calls to `pexpect.pxssh.pxssh` functions that execute commands
|
||||
* See https://pexpect.readthedocs.io/en/stable/api/pxssh.html
|
||||
*/
|
||||
class PexpectCommandExec extends RemoteCommandExecution::Range, API::CallNode {
|
||||
PexpectCommandExec() {
|
||||
this =
|
||||
API::moduleImport("pexpect")
|
||||
.getMember("pxssh")
|
||||
.getMember("pxssh")
|
||||
.getReturn()
|
||||
.getMember(["send", "sendline"])
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getParameter(0, "s").asSink() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `scrapli` PyPI package.
|
||||
* See https://pypi.org/project/scrapli/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
import experimental.semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides models for the `scrapli` PyPI package.
|
||||
* See https://pypi.org/project/scrapli/.
|
||||
*/
|
||||
private module Scrapli {
|
||||
/**
|
||||
* Gets `scrapli` package.
|
||||
*/
|
||||
private API::Node scrapli() { result = API::moduleImport("scrapli") }
|
||||
|
||||
/**
|
||||
* Gets `scrapli.driver` package.
|
||||
*/
|
||||
private API::Node scrapliDriver() { result = scrapli().getMember("driver") }
|
||||
|
||||
/**
|
||||
* Gets `scrapli.driver.core` package.
|
||||
*/
|
||||
private API::Node scrapliCore() { result = scrapliDriver().getMember("core") }
|
||||
|
||||
/**
|
||||
* A `send_command` method responsible for executing commands on remote secondary servers.
|
||||
*/
|
||||
class ScrapliSendCommand extends RemoteCommandExecution::Range, API::CallNode {
|
||||
ScrapliSendCommand() {
|
||||
this =
|
||||
scrapliCore()
|
||||
.getMember([
|
||||
"AsyncNXOSDriver", "AsyncJunosDriver", "AsyncEOSDriver", "AsyncIOSXEDriver",
|
||||
"AsyncIOSXRDriver", "NXOSDriver", "JunosDriver", "EOSDriver", "IOSXEDriver",
|
||||
"IOSXRDriver"
|
||||
])
|
||||
.getReturn()
|
||||
.getMember("send_command")
|
||||
.getACall()
|
||||
or
|
||||
this = scrapli().getMember("Scrapli").getReturn().getMember("send_command").getACall()
|
||||
or
|
||||
this =
|
||||
scrapliDriver().getMember("GenericDriver").getReturn().getMember("send_command").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
|
||||
}
|
||||
}
|
||||
39
python/ql/src/experimental/semmle/python/frameworks/Ssh2.qll
Normal file
39
python/ql/src/experimental/semmle/python/frameworks/Ssh2.qll
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `ssh2-python` PyPI package.
|
||||
* See https://pypi.org/project/ssh2-python/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
import experimental.semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides models for the `ssh2-python` PyPI package.
|
||||
* See https://pypi.org/project/ssh2-python/.
|
||||
*/
|
||||
private module Ssh2 {
|
||||
/**
|
||||
* Gets `ssh2` package.
|
||||
*/
|
||||
private API::Node ssh2() { result = API::moduleImport("ssh2") }
|
||||
|
||||
/**
|
||||
* Gets `ssh2.session.Session` return value.
|
||||
*/
|
||||
private API::Node ssh2Session() {
|
||||
result = ssh2().getMember("session").getMember("Session").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* An `execute` method responsible for executing commands on remote secondary servers.
|
||||
*/
|
||||
class Ssh2Execute extends RemoteCommandExecution::Range, API::CallNode {
|
||||
Ssh2Execute() {
|
||||
this = ssh2Session().getMember("open_session").getReturn().getMember("execute").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `twisted` PyPI package.
|
||||
* See https://twistedmatrix.com/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
import experimental.semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides models for the `twisted` PyPI package.
|
||||
* See https://docs.twistedmatrix.com/en/stable/api/twisted.conch.endpoints.SSHCommandClientEndpoint.html
|
||||
*/
|
||||
private module Twisted {
|
||||
/**
|
||||
* The `newConnection` and `existingConnection` functions of `twisted.conch.endpoints.SSHCommandClientEndpoint` class execute command on ssh target server
|
||||
*/
|
||||
class ParamikoExecCommand extends RemoteCommandExecution::Range, API::CallNode {
|
||||
ParamikoExecCommand() {
|
||||
this =
|
||||
API::moduleImport("twisted")
|
||||
.getMember("conch")
|
||||
.getMember("endpoints")
|
||||
.getMember("SSHCommandClientEndpoint")
|
||||
.getMember(["newConnection", "existingConnection"])
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getParameter(1, "command").asSink() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import python
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
import semmle.python.ApiGraphs
|
||||
import semmle.python.dataflow.new.internal.DataFlowPublic
|
||||
import codeql.util.Unit
|
||||
import experimental.semmle.python.Concepts
|
||||
|
||||
module RemoteCommandExecutionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink = any(RemoteCommandExecution rce).getCommand() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "secondary server command injection" vulnerabilities. */
|
||||
module RemoteCommandExecutionFlow = TaintTracking::Global<RemoteCommandExecutionConfig>;
|
||||
Reference in New Issue
Block a user