1- /** Provides class and predicates to track external data that
1+ /**
2+ * Provides class and predicates to track external data that
23 * may represent malicious OS commands.
34 *
45 * This module is intended to be imported into a taint-tracking query
56 * to extend `TaintKind` and `TaintSink`.
6- *
77 */
8- import python
98
9+ import python
1010import semmle.python.security.TaintTracking
1111import semmle.python.security.strings.Untrusted
1212
13+ /** Abstract taint sink that is potentially vulnerable to malicious shell commands. */
14+ abstract class CommandSink extends TaintSink { }
1315
1416private ModuleObject osOrPopenModule ( ) {
1517 result .getName ( ) = "os" or
1618 result .getName ( ) = "popen2"
1719}
1820
1921private Object makeOsCall ( ) {
20- exists ( string name |
21- result = ModuleObject:: named ( "subprocess" ) .attr ( name ) |
22+ exists ( string name | result = ModuleObject:: named ( "subprocess" ) .attr ( name ) |
2223 name = "Popen" or
23- name = "call" or
24+ name = "call" or
2425 name = "check_call" or
2526 name = "check_output" or
2627 name = "run"
@@ -29,40 +30,27 @@ private Object makeOsCall() {
2930
3031/**Special case for first element in sequence. */
3132class FirstElementKind extends TaintKind {
33+ FirstElementKind ( ) { this = "sequence[" + any ( ExternalStringKind key ) + "][0]" }
3234
33- FirstElementKind ( ) {
34- this = "sequence[" + any ( ExternalStringKind key ) + "][0]"
35- }
36-
37- override string repr ( ) {
38- result = "first item in sequence of " + this .getItem ( ) .repr ( )
39- }
35+ override string repr ( ) { result = "first item in sequence of " + this .getItem ( ) .repr ( ) }
4036
4137 /** Gets the taint kind for item in this sequence. */
42- ExternalStringKind getItem ( ) {
43- this = "sequence[" + result + "][0]"
44- }
45-
38+ ExternalStringKind getItem ( ) { this = "sequence[" + result + "][0]" }
4639}
4740
4841class FirstElementFlow extends DataFlowExtension:: DataFlowNode {
42+ FirstElementFlow ( ) { this = any ( SequenceNode s ) .getElement ( 0 ) }
4943
50- FirstElementFlow ( ) {
51- this = any ( SequenceNode s ) .getElement ( 0 )
52- }
53-
54- override
55- ControlFlowNode getASuccessorNode ( TaintKind fromkind , TaintKind tokind ) {
44+ override ControlFlowNode getASuccessorNode ( TaintKind fromkind , TaintKind tokind ) {
5645 result .( SequenceNode ) .getElement ( 0 ) = this and tokind .( FirstElementKind ) .getItem ( ) = fromkind
5746 }
58-
5947}
6048
61- /** A taint sink that is potentially vulnerable to malicious shell commands.
49+ /**
50+ * A taint sink that is potentially vulnerable to malicious shell commands.
6251 * The `vuln` in `subprocess.call(shell=vuln)` and similar calls.
6352 */
64- class ShellCommand extends TaintSink {
65-
53+ class ShellCommand extends CommandSink {
6654 override string toString ( ) { result = "shell command" }
6755
6856 ShellCommand ( ) {
@@ -75,7 +63,8 @@ class ShellCommand extends TaintSink {
7563 or
7664 exists ( CallNode call , string name |
7765 call .getAnArg ( ) = this and
78- call .getFunction ( ) .refersTo ( osOrPopenModule ( ) .attr ( name ) ) |
66+ call .getFunction ( ) .refersTo ( osOrPopenModule ( ) .attr ( name ) )
67+ |
7968 name = "system" or
8069 name = "popen" or
8170 name .matches ( "popen_" )
@@ -94,19 +83,18 @@ class ShellCommand extends TaintSink {
9483 /* List (or tuple) containing a tainted string command */
9584 kind instanceof ExternalStringSequenceKind
9685 }
97-
9886}
9987
100- /** A taint sink that is potentially vulnerable to malicious shell commands.
88+ /**
89+ * A taint sink that is potentially vulnerable to malicious shell commands.
10190 * The `vuln` in `subprocess.call(vuln, ...)` and similar calls.
10291 */
103- class OsCommandFirstArgument extends TaintSink {
104-
92+ class OsCommandFirstArgument extends CommandSink {
10593 override string toString ( ) { result = "OS command first argument" }
10694
10795 OsCommandFirstArgument ( ) {
10896 not this instanceof ShellCommand and
109- exists ( CallNode call |
97+ exists ( CallNode call |
11098 call .getFunction ( ) .refersTo ( makeOsCall ( ) ) and
11199 call .getArg ( 0 ) = this
112100 )
@@ -119,5 +107,127 @@ class OsCommandFirstArgument extends TaintSink {
119107 /* List (or tuple) whose first element is tainted */
120108 kind instanceof FirstElementKind
121109 }
110+ }
111+
112+ // -------------------------------------------------------------------------- //
113+ // Modeling of the 'invoke' package and 'fabric' package (v 2.x)
114+ //
115+ // Since fabric build so closely upon invoke, we model them together to avoid
116+ // duplication
117+ // -------------------------------------------------------------------------- //
118+ /**
119+ * A taint sink that is potentially vulnerable to malicious shell commands.
120+ * The `vuln` in `invoke.run(vuln, ...)` and similar calls.
121+ */
122+ class InvokeRun extends CommandSink {
123+ InvokeRun ( ) {
124+ this = Value:: named ( "invoke.run" ) .( FunctionValue ) .getArgumentForCall ( _, 0 )
125+ or
126+ this = Value:: named ( "invoke.sudo" ) .( FunctionValue ) .getArgumentForCall ( _, 0 )
127+ }
128+
129+ override string toString ( ) { result = "InvokeRun" }
130+
131+ override predicate sinks ( TaintKind kind ) { kind instanceof ExternalStringKind }
132+ }
133+
134+ /**
135+ * Internal TaintKind to track the invoke.Context instance passed to functions
136+ * marked with @invoke.task
137+ */
138+ private class InvokeContextArg extends TaintKind {
139+ InvokeContextArg ( ) { this = "InvokeContextArg" }
140+ }
141+
142+ /** Internal TaintSource to track the context passed to functions marked with @invoke.task */
143+ private class InvokeContextArgSource extends TaintSource {
144+ InvokeContextArgSource ( ) {
145+ exists ( Function f , Expr decorator |
146+ count ( f .getADecorator ( ) ) = 1 and
147+ (
148+ decorator = f .getADecorator ( ) and not decorator instanceof Call
149+ or
150+ decorator = f .getADecorator ( ) .( Call ) .getFunc ( )
151+ ) and
152+ (
153+ decorator .pointsTo ( Value:: named ( "invoke.task" ) )
154+ or
155+ decorator .pointsTo ( Value:: named ( "fabric.task" ) )
156+ )
157+ |
158+ this .( ControlFlowNode ) .getNode ( ) = f .getArg ( 0 )
159+ )
160+ }
161+
162+ override predicate isSourceOf ( TaintKind kind ) { kind instanceof InvokeContextArg }
163+ }
164+
165+ /**
166+ * A taint sink that is potentially vulnerable to malicious shell commands.
167+ * The `vuln` in `invoke.Context().run(vuln, ...)` and similar calls.
168+ */
169+ class InvokeContextRun extends CommandSink {
170+ InvokeContextRun ( ) {
171+ exists ( CallNode call |
172+ any ( InvokeContextArg k ) .taints ( call .getFunction ( ) .( AttrNode ) .getObject ( "run" ) )
173+ or
174+ call = Value:: named ( "invoke.Context" ) .( ClassValue ) .lookup ( "run" ) .getACall ( )
175+ or
176+ // fabric.connection.Connection is a subtype of invoke.context.Context
177+ // since fabric.Connection.run has a decorator, it doesn't work with FunctionValue :|
178+ // and `Value::named("fabric.Connection").(ClassValue).lookup("run").getACall()` returned no results,
179+ // so here is the hacky solution that works :\
180+ call .getFunction ( ) .( AttrNode ) .getObject ( "run" ) .pointsTo ( ) .getClass ( ) =
181+ Value:: named ( "fabric.Connection" )
182+ |
183+ this = call .getArg ( 0 )
184+ or
185+ this = call .getArgByName ( "command" )
186+ )
187+ }
188+
189+ override string toString ( ) { result = "InvokeContextRun" }
190+
191+ override predicate sinks ( TaintKind kind ) { kind instanceof ExternalStringKind }
192+ }
193+
194+ /**
195+ * A taint sink that is potentially vulnerable to malicious shell commands.
196+ * The `vuln` in `fabric.Group().run(vuln, ...)` and similar calls.
197+ */
198+ class FabricGroupRun extends CommandSink {
199+ FabricGroupRun ( ) {
200+ exists ( ClassValue cls |
201+ cls .getASuperType ( ) = Value:: named ( "fabric.Group" ) and
202+ this = cls .lookup ( "run" ) .( FunctionValue ) .getArgumentForCall ( _, 1 )
203+ )
204+ }
205+
206+ override string toString ( ) { result = "FabricGroupRun" }
207+
208+ override predicate sinks ( TaintKind kind ) { kind instanceof ExternalStringKind }
209+ }
210+
211+ // -------------------------------------------------------------------------- //
212+ // Modeling of the 'invoke' package and 'fabric' package (v 1.x)
213+ // -------------------------------------------------------------------------- //
214+ class FabricV1Commands extends CommandSink {
215+ FabricV1Commands ( ) {
216+ // since `run` and `sudo` are decorated, we can't use FunctionValue's :(
217+ exists ( CallNode call |
218+ call = Value:: named ( "fabric.api.local" ) .getACall ( )
219+ or
220+ call = Value:: named ( "fabric.api.run" ) .getACall ( )
221+ or
222+ call = Value:: named ( "fabric.api.sudo" ) .getACall ( )
223+ |
224+ this = call .getArg ( 0 )
225+ or
226+ this = call .getArgByName ( "command" )
227+ )
228+ }
229+
230+ override string toString ( ) { result = "FabricV1Commands" }
122231
232+ override predicate sinks ( TaintKind kind ) { kind instanceof ExternalStringKind }
123233}
0 commit comments