1
+ /**
2
+ * @name User Input to Invoke-Expression
3
+ * @description Finding cases where the user input is passed an Invoke-Expression command
4
+ * @kind path-problem
5
+ * @problem.severity error
6
+ * @security-severity 9.8
7
+ * @precision high
8
+ * @id powershell/microsoft/public/user-input-to-invoke-expression
9
+ * @tags security
10
+ * external/cwe/cwe-078
11
+ * external/cwe/cwe-088
12
+ */
13
+
14
+ import powershell
15
+ import semmle.code.powershell.dataflow.TaintTracking
16
+ import semmle.code.powershell.dataflow.DataFlow
17
+ import semmle.code.powershell.ApiGraphs
18
+
19
+ private module TestConfig implements DataFlow:: ConfigSig {
20
+ predicate isSource ( DataFlow:: Node source ) {
21
+ exists ( CmdCall c |
22
+ c .getName ( ) = "Read-Host" and
23
+ source .asExpr ( ) .getExpr ( ) = c ) }
24
+
25
+ predicate isSink ( DataFlow:: Node sink ) { sink instanceof Sink }
26
+ predicate isBarrier ( DataFlow:: Node node ) { node instanceof Sanitizer }
27
+ }
28
+
29
+ abstract class Source extends DataFlow:: Node { }
30
+
31
+ class ReadHostSource extends Source {
32
+ ReadHostSource ( ) {
33
+ exists ( CmdCall c |
34
+ this .asExpr ( ) .getExpr ( ) = c and
35
+ c .getName ( ) = "Read-Host" )
36
+ }
37
+ }
38
+
39
+ class GetContentSource extends Source {
40
+ GetContentSource ( ) {
41
+ exists ( CmdCall c |
42
+ this .asExpr ( ) .getExpr ( ) = c and
43
+ c .getName ( ) = "Get-Content" )
44
+ }
45
+ }
46
+
47
+ class ValueFromPipelineSource extends Source {
48
+ ValueFromPipelineSource ( ) {
49
+ exists ( Parameter p |
50
+ p .getAnAttribute ( ) .toString ( ) = "ValueFromPipeline" and
51
+ this .asExpr ( ) .getExpr ( ) = p .getAnAccess ( )
52
+ )
53
+ }
54
+ }
55
+
56
+ abstract class Sink extends DataFlow:: Node { }
57
+
58
+ class InvokeExpressionCall extends Sink {
59
+ InvokeExpressionCall ( ) {
60
+ exists ( CmdCall c |
61
+ this .asExpr ( ) .getExpr ( ) = c .getAnArgument ( ) and
62
+ c .getName ( ) = [ "Invoke-Expression" , "iex" , "Add-Type" ] )
63
+ }
64
+ }
65
+
66
+ class InvokeScriptSink extends Sink {
67
+ InvokeScriptSink ( ) {
68
+ exists ( InvokeMemberExpr ie |
69
+ this .asExpr ( ) .getExpr ( ) = ie .getAnArgument ( ) and
70
+ ie .getName ( ) = "InvokeScript" and
71
+ ie .getQualifier ( ) .toString ( ) = "InvokeCommand" and
72
+ ie .getQualifier ( ) .getAChild ( ) .toString ( ) = "executioncontext"
73
+ )
74
+ }
75
+ }
76
+
77
+ class CreateNestedPipelineSink extends Sink {
78
+ CreateNestedPipelineSink ( ) {
79
+ exists ( InvokeMemberExpr ie |
80
+ this .asExpr ( ) .getExpr ( ) = ie .getAnArgument ( ) and
81
+ ie .getName ( ) = "CreateNestedPipeline" and
82
+ ie .getQualifier ( ) .toString ( ) = "InvokeCommand" and
83
+ ie .getQualifier ( ) .getAChild ( ) .toString ( ) = "executioncontext" )
84
+ }
85
+ }
86
+
87
+ class AddScriptInvokeSink extends Sink {
88
+ AddScriptInvokeSink ( ) {
89
+ exists ( InvokeMemberExpr ie |
90
+ this .asExpr ( ) .getExpr ( ) = ie .getAnArgument ( ) and
91
+ ie .getName ( ) = "AddScript" and
92
+ ie .getQualifier ( ) .( InvokeMemberExpr ) .getName ( ) = "Create" and
93
+ ie .getQualifier ( ) .getAChild ( ) .toString ( ) = "PowerShell" and
94
+ ie .getParent ( ) .( InvokeMemberExpr ) .getName ( ) = "Invoke"
95
+ )
96
+ }
97
+ }
98
+
99
+ abstract class Sanitizer extends DataFlow:: Node { }
100
+
101
+ // class TypedParameterSanitizer extends Sanitizer{
102
+ // TypedParameterSanitizer() {
103
+ // exists(Function f, CmdCall c, Parameter p, Argument a |
104
+ // p = f.getAParameter() and
105
+ // a = c.getAnArgument() and
106
+ // p.getName().toLowerCase() = a.getName() and
107
+ // p.getStaticType() != "Object" and
108
+ // c.getName() = f.getName() and
109
+
110
+ // this.asExpr().getExpr() = a
111
+ // )
112
+ // }
113
+ // }
114
+
115
+ class SingleQuoteSanitizer extends Sanitizer {
116
+ SingleQuoteSanitizer ( ) {
117
+ exists ( Expr e , VarReadAccess v |
118
+ e = this .asExpr ( ) .getExpr ( ) .getParent ( ) and
119
+ e .toString ( ) .matches ( "%'$" + v .getVariable ( ) .getName ( ) + "'%" )
120
+ )
121
+ }
122
+ }
123
+
124
+ module TestFlow = TaintTracking:: Global< TestConfig > ;
125
+ import TestFlow:: PathGraph
126
+
127
+ // from TestFlow::PathNode source, TestFlow::PathNode sink
128
+ // where
129
+ // TestFlow::flowPath(source, sink) and
130
+ // sink.getNode().asExpr().getExpr().getLocation().getFile().getBaseName() = "sanitizers.ps1"
131
+ // select sink.getNode(), source, sink, "Flow from user input to Invoke-Expression"
132
+
133
+ // from Function f, CmdCall c
134
+ // where f.getLocation().getFile().getBaseName() = "sanitizers.ps1"
135
+ // select f, f.getAParameter().getStaticType(), f.getAParameter().getName()
136
+
137
+
138
+ //TBD, waiting on mathias on how to connect f and c
139
+ // from Function f, CmdCall c, Parameter p, Argument a
140
+ // where
141
+ // p = f.getAParameter() and
142
+ // a = c.getAnArgument() and
143
+ // p.getName().toLowerCase() = a.getName() and
144
+ // p.getStaticType() != "Object" and
145
+ // c.getName() = f.getName()
146
+ // select a, "argument has a specified static type"
147
+
148
+ // from Argument a, VarReadAccess v
149
+ // where a.getAChild() = v and
150
+ // v.getVariable().getName() = "UserInput"
151
+ // select a, v
152
+
153
+ // from Argument e
154
+ // where e.getLocation().getFile().getBaseName() = "sanitizers.ps1"
155
+ // and e.getLocation().getStartLine() = 14
156
+ // select e, e.getAChild(), e.getParent(), e.toString()
157
+
158
+
159
+ from Parameter p
160
+ where p .getLocation ( ) .getFile ( ) .getBaseName ( ) = "userinput.ps1"
161
+ // p.getAnAttribute().toString() = "ValueFromPipeline" and
162
+
163
+ select p , p .getName ( )
164
+
165
+ // from Expr e
166
+ // where e.getLocation().getFile().getBaseName() = "userinput.ps1"
167
+ // select e, e.getAQlClass()
168
+
169
+ // from InvokeMemberExpr ie
170
+ // where
171
+ // ie.getLocation().getStartLine() = 28 and ie.getName() = "AddScript"
172
+ // select ie, ie.getName(), ie.getQualifier().toString(), ie.getQualifier().getAChild().toString(), ie.getParent().(InvokeMemberExpr).getName()
0 commit comments