Skip to content

Commit ed553d3

Browse files
committed
merged work into CommandInjection query
1 parent 2266cd2 commit ed553d3

File tree

10 files changed

+392
-637
lines changed

10 files changed

+392
-637
lines changed

powershell/ql/lib/semmle/code/powershell/security/CommandInjectionCustomizations.qll

Lines changed: 166 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
private import semmle.code.powershell.dataflow.DataFlow
8+
import semmle.code.powershell.ApiGraphs
89
private import semmle.code.powershell.dataflow.flowsources.FlowSources
910
private import semmle.code.powershell.Cfg
1011

@@ -20,7 +21,9 @@ module CommandInjection {
2021
/**
2122
* A data flow sink for command-injection vulnerabilities.
2223
*/
23-
abstract class Sink extends DataFlow::Node { }
24+
abstract class Sink extends DataFlow::Node {
25+
abstract string getSinkType();
26+
}
2427

2528
/**
2629
* A sanitizer for command-injection vulnerabilities.
@@ -39,13 +42,16 @@ module CommandInjection {
3942
SystemCommandExecutionSink() {
4043
// An argument to a call
4144
exists(DataFlow::CallNode call |
42-
call.getName() = "Invoke-Expression" and
45+
call.getName() = ["Invoke-Expression", "iex"] and
4346
call.getAnArgument() = this
4447
)
4548
or
4649
// Or the call command itself in case it's a use of operator &.
4750
any(DataFlow::CallOperatorNode call).getCommand() = this
4851
}
52+
override string getSinkType() {
53+
result = "call to Invoke-Expression"
54+
}
4955
}
5056

5157
class AddTypeSink extends Sink {
@@ -55,11 +61,169 @@ module CommandInjection {
5561
call.getAnArgument() = this
5662
)
5763
}
64+
override string getSinkType() {
65+
result = "call to Add-Type"
66+
}
5867
}
5968

69+
class InvokeScriptSink extends Sink {
70+
InvokeScriptSink() {
71+
exists(API::Node call |
72+
API::getTopLevelMember("executioncontext").getMember("invokecommand").getMethod("invokescript") = call and
73+
this = call.getArgument(_).asSink()
74+
)
75+
}
76+
override string getSinkType(){
77+
result = "call to InvokeScript"
78+
}
79+
}
80+
81+
class CreateNestedPipelineSink extends Sink {
82+
CreateNestedPipelineSink() {
83+
exists(API::Node call |
84+
API::getTopLevelMember("host").getMember("runspace").getMethod("createnestedpipeline") = call and
85+
this = call.getArgument(_).asSink()
86+
)
87+
}
88+
override string getSinkType(){
89+
result = "call to CreateNestedPipeline"
90+
}
91+
}
92+
93+
class AddScriptInvokeSink extends Sink {
94+
AddScriptInvokeSink() {
95+
exists(InvokeMemberExpr ie |
96+
this.asExpr().getExpr() = ie.getAnArgument() and
97+
ie.getName() = "AddScript" and
98+
ie.getQualifier().(InvokeMemberExpr).getName() = "Create" and
99+
ie.getQualifier().getAChild().toString() = "PowerShell" and
100+
ie.getParent().(InvokeMemberExpr).getName() = "Invoke"
101+
)
102+
}
103+
override string getSinkType(){
104+
result = "call to AddScript"
105+
}
106+
}
107+
108+
class PowershellSink extends Sink {
109+
PowershellSink() {
110+
exists( CmdCall c |
111+
c.getName() = "powershell" |
112+
(
113+
this.asExpr().getExpr() = c.getArgument(1) and
114+
c.getArgument(0).getValue().toString() = "-command"
115+
) or
116+
(
117+
this.asExpr().getExpr() = c.getArgument(0)
118+
)
119+
)
120+
}
121+
override string getSinkType(){
122+
result = "call to Powershell"
123+
}
124+
}
125+
126+
class CmdSink extends Sink {
127+
CmdSink() {
128+
exists(CmdCall c |
129+
this.asExpr().getExpr() = c.getArgument(1) and
130+
c.getName() = "cmd" and
131+
c.getArgument(0).getValue().toString() = "/c"
132+
)
133+
}
134+
override string getSinkType(){
135+
result = "call to Cmd"
136+
}
137+
}
138+
139+
class ForEachObjectSink extends Sink {
140+
ForEachObjectSink() {
141+
exists(CmdCall c |
142+
this.asExpr().getExpr() = c.getAnArgument() and
143+
c.getName() = "Foreach-Object"
144+
)
145+
}
146+
override string getSinkType(){
147+
result = "call to ForEach-Object"
148+
}
149+
}
150+
151+
class InvokeSink extends Sink {
152+
InvokeSink() {
153+
exists(InvokeMemberExpr ie |
154+
this.asExpr().getExpr() = ie.getCallee() or
155+
this.asExpr().getExpr() = ie.getQualifier().getAChild*()
156+
)
157+
}
158+
override string getSinkType(){
159+
result = "call to Invoke"
160+
}
161+
}
162+
163+
class CreateScriptBlockSink extends Sink {
164+
CreateScriptBlockSink() {
165+
exists(InvokeMemberExpr ie |
166+
this.asExpr().getExpr() = ie.getAnArgument() and
167+
ie.getName() = "Create" and
168+
ie.getQualifier().toString() = "ScriptBlock"
169+
)
170+
}
171+
override string getSinkType(){
172+
result = "call to CreateScriptBlock"
173+
}
174+
}
175+
176+
class NewScriptBlockSink extends Sink {
177+
NewScriptBlockSink() {
178+
exists(API::Node call |
179+
API::getTopLevelMember("executioncontext").getMember("invokecommand").getMethod("newscriptblock") = call and
180+
this = call.getArgument(_).asSink()
181+
)
182+
}
183+
override string getSinkType(){
184+
result = "call to NewScriptBlock"
185+
}
186+
}
187+
188+
class ExpandStringSink extends Sink {
189+
ExpandStringSink() {
190+
exists(API::Node call | this = call.getArgument(_).asSink() |
191+
API::getTopLevelMember("executioncontext").getMember("invokecommand").getMethod("expandstring") = call or
192+
API::getTopLevelMember("executioncontext").getMember("sessionstate").getMember("invokecommand").getMethod("expandstring") = call
193+
194+
)
195+
}
196+
override string getSinkType(){
197+
result = "call to ExpandString"
198+
}
199+
}
200+
60201
private class ExternalCommandInjectionSink extends Sink {
61202
ExternalCommandInjectionSink() {
62203
this = ModelOutput::getASinkNode("command-injection").asSink()
63204
}
205+
override string getSinkType() {
206+
result = "external command injection"
207+
}
208+
}
209+
210+
class TypedParameterSanitizer extends Sanitizer {
211+
TypedParameterSanitizer() {
212+
exists(Function f, Parameter p |
213+
p = f.getAParameter() and
214+
p.getStaticType() != "Object" and
215+
this.asParameter() = p
216+
)
217+
}
218+
}
219+
220+
class SingleQuoteSanitizer extends Sanitizer {
221+
SingleQuoteSanitizer() {
222+
exists(Expr e, VarReadAccess v |
223+
e = this.asExpr().getExpr().getParent() and
224+
e.toString().matches("%'$" + v.getVariable().getName() + "'%")
225+
)
226+
}
64227
}
65228
}
229+

powershell/ql/src/queries/security/cwe-078/CommandInjection.qhelp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@
88
routine that executes a command, allows the user to execute malicious
99
code.</p>
1010

11+
<p>The following are considered dangerous sinks: </p>
12+
<ul>
13+
<li>Invoke-Expression</li>
14+
<li>InvokeScript</li>
15+
<li>CreateNestedPipeline</li>
16+
<li>AddScript</li>
17+
<li>powershell</li>
18+
<li>cmd</li>
19+
<li>Foreach-Object</li>
20+
<li>Invoke</li>
21+
<li>CreateScriptBlock</li>
22+
<li>NewScriptBlock</li>
23+
<li>ExpandString</li>
24+
</ul>
25+
1126
</overview>
1227
<recommendation>
1328

@@ -36,7 +51,10 @@ without examining it first.</p>
3651
OWASP:
3752
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
3853
</li>
39-
54+
<li>
55+
Injection Hunter:
56+
<a href="https://devblogs.microsoft.com/powershell/powershell-injection-hunter-security-auditing-for-powershell-scripts/">PowerShell Injection Hunter: Security Auditing for PowerShell Scripts</a>.
57+
</li>
4058
<!-- LocalWords: CWE untrusted unsanitized Runtime
4159
-->
4260

powershell/ql/src/queries/security/cwe-078/InjectionHunter/Sanitizers.qll

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)