Skip to content

Commit 6299b73

Browse files
committed
Python: Move CommandInjection configuration to own file
1 parent 7c04c59 commit 6299b73

2 files changed

Lines changed: 52 additions & 42 deletions

File tree

python/ql/src/Security/CWE-078/CommandInjection.ql

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,50 +15,9 @@
1515
*/
1616

1717
import python
18-
import semmle.python.dataflow.new.DataFlow
19-
import semmle.python.dataflow.new.TaintTracking
20-
import semmle.python.Concepts
21-
import semmle.python.dataflow.new.RemoteFlowSources
18+
import semmle.python.security.dataflow.CommandInjection
2219
import DataFlow::PathGraph
2320

24-
class CommandInjectionConfiguration extends TaintTracking::Configuration {
25-
CommandInjectionConfiguration() { this = "CommandInjectionConfiguration" }
26-
27-
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
28-
29-
override predicate isSink(DataFlow::Node sink) {
30-
sink = any(SystemCommandExecution e).getCommand() and
31-
// Since the implementation of standard library functions such `os.popen` looks like
32-
// ```py
33-
// def popen(cmd, mode="r", buffering=-1):
34-
// ...
35-
// proc = subprocess.Popen(cmd, ...)
36-
// ```
37-
// any time we would report flow to the `os.popen` sink, we can ALSO report the flow
38-
// from the `cmd` parameter to the `subprocess.Popen` sink -- obviously we don't
39-
// want that.
40-
//
41-
// However, simply removing taint edges out of a sink is not a good enough solution,
42-
// since we would only flag one of the `os.system` calls in the following example
43-
// due to use-use flow
44-
// ```py
45-
// os.system(cmd)
46-
// os.system(cmd)
47-
// ```
48-
//
49-
// Best solution I could come up with is to exclude all sinks inside the modules of
50-
// known sinks. This does have a downside: If we have overlooked a function in any
51-
// of these, that internally runs a command, we no longer give an alert :| -- and we
52-
// need to keep them updated (which is hard to remember)
53-
//
54-
// This does not only affect `os.popen`, but also the helper functions in
55-
// `subprocess`. See:
56-
// https://github.com/python/cpython/blob/fa7ce080175f65d678a7d5756c94f82887fc9803/Lib/os.py#L974
57-
// https://github.com/python/cpython/blob/fa7ce080175f65d678a7d5756c94f82887fc9803/Lib/subprocess.py#L341
58-
not sink.getScope().getEnclosingModule().getName() in ["os", "subprocess", "platform", "popen2"]
59-
}
60-
}
61-
6221
from CommandInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
6322
where config.hasFlowPath(source, sink)
6423
select sink.getNode(), source, sink, "This command depends on $@.", source.getNode(),
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Provides a taint-tracking configuration for reasoning about command injection
3+
* vulnerabilities.
4+
*/
5+
6+
import python
7+
import semmle.python.dataflow.new.DataFlow
8+
import semmle.python.dataflow.new.TaintTracking
9+
import semmle.python.Concepts
10+
import semmle.python.dataflow.new.RemoteFlowSources
11+
12+
/**
13+
* A taint-tracking configuration for reasoning about command injection vulnerabilities.
14+
*/
15+
class CommandInjectionConfiguration extends TaintTracking::Configuration {
16+
CommandInjectionConfiguration() { this = "CommandInjectionConfiguration" }
17+
18+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
19+
20+
override predicate isSink(DataFlow::Node sink) {
21+
sink = any(SystemCommandExecution e).getCommand() and
22+
// Since the implementation of standard library functions such `os.popen` looks like
23+
// ```py
24+
// def popen(cmd, mode="r", buffering=-1):
25+
// ...
26+
// proc = subprocess.Popen(cmd, ...)
27+
// ```
28+
// any time we would report flow to the `os.popen` sink, we can ALSO report the flow
29+
// from the `cmd` parameter to the `subprocess.Popen` sink -- obviously we don't
30+
// want that.
31+
//
32+
// However, simply removing taint edges out of a sink is not a good enough solution,
33+
// since we would only flag one of the `os.system` calls in the following example
34+
// due to use-use flow
35+
// ```py
36+
// os.system(cmd)
37+
// os.system(cmd)
38+
// ```
39+
//
40+
// Best solution I could come up with is to exclude all sinks inside the modules of
41+
// known sinks. This does have a downside: If we have overlooked a function in any
42+
// of these, that internally runs a command, we no longer give an alert :| -- and we
43+
// need to keep them updated (which is hard to remember)
44+
//
45+
// This does not only affect `os.popen`, but also the helper functions in
46+
// `subprocess`. See:
47+
// https://github.com/python/cpython/blob/fa7ce080175f65d678a7d5756c94f82887fc9803/Lib/os.py#L974
48+
// https://github.com/python/cpython/blob/fa7ce080175f65d678a7d5756c94f82887fc9803/Lib/subprocess.py#L341
49+
not sink.getScope().getEnclosingModule().getName() in ["os", "subprocess", "platform", "popen2"]
50+
}
51+
}

0 commit comments

Comments
 (0)