diff --git a/powershell/ql/lib/semmle/code/powershell/security/CommandInjectionCustomizations.qll b/powershell/ql/lib/semmle/code/powershell/security/CommandInjectionCustomizations.qll
index 1623941fb82..7f2ab885764 100644
--- a/powershell/ql/lib/semmle/code/powershell/security/CommandInjectionCustomizations.qll
+++ b/powershell/ql/lib/semmle/code/powershell/security/CommandInjectionCustomizations.qll
@@ -5,6 +5,7 @@
*/
private import semmle.code.powershell.dataflow.DataFlow
+import semmle.code.powershell.ApiGraphs
private import semmle.code.powershell.dataflow.flowsources.FlowSources
private import semmle.code.powershell.Cfg
@@ -20,7 +21,9 @@ module CommandInjection {
/**
* A data flow sink for command-injection vulnerabilities.
*/
- abstract class Sink extends DataFlow::Node { }
+ abstract class Sink extends DataFlow::Node {
+ abstract string getSinkType();
+ }
/**
* A sanitizer for command-injection vulnerabilities.
@@ -39,13 +42,16 @@ module CommandInjection {
SystemCommandExecutionSink() {
// An argument to a call
exists(DataFlow::CallNode call |
- call.getName() = "Invoke-Expression" and
+ call.getName() = ["Invoke-Expression", "iex"] and
call.getAnArgument() = this
)
or
// Or the call command itself in case it's a use of operator &.
any(DataFlow::CallOperatorNode call).getCommand() = this
}
+ override string getSinkType() {
+ result = "call to Invoke-Expression"
+ }
}
class AddTypeSink extends Sink {
@@ -55,11 +61,171 @@ module CommandInjection {
call.getAnArgument() = this
)
}
+ override string getSinkType() {
+ result = "call to Add-Type"
+ }
}
+ class InvokeScriptSink extends Sink {
+ InvokeScriptSink() {
+ exists(API::Node call |
+ API::getTopLevelMember("executioncontext").getMember("invokecommand").getMethod("invokescript") = call and
+ this = call.getArgument(_).asSink()
+ )
+ }
+ override string getSinkType(){
+ result = "call to InvokeScript"
+ }
+}
+
+class CreateNestedPipelineSink extends Sink {
+ CreateNestedPipelineSink() {
+ exists(API::Node call |
+ API::getTopLevelMember("host").getMember("runspace").getMethod("createnestedpipeline") = call and
+ this = call.getArgument(_).asSink()
+ )
+ }
+ override string getSinkType(){
+ result = "call to CreateNestedPipeline"
+ }
+}
+
+class AddScriptInvokeSink extends Sink {
+ AddScriptInvokeSink() {
+ exists(InvokeMemberExpr addscript, InvokeMemberExpr create |
+ this.asExpr().getExpr() = addscript.getAnArgument() and
+ addscript.getName() = "AddScript" and
+ create.getName() = "Create" and
+
+ addscript.getQualifier().(InvokeMemberExpr) = create and
+ create.getQualifier().(TypeNameExpr).getName() = "PowerShell"
+ )
+ }
+ override string getSinkType(){
+ result = "call to AddScript"
+ }
+}
+
+class PowershellSink extends Sink {
+ PowershellSink() {
+ exists( CmdCall c |
+ c.getName() = "powershell" |
+ (
+ this.asExpr().getExpr() = c.getArgument(1) and
+ c.getArgument(0).getValue().asString() = "-command"
+ ) or
+ (
+ this.asExpr().getExpr() = c.getArgument(0)
+ )
+ )
+ }
+ override string getSinkType(){
+ result = "call to Powershell"
+ }
+}
+
+class CmdSink extends Sink {
+ CmdSink() {
+ exists(CmdCall c |
+ this.asExpr().getExpr() = c.getArgument(1) and
+ c.getName() = "cmd" and
+ c.getArgument(0).getValue().asString() = "/c"
+ )
+ }
+ override string getSinkType(){
+ result = "call to Cmd"
+ }
+}
+
+class ForEachObjectSink extends Sink {
+ ForEachObjectSink() {
+ exists(CmdCall c |
+ this.asExpr().getExpr() = c.getAnArgument() and
+ c.getName() = "Foreach-Object"
+ )
+ }
+ override string getSinkType(){
+ result = "call to ForEach-Object"
+ }
+}
+
+class InvokeSink extends Sink {
+ InvokeSink() {
+ exists(InvokeMemberExpr ie |
+ this.asExpr().getExpr() = ie.getCallee() or
+ this.asExpr().getExpr() = ie.getQualifier().getAChild*()
+ )
+ }
+ override string getSinkType(){
+ result = "call to Invoke"
+ }
+}
+
+class CreateScriptBlockSink extends Sink {
+ CreateScriptBlockSink() {
+ exists(InvokeMemberExpr ie |
+ this.asExpr().getExpr() = ie.getAnArgument() and
+ ie.getName() = "Create" and
+ ie.getQualifier().(TypeNameExpr).getName() = "ScriptBlock"
+ )
+ }
+ override string getSinkType(){
+ result = "call to CreateScriptBlock"
+ }
+}
+
+class NewScriptBlockSink extends Sink {
+ NewScriptBlockSink() {
+ exists(API::Node call |
+ API::getTopLevelMember("executioncontext").getMember("invokecommand").getMethod("newscriptblock") = call and
+ this = call.getArgument(_).asSink()
+ )
+ }
+ override string getSinkType(){
+ result = "call to NewScriptBlock"
+ }
+}
+
+class ExpandStringSink extends Sink {
+ ExpandStringSink() {
+ exists(API::Node call | this = call.getArgument(_).asSink() |
+ API::getTopLevelMember("executioncontext").getMember("invokecommand").getMethod("expandstring") = call or
+ API::getTopLevelMember("executioncontext").getMember("sessionstate").getMember("invokecommand").getMethod("expandstring") = call
+
+ )
+ }
+ override string getSinkType(){
+ result = "call to ExpandString"
+ }
+}
+
private class ExternalCommandInjectionSink extends Sink {
ExternalCommandInjectionSink() {
this = ModelOutput::getASinkNode("command-injection").asSink()
}
+ override string getSinkType() {
+ result = "external command injection"
+ }
+ }
+
+ class TypedParameterSanitizer extends Sanitizer {
+ TypedParameterSanitizer() {
+ exists(Function f, Parameter p |
+ p = f.getAParameter() and
+ p.getStaticType() != "Object" and
+ this.asParameter() = p
+ )
+ }
+ }
+
+ class SingleQuoteSanitizer extends Sanitizer {
+ SingleQuoteSanitizer() {
+ exists(ExpandableStringExpr e, VarReadAccess v |
+ v = this.asExpr().getExpr() and
+ e.getUnexpandedValue().matches("%'$" + v.getVariable().getName() + "'%") and
+ e.getAnExpr() = v
+ )
+ }
}
}
+
diff --git a/powershell/ql/src/queries/security/cwe-078/CommandInjection.qhelp b/powershell/ql/src/queries/security/cwe-078/CommandInjection.qhelp
index b75401a5d70..e89985142d9 100644
--- a/powershell/ql/src/queries/security/cwe-078/CommandInjection.qhelp
+++ b/powershell/ql/src/queries/security/cwe-078/CommandInjection.qhelp
@@ -8,6 +8,21 @@
routine that executes a command, allows the user to execute malicious
code.
+The following are considered dangerous sinks:
+
+ - Invoke-Expression
+ - InvokeScript
+ - CreateNestedPipeline
+ - AddScript
+ - powershell
+ - cmd
+ - Foreach-Object
+ - Invoke
+ - CreateScriptBlock
+ - NewScriptBlock
+ - ExpandString
+
+
@@ -36,7 +51,10 @@ without examining it first.
OWASP:
Command Injection.
-
+
+Injection Hunter:
+PowerShell Injection Hunter: Security Auditing for PowerShell Scripts.
+
diff --git a/powershell/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected b/powershell/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected
index 144255ccc3c..e8e97671e55 100644
--- a/powershell/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected
+++ b/powershell/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected
@@ -1,12 +1,137 @@
edges
-| test.ps1:1:8:1:9 | x | test.ps1:3:28:3:47 | Get-Process -Id $x | provenance | |
-| test.ps1:5:10:5:20 | my_var | test.ps1:7:3:7:19 | $code --enabled | provenance | |
+| test.ps1:3:11:3:20 | UserInput | test.ps1:4:23:4:52 | Get-Process -Name $UserInput | provenance | |
+| test.ps1:9:11:9:20 | UserInput | test.ps1:10:9:10:38 | Get-Process -Name $UserInput | provenance | |
+| test.ps1:15:11:15:20 | UserInput | test.ps1:16:50:16:79 | Get-Process -Name $UserInput | provenance | |
+| test.ps1:21:11:21:20 | UserInput | test.ps1:22:41:22:70 | Get-Process -Name $UserInput | provenance | |
+| test.ps1:21:11:21:20 | UserInput | test.ps1:22:60:22:69 | UserInput | provenance | |
+| test.ps1:27:11:27:20 | UserInput | test.ps1:28:38:28:67 | Get-Process -Name $UserInput | provenance | |
+| test.ps1:27:11:27:20 | UserInput | test.ps1:28:57:28:66 | UserInput | provenance | |
+| test.ps1:33:11:33:20 | UserInput | test.ps1:34:14:34:46 | public class Foo { $UserInput } | provenance | |
+| test.ps1:39:11:39:20 | UserInput | test.ps1:40:30:40:62 | public class Foo { $UserInput } | provenance | |
+| test.ps1:45:11:45:20 | UserInput | test.ps1:48:30:48:34 | code | provenance | |
+| test.ps1:73:11:73:20 | UserInput | test.ps1:75:25:75:54 | Get-Process -Name $UserInput | provenance | |
+| test.ps1:80:11:80:20 | UserInput | test.ps1:82:16:82:45 | Get-Process -Name $UserInput | provenance | |
+| test.ps1:87:11:87:20 | UserInput | test.ps1:89:12:89:28 | ping $UserInput | provenance | |
+| test.ps1:94:11:94:20 | UserInput | test.ps1:98:33:98:62 | Get-Process -Name $UserInput | provenance | |
+| test.ps1:104:11:104:20 | UserInput | test.ps1:108:58:108:87 | Get-Process -Name $UserInput | provenance | |
+| test.ps1:114:11:114:20 | UserInput | test.ps1:116:34:116:43 | UserInput | provenance | |
+| test.ps1:121:11:121:20 | UserInput | test.ps1:123:28:123:37 | UserInput | provenance | |
+| test.ps1:128:11:128:20 | UserInput | test.ps1:130:28:130:37 | UserInput | provenance | |
+| test.ps1:136:11:136:20 | UserInput | test.ps1:139:50:139:59 | UserInput | provenance | |
+| test.ps1:144:11:144:20 | UserInput | test.ps1:147:63:147:72 | UserInput | provenance | |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:154:46:154:51 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:155:46:155:51 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:156:46:156:51 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:157:46:157:51 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:158:46:158:51 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:159:46:159:51 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:160:46:160:51 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:161:46:161:51 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:163:48:163:53 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:164:48:164:53 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:165:48:165:53 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:166:41:166:46 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:167:41:167:46 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:168:36:168:41 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:169:36:169:41 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:170:36:170:41 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:172:42:172:47 | input | provenance | Src:MaD:11464 |
+| test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:173:42:173:47 | input | provenance | Src:MaD:11464 |
+| test.ps1:154:46:154:51 | input | test.ps1:3:11:3:20 | UserInput | provenance | |
+| test.ps1:155:46:155:51 | input | test.ps1:9:11:9:20 | UserInput | provenance | |
+| test.ps1:156:46:156:51 | input | test.ps1:15:11:15:20 | UserInput | provenance | |
+| test.ps1:157:46:157:51 | input | test.ps1:21:11:21:20 | UserInput | provenance | |
+| test.ps1:158:46:158:51 | input | test.ps1:27:11:27:20 | UserInput | provenance | |
+| test.ps1:159:46:159:51 | input | test.ps1:33:11:33:20 | UserInput | provenance | |
+| test.ps1:160:46:160:51 | input | test.ps1:39:11:39:20 | UserInput | provenance | |
+| test.ps1:161:46:161:51 | input | test.ps1:45:11:45:20 | UserInput | provenance | |
+| test.ps1:163:48:163:53 | input | test.ps1:73:11:73:20 | UserInput | provenance | |
+| test.ps1:164:48:164:53 | input | test.ps1:80:11:80:20 | UserInput | provenance | |
+| test.ps1:165:48:165:53 | input | test.ps1:87:11:87:20 | UserInput | provenance | |
+| test.ps1:166:41:166:46 | input | test.ps1:94:11:94:20 | UserInput | provenance | |
+| test.ps1:167:41:167:46 | input | test.ps1:104:11:104:20 | UserInput | provenance | |
+| test.ps1:168:36:168:41 | input | test.ps1:114:11:114:20 | UserInput | provenance | |
+| test.ps1:169:36:169:41 | input | test.ps1:121:11:121:20 | UserInput | provenance | |
+| test.ps1:170:36:170:41 | input | test.ps1:128:11:128:20 | UserInput | provenance | |
+| test.ps1:172:42:172:47 | input | test.ps1:136:11:136:20 | UserInput | provenance | |
+| test.ps1:173:42:173:47 | input | test.ps1:144:11:144:20 | UserInput | provenance | |
nodes
-| test.ps1:1:8:1:9 | x | semmle.label | x |
-| test.ps1:3:28:3:47 | Get-Process -Id $x | semmle.label | Get-Process -Id $x |
-| test.ps1:5:10:5:20 | my_var | semmle.label | my_var |
-| test.ps1:7:3:7:19 | $code --enabled | semmle.label | $code --enabled |
+| test.ps1:3:11:3:20 | UserInput | semmle.label | UserInput |
+| test.ps1:4:23:4:52 | Get-Process -Name $UserInput | semmle.label | Get-Process -Name $UserInput |
+| test.ps1:9:11:9:20 | UserInput | semmle.label | UserInput |
+| test.ps1:10:9:10:38 | Get-Process -Name $UserInput | semmle.label | Get-Process -Name $UserInput |
+| test.ps1:15:11:15:20 | UserInput | semmle.label | UserInput |
+| test.ps1:16:50:16:79 | Get-Process -Name $UserInput | semmle.label | Get-Process -Name $UserInput |
+| test.ps1:21:11:21:20 | UserInput | semmle.label | UserInput |
+| test.ps1:22:41:22:70 | Get-Process -Name $UserInput | semmle.label | Get-Process -Name $UserInput |
+| test.ps1:22:60:22:69 | UserInput | semmle.label | UserInput |
+| test.ps1:27:11:27:20 | UserInput | semmle.label | UserInput |
+| test.ps1:28:38:28:67 | Get-Process -Name $UserInput | semmle.label | Get-Process -Name $UserInput |
+| test.ps1:28:57:28:66 | UserInput | semmle.label | UserInput |
+| test.ps1:33:11:33:20 | UserInput | semmle.label | UserInput |
+| test.ps1:34:14:34:46 | public class Foo { $UserInput } | semmle.label | public class Foo { $UserInput } |
+| test.ps1:39:11:39:20 | UserInput | semmle.label | UserInput |
+| test.ps1:40:30:40:62 | public class Foo { $UserInput } | semmle.label | public class Foo { $UserInput } |
+| test.ps1:45:11:45:20 | UserInput | semmle.label | UserInput |
+| test.ps1:48:30:48:34 | code | semmle.label | code |
+| test.ps1:73:11:73:20 | UserInput | semmle.label | UserInput |
+| test.ps1:75:25:75:54 | Get-Process -Name $UserInput | semmle.label | Get-Process -Name $UserInput |
+| test.ps1:80:11:80:20 | UserInput | semmle.label | UserInput |
+| test.ps1:82:16:82:45 | Get-Process -Name $UserInput | semmle.label | Get-Process -Name $UserInput |
+| test.ps1:87:11:87:20 | UserInput | semmle.label | UserInput |
+| test.ps1:89:12:89:28 | ping $UserInput | semmle.label | ping $UserInput |
+| test.ps1:94:11:94:20 | UserInput | semmle.label | UserInput |
+| test.ps1:98:33:98:62 | Get-Process -Name $UserInput | semmle.label | Get-Process -Name $UserInput |
+| test.ps1:104:11:104:20 | UserInput | semmle.label | UserInput |
+| test.ps1:108:58:108:87 | Get-Process -Name $UserInput | semmle.label | Get-Process -Name $UserInput |
+| test.ps1:114:11:114:20 | UserInput | semmle.label | UserInput |
+| test.ps1:116:34:116:43 | UserInput | semmle.label | UserInput |
+| test.ps1:121:11:121:20 | UserInput | semmle.label | UserInput |
+| test.ps1:123:28:123:37 | UserInput | semmle.label | UserInput |
+| test.ps1:128:11:128:20 | UserInput | semmle.label | UserInput |
+| test.ps1:130:28:130:37 | UserInput | semmle.label | UserInput |
+| test.ps1:136:11:136:20 | UserInput | semmle.label | UserInput |
+| test.ps1:139:50:139:59 | UserInput | semmle.label | UserInput |
+| test.ps1:144:11:144:20 | UserInput | semmle.label | UserInput |
+| test.ps1:147:63:147:72 | UserInput | semmle.label | UserInput |
+| test.ps1:152:10:152:32 | Call to Read-Host | semmle.label | Call to Read-Host |
+| test.ps1:154:46:154:51 | input | semmle.label | input |
+| test.ps1:155:46:155:51 | input | semmle.label | input |
+| test.ps1:156:46:156:51 | input | semmle.label | input |
+| test.ps1:157:46:157:51 | input | semmle.label | input |
+| test.ps1:158:46:158:51 | input | semmle.label | input |
+| test.ps1:159:46:159:51 | input | semmle.label | input |
+| test.ps1:160:46:160:51 | input | semmle.label | input |
+| test.ps1:161:46:161:51 | input | semmle.label | input |
+| test.ps1:163:48:163:53 | input | semmle.label | input |
+| test.ps1:164:48:164:53 | input | semmle.label | input |
+| test.ps1:165:48:165:53 | input | semmle.label | input |
+| test.ps1:166:41:166:46 | input | semmle.label | input |
+| test.ps1:167:41:167:46 | input | semmle.label | input |
+| test.ps1:168:36:168:41 | input | semmle.label | input |
+| test.ps1:169:36:169:41 | input | semmle.label | input |
+| test.ps1:170:36:170:41 | input | semmle.label | input |
+| test.ps1:172:42:172:47 | input | semmle.label | input |
+| test.ps1:173:42:173:47 | input | semmle.label | input |
subpaths
#select
-| test.ps1:3:28:3:47 | Get-Process -Id $x | test.ps1:1:8:1:9 | x | test.ps1:3:28:3:47 | Get-Process -Id $x | This command depends on a $@. | test.ps1:1:8:1:9 | x | user-provided value |
-| test.ps1:7:3:7:19 | $code --enabled | test.ps1:5:10:5:20 | my_var | test.ps1:7:3:7:19 | $code --enabled | This command depends on a $@. | test.ps1:5:10:5:20 | my_var | user-provided value |
+| test.ps1:4:23:4:52 | Get-Process -Name $UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:4:23:4:52 | Get-Process -Name $UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:10:9:10:38 | Get-Process -Name $UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:10:9:10:38 | Get-Process -Name $UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:16:50:16:79 | Get-Process -Name $UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:16:50:16:79 | Get-Process -Name $UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:22:41:22:70 | Get-Process -Name $UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:22:41:22:70 | Get-Process -Name $UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:22:60:22:69 | UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:22:60:22:69 | UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:28:38:28:67 | Get-Process -Name $UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:28:38:28:67 | Get-Process -Name $UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:28:57:28:66 | UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:28:57:28:66 | UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:34:14:34:46 | public class Foo { $UserInput } | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:34:14:34:46 | public class Foo { $UserInput } | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:40:30:40:62 | public class Foo { $UserInput } | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:40:30:40:62 | public class Foo { $UserInput } | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:48:30:48:34 | code | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:48:30:48:34 | code | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:75:25:75:54 | Get-Process -Name $UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:75:25:75:54 | Get-Process -Name $UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:82:16:82:45 | Get-Process -Name $UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:82:16:82:45 | Get-Process -Name $UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:89:12:89:28 | ping $UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:89:12:89:28 | ping $UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:98:33:98:62 | Get-Process -Name $UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:98:33:98:62 | Get-Process -Name $UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:108:58:108:87 | Get-Process -Name $UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:108:58:108:87 | Get-Process -Name $UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:116:34:116:43 | UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:116:34:116:43 | UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:123:28:123:37 | UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:123:28:123:37 | UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:130:28:130:37 | UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:130:28:130:37 | UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:139:50:139:59 | UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:139:50:139:59 | UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
+| test.ps1:147:63:147:72 | UserInput | test.ps1:152:10:152:32 | Call to Read-Host | test.ps1:147:63:147:72 | UserInput | This command depends on a $@. | test.ps1:152:10:152:32 | Call to Read-Host | user-provided value |
diff --git a/powershell/ql/test/query-tests/security/cwe-078/CommandInjection/test.ps1 b/powershell/ql/test/query-tests/security/cwe-078/CommandInjection/test.ps1
index 682b1af3752..fd1bc38ce08 100644
--- a/powershell/ql/test/query-tests/security/cwe-078/CommandInjection/test.ps1
+++ b/powershell/ql/test/query-tests/security/cwe-078/CommandInjection/test.ps1
@@ -1,7 +1,210 @@
-param ($x)
+function Invoke-InvokeExpressionInjection1
+{
+ param($UserInput)
+ Invoke-Expression "Get-Process -Name $UserInput"
+}
-Invoke-Expression -Command "Get-Process -Id $x" # BAD
+function Invoke-InvokeExpressionInjection2
+{
+ param($UserInput)
+ iex "Get-Process -Name $UserInput"
+}
-$code = "$Env:MY_VAR"
+function Invoke-InvokeExpressionInjection3
+{
+ param($UserInput)
+ $executionContext.InvokeCommand.InvokeScript("Get-Process -Name $UserInput")
+}
-& "$code --enabled" # BAD
\ No newline at end of file
+function Invoke-InvokeExpressionInjection4
+{
+ param($UserInput)
+ $host.Runspace.CreateNestedPipeline("Get-Process -Name $UserInput", $false).Invoke()
+}
+
+function Invoke-InvokeExpressionInjection5
+{
+ param($UserInput)
+ [PowerShell]::Create().AddScript("Get-Process -Name $UserInput").Invoke()
+}
+
+function Invoke-InvokeExpressionInjection6
+{
+ param($UserInput)
+ Add-Type "public class Foo { $UserInput }"
+}
+
+function Invoke-InvokeExpressionInjection7
+{
+ param($UserInput)
+ Add-Type -TypeDefinition "public class Foo { $UserInput }"
+}
+
+function Invoke-InvokeExpressionInjection8
+{
+ param($UserInput)
+
+ $code = "public class Foo { $UserInput }"
+ Add-Type -TypeDefinition $code
+}
+
+function Invoke-InvokeExpressionInjectionFP
+{
+ param($UserInput)
+
+ $code = @"
+ public class BasicTest
+ {
+ public static int Add(int a, int b)
+ {
+ return (a + b);
+ }
+ public int Multiply(int a, int b)
+ {
+ return (a * b);
+ }
+ }
+"@
+ Add-Type -TypeDefinition $code
+}
+
+function Invoke-ExploitableCommandInjection1
+{
+ param($UserInput)
+
+ powershell -command "Get-Process -Name $UserInput"
+}
+
+function Invoke-ExploitableCommandInjection2
+{
+ param($UserInput)
+
+ powershell "Get-Process -Name $UserInput"
+}
+
+function Invoke-ExploitableCommandInjection3
+{
+ param($UserInput)
+
+ cmd /c "ping $UserInput"
+}
+
+function Invoke-ScriptBlockInjection1
+{
+ param($UserInput)
+
+ ## Often used when making remote connections
+
+ $sb = [ScriptBlock]::Create("Get-Process -Name $UserInput")
+ Invoke-Command RemoteServer $sb
+}
+
+function Invoke-ScriptBlockInjection2
+{
+ param($UserInput)
+
+ ## Often used when making remote connections
+
+ $sb = $executionContext.InvokeCommand.NewScriptBlock("Get-Process -Name $UserInput")
+ Invoke-Command RemoteServer $sb
+}
+
+function Invoke-MethodInjection1
+{
+ param($UserInput)
+
+ Get-Process | Foreach-Object $UserInput
+}
+
+function Invoke-MethodInjection2
+{
+ param($UserInput)
+
+ (Get-Process -Id $pid).$UserInput()
+}
+
+function Invoke-MethodInjection3
+{
+ param($UserInput)
+
+ (Get-Process -Id $pid).$UserInput.Invoke()
+}
+
+#TODO: currently a FN
+function Invoke-ExpandStringInjection1
+{
+ param($UserInput)
+
+ ## Used to attempt a variable resolution
+ $executionContext.InvokeCommand.ExpandString($UserInput)
+}
+
+function Invoke-ExpandStringInjection2
+{
+ param($UserInput)
+
+ ## Used to attempt a variable resolution
+ $executionContext.SessionState.InvokeCommand.ExpandString($UserInput)
+}
+
+
+
+$input = Read-Host "enter input"
+
+Invoke-InvokeExpressionInjection1 -UserInput $input
+Invoke-InvokeExpressionInjection2 -UserInput $input
+Invoke-InvokeExpressionInjection3 -UserInput $input
+Invoke-InvokeExpressionInjection4 -UserInput $input
+Invoke-InvokeExpressionInjection5 -UserInput $input
+Invoke-InvokeExpressionInjection6 -UserInput $input
+Invoke-InvokeExpressionInjection7 -UserInput $input
+Invoke-InvokeExpressionInjection8 -UserInput $input
+Invoke-InvokeExpressionInjectionFP -UserInput $input
+Invoke-ExploitableCommandInjection1 -UserInput $input
+Invoke-ExploitableCommandInjection2 -UserInput $input
+Invoke-ExploitableCommandInjection3 -UserInput $input
+Invoke-ScriptBlockInjection1 -UserInput $input
+Invoke-ScriptBlockInjection2 -UserInput $input
+Invoke-MethodInjection1 -UserInput $input
+Invoke-MethodInjection2 -UserInput $input
+Invoke-MethodInjection3 -UserInput $input
+Invoke-PropertyInjection -UserInput $input
+Invoke-ExpandStringInjection1 -UserInput $input
+Invoke-ExpandStringInjection2 -UserInput $input
+
+#typed input
+function Invoke-InvokeExpressionInjectionSafe1
+{
+ param([int] $UserInput)
+ Invoke-Expression "Get-Process -Name $UserInput"
+}
+
+#single quotes to treat them as string literal
+function Invoke-InvokeExpressionInjectionSafe2
+{
+ param($UserInput)
+ Invoke-Expression "Get-Process -Name '$UserInput'"
+}
+#EscapeSingleQuotedStringContent API
+function Invoke-InvokeExpressionInjectionSafe3
+{
+ param([int] $UserInput)
+
+ $UserInputClean = [System.Management.Automation.Language.CodeGeneration]::
+ EscapeSingleQuotedStringContent("$UserInput")
+ Invoke-Expression "Get-Process -Name $UserInputClean"
+}
+
+#EscapeSingleQuotedStringContent API 2
+function Invoke-InvokeExpressionInjectionSafe4
+{
+ param([int] $UserInput)
+
+ $UserInputClean = [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent("$UserInput")
+ Invoke-Expression "Get-Process -Name $UserInputClean"
+}
+
+Invoke-InvokeExpressionInjectionSafe1 -UserInput $input
+Invoke-InvokeExpressionInjectionSafe2 -UserInput $input
+Invoke-InvokeExpressionInjectionSafe3 -UserInput $input
+Invoke-InvokeExpressionInjectionSafe4 -UserInput $input
\ No newline at end of file