Skip to content

Commit 5f64350

Browse files
committed
added script block, expandstring sinks, moved sanitizers to separate file
1 parent b4d8673 commit 5f64350

File tree

3 files changed

+134
-38
lines changed

3 files changed

+134
-38
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import powershell
2+
import semmle.code.powershell.dataflow.TaintTracking
3+
import semmle.code.powershell.dataflow.DataFlow
4+
import semmle.code.powershell.ApiGraphs
5+
6+
7+
abstract class Sanitizer extends DataFlow::Node {}
8+
9+
class TypedParameterSanitizer extends Sanitizer {
10+
TypedParameterSanitizer() {
11+
exists(Function f, Parameter p |
12+
p = f.getAParameter() and
13+
p.getStaticType() != "Object" and
14+
this.asParameter() = p
15+
)
16+
}
17+
}
18+
19+
class SingleQuoteSanitizer extends Sanitizer {
20+
SingleQuoteSanitizer() {
21+
exists(Expr e, VarReadAccess v |
22+
e = this.asExpr().getExpr().getParent() and
23+
e.toString().matches("%'$" + v.getVariable().getName() + "'%")
24+
)
25+
}
26+
}

powershell/ql/src/experimental/InjectionHunter/UserInput.qll

Whitespace-only changes.

powershell/ql/src/experimental/InjectionHunter/UserInputToDangerousMethod.ql

Lines changed: 108 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
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
1515
import semmle.code.powershell.dataflow.TaintTracking
1616
import semmle.code.powershell.dataflow.DataFlow
1717
import semmle.code.powershell.ApiGraphs
18+
import semmle.code.powershell.dataflow.flowsources.FlowSources
19+
20+
import Sanitizers
1821

1922
private 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

6669
class 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

7778
class 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

120171
module TestFlow = TaintTracking::Global<TestConfig>;
121172
import TestFlow::PathGraph
122173

123174
from TestFlow::PathNode source, TestFlow::PathNode sink
124175
where
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

Comments
 (0)