11/**
22 * @name User Input to Invoke-Expression
3- * @description Finding cases where the user input is passed an Invoke-Expression command
3+ * @description Finding cases where the user input is passed an dangerous method that can lead to RCE
44 * @kind path-problem
55 * @problem.severity error
66 * @security-severity 9.8
@@ -15,14 +15,17 @@ import powershell
1515import semmle.code.powershell.dataflow.TaintTracking
1616import semmle.code.powershell.dataflow.DataFlow
1717import semmle.code.powershell.ApiGraphs
18+ import semmle.code.powershell.dataflow.flowsources.FlowSources
19+
20+ import Sanitizers
1821
1922private module TestConfig implements DataFlow:: ConfigSig {
2023 predicate isSource ( DataFlow:: Node source ) {
21- exists ( CmdCall c |
22- c . getName ( ) = "Read-Host" and
23- source . asExpr ( ) . getExpr ( ) = c ) }
24+ source instanceof SourceNode or
25+ source instanceof Source
26+ }
2427
25- predicate isSink ( DataFlow:: Node sink ) { any ( ) } // sink instanceof Sink }
28+ predicate isSink ( DataFlow:: Node sink ) { sink instanceof Sink }
2629 predicate isBarrier ( DataFlow:: Node node ) { node instanceof Sanitizer }
2730}
2831
@@ -65,22 +68,19 @@ class InvokeExpressionCall extends Sink {
6568
6669class InvokeScriptSink extends Sink {
6770 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"
71+ exists ( API:: Node call |
72+ API:: getTopLevelMember ( "executioncontext" ) .getMember ( "invokecommand" ) .getMethod ( "invokescript" ) = call and
73+ this = call .getArgument ( _) .asSink ( )
7374 )
7475 }
7576}
7677
7778class CreateNestedPipelineSink extends Sink {
7879 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" )
80+ exists ( API:: Node call |
81+ API:: getTopLevelMember ( "host" ) .getMember ( "runspace" ) .getMethod ( "createnestedpipeline" ) = call and
82+ this = call .getArgument ( _) .asSink ( )
83+ )
8484 }
8585}
8686
@@ -96,35 +96,105 @@ class AddScriptInvokeSink extends Sink {
9696 }
9797}
9898
99- abstract class Sanitizer extends DataFlow:: Node { }
100-
101- // class TypedParameterSanitizer extends Sanitizer {
102- // TypedParameterSanitizer() {
103- // exists(Function f, Parameter p |
104- // p = f.getAParameter() and
105- // p.getStaticType() != "Object" and
106- // this.asParameter() = p
107- // )
108- // }
109- // }
110-
111- // class SingleQuoteSanitizer extends Sanitizer {
112- // SingleQuoteSanitizer() {
113- // exists(Expr e, VarReadAccess v |
114- // e = this.asExpr().getExpr().getParent() and
115- // e.toString().matches("%'$" + v.getVariable().getName() + "'%")
116- // )
117- // }
118- // }
99+ class PowershellSink extends Sink {
100+ PowershellSink ( ) {
101+ exists ( CmdCall c |
102+ c .getName ( ) = "powershell" |
103+ (
104+ this .asExpr ( ) .getExpr ( ) = c .getArgument ( 1 ) and
105+ c .getArgument ( 0 ) .getValue ( ) .toString ( ) = "-command"
106+ ) or
107+ (
108+ this .asExpr ( ) .getExpr ( ) = c .getArgument ( 0 )
109+ )
110+ )
111+ }
112+ }
113+
114+ class CmdSink extends Sink {
115+ CmdSink ( ) {
116+ exists ( CmdCall c |
117+ this .asExpr ( ) .getExpr ( ) = c .getArgument ( 1 ) and
118+ c .getName ( ) = "cmd" and
119+ c .getArgument ( 0 ) .getValue ( ) .toString ( ) = "/c"
120+ )
121+ }
122+ }
123+
124+ class ForEachObjectSink extends Sink {
125+ ForEachObjectSink ( ) {
126+ exists ( CmdCall c |
127+ this .asExpr ( ) .getExpr ( ) = c .getAnArgument ( ) and
128+ c .getName ( ) = "Foreach-Object"
129+ )
130+ }
131+ }
132+
133+ class InvokeSink extends Sink {
134+ InvokeSink ( ) {
135+ exists ( InvokeMemberExpr ie |
136+ this .asExpr ( ) .getExpr ( ) = ie .getCallee ( ) or
137+ this .asExpr ( ) .getExpr ( ) = ie .getQualifier ( ) .getAChild * ( )
138+ )
139+ }
140+ }
141+
142+ class CreateScriptBlockSink extends Sink {
143+ CreateScriptBlockSink ( ) {
144+ exists ( InvokeMemberExpr ie |
145+ this .asExpr ( ) .getExpr ( ) = ie .getAnArgument ( ) and
146+ ie .getName ( ) = "Create" and
147+ ie .getQualifier ( ) .toString ( ) = "ScriptBlock"
148+ )
149+ }
150+ }
151+
152+ class NewScriptBlockSink extends Sink {
153+ NewScriptBlockSink ( ) {
154+ exists ( API:: Node call |
155+ API:: getTopLevelMember ( "executioncontext" ) .getMember ( "invokecommand" ) .getMethod ( "newscriptblock" ) = call and
156+ this = call .getArgument ( _) .asSink ( )
157+ )
158+ }
159+ }
160+
161+ class ExpandStringSink extends Sink {
162+ ExpandStringSink ( ) {
163+ exists ( API:: Node call | this = call .getArgument ( _) .asSink ( ) |
164+ API:: getTopLevelMember ( "executioncontext" ) .getMember ( "invokecommand" ) .getMethod ( "expandstring" ) = call or
165+ API:: getTopLevelMember ( "executioncontext" ) .getMember ( "sessionstate" ) .getMember ( "invokecommand" ) .getMethod ( "expandstring" ) = call
166+
167+ )
168+ }
169+ }
119170
120171module TestFlow = TaintTracking:: Global< TestConfig > ;
121172import TestFlow:: PathGraph
122173
123174from TestFlow:: PathNode source , TestFlow:: PathNode sink
124175where
125- TestFlow:: flowPath ( source , sink ) and
126- sink .getNode ( ) .asExpr ( ) .getExpr ( ) .getLocation ( ) .getFile ( ) .getBaseName ( ) = "sanitizers.ps1"
127- select sink .getNode ( ) , source , sink , "Flow from user input to Invoke-Expression"
176+ TestFlow:: flowPath ( source , sink )
177+ select sink .getNode ( ) , source , sink , "Flow from user input to dangerous method"
178+
179+ // from CmdCall c
180+ // where c.getName() = "cmd"
181+ // and c.getArgument(0).getValue().toString() = "/c"
182+ // select c.getArgument(1)
183+
184+ // from InvokeMemberExpr ie
185+ // where ie.getName() = "Create" and
186+ // ie.getQualifier().toString() = "ScriptBlock"
187+ // select ie, ie.getQualifier(), ie.getAnArgument()
188+
189+ // from API::Node call
190+ // where API::getTopLevelMember("executioncontext").getMember("invokecommand").getMethod("newscriptblock") = call
191+ // select call, call.getArgument(_).asSink()
192+
193+ // from Expr e
194+ // where e.getLocation().getFile().getBaseName() = "InjectionHunterTests.ps1"
195+ // and e.getLocation().getStartLine() = 106
196+ // select e, e.getAQlClass()
197+
128198
129199// from Function f, CmdCall c
130200// where f.getLocation().getFile().getBaseName() = "sanitizers.ps1"
0 commit comments