Skip to content

Commit 52a8091

Browse files
committed
SecondaryCommandInjection to RemoteCommandExecution, change RemoteCommandExecution to module like SystemCommandExecution module
1 parent fd9e6f4 commit 52a8091

File tree

8 files changed

+55
-64
lines changed

8 files changed

+55
-64
lines changed

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,23 @@ private import semmle.python.dataflow.new.TaintTracking
1515
private import experimental.semmle.python.Frameworks
1616
private import semmle.python.Concepts
1717

18-
/**
19-
* A data-flow node that responsible for a command that can be executed on a secondary remote system,
20-
*
21-
* Extend this class to model new APIs.
22-
*/
23-
abstract class SecondaryCommandInjection extends DataFlow::Node { }
18+
/** Provides classes for modeling remote server command execution related APIs. */
19+
module RemoteCommandExecution {
20+
/**
21+
* A data-flow node that executes an operating system command,
22+
* on a remote server likely by SSH connections.
23+
*
24+
* Extend this class to model new APIs. If you want to refine existing API models,
25+
* extend `SystemCommandExecution` instead.
26+
*/
27+
abstract class Range extends DataFlow::Node {
28+
/** Gets the argument that specifies the command to be executed. */
29+
abstract DataFlow::Node getCommand();
30+
31+
/** Holds if a shell interprets `arg`. */
32+
predicate isShellInterpreted(DataFlow::Node arg) { none() }
33+
}
34+
}
2435

