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