initial query

This commit is contained in:
Chanel Young
2025-04-03 11:23:49 -07:00
parent b452339b23
commit 656b734391
3 changed files with 212 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Code that passes user input directly to
<code>Invoke-Expression</code>, <code>&</code>, or some other library
routine that executes a command, allows the user to execute malicious
code.</p>
</overview>
<recommendation>
<p>Possible script injection risk via the Invoke-Expression cmdlet. 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.</p>
</recommendation>
<example>
<p>The following example shows code that takes a shell script that can be changed
maliciously by a user, and passes it straight to <code>Invoke-Expression</code>
without examining it first.</p>
<sample src="examples/command_injection.ps1" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
</li>
<!-- LocalWords: CWE untrusted unsanitized Runtime
-->
</references>
</qhelp>

View File

@@ -0,0 +1,172 @@
/**
* @name User Input to Invoke-Expression
* @description Finding cases where the user input is passed an Invoke-Expression command
* @kind path-problem
* @problem.severity error
* @security-severity 9.8
* @precision high
* @id powershell/microsoft/public/user-input-to-invoke-expression
* @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
private module TestConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
exists(CmdCall c |
c.getName() = "Read-Host" and
source.asExpr().getExpr() = c) }
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) {node instanceof Sanitizer}
}
abstract class Source extends DataFlow::Node {}
class ReadHostSource extends Source {
ReadHostSource() {
exists(CmdCall c |
this.asExpr().getExpr() = c and
c.getName() = "Read-Host" )
}
}
class GetContentSource extends Source {
GetContentSource() {
exists(CmdCall c |
this.asExpr().getExpr() = c and
c.getName() = "Get-Content" )
}
}
class ValueFromPipelineSource extends Source {
ValueFromPipelineSource() {
exists(Parameter p |
p.getAnAttribute().toString() = "ValueFromPipeline" and
this.asExpr().getExpr() = p.getAnAccess()
)
}
}
abstract class Sink extends DataFlow::Node {}
class InvokeExpressionCall extends Sink {
InvokeExpressionCall() {
exists(CmdCall c |
this.asExpr().getExpr() = c.getAnArgument() and
c.getName() = ["Invoke-Expression", "iex", "Add-Type" ] )
}
}
class InvokeScriptSink extends Sink {
InvokeScriptSink() {
exists(InvokeMemberExpr ie |
this.asExpr().getExpr() = ie.getAnArgument() and
ie.getName() = "InvokeScript" and
ie.getQualifier().toString() = "InvokeCommand" and
ie.getQualifier().getAChild().toString() = "executioncontext"
)
}
}
class CreateNestedPipelineSink extends Sink {
CreateNestedPipelineSink() {
exists(InvokeMemberExpr ie |
this.asExpr().getExpr() = ie.getAnArgument() and
ie.getName() = "CreateNestedPipeline" and
ie.getQualifier().toString() = "InvokeCommand" and
ie.getQualifier().getAChild().toString() = "executioncontext")
}
}
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"
)
}
}
abstract class Sanitizer extends DataFlow::Node {}
// class TypedParameterSanitizer extends Sanitizer{
// TypedParameterSanitizer() {
// exists(Function f, CmdCall c, Parameter p, Argument a |
// p = f.getAParameter() and
// a = c.getAnArgument() and
// p.getName().toLowerCase() = a.getName() and
// p.getStaticType() != "Object" and
// c.getName() = f.getName() and
// this.asExpr().getExpr() = a
// )
// }
// }
class SingleQuoteSanitizer extends Sanitizer {
SingleQuoteSanitizer() {
exists(Expr e, VarReadAccess v |
e = this.asExpr().getExpr().getParent() and
e.toString().matches("%'$" + v.getVariable().getName() + "'%")
)
}
}
module TestFlow = TaintTracking::Global<TestConfig>;
import TestFlow::PathGraph
// from TestFlow::PathNode source, TestFlow::PathNode sink
// where
// TestFlow::flowPath(source, sink) and
// sink.getNode().asExpr().getExpr().getLocation().getFile().getBaseName() = "sanitizers.ps1"
// select sink.getNode(), source, sink, "Flow from user input to Invoke-Expression"
// from Function f, CmdCall c
// where f.getLocation().getFile().getBaseName() = "sanitizers.ps1"
// select f, f.getAParameter().getStaticType(), f.getAParameter().getName()
//TBD, waiting on mathias on how to connect f and c
// from Function f, CmdCall c, Parameter p, Argument a
// where
// p = f.getAParameter() and
// a = c.getAnArgument() and
// p.getName().toLowerCase() = a.getName() and
// p.getStaticType() != "Object" and
// c.getName() = f.getName()
// select a, "argument has a specified static type"
// from Argument a, VarReadAccess v
// where a.getAChild() = v and
// v.getVariable().getName() = "UserInput"
// select a, v
// from Argument e
// where e.getLocation().getFile().getBaseName() = "sanitizers.ps1"
// and e.getLocation().getStartLine() = 14
// select e, e.getAChild(), e.getParent(), e.toString()
from Parameter p
where p.getLocation().getFile().getBaseName() = "userinput.ps1"
// p.getAnAttribute().toString() = "ValueFromPipeline" and
select p, p.getName()
// from Expr e
// where e.getLocation().getFile().getBaseName() = "userinput.ps1"
// select e, e.getAQlClass()
// from InvokeMemberExpr ie
// where
// ie.getLocation().getStartLine() = 28 and ie.getName() = "AddScript"
// select ie, ie.getName(), ie.getQualifier().toString(), ie.getQualifier().getAChild().toString(), ie.getParent().(InvokeMemberExpr).getName()