2536
/** Provides classes for modeling copying file related APIs. */
2637
module CopyFile {

python/ql/src/experimental/semmle/python/frameworks/AsyncSsh.qll

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,9 @@ private module Asyncssh {
2222
/**
2323
* A `run` method responsible for executing commands on remote secondary servers.
2424
*/
25-
class AsyncsshRun extends SecondaryCommandInjection {
26-
AsyncsshRun() {
27-
this =
28-
asyncssh()
29-
.getMember("connect")
30-
.getReturn()
31-
.getMember("run")
32-
.getACall()
33-
.getParameter(0, "command")
34-
.asSink()
35-
}
25+
class AsyncsshRun extends RemoteCommandExecution::Range, API::CallNode {
26+
AsyncsshRun() { this = asyncssh().getMember("connect").getReturn().getMember("run").getACall() }
27+
28+
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
3629
}
3730
}

python/ql/src/experimental/semmle/python/frameworks/Netmiko.qll

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,27 @@ private module Netmiko {
2929
/**
3030
* The `send_*` methods responsible for executing commands on remote secondary servers.
3131
*/
32-
class NetmikoSendCommand extends SecondaryCommandInjection {
32+
class NetmikoSendCommand extends RemoteCommandExecution::Range, API::CallNode {
33+
boolean isMultiline;
34+
3335
NetmikoSendCommand() {
3436
this =
3537
netmikoConnectHandler()
3638
.getMember(["send_command", "send_command_expect", "send_command_timing"])
37-
.getACall()
38-
.getParameter(0, "command_string")
39-
.asSink()
39+
.getACall() and
40+
isMultiline = false
4041
or
4142
this =
42-
netmikoConnectHandler()
43-
.getMember(["send_multiline", "send_multiline_timing"])
44-
.getACall()
45-
.getParameter(0, "commands")
46-
.asSink()
43+
netmikoConnectHandler().getMember(["send_multiline", "send_multiline_timing"]).getACall() and
44+
isMultiline = true
45+
}
46+
47+
override DataFlow::Node getCommand() {
48+
result = this.getParameter(0, "command_string").asSink() and
49+
isMultiline = false
50+
or
51+
result = this.getParameter(0, "commands").asSink() and
52+
isMultiline = true
4753
}
4854
}
4955
}

python/ql/src/experimental/semmle/python/frameworks/Paramiko.qll

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,9 @@ private module Paramiko {
2727
/**
2828
* The `exec_command` of `paramiko.SSHClient` class execute command on ssh target server
2929
*/
30-
class ParamikoExecCommand extends SecondaryCommandInjection {
31-
ParamikoExecCommand() {
32-
this =
33-
paramikoClient().getMember("exec_command").getACall().getParameter(0, "command").asSink()
34-
}
30+
class ParamikoExecCommand extends RemoteCommandExecution::Range, API::CallNode {
31+
ParamikoExecCommand() { this = paramikoClient().getMember("exec_command").getACall() }
32+
33+
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
3534
}
3635
}

python/ql/src/experimental/semmle/python/frameworks/Pexpect.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ private module Pexpect {
1717
* The calls to `pexpect.pxssh.pxssh` functions that execute commands
1818
* See https://pexpect.readthedocs.io/en/stable/api/pxssh.html
1919
*/
20-
class PexpectCommandExec extends SecondaryCommandInjection {
20+
class PexpectCommandExec extends RemoteCommandExecution::Range, API::CallNode {
2121
PexpectCommandExec() {
2222
this =
2323
API::moduleImport("pexpect")
@@ -26,8 +26,8 @@ private module Pexpect {
2626
.getReturn()
2727
.getMember(["send", "sendline"])
2828
.getACall()
29-
.getParameter(0, "s")
30-
.asSink()
3129
}
30+
31+
override DataFlow::Node getCommand() { result = this.getParameter(0, "s").asSink() }
3232
}
3333
}

python/ql/src/experimental/semmle/python/frameworks/Scrapli.qll

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ private module Scrapli {
3232
/**
3333
* A `send_command` method responsible for executing commands on remote secondary servers.
3434
*/
35-
class ScrapliSendCommand extends SecondaryCommandInjection {
35+
class ScrapliSendCommand extends RemoteCommandExecution::Range, API::CallNode {
3636
ScrapliSendCommand() {
3737
this =
3838
scrapliCore()
@@ -44,26 +44,13 @@ private module Scrapli {
4444
.getReturn()
4545
.getMember("send_command")
4646
.getACall()
47-
.getParameter(0, "command")
48-
.asSink()
4947
or
50-
this =
51-
scrapli()
52-
.getMember("Scrapli")
53-
.getReturn()
54-
.getMember("send_command")
55-
.getACall()
56-
.getParameter(0, "command")
57-
.asSink()
48+
this = scrapli().getMember("Scrapli").getReturn().getMember("send_command").getACall()
5849
or
5950
this =
60-
scrapliDriver()
61-
.getMember("GenericDriver")
62-
.getReturn()
63-
.getMember("send_command")
64-
.getACall()
65-
.getParameter(0, "command")
66-
.asSink()
51+
scrapliDriver().getMember("GenericDriver").getReturn().getMember("send_command").getACall()
6752
}
53+
54+
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
6855
}
6956
}

python/ql/src/experimental/semmle/python/frameworks/Ssh2.qll

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,11 @@ private module Ssh2 {
2929
/**
3030
* An `execute` method responsible for executing commands on remote secondary servers.
3131
*/
32-
class Ssh2Execute extends SecondaryCommandInjection {
32+
class Ssh2Execute extends RemoteCommandExecution::Range, API::CallNode {
3333
Ssh2Execute() {
34-
this =
35-
ssh2Session()
36-
.getMember("open_session")
37-
.getReturn()
38-
.getMember("execute")
39-
.getACall()
40-
.getParameter(0, "command")
41-
.asSink()
34+
this = ssh2Session().getMember("open_session").getReturn().getMember("execute").getACall()
4235
}
36+
37+
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
4338
}
4439
}

python/ql/src/experimental/semmle/python/frameworks/Twisted.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ private module Twisted {
1919
/**
2020
* The `newConnection` and `existingConnection` functions of `twisted.conch.endpoints.SSHCommandClientEndpoint` class execute command on ssh target server
2121
*/
22-
class ParamikoExecCommand extends SecondaryCommandInjection {
22+
class ParamikoExecCommand extends RemoteCommandExecution::Range, API::CallNode {
2323
ParamikoExecCommand() {
2424
this =
2525
API::moduleImport("twisted")
@@ -28,8 +28,8 @@ private module Twisted {
2828
.getMember("SSHCommandClientEndpoint")
2929
.getMember(["newConnection", "existingConnection"])
3030
.getACall()
31-
.getParameter(1, "command")
32-
.asSink()
3331
}
32+
33+
override DataFlow::Node getCommand() { result = this.getParameter(1, "command").asSink() }
3434
}
3535
}

0 commit comments

Comments
 (0)