Skip to content

Commit 7dd1389

Browse files
committed
add twisted SSH client as secondary server command injection sinks, add proper test cases
1 parent ab21990 commit 7dd1389

File tree

4 files changed

+69
-2
lines changed

4 files changed

+69
-2
lines changed

python/ql/src/experimental/semmle/python/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ private import experimental.semmle.python.frameworks.Netmiko
1212
private import experimental.semmle.python.frameworks.Paramiko
1313
private import experimental.semmle.python.frameworks.Pexpect
1414
private import experimental.semmle.python.frameworks.Scrapli
15+
private import experimental.semmle.python.frameworks.Twisted
1516
private import experimental.semmle.python.frameworks.JWT
1617
private import experimental.semmle.python.frameworks.Csv
1718
private import experimental.semmle.python.libraries.PyJWT
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `twisted` PyPI package.
3+
* See https://twistedmatrix.com/.
4+
*/
5+
6+
private import python
7+
private import semmle.python.dataflow.new.DataFlow
8+
private import semmle.python.dataflow.new.RemoteFlowSources
9+
private import semmle.python.dataflow.new.TaintTracking
10+
private import semmle.python.Concepts
11+
private import semmle.python.ApiGraphs
12+
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
13+
import experimental.semmle.python.Concepts
14+
15+
/**
16+
* Provides models for the `twisted` PyPI package.
17+
* See https://twistedmatrix.com/.
18+
*/
19+
private module Twisted {
20+
/**
21+
* The `newConnection` and `existingConnection` functions of `twisted.conch.endpoints.SSHCommandClientEndpoint` class execute command on ssh target server
22+
*/
23+
class ParamikoExecCommand extends SecondaryCommandInjection {
24+
ParamikoExecCommand() {
25+
this =
26+
API::moduleImport("twisted")
27+
.getMember("conch")
28+
.getMember("endpoints")
29+
.getMember("SSHCommandClientEndpoint")
30+
.getMember(["newConnection", "existingConnection"])
31+
.getACall()
32+
.getParameter(1, "command")
33+
.asSink()
34+
}
35+
}
36+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env python
2+
3+
from fastapi import FastAPI
4+
from twisted.conch.endpoints import SSHCommandClientEndpoint
5+
from twisted.internet.protocol import Factory
6+
from twisted.internet import reactor
7+
8+
9+
app = FastAPI()
10+
11+
12+
@app.get("/bad1")
13+
async def bad1(cmd: bytes):
14+
endpoint = SSHCommandClientEndpoint.newConnection(
15+
reactor,
16+
cmd, # $ result=BAD getSecondaryCommand=cmd
17+
b"username",
18+
b"ssh.example.com",
19+
22,
20+
password=b"password")
21+
22+
SSHCommandClientEndpoint.existingConnection(
23+
endpoint,
24+
cmd) # $ result=BAD getSecondaryCommand=cmd
25+
26+
factory = Factory()
27+
d = endpoint.connect(factory)
28+
d.addCallback(lambda protocol: protocol.finished)
29+
30+
return {"success": "Dangerous"}

python/ql/test/experimental/query-tests/Security/CWE-074-SecondaryServerCmdInjection/paramiko.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
@app.get("/bad1")
1515
async def bad1(cmd: str):
1616
stdin, stdout, stderr = paramiko_ssh_client.exec_command(cmd) # $ result=BAD getSecondaryCommand=cmd
17-
return {"success": stdout}
17+
return {"success": "Dangerous"}
1818

1919
@app.get("/bad2")
2020
async def bad2(cmd: str):
2121
stdin, stdout, stderr = paramiko_ssh_client.exec_command(command=cmd) # $ result=BAD getSecondaryCommand=cmd
22-
return {"success": "OK"}
22+
return {"success": "Dangerous"}

0 commit comments

Comments
 (0)