From ed553d393b7bbe6c80974a07fb72c84aa9c287ab Mon Sep 17 00:00:00 2001 From: Chanel Young Date: Wed, 16 Apr 2025 14:32:30 -0700 Subject: [PATCH] merged work into CommandInjection query --- .../CommandInjectionCustomizations.qll | 168 ++++++++++++- .../security/cwe-078/CommandInjection.qhelp | 20 +- .../cwe-078/InjectionHunter/Sanitizers.qll | 26 --- .../cwe-078/InjectionHunter/Sinks.qll | 152 ------------ .../UserInputToDangerousMethod.qhelp | 48 ---- .../UserInputToDangerousMethod.ql | 36 --- .../cwe-078/CommandInjection/test.ps1 | 211 ++++++++++++++++- .../InjectionHunter/InjectionHunter.expected | 146 ------------ .../InjectionHunter/InjectionHunter.qlref | 1 - .../security/cwe-078/InjectionHunter/test.ps1 | 221 ------------------ 10 files changed, 392 insertions(+), 637 deletions(-) delete mode 100644 powershell/ql/src/queries/security/cwe-078/InjectionHunter/Sanitizers.qll delete mode 100644 powershell/ql/src/queries/security/cwe-078/InjectionHunter/Sinks.qll delete mode 100644 powershell/ql/src/queries/security/cwe-078/InjectionHunter/UserInputToDangerousMethod.qhelp delete mode 100644 powershell/ql/src/queries/security/cwe-078/InjectionHunter/UserInputToDangerousMethod.ql delete mode 100644 powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/InjectionHunter.expected delete mode 100644 powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/InjectionHunter.qlref delete mode 100644 powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/test.ps1 diff --git a/powershell/ql/lib/semmle/code/powershell/security/CommandInjectionCustomizations.qll b/powershell/ql/lib/semmle/code/powershell/security/CommandInjectionCustomizations.qll index 1623941fb82..a75fd0028ca 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,169 @@ 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 ie | + this.asExpr().getExpr() = ie.getAnArgument() and + ie.getName() = "AddScript" and + ie.getQualifier().(InvokeMemberExpr).getName() = "Create" and + ie.getQualifier().getAChild().toString() = "PowerShell" and + ie.getParent().(InvokeMemberExpr).getName() = "Invoke" + ) + } + 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().toString() = "-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().toString() = "/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().toString() = "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(Expr e, VarReadAccess v | + e = this.asExpr().getExpr().getParent() and + e.toString().matches("%'$" + v.getVariable().getName() + "'%") + ) + } } } + 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:

+ + @@ -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/src/queries/security/cwe-078/InjectionHunter/Sanitizers.qll b/powershell/ql/src/queries/security/cwe-078/InjectionHunter/Sanitizers.qll deleted file mode 100644 index ac635928e10..00000000000 --- a/powershell/ql/src/queries/security/cwe-078/InjectionHunter/Sanitizers.qll +++ /dev/null @@ -1,26 +0,0 @@ -import powershell -import semmle.code.powershell.dataflow.TaintTracking -import semmle.code.powershell.dataflow.DataFlow -import semmle.code.powershell.ApiGraphs - - -abstract class Sanitizer extends DataFlow::Node {} - -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(Expr e, VarReadAccess v | - e = this.asExpr().getExpr().getParent() and - e.toString().matches("%'$" + v.getVariable().getName() + "'%") - ) - } -} diff --git a/powershell/ql/src/queries/security/cwe-078/InjectionHunter/Sinks.qll b/powershell/ql/src/queries/security/cwe-078/InjectionHunter/Sinks.qll deleted file mode 100644 index 4c62966746f..00000000000 --- a/powershell/ql/src/queries/security/cwe-078/InjectionHunter/Sinks.qll +++ /dev/null @@ -1,152 +0,0 @@ -import powershell -import semmle.code.powershell.dataflow.TaintTracking -import semmle.code.powershell.dataflow.DataFlow -import semmle.code.powershell.ApiGraphs -import semmle.code.powershell.dataflow.flowsources.FlowSources - -abstract class InjectionSink extends DataFlow::Node { - abstract string getSinkType(); -} - -class InvokeExpressionCall extends InjectionSink { - InvokeExpressionCall() { - exists(CmdCall c | - this.asExpr().getExpr() = c.getAnArgument() and - c.getName() = ["Invoke-Expression", "iex", "Add-Type" ] ) - } - override string getSinkType(){ - result = "call to Invoke-Expression" - } -} - -class InvokeScriptSink extends InjectionSink { - 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 InjectionSink { - 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 InjectionSink { - AddScriptInvokeSink() { - exists(InvokeMemberExpr ie | - this.asExpr().getExpr() = ie.getAnArgument() and - ie.getName() = "AddScript" and - ie.getQualifier().(InvokeMemberExpr).getName() = "Create" and - ie.getQualifier().getAChild().toString() = "PowerShell" and - ie.getParent().(InvokeMemberExpr).getName() = "Invoke" - ) - } - override string getSinkType(){ - result = "call to AddScript" - } -} - -class PowershellSink extends InjectionSink { - PowershellSink() { - exists( CmdCall c | - c.getName() = "powershell" | - ( - this.asExpr().getExpr() = c.getArgument(1) and - c.getArgument(0).getValue().toString() = "-command" - ) or - ( - this.asExpr().getExpr() = c.getArgument(0) - ) - ) - } - override string getSinkType(){ - result = "call to Powershell" - } -} - -class CmdSink extends InjectionSink { - CmdSink() { - exists(CmdCall c | - this.asExpr().getExpr() = c.getArgument(1) and - c.getName() = "cmd" and - c.getArgument(0).getValue().toString() = "/c" - ) - } - override string getSinkType(){ - result = "call to Cmd" - } -} - -class ForEachObjectSink extends InjectionSink { - 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 InjectionSink { - 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 InjectionSink { - CreateScriptBlockSink() { - exists(InvokeMemberExpr ie | - this.asExpr().getExpr() = ie.getAnArgument() and - ie.getName() = "Create" and - ie.getQualifier().toString() = "ScriptBlock" - ) - } - override string getSinkType(){ - result = "call to CreateScriptBlock" - } -} - -class NewScriptBlockSink extends InjectionSink { - 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 InjectionSink { - 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" - } -} \ No newline at end of file diff --git a/powershell/ql/src/queries/security/cwe-078/InjectionHunter/UserInputToDangerousMethod.qhelp b/powershell/ql/src/queries/security/cwe-078/InjectionHunter/UserInputToDangerousMethod.qhelp deleted file mode 100644 index de459c2e84f..00000000000 --- a/powershell/ql/src/queries/security/cwe-078/InjectionHunter/UserInputToDangerousMethod.qhelp +++ /dev/null @@ -1,48 +0,0 @@ - - - -

    Code that passes user input directly to -Invoke-Expression, &, or some other library -routine that executes a command, allows the user to execute malicious -code.

    - -

    This is a port of the InjectionHunter tool by Lee Holmes, and checks when user input is passed to any of the following:

    -
      -
    • Invoke-Expression
    • -
    • InvokeScript
    • -
    • CreateNestedPipeline
    • -
    • AddScript
    • -
    • powershell
    • -
    • cmd
    • -
    • Foreach-Object
    • -
    • Invoke
    • -
    • CreateScriptBlock
    • -
    • NewScriptBlock
    • -
    • ExpandString
    • -
    - -
    - - -

    Possible script injection risk. Untrusted input can cause arbitrary PowerShell expressions to be run. -Variables may be used directly for dynamic parameter arguments, splatting can be used for dynamic parameter names, -and the invocation operator can be used for dynamic command names. If content escaping is truly needed, PowerShell has several valid quote characters, -so [System.Management.Automation.Language.CodeGeneration]::Escape* should be used.

    - -
    - - - -
  • -OWASP: -Command Injection. -
  • -
  • -Injection Hunter: -PowerShell Injection Hunter: Security Auditing for PowerShell Scripts. -
  • - -
    -
    diff --git a/powershell/ql/src/queries/security/cwe-078/InjectionHunter/UserInputToDangerousMethod.ql b/powershell/ql/src/queries/security/cwe-078/InjectionHunter/UserInputToDangerousMethod.ql deleted file mode 100644 index 11730a65299..00000000000 --- a/powershell/ql/src/queries/security/cwe-078/InjectionHunter/UserInputToDangerousMethod.ql +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @name User Input to injection sink - * @description Finding cases where the user input is passed an dangerous method that can lead to RCE - * @kind path-problem - * @problem.severity error - * @security-severity 9.8 - * @precision high - * @id powershell/microsoft/public/user-input-to-injection-sink - * @tags security - * external/cwe/cwe-078 - * external/cwe/cwe-088 - */ - -import powershell -import semmle.code.powershell.dataflow.TaintTracking -import semmle.code.powershell.dataflow.DataFlow -import semmle.code.powershell.ApiGraphs -import semmle.code.powershell.dataflow.flowsources.FlowSources - -import Sanitizers -import Sinks - -private module InjectionConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - source instanceof SourceNode - } - predicate isSink(DataFlow::Node sink) { sink instanceof InjectionSink } - predicate isBarrier(DataFlow::Node node) {node instanceof Sanitizer} -} - -module InjectionFlow = TaintTracking::Global; -import InjectionFlow::PathGraph - -from InjectionFlow::PathNode source, InjectionFlow::PathNode sink -where InjectionFlow::flowPath(source, sink) -select sink.getNode(), source, sink, "Possible injection path from user input to dangerous " + sink.getNode().(InjectionSink).getSinkType() 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 diff --git a/powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/InjectionHunter.expected b/powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/InjectionHunter.expected deleted file mode 100644 index 8bfcc1dafec..00000000000 --- a/powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/InjectionHunter.expected +++ /dev/null @@ -1,146 +0,0 @@ -edges -| 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:102:11:102:20 | UserInput | test.ps1:106:33:106:62 | Get-Process -Name $UserInput | provenance | | -| test.ps1:112:11:112:20 | UserInput | test.ps1:116:58:116:87 | Get-Process -Name $UserInput | provenance | | -| test.ps1:122:11:122:20 | UserInput | test.ps1:124:34:124:43 | UserInput | provenance | | -| test.ps1:129:11:129:20 | UserInput | test.ps1:131:28:131:37 | UserInput | provenance | | -| test.ps1:136:11:136:20 | UserInput | test.ps1:138:28:138:37 | UserInput | provenance | | -| test.ps1:165:11:165:20 | UserInput | test.ps1:168:50:168:59 | UserInput | provenance | | -| test.ps1:173:11:173:20 | UserInput | test.ps1:176:63:176:72 | UserInput | provenance | | -| test.ps1:189:11:189:20 | UserInput | test.ps1:192:23:192:54 | Get-Process -Name "$escaped" | provenance | | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:197:46:197:51 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:198:46:198:51 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:199:46:199:51 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:200:46:200:51 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:201:46:201:51 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:202:46:202:51 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:203:46:203:51 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:204:46:204:51 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:206:48:206:53 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:207:48:207:53 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:208:48:208:53 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:210:41:210:46 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:211:41:211:46 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:212:36:212:41 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:213:36:213:41 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:214:36:214:41 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:218:42:218:47 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:219:42:219:47 | input | provenance | Src:MaD:11464 | -| test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:221:33:221:38 | input | provenance | Src:MaD:11464 | -| test.ps1:197:46:197:51 | input | test.ps1:3:11:3:20 | UserInput | provenance | | -| test.ps1:198:46:198:51 | input | test.ps1:9:11:9:20 | UserInput | provenance | | -| test.ps1:199:46:199:51 | input | test.ps1:15:11:15:20 | UserInput | provenance | | -| test.ps1:200:46:200:51 | input | test.ps1:21:11:21:20 | UserInput | provenance | | -| test.ps1:201:46:201:51 | input | test.ps1:27:11:27:20 | UserInput | provenance | | -| test.ps1:202:46:202:51 | input | test.ps1:33:11:33:20 | UserInput | provenance | | -| test.ps1:203:46:203:51 | input | test.ps1:39:11:39:20 | UserInput | provenance | | -| test.ps1:204:46:204:51 | input | test.ps1:45:11:45:20 | UserInput | provenance | | -| test.ps1:206:48:206:53 | input | test.ps1:73:11:73:20 | UserInput | provenance | | -| test.ps1:207:48:207:53 | input | test.ps1:80:11:80:20 | UserInput | provenance | | -| test.ps1:208:48:208:53 | input | test.ps1:87:11:87:20 | UserInput | provenance | | -| test.ps1:210:41:210:46 | input | test.ps1:102:11:102:20 | UserInput | provenance | | -| test.ps1:211:41:211:46 | input | test.ps1:112:11:112:20 | UserInput | provenance | | -| test.ps1:212:36:212:41 | input | test.ps1:122:11:122:20 | UserInput | provenance | | -| test.ps1:213:36:213:41 | input | test.ps1:129:11:129:20 | UserInput | provenance | | -| test.ps1:214:36:214:41 | input | test.ps1:136:11:136:20 | UserInput | provenance | | -| test.ps1:218:42:218:47 | input | test.ps1:165:11:165:20 | UserInput | provenance | | -| test.ps1:219:42:219:47 | input | test.ps1:173:11:173:20 | UserInput | provenance | | -| test.ps1:221:33:221:38 | input | test.ps1:189:11:189:20 | UserInput | provenance | | -nodes -| 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:102:11:102:20 | UserInput | semmle.label | UserInput | -| test.ps1:106:33:106:62 | Get-Process -Name $UserInput | semmle.label | Get-Process -Name $UserInput | -| test.ps1:112:11:112:20 | UserInput | semmle.label | UserInput | -| test.ps1:116:58:116:87 | Get-Process -Name $UserInput | semmle.label | Get-Process -Name $UserInput | -| test.ps1:122:11:122:20 | UserInput | semmle.label | UserInput | -| test.ps1:124:34:124:43 | UserInput | semmle.label | UserInput | -| test.ps1:129:11:129:20 | UserInput | semmle.label | UserInput | -| test.ps1:131:28:131:37 | UserInput | semmle.label | UserInput | -| test.ps1:136:11:136:20 | UserInput | semmle.label | UserInput | -| test.ps1:138:28:138:37 | UserInput | semmle.label | UserInput | -| test.ps1:165:11:165:20 | UserInput | semmle.label | UserInput | -| test.ps1:168:50:168:59 | UserInput | semmle.label | UserInput | -| test.ps1:173:11:173:20 | UserInput | semmle.label | UserInput | -| test.ps1:176:63:176:72 | UserInput | semmle.label | UserInput | -| test.ps1:189:11:189:20 | UserInput | semmle.label | UserInput | -| test.ps1:192:23:192:54 | Get-Process -Name "$escaped" | semmle.label | Get-Process -Name "$escaped" | -| test.ps1:195:10:195:32 | Call to Read-Host | semmle.label | Call to Read-Host | -| test.ps1:197:46:197:51 | input | semmle.label | input | -| test.ps1:198:46:198:51 | input | semmle.label | input | -| test.ps1:199:46:199:51 | input | semmle.label | input | -| test.ps1:200:46:200:51 | input | semmle.label | input | -| test.ps1:201:46:201:51 | input | semmle.label | input | -| test.ps1:202:46:202:51 | input | semmle.label | input | -| test.ps1:203:46:203:51 | input | semmle.label | input | -| test.ps1:204:46:204:51 | input | semmle.label | input | -| test.ps1:206:48:206:53 | input | semmle.label | input | -| test.ps1:207:48:207:53 | input | semmle.label | input | -| test.ps1:208:48:208:53 | input | semmle.label | input | -| test.ps1:210:41:210:46 | input | semmle.label | input | -| test.ps1:211:41:211:46 | input | semmle.label | input | -| test.ps1:212:36:212:41 | input | semmle.label | input | -| test.ps1:213:36:213:41 | input | semmle.label | input | -| test.ps1:214:36:214:41 | input | semmle.label | input | -| test.ps1:218:42:218:47 | input | semmle.label | input | -| test.ps1:219:42:219:47 | input | semmle.label | input | -| test.ps1:221:33:221:38 | input | semmle.label | input | -subpaths -#select -| test.ps1:4:23:4:52 | Get-Process -Name $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:4:23:4:52 | Get-Process -Name $UserInput | Possible injection path from user input to dangerous call to Invoke-Expression | -| test.ps1:10:9:10:38 | Get-Process -Name $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:10:9:10:38 | Get-Process -Name $UserInput | Possible injection path from user input to dangerous call to Invoke-Expression | -| test.ps1:16:50:16:79 | Get-Process -Name $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:16:50:16:79 | Get-Process -Name $UserInput | Possible injection path from user input to dangerous call to InvokeScript | -| test.ps1:22:41:22:70 | Get-Process -Name $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:22:41:22:70 | Get-Process -Name $UserInput | Possible injection path from user input to dangerous call to CreateNestedPipeline | -| test.ps1:22:41:22:70 | Get-Process -Name $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:22:41:22:70 | Get-Process -Name $UserInput | Possible injection path from user input to dangerous call to Invoke | -| test.ps1:22:60:22:69 | UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:22:60:22:69 | UserInput | Possible injection path from user input to dangerous call to Invoke | -| test.ps1:28:38:28:67 | Get-Process -Name $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:28:38:28:67 | Get-Process -Name $UserInput | Possible injection path from user input to dangerous call to AddScript | -| test.ps1:28:38:28:67 | Get-Process -Name $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:28:38:28:67 | Get-Process -Name $UserInput | Possible injection path from user input to dangerous call to Invoke | -| test.ps1:28:57:28:66 | UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:28:57:28:66 | UserInput | Possible injection path from user input to dangerous call to Invoke | -| test.ps1:34:14:34:46 | public class Foo { $UserInput } | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:34:14:34:46 | public class Foo { $UserInput } | Possible injection path from user input to dangerous call to Invoke-Expression | -| test.ps1:40:30:40:62 | public class Foo { $UserInput } | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:40:30:40:62 | public class Foo { $UserInput } | Possible injection path from user input to dangerous call to Invoke-Expression | -| test.ps1:48:30:48:34 | code | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:48:30:48:34 | code | Possible injection path from user input to dangerous call to Invoke-Expression | -| test.ps1:75:25:75:54 | Get-Process -Name $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:75:25:75:54 | Get-Process -Name $UserInput | Possible injection path from user input to dangerous call to Powershell | -| test.ps1:82:16:82:45 | Get-Process -Name $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:82:16:82:45 | Get-Process -Name $UserInput | Possible injection path from user input to dangerous call to Powershell | -| test.ps1:89:12:89:28 | ping $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:89:12:89:28 | ping $UserInput | Possible injection path from user input to dangerous call to Cmd | -| test.ps1:106:33:106:62 | Get-Process -Name $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:106:33:106:62 | Get-Process -Name $UserInput | Possible injection path from user input to dangerous call to CreateScriptBlock | -| test.ps1:116:58:116:87 | Get-Process -Name $UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:116:58:116:87 | Get-Process -Name $UserInput | Possible injection path from user input to dangerous call to NewScriptBlock | -| test.ps1:124:34:124:43 | UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:124:34:124:43 | UserInput | Possible injection path from user input to dangerous call to ForEach-Object | -| test.ps1:131:28:131:37 | UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:131:28:131:37 | UserInput | Possible injection path from user input to dangerous call to Invoke | -| test.ps1:138:28:138:37 | UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:138:28:138:37 | UserInput | Possible injection path from user input to dangerous call to Invoke | -| test.ps1:168:50:168:59 | UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:168:50:168:59 | UserInput | Possible injection path from user input to dangerous call to ExpandString | -| test.ps1:176:63:176:72 | UserInput | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:176:63:176:72 | UserInput | Possible injection path from user input to dangerous call to ExpandString | -| test.ps1:192:23:192:54 | Get-Process -Name "$escaped" | test.ps1:195:10:195:32 | Call to Read-Host | test.ps1:192:23:192:54 | Get-Process -Name "$escaped" | Possible injection path from user input to dangerous call to Invoke-Expression | diff --git a/powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/InjectionHunter.qlref b/powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/InjectionHunter.qlref deleted file mode 100644 index 61447f65050..00000000000 --- a/powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/InjectionHunter.qlref +++ /dev/null @@ -1 +0,0 @@ -queries/security/cwe-078/InjectionHunter/UserInputToDangerousMethod.ql \ No newline at end of file diff --git a/powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/test.ps1 b/powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/test.ps1 deleted file mode 100644 index 3757d9d4f2c..00000000000 --- a/powershell/ql/test/query-tests/security/cwe-078/InjectionHunter/test.ps1 +++ /dev/null @@ -1,221 +0,0 @@ -function Invoke-InvokeExpressionInjection1 -{ - param($UserInput) - Invoke-Expression "Get-Process -Name $UserInput" -} - -function Invoke-InvokeExpressionInjection2 -{ - param($UserInput) - iex "Get-Process -Name $UserInput" -} - -function Invoke-InvokeExpressionInjection3 -{ - param($UserInput) - $executionContext.InvokeCommand.InvokeScript("Get-Process -Name $UserInput") -} - -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" -} - -#Allowed -function Invoke-ExploitableCommandInjectionFP -{ - param($UserInput) - - cmd /c "ping localhost" -} - -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() -} - -#ALLOWED , uses script block -function Invoke-MethodInjectionFP1 -{ - param($UserInput) - - Get-Process | Foreach-Object { $_.Name } -} -#ALLOWED, uses constant member access -function Invoke-MethodInjectionFP2 -{ - param($UserInput) - - Get-Process | Foreach-Object "Name" -} - -function Invoke-PropertyInjection -{ - param($UserInput) - - [DateTime]::$UserInput -} - -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) -} - -function Invoke-UnsafeEscape1 -{ - param($UserInput) - - $escaped = $UserInput -replace "'", "''" - Invoke-Expression "Get-Process -Name '$escaped'" -} - -function Invoke-UnsafeEscape2 -{ - param($UserInput) - - $escaped = $UserInput -replace '"', '`"' - Invoke-Expression "Get-Process -Name `"$escaped`"" -} - -$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-ExploitableCommandInjectionFP -UserInput $input -Invoke-ScriptBlockInjection1 -UserInput $input -Invoke-ScriptBlockInjection2 -UserInput $input -Invoke-MethodInjection1 -UserInput $input -Invoke-MethodInjection2 -UserInput $input -Invoke-MethodInjection3 -UserInput $input -Invoke-MethodInjectionFP1 -UserInput $input -Invoke-MethodInjectionFP2 -UserInput $input -Invoke-PropertyInjection -UserInput $input -Invoke-ExpandStringInjection1 -UserInput $input -Invoke-ExpandStringInjection2 -UserInput $input -Invoke-UnsafeEscape1 -UserInput $input -Invoke-UnsafeEscape2 -UserInput $input \ No newline at end of file