diff --git a/.github/workflows/microsoft-codeql-pack-publish.yml b/.github/workflows/microsoft-codeql-pack-publish.yml index 9dc8a4c38999..55b553e738f2 100644 --- a/.github/workflows/microsoft-codeql-pack-publish.yml +++ b/.github/workflows/microsoft-codeql-pack-publish.yml @@ -100,7 +100,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - language: ['csharp', 'cpp', 'java', 'javascript', 'python', 'ruby', 'go', 'rust', 'swift', 'powershell'] + language: ['csharp', 'cpp', 'java', 'javascript', 'python', 'ruby', 'go', 'rust', 'swift', 'powershell', 'iac'] steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000000..9a2df7094cc1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "iac"] + path = iac + url = https://github.com/advanced-security/codeql-extractor-iac diff --git a/iac b/iac new file mode 160000 index 000000000000..1709e321895e --- /dev/null +++ b/iac @@ -0,0 +1 @@ +Subproject commit 1709e321895e42d5b3cc1c4047ce4fc70639ad95 diff --git a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll index 78cd43b5e8cc..1bea01e1f917 100644 --- a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll +++ b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll @@ -563,7 +563,7 @@ module API { ) or exists(DataFlow::AutomaticVariableNode automatic | - automatic.getName() = name and + automatic.getLowerCaseName() = name and succ = getForwardStartNode(automatic) ) or @@ -571,7 +571,7 @@ module API { ) or exists(DataFlow::QualifiedTypeNameNode typeName | - typeName.getName() = name and + typeName.getLowerCaseName() = name and pred = MkNamespaceOfTypeNameNode(typeName) and succ = getForwardStartNode(typeName) ) diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/AutomaticVariable.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/AutomaticVariable.qll index 4196280a638a..0fe155f15667 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/AutomaticVariable.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/AutomaticVariable.qll @@ -1,11 +1,15 @@ private import AstImport class AutomaticVariable extends Expr, TAutomaticVariable { - final override string toString() { result = this.getName() } + final override string toString() { result = this.getLowerCaseName() } - string getName() { any(Synthesis s).automaticVariableName(this, result) } + string getLowerCaseName() { any(Synthesis s).automaticVariableName(this, result) } + + bindingset[result] + pragma[inline_late] + string getAName() { result.toLowerCase() = this.getLowerCaseName() } } class MyInvocation extends AutomaticVariable { - MyInvocation() { this.getName() = "myinvocation" } + MyInvocation() { this.getLowerCaseName() = "myinvocation" } } diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/ChildIndex.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/ChildIndex.qll index 9ec865440d11..fdb4a49e1c37 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/ChildIndex.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/ChildIndex.qll @@ -26,11 +26,7 @@ newtype ChildIndex = ExprRedirection(int i) { exists(any(Raw::Cmd cmdExpr).getRedirection(i)) } or FunDefFun() or TypeDefType() or - TypeMember(int i) { - exists(any(Raw::TypeStmt typedef).getMember(i)) - // or - // hasMemberInType(_, _, i, _) - } or + TypeMember(int i) { exists(any(Raw::TypeStmt typedef).getMember(i)) } or ThisVar() or PipelineIteratorVar() or PipelineByPropertyNameIteratorVar(Raw::PipelineByPropertyNameParameter p) or @@ -38,7 +34,8 @@ newtype ChildIndex = ProcessBlockPipelineVarReadAccess() or ProcessBlockPipelineByPropertyNameVarReadAccess(string name) { name = any(Raw::PipelineByPropertyNameParameter p).getLowerCaseName() - } + } or + EnvVar(string var) { Raw::isEnvVariableAccess(_, var) } int synthPipelineParameterChildIndex(Raw::ScriptBlock sb) { // If there is a parameter block, but no pipeline parameter diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/Command.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/Command.qll index c1984d5286e6..a2ae0bde31e0 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/Command.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/Command.qll @@ -76,14 +76,14 @@ class CmdCall extends CallExpr, TCmd { class CallOperator extends CmdCall { CallOperator() { getRawAst(this) instanceof Raw::CallOperator } - Expr getCommand() { result = this.getArgument(0) } + Expr getCommand() { result = this.getCallee() } } /** A call to the dot-sourcing `.`. */ class DotSourcingOperator extends CmdCall { DotSourcingOperator() { getRawAst(this) instanceof Raw::DotSourcingOperator } - Expr getPath() { result = this.getArgument(0) } + Expr getCommand() { result = this.getCallee() } } class JoinPath extends CmdCall { diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/EnvVariable.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/EnvVariable.qll index 5d61c101bd44..3bbd2bf2f485 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/EnvVariable.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/EnvVariable.qll @@ -1,11 +1,15 @@ private import AstImport -class EnvVariable extends Expr, TEnvVariable { - final override string toString() { result = this.getName() } +class EnvVariable extends Variable instanceof EnvVariableImpl { + string getLowerCaseName() { result = super.getLowerCaseNameImpl() } - string getName() { any(Synthesis s).envVariableName(this, result) } -} + bindingset[name] + pragma[inline_late] + final predicate matchesName(string name) { this.getLowerCaseName() = name.toLowerCase() } + + bindingset[result] + pragma[inline_late] + final string getAName() { result.toLowerCase() = this.getLowerCaseName() } -class SystemDrive extends EnvVariable { - SystemDrive() { this.getName() = "systemdrive" } -} \ No newline at end of file + override Ast getChild(ChildIndex childIndex) { none() } +} diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/InvokeMemberExpression.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/InvokeMemberExpression.qll index 4772233c9f5a..9c870d9957a3 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/InvokeMemberExpression.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/InvokeMemberExpression.qll @@ -71,8 +71,13 @@ class ConstructorCall extends InvokeMemberExpr { this.isStatic() and typename = this.getQualifier() and this.getLowerCaseName() = "new" } + /** Gets a name of the type being constructed by this constructor call. */ + bindingset[result] + pragma[inline_late] + string getAConstructedTypeName() { result = typename.getAName() } + /** Gets the name of the type being constructed by this constructor call. */ - string getConstructedTypeName() { result = typename.getName() } + string getLowerCaseConstructedTypeName() { result = typename.getLowerCaseName() } } /** diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/ObjectCreation.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/ObjectCreation.qll index a2b65a8b0c4f..d5a51be00a37 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/ObjectCreation.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/ObjectCreation.qll @@ -2,7 +2,11 @@ import powershell abstract private class AbstractObjectCreation extends CallExpr { /** The name of the type of the object being constructed. */ - abstract string getConstructedTypeName(); + bindingset[result] + pragma[inline_late] + string getAConstructedTypeName() { result.toLowerCase() = this.getLowerCaseConstructedTypeName() } + + abstract string getLowerCaseConstructedTypeName(); abstract Expr getConstructedTypeExpr(); } @@ -14,8 +18,14 @@ abstract private class AbstractObjectCreation extends CallExpr { * ``` */ class NewObjectCreation extends AbstractObjectCreation, ConstructorCall { - final override string getConstructedTypeName() { - result = ConstructorCall.super.getConstructedTypeName() + final override string getLowerCaseConstructedTypeName() { + result = ConstructorCall.super.getLowerCaseConstructedTypeName() + } + + bindingset[result] + pragma[inline_late] + final override string getAConstructedTypeName() { + result = ConstructorCall.super.getAConstructedTypeName() } final override Expr getConstructedTypeExpr() { result = typename } @@ -30,8 +40,8 @@ class NewObjectCreation extends AbstractObjectCreation, ConstructorCall { class DotNetObjectCreation extends AbstractObjectCreation, CmdCall { DotNetObjectCreation() { this.getLowerCaseName() = "new-object" } - final override string getConstructedTypeName() { - result = this.getConstructedTypeExpr().(StringConstExpr).getValueString() + final override string getLowerCaseConstructedTypeName() { + result = this.getConstructedTypeExpr().(StringConstExpr).getValueString().toLowerCase() } final override Expr getConstructedTypeExpr() { diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/Raw/NamedAttributeArgument.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/Raw/NamedAttributeArgument.qll index 324dec4d27e7..656a19b05dcd 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/Raw/NamedAttributeArgument.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/Raw/NamedAttributeArgument.qll @@ -15,9 +15,11 @@ class NamedAttributeArgument extends @named_attribute_argument, Ast { } class ValueFromPipelineAttribute extends NamedAttributeArgument { - ValueFromPipelineAttribute() { this.getName() = "ValueFromPipeline" } + ValueFromPipelineAttribute() { this.getName().toLowerCase() = "valuefrompipeline" } } class ValueFromPipelineByPropertyName extends NamedAttributeArgument { - ValueFromPipelineByPropertyName() { this.getName() = "ValueFromPipelineByPropertyName" } + ValueFromPipelineByPropertyName() { + this.getName().toLowerCase() = "valuefrompipelinebypropertyname" + } } diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/Synthesis.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/Synthesis.qll index 7afca387379e..dc2ec0858a38 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/Synthesis.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/Synthesis.qll @@ -15,13 +15,13 @@ private import Type private import Scopes private import BoolLiteral private import Member -private import EnvVariable private import Raw.Raw as Raw private import codeql.util.Boolean private import AutomaticVariable newtype VarKind = ThisVarKind() or + EnvVarKind(string var) { Raw::isEnvVariableAccess(_, var) } or ParamVarRealKind() or PipelineIteratorKind() or PipelineByPropertyNameIteratorKind(string name) { @@ -39,7 +39,6 @@ newtype SynthKind = TypeSynthKind() or BoolLiteralKind(Boolean b) or NullLiteralKind() or - EnvVariableKind(string var) { Raw::isEnvVariableAccess(_, var) } or AutomaticVariableKind(string var) { Raw::isAutomaticVariableAccess(_, var) } or VarSynthKind(VarKind k) @@ -96,8 +95,6 @@ class Synthesis extends TSynthesis { predicate booleanValue(BoolLiteral b, boolean value) { none() } - predicate envVariableName(EnvVariable var, string name) { none() } - predicate automaticVariableName(AutomaticVariable var, string name) { none() } final string toString() { none() } @@ -152,6 +149,77 @@ private module ThisSynthesis { } } +private module EnvironmentVariables { + bindingset[var] + private Raw::TopLevelScriptBlock getScope(string var) { + result = + min(Raw::TopLevelScriptBlock scriptBlock, Raw::VarAccess va, Location loc | + Raw::isEnvVariableAccess(va, var) and + va.getParent+() = scriptBlock and + loc = scriptBlock.getLocation() + | + scriptBlock order by loc.getFile().getAbsolutePath() + ) + } + + private class EnvironmentVariables extends Synthesis { + final override predicate child(Raw::Ast parent, ChildIndex i, Child child) { + exists(Raw::VarAccess va, string s0 | + parent = getScope(s0) and + va.getUserPath().toLowerCase() = "env:" + s0 and + Raw::isEnvVariableAccess(va, s0) and + child = SynthChild(VarSynthKind(EnvVarKind(s0))) and + i = EnvVar(s0) + ) + } + + override predicate variableSynthName(VariableSynth v, string name) { + exists(string name0 | + v = TVariableSynth(_, EnvVar(name0)) and + name = "env:" + name0 + ) + } + } +} + +private module EnvironmentVariableAccessSynth { + private class EnvVarAccessSynthesis extends Synthesis { + final override predicate isRelevant(Raw::Ast a) { Raw::isEnvVariableAccess(a, _) } + + final override VarAccess getResultAstImpl(Raw::Ast r) { + exists(Raw::Ast parent, ChildIndex i | + this.envVarAccess(parent, i, _, r, _) and + result = TVarAccessSynth(parent, i) + ) + } + + private predicate envVarAccess(Raw::Ast parent, ChildIndex i, Child child, Raw::VarAccess va, string var) { + va = parent.getChild(toRawChildIndex(i)) and + Raw::isEnvVariableAccess(va, var) and + child = SynthChild(VarAccessSynthKind(TVariableSynth(_, EnvVar(var)))) + } + + override predicate child(Raw::Ast parent, ChildIndex i, Child child) { + this.envVarAccess(parent, i, child, _, _) + } + + final override predicate getAnAccess(VarAccessSynth va, Variable v) { + exists(Raw::Ast parent, ChildIndex i, string var | + this.envVarAccess(parent, i, _, _, var) and + v = TVariableSynth(_, EnvVar(var)) and + va = TVarAccessSynth(parent, i) + ) + } + + override Location getLocation(Ast n) { + exists(Raw::Ast scope | + n = TVariableSynth(scope, EnvVar(_)) and + result = scope.getLocation() + ) + } + } +} + private module SetVariableAssignment { private class SetVariableAssignment extends Synthesis { override predicate explicitAssignment(Raw::Ast dest, string name, Raw::Ast assignment) { @@ -277,7 +345,7 @@ private module ParameterSynth { // has a static type. this.parameter(parent, i, p, _) and n = TVariableSynth(parent, i) and - type = p.getStaticType() + type = p.getStaticType().toLowerCase() ) } } @@ -462,7 +530,7 @@ private module TypeSynth { override predicate typeName(Type t, string name) { exists(Raw::TypeStmt typeStmt | t = TTypeSynth(typeStmt, _) and - typeStmt.getName() = name + typeStmt.getName().toLowerCase() = name ) } @@ -673,7 +741,6 @@ private module LiteralSynth { exists(Raw::Ast parent, ChildIndex i | this.child(parent, i, _, r) | result = TBoolLiteral(parent, i) or result = TNullLiteral(parent, i) or - result = TEnvVariable(parent, i) or result = TAutomaticVariable(parent, i) ) } @@ -692,12 +759,6 @@ private module LiteralSynth { s = "null" and child = SynthChild(NullLiteralKind()) or - exists(string s0 | - s = "env:" + s0 and - Raw::isEnvVariableAccess(va, s0) and - child = SynthChild(EnvVariableKind(s0)) - ) - or isAutomaticVariableAccess(va, s) and child = SynthChild(AutomaticVariableKind(s)) ) @@ -714,13 +775,6 @@ private module LiteralSynth { ) } - final override predicate envVariableName(EnvVariable var, string name) { - exists(Raw::Ast parent, ChildIndex i | - var = TEnvVariable(parent, i) and - this.child(parent, i, SynthChild(EnvVariableKind(name))) - ) - } - final override predicate automaticVariableName(AutomaticVariable var, string name) { exists(Raw::Ast parent, ChildIndex i | var = TAutomaticVariable(parent, i) and diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/TAst.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/TAst.qll index b8f91de3cac8..cda32d1b2fa8 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/TAst.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/TAst.qll @@ -160,7 +160,6 @@ private module Cached { TUsingExpr(Raw::UsingExpr u) or TBoolLiteral(Raw::Ast parent, ChildIndex i) { mkSynthChild(BoolLiteralKind(_), parent, i) } or TNullLiteral(Raw::Ast parent, ChildIndex i) { mkSynthChild(NullLiteralKind(), parent, i) } or - TEnvVariable(Raw::Ast parent, ChildIndex i) { mkSynthChild(EnvVariableKind(_), parent, i) } or TAutomaticVariable(Raw::Ast parent, ChildIndex i) { mkSynthChild(AutomaticVariableKind(_), parent, i) } @@ -180,7 +179,7 @@ private module Cached { class TAstSynth = TExprStmtSynth or TFunctionSynth or TBoolLiteral or TNullLiteral or TVarAccessSynth or - TEnvVariable or TTypeSynth or TAutomaticVariable or TVariableSynth; + TTypeSynth or TAutomaticVariable or TVariableSynth; class TExpr = TArrayExpr or TArrayLiteral or TOperation or TConstExpr or TConvertExpr or TErrorExpr or @@ -188,7 +187,7 @@ private module Cached { TPipelineChain or TStringConstExpr or TConditionalExpr or TVarAccess or TExpandableStringExpr or TScriptBlockExpr or TExpandableSubExpr or TTypeNameExpr or TUsingExpr or TAttributedExpr or TIf or TBoolLiteral or TNullLiteral or TThisExpr or - TEnvVariable or TAutomaticVariable or TParenExpr; + TAutomaticVariable or TParenExpr; class TStmt = TAssignStmt or TBreakStmt or TContinueStmt or TDataStmt or TDoUntilStmt or TDoWhileStmt or @@ -308,7 +307,6 @@ private module Cached { result = TBoolLiteral(parent, i) or result = TNullLiteral(parent, i) or result = TVarAccessSynth(parent, i) or - result = TEnvVariable(parent, i) or result = TTypeSynth(parent, i) or result = TAutomaticVariable(parent, i) or result = TVariableSynth(parent, i) diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/TypeExpression.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/TypeExpression.qll index 92c015e9e13b..2d994035ac3e 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/TypeExpression.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/TypeExpression.qll @@ -14,15 +14,21 @@ class TypeNameExpr extends Expr, TTypeNameExpr { ) } - string getName() { this.parseName(_, result) } + string getLowerCaseName() { this.parseName(_, result) } + + bindingset[result] + pragma[inline_late] + string getAName() { this.parseName(_, result.toLowerCase()) } /** If any */ - string getPossiblyQualifiedName() { result = getRawAst(this).(Raw::TypeNameExpr).getName() } + string getPossiblyQualifiedName() { + result = getRawAst(this).(Raw::TypeNameExpr).getName().toLowerCase() + } // TODO: What to do when System is omitted? string getNamespace() { this.parseName(result, _) } - override string toString() { result = this.getName() } + override string toString() { result = this.getLowerCaseName() } predicate isQualified() { this.getNamespace() != "" } diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/Variable.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/Variable.qll index b9791ab06bd0..41f374373426 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/Variable.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/Variable.qll @@ -106,6 +106,10 @@ module Private { } } + class EnvVariableImpl extends VariableSynth { + override EnvVar i; + } + abstract class VarAccessImpl extends Expr, TVarAccess { abstract VariableImpl getVariableImpl(); } diff --git a/powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll b/powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll index dcab50e1f023..9c9eb0fd5ffb 100644 --- a/powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll +++ b/powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll @@ -587,7 +587,15 @@ module ExprNodes { override ObjectCreation getExpr() { result = e } - string getConstructedTypeName() { result = this.getExpr().getConstructedTypeName() } + string getLowerCaseConstructedTypeName() { + result = this.getExpr().getLowerCaseConstructedTypeName() + } + + bindingset[result] + pragma[inline_late] + string getAConstructedTypeName() { + result.toLowerCase() = this.getLowerCaseConstructedTypeName() + } ExprCfgNode getConstructedTypeExpr() { e.hasCfgChild(this.getExpr().getConstructedTypeExpr(), this, result) @@ -605,7 +613,22 @@ module ExprNodes { override CallOperator getExpr() { result = e } - ExprCfgNode getCommand() { result = this.getArgument(0) } + ExprCfgNode getCommand() { result = this.getCallee() } + } + + private class DotSourcingOperatorChildMapping extends CallExprChildMapping instanceof DotSourcingOperator + { + override predicate relevantChild(Ast child) { super.relevantChild(child) } + } + + class DotSourcingOperatorCfgNode extends CallExprCfgNode { + override string getAPrimaryQlClass() { result = "DotSourcingOperatorCfgNode" } + + override DotSourcingOperatorChildMapping e; + + override DotSourcingOperator getExpr() { result = e } + + ExprCfgNode getCommand() { result = this.getCallee() } } private class ToStringCallChildmapping extends CallExprChildMapping instanceof ToStringCall { @@ -702,7 +725,11 @@ module ExprNodes { override TypeNameExpr getExpr() { result = e } - string getName() { result = e.getName() } + bindingset[result] + pragma[inline_late] + string getAName() { result = e.getAName() } + + string getLowerCaseName() { result = e.getLowerCaseName() } string getNamespace() { result = e.getNamespace() } @@ -1078,20 +1105,6 @@ module ExprNodes { CallExprCfgNode getCall() { result.getPipelineArgument() = this } } - private class EnvVariableChildMapping extends ExprChildMapping, EnvVariable { - override predicate relevantChild(Ast child) { none() } - } - - class EnvVariableCfgNode extends ExprCfgNode { - override string getAPrimaryQlClass() { result = "EnvVariableCfgNode" } - - override EnvVariableChildMapping e; - - override EnvVariable getExpr() { result = e } - - string getName() { result = e.getName() } - } - private class OperationChildMapping extends ExprChildMapping, Operation { override predicate relevantChild(Ast child) { child = this.getAnOperand() } } @@ -1117,7 +1130,11 @@ module ExprNodes { override AutomaticVariable getExpr() { result = e } - string getName() { result = e.getName() } + bindingset[result] + pragma[inline_late] + string getAName() { result = e.getAName() } + + string getLowerCaseName() { result = e.getLowerCaseName() } } } diff --git a/powershell/ql/lib/semmle/code/powershell/controlflow/internal/ControlFlowGraphImpl.qll b/powershell/ql/lib/semmle/code/powershell/controlflow/internal/ControlFlowGraphImpl.qll index a1f88274a98c..c66fb7a78ac1 100644 --- a/powershell/ql/lib/semmle/code/powershell/controlflow/internal/ControlFlowGraphImpl.qll +++ b/powershell/ql/lib/semmle/code/powershell/controlflow/internal/ControlFlowGraphImpl.qll @@ -736,8 +736,6 @@ module Trees { class VarTree extends LeafTree instanceof Variable { } - class EnvVariableTree extends LeafTree instanceof EnvVariable { } - class AutomaticVariableTree extends LeafTree instanceof AutomaticVariable { } class BinaryExprTree extends StandardPostOrderTree instanceof BinaryExpr { diff --git a/powershell/ql/lib/semmle/code/powershell/dataflow/Ssa.qll b/powershell/ql/lib/semmle/code/powershell/dataflow/Ssa.qll index 7c6e9c35692a..4c24f99b75f5 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/Ssa.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/Ssa.qll @@ -130,6 +130,21 @@ module Ssa { final override Location getLocation() { result = this.getBasicBlock().getLocation() } } + class InitialEnvVarDefinition extends Definition, SsaImpl::WriteDefinition { + private EnvVariable v; + + InitialEnvVarDefinition() { + exists(BasicBlock bb, int i | + this.definesAt(v, bb, i) and + SsaImpl::envVarWrite(bb, i, v) + ) + } + + final override string toString() { result = " " + v } + + final override Location getLocation() { result = this.getBasicBlock().getLocation() } + } + /** phi node. */ class PhiNode extends Definition, SsaImpl::PhiNode { /** Gets an input of this phi node. */ diff --git a/powershell/ql/lib/semmle/code/powershell/dataflow/flowsources/Local.qll b/powershell/ql/lib/semmle/code/powershell/dataflow/flowsources/Local.qll index 178f5f81c69c..4ffff2d35c62 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/flowsources/Local.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/flowsources/Local.qll @@ -31,7 +31,7 @@ abstract class EnvironmentVariableSource extends LocalFlowSource { } private class EnvironmentVariableEnv extends EnvironmentVariableSource { - EnvironmentVariableEnv() { this.asExpr().getExpr() instanceof EnvVariable } + EnvironmentVariableEnv() { this.asExpr().getExpr() = any(EnvVariable env).getAnAccess() } } private class ExternalEnvironmentVariableSource extends EnvironmentVariableSource { diff --git a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowDispatch.qll b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowDispatch.qll index 863bea7779c3..f2cc76d060f9 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowDispatch.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowDispatch.qll @@ -159,10 +159,10 @@ private predicate localFlowStep(Node nodeFrom, Node nodeTo, StepSummary summary) private module TrackInstanceInput implements CallGraphConstruction::InputSig { private predicate start0(Node start, string typename, boolean exact) { - start.(ObjectCreationNode).getObjectCreationNode().getConstructedTypeName() = typename and + start.(ObjectCreationNode).getObjectCreationNode().getLowerCaseConstructedTypeName() = typename and exact = true or - start.asExpr().(CfgNodes::ExprNodes::TypeNameExprCfgNode).getName() = typename and + start.asExpr().(CfgNodes::ExprNodes::TypeNameExprCfgNode).getLowerCaseName() = typename and exact = true or start.asParameter().getStaticType() = typename and diff --git a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll index 004e4ba4e7df..123ab0278eb7 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll @@ -65,7 +65,7 @@ module SsaFlow { Impl::Node asNode(Node n) { n = TSsaNode(result) or - result.(Impl::ExprNode).getExpr() = n.asExpr() + result.(Impl::ExprNode).getExpr().asExprCfgNode() = n.asExpr() or exists(CfgNodes::ProcessBlockCfgNode pb, BasicBlock bb, int i | n.(ProcessNode).getProcessBlock() = pb and @@ -86,7 +86,8 @@ module SsaFlow { result.(Impl::SsaDefinitionNode).getDefinition().definesAt(p.getIteratorVariable(), bb, i) ) or - result.(Impl::ExprPostUpdateNode).getExpr() = n.(PostUpdateNode).getPreUpdateNode().asExpr() + result.(Impl::ExprPostUpdateNode).getExpr().asExprCfgNode() = + n.(PostUpdateNode).getPreUpdateNode().asExpr() or exists(SsaImpl::ParameterExt p | n = toParameterNode(p) and @@ -94,6 +95,11 @@ module SsaFlow { ) or result.(Impl::WriteDefSourceNode).getDefinition().(Ssa::WriteDefinition).assigns(n.asExpr()) + or + exists(Scope scope, EnvVariable v | + result.(Impl::ExprNode).getExpr().isFinalEnvVarRead(scope, v) and + n = TFinalEnvVarRead(scope, v) + ) } predicate localFlowStep( @@ -241,7 +247,15 @@ private module Cached { // We want to prune irrelevant models before materialising data flow nodes, so types contributed // directly from CodeQL must expose their pruning info without depending on data flow nodes. (any(ModelInput::TypeModel tm).isTypeUsed("") implies any()) - } + } or + TFinalEnvVarRead(Scope scope, EnvVariable envVar) { + exists(ExitBasicBlock exit | + envVar.getAnAccess().getEnclosingScope() = scope and + exit.getScope() = scope and + SsaImpl::envVarRead(exit, _, envVar) + ) + } or + TEnvVarNode(EnvVariable envVar) cached Location getLocation(NodeImpl n) { result = n.getLocationImpl() } @@ -414,7 +428,11 @@ predicate nodeIsHidden(Node n) { n.(NodeImpl).nodeIsHidden() } * Holds if `n` should never be skipped over in the `PathGraph` and in path * explanations. */ -predicate neverSkipInPathGraph(Node n) { isReturned(n.(AstNode).getCfgNode()) } +predicate neverSkipInPathGraph(Node n) { + isReturned(n.(AstNode).getCfgNode()) + or + n = any(SsaDefinitionNodeImpl def | not def.nodeIsHidden()) +} /** An SSA node. */ class SsaNode extends NodeImpl, TSsaNode { @@ -425,9 +443,6 @@ class SsaNode extends NodeImpl, TSsaNode { /** Gets the underlying variable. */ Variable getVariable() { result = node.getSourceVariable() } - /** Holds if this node should be hidden from path explanations. */ - predicate isHidden() { any() } - override CfgScope getCfgScope() { result = node.getBasicBlock().getScope() } override Location getLocationImpl() { result = node.getLocation() } @@ -440,7 +455,7 @@ class SsaDefinitionNodeImpl extends SsaNode { Ssa::Definition getDefinition() { result = node.getDefinition() } - override predicate isHidden() { + override predicate nodeIsHidden() { exists(SsaImpl::Definition def | def = this.getDefinition() | not def instanceof Ssa::WriteDefinition or @@ -900,6 +915,15 @@ private module OutNodes { import OutNodes predicate jumpStep(Node pred, Node succ) { + // final env read -> env variable node + pred.(FinalEnvVarRead).getVariable() = succ.(EnvVarNode).getVariable() + or + // env variable node -> initial env def + exists(SsaImpl::Definition def | + succ.(SsaDefinitionNodeImpl).getDefinition() = def and + def.definesAt(pred.(EnvVarNode).getVariable(), any(EntryBasicBlock entry), -1) + ) + or FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(), succ.(FlowSummaryNode).getSummaryNode()) } @@ -1308,6 +1332,39 @@ class ScriptBlockNode extends TScriptBlockNode, NodeImpl { override predicate nodeIsHidden() { any() } } +class EnvVarNode extends TEnvVarNode, NodeImpl { + private EnvVariable v; + + EnvVarNode() { this = TEnvVarNode(v) } + + EnvVariable getVariable() { result = v } + + override CfgScope getCfgScope() { result = v.getEnclosingScope() } + + override Location getLocationImpl() { result = v.getLocation() } + + override string toStringImpl() { result = v.toString() } + + override predicate nodeIsHidden() { any() } +} + +class FinalEnvVarRead extends TFinalEnvVarRead, NodeImpl { + Scope scope; + private EnvVariable v; + + FinalEnvVarRead() { this = TFinalEnvVarRead(scope, v) } + + EnvVariable getVariable() { result = v } + + override CfgScope getCfgScope() { result = scope } + + override Location getLocationImpl() { result = scope.getLocation() } + + override string toStringImpl() { result = v.toString() + " after " + scope } + + override predicate nodeIsHidden() { any() } +} + /** A node that performs a type cast. */ class CastNode extends Node { CastNode() { none() } @@ -1415,30 +1472,3 @@ ContentApprox getContentApprox(Content c) { c instanceof Content::UnknownKeyOrPositionContent and result = TUnknownContentApprox() } - -// TFieldContent(string name) { -// name = any(PropertyMember member).getName() -// or -// name = any(MemberExpr me).getMemberName() -// } or -// // A known map key -// TKnownKeyContent(ConstantValue cv) { exists(cv.asString()) } or -// // A known array index -// TKnownPositionalContent(ConstantValue cv) { cv.asInt() = [0 .. 10] } or -// // An unknown key -// TUnknownKeyContent() or -// // An unknown positional element -// TUnknownPositionalContent() or -// // A unknown position or key - and we dont even know what kind it is -// TUnknownKeyOrPositionContent() -/** - * A unit class for adding additional jump steps. - * - * Extend this class to add additional jump steps. - */ -class AdditionalJumpStep extends Unit { - /** - * Holds if data can flow from `pred` to `succ` in a way that discards call contexts. - */ - abstract predicate step(Node pred, Node succ); -} diff --git a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll index e8e522cc4942..2959f4583690 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll @@ -495,7 +495,11 @@ class ObjectCreationNode extends ExprNode { */ Node getConstructedTypeNode() { result.asExpr() = objectCreation.getConstructedTypeExpr() } - string getConstructedTypeName() { result = this.getObjectCreationNode().getConstructedTypeName() } + bindingset[result] + pragma[inline_late] + string getAConstructedTypeName() { + result = this.getObjectCreationNode().getAConstructedTypeName() + } } /** A call, viewed as a node in a data flow graph. */ @@ -540,11 +544,18 @@ class CallNode extends ExprNode { Node getCallee() { result.asExpr() = call.getCallee() } } -/** A call to operator `&`, viwed as a node in a data flow graph. */ +/** A call to operator `&`, viewed as a node in a data flow graph. */ class CallOperatorNode extends CallNode { override CfgNodes::ExprNodes::CallOperatorCfgNode call; - Node getCommand() { result.asExpr() = call.getCommand() } // TODO: Alternatively, we could remap calls to & as command expressions. + Node getCommand() { result.asExpr() = call.getCommand() } +} + +/** A call to operator `.`, viewed as a node in a data flow graph. */ +class DotSourcingOperatorNode extends CallNode { + override CfgNodes::ExprNodes::DotSourcingOperatorCfgNode call; + + Node getCommand() { result.asExpr() = call.getCommand() } } /** @@ -560,7 +571,11 @@ class TypeNameNode extends ExprNode { override CfgNodes::ExprNodes::TypeNameExprCfgNode getExprNode() { result = n } - string getName() { result = n.getName() } + bindingset[result] + pragma[inline_late] + string getAName() { result = n.getAName() } + + string getLowerCaseName() { result = n.getLowerCaseName() } predicate isQualified() { n.isQualified() } @@ -586,5 +601,9 @@ class AutomaticVariableNode extends ExprNode { final override CfgNodes::ExprNodes::AutomaticVariableCfgNode getExprNode() { result = n } - string getName() { result = n.getName() } + bindingset[result] + pragma[inline_late] + string getAName() { result = n.getAName() } + + string getLowerCaseName() { result = n.getLowerCaseName() } } diff --git a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/SsaImpl.qll b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/SsaImpl.qll index 23ae5e5397e0..7f48ec52dbb1 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/SsaImpl.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/SsaImpl.qll @@ -29,6 +29,8 @@ module SsaInput implements SsaImplCommon::InputSig { ( uninitializedWrite(bb, i, v) or + envVarWrite(bb, i, v) + or variableWriteActual(bb, i, v, _) or exists(ProcessBlockCfgNode processBlock | bb.getNode(i) = processBlock | @@ -43,7 +45,11 @@ module SsaInput implements SsaImplCommon::InputSig { } predicate variableRead(BasicBlock bb, int i, Variable v, boolean certain) { - variableReadActual(bb, i, v) and + ( + variableReadActual(bb, i, v) + or + envVarRead(bb, i, v) + ) and certain = true } } @@ -66,6 +72,14 @@ predicate uninitializedWrite(Cfg::EntryBasicBlock bb, int i, Variable v) { bb.getANode().getAstNode() = v } +predicate envVarWrite(Cfg::EntryBasicBlock bb, int i, EnvVariable v) { + exists(VarReadAccess va | + va.getVariable() = v and + va.getEnclosingFunction().getBody() = bb.getScope() and + i = -1 + ) +} + predicate parameterWrite(Cfg::EntryBasicBlock bb, int i, Parameter p) { bb.getNode(i).getAstNode() = p } @@ -78,6 +92,14 @@ private predicate variableReadActual(Cfg::BasicBlock bb, int i, Variable v) { ) } +predicate envVarRead(Cfg::ExitBasicBlock bb, int i, EnvVariable v) { + exists(VarWriteAccess va | + va.getVariable() = v and + va.getEnclosingFunction().getBody() = bb.getScope() and + bb.getNode(i) instanceof ExitNode + ) +} + cached private module Cached { /** @@ -165,7 +187,7 @@ private module Cached { private predicate guardChecksAdjTypes( DataFlowIntegrationInput::Guard g, DataFlowIntegrationInput::Expr e, boolean branch ) { - guardChecks(g, e, branch) + guardChecks(g, e.asExprCfgNode(), branch) } private Node getABarrierNodeImpl() { @@ -261,11 +283,48 @@ class ParameterExt extends TParameterExt { } private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInputSig { - class Expr extends Cfg::CfgNodes::ExprCfgNode { - predicate hasCfgNode(SsaInput::BasicBlock bb, int i) { this = bb.getNode(i) } + private newtype TExpr = + TExprCfgNode(Cfg::CfgNodes::ExprCfgNode e) or + TFinalEnvVarRead(Scope scope, EnvVariable v) { + exists(Cfg::ExitBasicBlock exit | + exit.getScope() = scope and + envVarRead(exit, _, v) + ) + } + + class Expr extends TExpr { + Cfg::CfgNodes::ExprCfgNode asExprCfgNode() { this = TExprCfgNode(result) } + + predicate isFinalEnvVarRead(Scope scope, EnvVariable v) { this = TFinalEnvVarRead(scope, v) } + + predicate hasCfgNode(SsaInput::BasicBlock bb, int i) { + this.asExprCfgNode() = bb.getNode(i) + or + exists(EnvVariable v | + this.isFinalEnvVarRead(bb.getScope(), v) and + bb.getNode(i) instanceof ExitNode + ) + } + + string toString() { + result = this.asExprCfgNode().toString() + or + exists(EnvVariable v | + this.isFinalEnvVarRead(_, v) and + result = v.toString() + ) + } } - Expr getARead(Definition def) { result = Cached::getARead(def) } + Expr getARead(Definition def) { + result.asExprCfgNode() = Cached::getARead(def) + or + exists(Variable v, Cfg::BasicBlock bb, int i | + Impl::ssaDefReachesRead(v, def, bb, i) and + envVarRead(bb, i, v) and + result.isFinalEnvVarRead(bb.getScope(), v) + ) + } predicate ssaDefHasSource(WriteDefinition def) { any(ParameterExt p).isInitializedBy(def) or def.(Ssa::WriteDefinition).assigns(_) @@ -280,6 +339,7 @@ private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInpu predicate controlsBranchEdge(SsaInput::BasicBlock bb1, SsaInput::BasicBlock bb2, boolean branch) { this.hasBranchEdge(bb1, bb2, branch) } + /** * Holds if the evaluation of this guard to `branch` corresponds to the edge * from `bb1` to `bb2`. @@ -291,8 +351,6 @@ private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInpu s.getValue() = branch ) } - - } /** Holds if the guard `guard` controls block `bb` upon evaluating to `branch`. */ diff --git a/powershell/ql/lib/semmle/code/powershell/frameworks/PowerShell.qll b/powershell/ql/lib/semmle/code/powershell/frameworks/PowerShell.qll index a3b606cb9ed9..7c316aba255b 100644 --- a/powershell/ql/lib/semmle/code/powershell/frameworks/PowerShell.qll +++ b/powershell/ql/lib/semmle/code/powershell/frameworks/PowerShell.qll @@ -6,7 +6,7 @@ module PowerShell { private class PowerShellGlobalEntry extends ModelInput::TypeModel { override DataFlow::Node getASource(string type) { type = "System.Management.Automation.PowerShell!" and - result.asExpr().getExpr().(TypeNameExpr).getName().toLowerCase() = "powershell" + result.asExpr().getExpr().(TypeNameExpr).getLowerCaseName() = "powershell" } } } diff --git a/powershell/ql/lib/semmle/code/powershell/frameworks/Runspaces.qll b/powershell/ql/lib/semmle/code/powershell/frameworks/Runspaces.qll index fd4e6e9c6f3e..a0b147413113 100644 --- a/powershell/ql/lib/semmle/code/powershell/frameworks/Runspaces.qll +++ b/powershell/ql/lib/semmle/code/powershell/frameworks/Runspaces.qll @@ -6,7 +6,7 @@ module RunspaceFactory { private class RunspaceFactoryGlobalEntry extends ModelInput::TypeModel { override DataFlow::Node getASource(string type) { type = "System.Management.Automation.Runspaces.RunspaceFactory!" and - result.asExpr().getExpr().(TypeNameExpr).getName().toLowerCase() = "runspacefactory" + result.asExpr().getExpr().(TypeNameExpr).getLowerCaseName() = "runspacefactory" } } } diff --git a/powershell/ql/lib/semmle/code/powershell/security/CommandInjectionCustomizations.qll b/powershell/ql/lib/semmle/code/powershell/security/CommandInjectionCustomizations.qll index 2309490cb2aa..b78d0a561e44 100644 --- a/powershell/ql/lib/semmle/code/powershell/security/CommandInjectionCustomizations.qll +++ b/powershell/ql/lib/semmle/code/powershell/security/CommandInjectionCustomizations.qll @@ -31,7 +31,12 @@ module CommandInjection { abstract class Sanitizer extends DataFlow::Node { } /** A source of user input, considered as a flow source for command injection. */ - class FlowSourceAsSource extends Source instanceof SourceNode { + class FlowSourceAsSource extends Source { + FlowSourceAsSource() { + this instanceof SourceNode and + not this instanceof EnvironmentVariableSource + } + override string getSourceType() { result = "user-provided value" } } @@ -46,8 +51,10 @@ module CommandInjection { call.getAnArgument() = this ) or - // Or the call command itself in case it's a use of operator &. + // Or the call command itself in case it's a use of "operator &" or "operator .". any(DataFlow::CallOperatorNode call).getCommand() = this + or + any(DataFlow::DotSourcingOperatorNode call).getCommand() = this } override string getSinkType() { result = "call to Invoke-Expression" } @@ -96,7 +103,7 @@ module CommandInjection { addscript.matchesName("AddScript") and create.matchesName("Create") and addscript.getQualifier().(InvokeMemberExpr) = create and - create.getQualifier().(TypeNameExpr).getName() = "PowerShell" + create.getQualifier().(TypeNameExpr).getAName() = "PowerShell" ) } @@ -157,7 +164,7 @@ module CommandInjection { exists(InvokeMemberExpr ie | this.asExpr().getExpr() = ie.getAnArgument() and ie.matchesName("Create") and - ie.getQualifier().(TypeNameExpr).getName() = "ScriptBlock" + ie.getQualifier().(TypeNameExpr).getAName() = "ScriptBlock" ) } @@ -205,7 +212,7 @@ module CommandInjection { TypedParameterSanitizer() { exists(Function f, Parameter p | p = f.getAParameter() and - p.getStaticType() != "Object" and + p.getStaticType() != "object" and this.asParameter() = p ) } diff --git a/powershell/ql/lib/semmle/code/powershell/security/Sanitizers.qll b/powershell/ql/lib/semmle/code/powershell/security/Sanitizers.qll new file mode 100644 index 000000000000..29b82e5030a6 --- /dev/null +++ b/powershell/ql/lib/semmle/code/powershell/security/Sanitizers.qll @@ -0,0 +1,14 @@ +private import powershell +private import semmle.code.powershell.dataflow.DataFlow + +/** + * A dataflow node that is guarenteed to have a "simple" type. + * + * Simple types include integers, floats, characters, booleans, and `datetime`. + */ +class SimpleTypeSanitizer extends DataFlow::Node { + SimpleTypeSanitizer() { + this.asParameter().getStaticType() = + ["int32", "int64", "single", "double", "decimal", "char", "boolean", "datetime"] + } +} diff --git a/powershell/ql/lib/semmle/code/powershell/security/SqlInjectionCustomizations.qll b/powershell/ql/lib/semmle/code/powershell/security/SqlInjectionCustomizations.qll index c3ae436f5477..d8b9fc193f8a 100644 --- a/powershell/ql/lib/semmle/code/powershell/security/SqlInjectionCustomizations.qll +++ b/powershell/ql/lib/semmle/code/powershell/security/SqlInjectionCustomizations.qll @@ -8,6 +8,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 +private import semmle.code.powershell.security.Sanitizers module SqlInjection { /** @@ -38,8 +39,13 @@ module SqlInjection { abstract class Sanitizer extends DataFlow::Node { } /** A source of user input, considered as a flow source for command injection. */ - class FlowSourceAsSource extends Source instanceof SourceNode { - override string getSourceType() { result = SourceNode.super.getSourceType() } + class FlowSourceAsSource extends Source { + FlowSourceAsSource() { + this instanceof SourceNode and + not this instanceof EnvironmentVariableSource + } + + override string getSourceType() { result = this.(SourceNode).getSourceType() } } class InvokeSqlCmdSink extends Sink { @@ -94,4 +100,6 @@ module SqlInjection { override string getSinkType() { result = "call to sqlcmd" } } + + class TypeSanitizer extends Sanitizer instanceof SimpleTypeSanitizer { } } diff --git a/powershell/ql/lib/semmle/code/powershell/security/UnsafeDeserializationCustomizations.qll b/powershell/ql/lib/semmle/code/powershell/security/UnsafeDeserializationCustomizations.qll new file mode 100644 index 000000000000..25615acd77d7 --- /dev/null +++ b/powershell/ql/lib/semmle/code/powershell/security/UnsafeDeserializationCustomizations.qll @@ -0,0 +1,53 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about + * unsafe deserialization vulnerabilities, as well as extension points for + * adding your own. + */ + +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 + +module UnsafeDeserialization { + /** + * A data flow source for SQL-injection vulnerabilities. + */ + abstract class Source extends DataFlow::Node { + /** Gets a string that describes the type of this flow source. */ + abstract string getSourceType(); + } + + /** + * A data flow sink for SQL-injection vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { + /** Gets a description of this sink. */ + abstract string getSinkType(); + + } + + /** + * A sanitizer for Unsafe Deserialization vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** A source of user input, considered as a flow source for unsafe deserialization. */ + class FlowSourceAsSource extends Source instanceof SourceNode { + override string getSourceType() { result = SourceNode.super.getSourceType() } + } + + class BinaryFormatterDeserializeSink extends Sink { + BinaryFormatterDeserializeSink() { + exists(DataFlow::ObjectCreationNode ocn, DataFlow::CallNode cn | + cn.getQualifier().getALocalSource() = ocn and + ocn.getExprNode().getExpr().(CallExpr).getAnArgument().getValue().asString() = "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter" and + cn.getLowerCaseName() = "deserialize" and + cn.getAnArgument() = this + ) + } + + override string getSinkType() { result = "call to BinaryFormatter.Deserialize" } + + } +} diff --git a/powershell/ql/lib/semmle/code/powershell/security/UnsafeDeserializationQuery.qll b/powershell/ql/lib/semmle/code/powershell/security/UnsafeDeserializationQuery.qll new file mode 100644 index 000000000000..fba30b507ad5 --- /dev/null +++ b/powershell/ql/lib/semmle/code/powershell/security/UnsafeDeserializationQuery.qll @@ -0,0 +1,28 @@ +/** + * Provides a taint tracking configuration for reasoning about + * deserialization vulnerabilities (CWE-502). + * + * Note, for performance reasons: only import this file if + * `UnsafeDeserializationFlow` is needed, otherwise + * `UnsafeDeserializationCustomizations` should be imported instead. + */ + +import powershell +import semmle.code.powershell.dataflow.flowsources.FlowSources +import semmle.code.powershell.dataflow.DataFlow +import semmle.code.powershell.dataflow.TaintTracking +import UnsafeDeserializationCustomizations::UnsafeDeserialization + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo){ + exists(InvokeMemberExpr ime | + nodeTo.asExpr().getExpr() = ime and + nodeFrom.asExpr().getExpr() = ime.getAnArgument() + ) + } +} + +module UnsafeDeserializationFlow = TaintTracking::Global; \ No newline at end of file diff --git a/powershell/ql/src/queries/security/cwe-250/InsecureExecutionPolicy.qhelp b/powershell/ql/src/queries/security/cwe-250/InsecureExecutionPolicy.qhelp index f6531fc552f1..fc55462ffc64 100644 --- a/powershell/ql/src/queries/security/cwe-250/InsecureExecutionPolicy.qhelp +++ b/powershell/ql/src/queries/security/cwe-250/InsecureExecutionPolicy.qhelp @@ -12,7 +12,7 @@ allowing any script—including malicious or unsigned code—to run without rest

Always prefer AllSigned to enforce full signature verification.

If this is not possible, set the execution policy to RemoteSigned to allow local scripts while requiring downloaded scripts to be signed.

-

Always limit the scope of the execution policy by supplying the most restrictive Scope as possible. Use Process to limit the execution policy to the current PowerShell session. When no Scope is supplied the execution policy change is applied system-wide. +

Always limit the scope of the execution policy by supplying the most restrictive Scope as possible. Use Process to limit the execution policy to the current PowerShell session. When no Scope is supplied the execution policy change is applied system-wide.

diff --git a/powershell/ql/src/queries/security/cwe-319/UnsafeSMBSettings.qhelp b/powershell/ql/src/queries/security/cwe-319/UnsafeSMBSettings.qhelp new file mode 100644 index 000000000000..e0aae447fa38 --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-319/UnsafeSMBSettings.qhelp @@ -0,0 +1,30 @@ + + + +

The commandsSet-SmbClientConfiguration and Set-SmbServerConfiguration are used to set configurations for SMB traffic. +Insecure configurations such as outdated versions, or turning off encryption, can make connections susceptible to attackers. +

+
+ + +

The minimum version of SMB is 3.0, but it is recommended to use the latest version. For example, use: +Set-SmbServerConfiguration -Smb2DialectMin SMB300 or Set-SmbClientConfiguration -Smb2DialectMin SMB300 +

+

+SMB encryption should be enabled. For example, use: + Set-SmbServerConfiguration -encryptdata $true -rejectunencryptedaccess $true or Set-SmbClientConfiguration -RequireEncryption $true +

+ +

+SMB NTLM blocking should be enabled. For example: Set-SMbClientConfiguration -BlockNTLM $true +

+
+ + +
  • MSDN: Set-SmbServerConfiguration.
  • +
  • MSDN: Set-SmbClientConfiguration.
  • + +
    +
    diff --git a/powershell/ql/src/queries/security/cwe-319/UnsafeSMBSettings.ql b/powershell/ql/src/queries/security/cwe-319/UnsafeSMBSettings.ql new file mode 100644 index 000000000000..638c4e0987eb --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-319/UnsafeSMBSettings.ql @@ -0,0 +1,88 @@ +/** + * @name Insecure SMB settings + * @description Use of insecure SMB configurations allow attackers to access connections + * @kind problem + * @problem.severity error + * @security-severity 8.8 + * @precision high + * @id powershell/microsoft/public/insecure-smb-setting + * @tags correctness + * security + * external/cwe/cwe-315 + */ + +import powershell + +abstract class SMBConfiguration extends CmdCall { + abstract Expr getAMisconfiguredSetting(); + + /** Gets the minimum version of the SMB protocol to be used */ + Expr getMisconfiguredSmb2DialectMin() { + exists(Expr dialectMin | + dialectMin = this.getNamedArgument("smb2dialectmin") and + dialectMin.getValue().stringMatches(["none", "smb202", "smb210"]) and + result = dialectMin + ) + } +} + +/** A call to `Set-SmbServerConfiguration`. */ +class SetSMBClientConfiguration extends SMBConfiguration { + SetSMBClientConfiguration() { this.getAName() = "Set-SmbClientConfiguration" } + + /** holds if the argument `requireencryption` is supplied with a `$false` value. */ + Expr getMisconfiguredRequireEncryption() { + exists(Expr requireEncryption | + requireEncryption = this.getNamedArgument("requireencryption") and + requireEncryption.getValue().asBoolean() = false and + result = requireEncryption + ) + } + + /** Holds if the argument `blockntlm` is supplied with a `$false` value. */ + Expr getMisconfiguredBlocksNTLM() { + exists(Expr blocksNTLM | + blocksNTLM = this.getNamedArgument("blockntlm") and + blocksNTLM.getValue().asBoolean() = false and + result = blocksNTLM + ) + } + + override Expr getAMisconfiguredSetting() { + result = this.getMisconfiguredRequireEncryption() or + result = this.getMisconfiguredBlocksNTLM() or + result = this.getMisconfiguredSmb2DialectMin() + } +} + +/** A call to `Set-SmbServerConfiguration`. */ +class SetSMBServerConfiguration extends SMBConfiguration { + SetSMBServerConfiguration() { this.getAName() = "Set-SmbServerConfiguration" } + + /** holds if the argument `encryptdata` is supplied with a `$false` value. */ + Expr getMisconfiguredEncryptData() { + exists(Expr encryptData | + encryptData = this.getNamedArgument("encryptdata") and + encryptData.getValue().asBoolean() = false and + result = encryptData + ) + } + + /** holds if the argument `encryptdata` is supplied with a `$false` value. */ + Expr getMisconfiguredRejectUnencryptedAccess() { + exists(Expr rejectUnencryptedAccess | + rejectUnencryptedAccess = this.getNamedArgument("rejectunencryptedaccess") and + rejectUnencryptedAccess.getValue().asBoolean() = false and + result = rejectUnencryptedAccess + ) + } + + override Expr getAMisconfiguredSetting() { + result = this.getMisconfiguredEncryptData() or + result = this.getMisconfiguredRejectUnencryptedAccess() or + result = this.getMisconfiguredSmb2DialectMin() + } +} + +from SMBConfiguration config +select config.getAMisconfiguredSetting(), "Unsafe SMB setting" diff --git a/powershell/ql/src/queries/security/cwe-502/BinaryFormatterDeserialization.qhelp b/powershell/ql/src/queries/security/cwe-502/BinaryFormatterDeserialization.qhelp new file mode 100644 index 000000000000..b54aff20bf5e --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-502/BinaryFormatterDeserialization.qhelp @@ -0,0 +1,37 @@ + + + + +

    Using BinaryFormatter to deserialize an object from untrusted input may result in security problems, such +as denial of service or remote code execution.

    + +
    + + +

    Avoid using BinaryFormatter.

    + +
    + + +

    In this example, a string is deserialized using a +BinaryFormatter. BinaryFormatter is an easily exploited deserializer.

    + + + +
    + + +
  • +Muñoz, Alvaro and Mirosh, Oleksandr: +JSON Attacks. +
  • + +
  • +Microsoft: +Deserialization risks in use of BinaryFormatter and related types. +
  • + +
    +
    diff --git a/powershell/ql/src/queries/security/cwe-502/BinaryFormatterDeserialization.ql b/powershell/ql/src/queries/security/cwe-502/BinaryFormatterDeserialization.ql new file mode 100644 index 000000000000..c79f6ecda29b --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-502/BinaryFormatterDeserialization.ql @@ -0,0 +1,18 @@ +/** + * @name Use of Binary Formatter deserialization + * @description Use of Binary Formatter is unsafe + * @kind problem + * @problem.severity error + * @security-severity 8.8 + * @precision high + * @id powershell/microsoft/public/binary-formatter-deserialization + * @tags correctness + * security + * external/cwe/cwe-502 + */ + +import powershell +import semmle.code.powershell.security.UnsafeDeserializationCustomizations::UnsafeDeserialization + +from BinaryFormatterDeserializeSink sink +select sink, "Call to BinaryFormatter.Deserialize" diff --git a/powershell/ql/src/queries/security/cwe-502/UnsafeDeserialization.qhelp b/powershell/ql/src/queries/security/cwe-502/UnsafeDeserialization.qhelp new file mode 100644 index 000000000000..26d5bda06000 --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-502/UnsafeDeserialization.qhelp @@ -0,0 +1,37 @@ + + + + +

    Deserializing an object from untrusted input may result in security problems, such +as denial of service or remote code execution.

    + +
    + + +

    Avoid using an unsafe deserialization framework.

    + +
    + + +

    In this example, a string is deserialized using a +BinaryFormatter. BinaryFormatter is an easily exploited deserializer.

    + + + +
    + + +
  • +Muñoz, Alvaro and Mirosh, Oleksandr: +JSON Attacks. +
  • + +
  • +Microsoft: +Deserialization risks in use of BinaryFormatter and related types. +
  • + +
    +
    diff --git a/powershell/ql/src/queries/security/cwe-502/UnsafeDeserialization.ql b/powershell/ql/src/queries/security/cwe-502/UnsafeDeserialization.ql new file mode 100644 index 000000000000..3713b0d5ed5f --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-502/UnsafeDeserialization.ql @@ -0,0 +1,26 @@ +/** + * @name Unsafe deserializer + * @description Calling an unsafe deserializer with data controlled by an attacker + * can lead to denial of service and other security problems. + * @kind path-problem + * @problem.severity error + * @security-severity 8.8 + * @precision high + * @id powershell/microsoft/public/unsafe-deserialization + * @tags correctness + * security + * external/cwe/cwe-502 + */ + +import powershell +import semmle.code.powershell.security.UnsafeDeserializationQuery +import UnsafeDeserializationFlow::PathGraph + +from + UnsafeDeserializationFlow::PathNode source, UnsafeDeserializationFlow::PathNode sink, + Source sourceNode +where + UnsafeDeserializationFlow::flowPath(source, sink) and + sourceNode = source.getNode() +select sink.getNode(), source, sink, "This unsafe deserializer deserializes on a $@.", sourceNode, + sourceNode.getSourceType() diff --git a/powershell/ql/src/queries/security/cwe-502/examples/BinaryFormatterDeserialization.ps1 b/powershell/ql/src/queries/security/cwe-502/examples/BinaryFormatterDeserialization.ps1 new file mode 100644 index 000000000000..b222b1e53279 --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-502/examples/BinaryFormatterDeserialization.ps1 @@ -0,0 +1,6 @@ +$untrustedBase64 = Read-Host "Enter user input" + +$formatter = New-Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter +$stream = [System.IO.MemoryStream]::new([Convert]::FromBase64String($untrustedBase64)) + +$obj = $formatter.Deserialize($stream) diff --git a/powershell/ql/test/library-tests/ast/Expressions/expressions.expected b/powershell/ql/test/library-tests/ast/Expressions/expressions.expected index cebf2918950c..7116d0675cd7 100644 --- a/powershell/ql/test/library-tests/ast/Expressions/expressions.expected +++ b/powershell/ql/test/library-tests/ast/Expressions/expressions.expected @@ -15,5 +15,5 @@ invokeMemoryExpression expandableString | ExpandableString.ps1:1:1:1:39 | Date: $([DateTime]::Now)\nName: $name | 1 | ExpandableString.ps1:1:21:1:38 | $(...) | memberExpr -| ExpandableString.ps1:1:23:1:37 | now | ExpandableString.ps1:1:23:1:32 | DateTime | -| MemberExpression.ps1:2:1:2:14 | ... | MemberExpression.ps1:2:1:2:10 | DateTime | +| ExpandableString.ps1:1:23:1:37 | now | ExpandableString.ps1:1:23:1:32 | datetime | +| MemberExpression.ps1:2:1:2:14 | ... | MemberExpression.ps1:2:1:2:10 | datetime | diff --git a/powershell/ql/test/library-tests/ast/parent.expected b/powershell/ql/test/library-tests/ast/parent.expected index c2907b33c6d9..3e1bcf300caa 100644 --- a/powershell/ql/test/library-tests/ast/parent.expected +++ b/powershell/ql/test/library-tests/ast/parent.expected @@ -229,7 +229,7 @@ | Expressions/ExpandableString.ps1:1:1:1:39 | {...} | Expressions/ExpandableString.ps1:1:1:1:39 | toplevel function for ExpandableString.ps1 | | Expressions/ExpandableString.ps1:1:1:1:39 | {...} | Expressions/ExpandableString.ps1:1:1:1:39 | {...} | | Expressions/ExpandableString.ps1:1:21:1:38 | $(...) | Expressions/ExpandableString.ps1:1:1:1:39 | Date: $([DateTime]::Now)\nName: $name | -| Expressions/ExpandableString.ps1:1:23:1:32 | DateTime | Expressions/ExpandableString.ps1:1:23:1:37 | now | +| Expressions/ExpandableString.ps1:1:23:1:32 | datetime | Expressions/ExpandableString.ps1:1:23:1:37 | now | | Expressions/ExpandableString.ps1:1:23:1:37 | [Stmt] now | Expressions/ExpandableString.ps1:1:23:1:37 | {...} | | Expressions/ExpandableString.ps1:1:23:1:37 | now | Expressions/ExpandableString.ps1:1:23:1:37 | [Stmt] now | | Expressions/ExpandableString.ps1:1:23:1:37 | {...} | Expressions/ExpandableString.ps1:1:21:1:38 | $(...) | @@ -238,7 +238,7 @@ | Expressions/MemberExpression.ps1:1:1:2:14 | {...} | Expressions/MemberExpression.ps1:1:1:2:14 | toplevel function for MemberExpression.ps1 | | Expressions/MemberExpression.ps1:1:1:2:14 | {...} | Expressions/MemberExpression.ps1:1:1:2:14 | {...} | | Expressions/MemberExpression.ps1:1:7:1:8 | x | Expressions/MemberExpression.ps1:1:1:2:14 | {...} | -| Expressions/MemberExpression.ps1:2:1:2:10 | DateTime | Expressions/MemberExpression.ps1:2:1:2:14 | ... | +| Expressions/MemberExpression.ps1:2:1:2:10 | datetime | Expressions/MemberExpression.ps1:2:1:2:14 | ... | | Expressions/MemberExpression.ps1:2:1:2:14 | ... | Expressions/MemberExpression.ps1:2:1:2:14 | [Stmt] ... | | Expressions/MemberExpression.ps1:2:1:2:14 | [Stmt] ... | Expressions/MemberExpression.ps1:1:1:2:14 | {...} | | Expressions/MemberExpression.ps1:2:13:2:14 | x | Expressions/MemberExpression.ps1:2:1:2:14 | ... | diff --git a/powershell/ql/test/library-tests/dataflow/fields/test.expected b/powershell/ql/test/library-tests/dataflow/fields/test.expected index d30663ac3f93..5be94e2ee03b 100644 --- a/powershell/ql/test/library-tests/dataflow/fields/test.expected +++ b/powershell/ql/test/library-tests/dataflow/fields/test.expected @@ -33,15 +33,19 @@ edges | test.ps1:32:6:32:13 | ...[...] [unknown] | test.ps1:32:6:32:16 | ...[...] | provenance | | | test.ps1:33:6:33:10 | arr7 [unknown, unknown] | test.ps1:33:6:33:21 | ...[...] [unknown] | provenance | | | test.ps1:33:6:33:21 | ...[...] [unknown] | test.ps1:33:6:33:32 | ...[...] | provenance | | -| test.ps1:35:6:35:16 | Call to source | test.ps1:37:15:37:16 | x | provenance | | -| test.ps1:37:9:37:16 | ...,... [element 2] | test.ps1:40:6:40:10 | arr8 [element 2] | provenance | | -| test.ps1:37:9:37:16 | ...,... [element 2] | test.ps1:41:6:41:10 | arr8 [element 2] | provenance | | +| test.ps1:35:1:35:2 | x | test.ps1:37:15:37:16 | x | provenance | | +| test.ps1:35:6:35:16 | Call to source | test.ps1:35:1:35:2 | x | provenance | | +| test.ps1:37:1:37:5 | arr8 [element 2] | test.ps1:40:6:40:10 | arr8 [element 2] | provenance | | +| test.ps1:37:1:37:5 | arr8 [element 2] | test.ps1:41:6:41:10 | arr8 [element 2] | provenance | | +| test.ps1:37:9:37:16 | ...,... [element 2] | test.ps1:37:1:37:5 | arr8 [element 2] | provenance | | | test.ps1:37:15:37:16 | x | test.ps1:37:9:37:16 | ...,... [element 2] | provenance | | | test.ps1:40:6:40:10 | arr8 [element 2] | test.ps1:40:6:40:13 | ...[...] | provenance | | | test.ps1:41:6:41:10 | arr8 [element 2] | test.ps1:41:6:41:20 | ...[...] | provenance | | -| test.ps1:43:6:43:16 | Call to source | test.ps1:45:17:45:18 | y | provenance | | -| test.ps1:45:9:45:19 | @(...) [element 2] | test.ps1:48:6:48:10 | arr9 [element 2] | provenance | | -| test.ps1:45:9:45:19 | @(...) [element 2] | test.ps1:49:6:49:10 | arr9 [element 2] | provenance | | +| test.ps1:43:1:43:2 | y | test.ps1:45:17:45:18 | y | provenance | | +| test.ps1:43:6:43:16 | Call to source | test.ps1:43:1:43:2 | y | provenance | | +| test.ps1:45:1:45:5 | arr9 [element 2] | test.ps1:48:6:48:10 | arr9 [element 2] | provenance | | +| test.ps1:45:1:45:5 | arr9 [element 2] | test.ps1:49:6:49:10 | arr9 [element 2] | provenance | | +| test.ps1:45:9:45:19 | @(...) [element 2] | test.ps1:45:1:45:5 | arr9 [element 2] | provenance | | | test.ps1:45:17:45:18 | y | test.ps1:45:9:45:19 | @(...) [element 2] | provenance | | | test.ps1:48:6:48:10 | arr9 [element 2] | test.ps1:48:6:48:13 | ...[...] | provenance | | | test.ps1:49:6:49:10 | arr9 [element 2] | test.ps1:49:6:49:20 | ...[...] | provenance | | @@ -50,20 +54,29 @@ edges | test.ps1:61:1:61:8 | [post] myClass [field] | test.ps1:63:1:63:8 | myClass [field] | provenance | | | test.ps1:61:18:61:28 | Call to source | test.ps1:61:1:61:8 | [post] myClass [field] | provenance | | | test.ps1:63:1:63:8 | myClass [field] | test.ps1:54:5:56:5 | this [field] | provenance | | -| test.ps1:66:10:66:20 | Call to source | test.ps1:69:5:69:6 | x | provenance | | -| test.ps1:67:10:67:20 | Call to source | test.ps1:70:5:70:6 | y | provenance | | -| test.ps1:68:10:68:20 | Call to source | test.ps1:70:9:70:10 | z | provenance | | +| test.ps1:66:5:66:6 | x | test.ps1:69:5:69:6 | x | provenance | | +| test.ps1:66:5:66:6 | x | test.ps1:69:5:69:6 | x | provenance | | +| test.ps1:66:10:66:20 | Call to source | test.ps1:66:5:66:6 | x | provenance | | +| test.ps1:66:10:66:20 | Call to source | test.ps1:66:5:66:6 | x | provenance | | +| test.ps1:67:5:67:6 | y | test.ps1:70:5:70:6 | y | provenance | | +| test.ps1:67:5:67:6 | y | test.ps1:70:5:70:6 | y | provenance | | +| test.ps1:67:10:67:20 | Call to source | test.ps1:67:5:67:6 | y | provenance | | +| test.ps1:67:10:67:20 | Call to source | test.ps1:67:5:67:6 | y | provenance | | +| test.ps1:68:5:68:6 | z | test.ps1:70:9:70:10 | z | provenance | | +| test.ps1:68:10:68:20 | Call to source | test.ps1:68:5:68:6 | z | provenance | | | test.ps1:69:5:69:6 | x | test.ps1:73:6:73:12 | Call to produce [unknown index] | provenance | | | test.ps1:70:5:70:6 | y | test.ps1:73:6:73:12 | Call to produce [unknown index] | provenance | | | test.ps1:70:9:70:10 | z | test.ps1:73:6:73:12 | Call to produce [unknown index] | provenance | | -| test.ps1:73:6:73:12 | Call to produce [unknown index] | test.ps1:74:6:74:7 | x [unknown index] | provenance | | -| test.ps1:73:6:73:12 | Call to produce [unknown index] | test.ps1:75:6:75:7 | x [unknown index] | provenance | | -| test.ps1:73:6:73:12 | Call to produce [unknown index] | test.ps1:76:6:76:7 | x [unknown index] | provenance | | +| test.ps1:73:1:73:2 | x [unknown index] | test.ps1:74:6:74:7 | x [unknown index] | provenance | | +| test.ps1:73:1:73:2 | x [unknown index] | test.ps1:75:6:75:7 | x [unknown index] | provenance | | +| test.ps1:73:1:73:2 | x [unknown index] | test.ps1:76:6:76:7 | x [unknown index] | provenance | | +| test.ps1:73:6:73:12 | Call to produce [unknown index] | test.ps1:73:1:73:2 | x [unknown index] | provenance | | | test.ps1:74:6:74:7 | x [unknown index] | test.ps1:74:6:74:10 | ...[...] | provenance | | | test.ps1:75:6:75:7 | x [unknown index] | test.ps1:75:6:75:10 | ...[...] | provenance | | | test.ps1:76:6:76:7 | x [unknown index] | test.ps1:76:6:76:10 | ...[...] | provenance | | -| test.ps1:78:9:81:1 | ${...} [element a] | test.ps1:83:6:83:10 | hash [element a] | provenance | | -| test.ps1:78:9:81:1 | ${...} [element a] | test.ps1:87:6:87:10 | hash [element a] | provenance | | +| test.ps1:78:1:78:5 | hash [element a] | test.ps1:83:6:83:10 | hash [element a] | provenance | | +| test.ps1:78:1:78:5 | hash [element a] | test.ps1:87:6:87:10 | hash [element a] | provenance | | +| test.ps1:78:9:81:1 | ${...} [element a] | test.ps1:78:1:78:5 | hash [element a] | provenance | | | test.ps1:79:7:79:17 | Call to source | test.ps1:78:9:81:1 | ${...} [element a] | provenance | | | test.ps1:83:6:83:10 | hash [element a] | test.ps1:83:6:83:15 | ...[...] | provenance | | | test.ps1:87:6:87:10 | hash [element a] | test.ps1:87:6:87:15 | ...[...] | provenance | | @@ -112,14 +125,18 @@ nodes | test.ps1:33:6:33:10 | arr7 [unknown, unknown] | semmle.label | arr7 [unknown, unknown] | | test.ps1:33:6:33:21 | ...[...] [unknown] | semmle.label | ...[...] [unknown] | | test.ps1:33:6:33:32 | ...[...] | semmle.label | ...[...] | +| test.ps1:35:1:35:2 | x | semmle.label | x | | test.ps1:35:6:35:16 | Call to source | semmle.label | Call to source | +| test.ps1:37:1:37:5 | arr8 [element 2] | semmle.label | arr8 [element 2] | | test.ps1:37:9:37:16 | ...,... [element 2] | semmle.label | ...,... [element 2] | | test.ps1:37:15:37:16 | x | semmle.label | x | | test.ps1:40:6:40:10 | arr8 [element 2] | semmle.label | arr8 [element 2] | | test.ps1:40:6:40:13 | ...[...] | semmle.label | ...[...] | | test.ps1:41:6:41:10 | arr8 [element 2] | semmle.label | arr8 [element 2] | | test.ps1:41:6:41:20 | ...[...] | semmle.label | ...[...] | +| test.ps1:43:1:43:2 | y | semmle.label | y | | test.ps1:43:6:43:16 | Call to source | semmle.label | Call to source | +| test.ps1:45:1:45:5 | arr9 [element 2] | semmle.label | arr9 [element 2] | | test.ps1:45:9:45:19 | @(...) [element 2] | semmle.label | @(...) [element 2] | | test.ps1:45:17:45:18 | y | semmle.label | y | | test.ps1:48:6:48:10 | arr9 [element 2] | semmle.label | arr9 [element 2] | @@ -132,12 +149,18 @@ nodes | test.ps1:61:1:61:8 | [post] myClass [field] | semmle.label | [post] myClass [field] | | test.ps1:61:18:61:28 | Call to source | semmle.label | Call to source | | test.ps1:63:1:63:8 | myClass [field] | semmle.label | myClass [field] | +| test.ps1:66:5:66:6 | x | semmle.label | x | +| test.ps1:66:5:66:6 | x | semmle.label | x | | test.ps1:66:10:66:20 | Call to source | semmle.label | Call to source | +| test.ps1:67:5:67:6 | y | semmle.label | y | +| test.ps1:67:5:67:6 | y | semmle.label | y | | test.ps1:67:10:67:20 | Call to source | semmle.label | Call to source | +| test.ps1:68:5:68:6 | z | semmle.label | z | | test.ps1:68:10:68:20 | Call to source | semmle.label | Call to source | | test.ps1:69:5:69:6 | x | semmle.label | x | | test.ps1:70:5:70:6 | y | semmle.label | y | | test.ps1:70:9:70:10 | z | semmle.label | z | +| test.ps1:73:1:73:2 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:73:6:73:12 | Call to produce [unknown index] | semmle.label | Call to produce [unknown index] | | test.ps1:74:6:74:7 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:74:6:74:10 | ...[...] | semmle.label | ...[...] | @@ -145,6 +168,7 @@ nodes | test.ps1:75:6:75:10 | ...[...] | semmle.label | ...[...] | | test.ps1:76:6:76:7 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:76:6:76:10 | ...[...] | semmle.label | ...[...] | +| test.ps1:78:1:78:5 | hash [element a] | semmle.label | hash [element a] | | test.ps1:78:9:81:1 | ${...} [element a] | semmle.label | ${...} [element a] | | test.ps1:79:7:79:17 | Call to source | semmle.label | Call to source | | test.ps1:83:6:83:10 | hash [element a] | semmle.label | hash [element a] | diff --git a/powershell/ql/test/library-tests/dataflow/global/test.expected b/powershell/ql/test/library-tests/dataflow/global/test.expected new file mode 100644 index 000000000000..3a231a438dd8 --- /dev/null +++ b/powershell/ql/test/library-tests/dataflow/global/test.expected @@ -0,0 +1,17 @@ +models +edges +| test.ps1:2:5:2:23 | env:x | test.ps1:6:5:6:15 | env:x | provenance | | +| test.ps1:2:14:2:23 | Call to source | test.ps1:2:5:2:23 | env:x | provenance | | +| test.ps1:16:9:16:27 | env:x | test.ps1:6:5:6:15 | env:x | provenance | | +| test.ps1:16:18:16:27 | Call to source | test.ps1:16:9:16:27 | env:x | provenance | | +nodes +| test.ps1:2:5:2:23 | env:x | semmle.label | env:x | +| test.ps1:2:14:2:23 | Call to source | semmle.label | Call to source | +| test.ps1:6:5:6:15 | env:x | semmle.label | env:x | +| test.ps1:16:9:16:27 | env:x | semmle.label | env:x | +| test.ps1:16:18:16:27 | Call to source | semmle.label | Call to source | +subpaths +testFailures +#select +| test.ps1:6:5:6:15 | env:x | test.ps1:2:14:2:23 | Call to source | test.ps1:6:5:6:15 | env:x | $@ | test.ps1:2:14:2:23 | Call to source | Call to source | +| test.ps1:6:5:6:15 | env:x | test.ps1:16:18:16:27 | Call to source | test.ps1:6:5:6:15 | env:x | $@ | test.ps1:16:18:16:27 | Call to source | Call to source | diff --git a/powershell/ql/test/library-tests/dataflow/global/test.ps1 b/powershell/ql/test/library-tests/dataflow/global/test.ps1 new file mode 100644 index 000000000000..acdba136e94c --- /dev/null +++ b/powershell/ql/test/library-tests/dataflow/global/test.ps1 @@ -0,0 +1,20 @@ +function WriteToEnvVar { + $env:x = Source "1" +} + +function ReadFromEndVar { + Sink $Env:x # $ hasValueFlow=1 hasValueFlow=3 +} + +function WriteAndThenOverWriteEnvVar { + $env:x = Source "2" + $env:x = 0 +} + +function MaybeWriteToEnvVar($b) { + if($b -eq $true) { + $env:x = Source "3" + } else { + $env:x = 0 + } +} \ No newline at end of file diff --git a/powershell/ql/test/library-tests/dataflow/global/test.ql b/powershell/ql/test/library-tests/dataflow/global/test.ql new file mode 100644 index 000000000000..9a27a79c4478 --- /dev/null +++ b/powershell/ql/test/library-tests/dataflow/global/test.ql @@ -0,0 +1,13 @@ +/** + * @kind path-problem + */ + +import powershell +import semmle.code.powershell.dataflow.DataFlow +private import TestUtilities.InlineFlowTest +import DefaultFlowTest +import ValueFlow::PathGraph + +from ValueFlow::PathNode source, ValueFlow::PathNode sink +where ValueFlow::flowPath(source, sink) +select sink, source, sink, "$@", source, source.toString() diff --git a/powershell/ql/test/library-tests/dataflow/mad/flow.expected b/powershell/ql/test/library-tests/dataflow/mad/flow.expected index c1025f2bd751..0a3a3a249163 100644 --- a/powershell/ql/test/library-tests/dataflow/mad/flow.expected +++ b/powershell/ql/test/library-tests/dataflow/mad/flow.expected @@ -5,19 +5,24 @@ edges | file://:0:0:0:0 | [summary param] pos(0, {}) in system.management.automation.language.codegeneration!;Method[escapesinglequotedstringcontent] | file://:0:0:0:0 | [summary] to write: ReturnValue in system.management.automation.language.codegeneration!;Method[escapesinglequotedstringcontent] | provenance | | | file://:0:0:0:0 | [summary] read: Argument[pipeline].Element[?] in microsoft.powershell.utility!;Method[join-string] | file://:0:0:0:0 | [summary] to write: ReturnValue in microsoft.powershell.utility!;Method[join-string] | provenance | | | file://:0:0:0:0 | [summary] read: Argument[pipeline].Element[?] in microsoft.powershell.utility!;Method[join-string] | file://:0:0:0:0 | [summary] to write: ReturnValue in microsoft.powershell.utility!;Method[join-string] | provenance | | -| test.ps1:1:6:1:15 | Call to source | test.ps1:2:94:2:95 | x | provenance | | -| test.ps1:2:6:2:96 | Call to escapesinglequotedstringcontent | test.ps1:3:6:3:7 | y | provenance | | +| test.ps1:1:1:1:2 | x | test.ps1:2:94:2:95 | x | provenance | | +| test.ps1:1:6:1:15 | Call to source | test.ps1:1:1:1:2 | x | provenance | | +| test.ps1:2:1:2:2 | y | test.ps1:3:6:3:7 | y | provenance | | +| test.ps1:2:6:2:96 | Call to escapesinglequotedstringcontent | test.ps1:2:1:2:2 | y | provenance | | | test.ps1:2:94:2:95 | x | file://:0:0:0:0 | [summary param] pos(0, {}) in system.management.automation.language.codegeneration!;Method[escapesinglequotedstringcontent] | provenance | | | test.ps1:2:94:2:95 | x | test.ps1:2:6:2:96 | Call to escapesinglequotedstringcontent | provenance | | -| test.ps1:5:6:5:15 | Call to source | test.ps1:7:6:7:7 | x | provenance | | -| test.ps1:6:6:6:15 | Call to source | test.ps1:7:10:7:11 | y | provenance | | +| test.ps1:5:1:5:2 | x | test.ps1:7:6:7:7 | x | provenance | | +| test.ps1:5:6:5:15 | Call to source | test.ps1:5:1:5:2 | x | provenance | | +| test.ps1:6:1:6:2 | y | test.ps1:7:10:7:11 | y | provenance | | +| test.ps1:6:6:6:15 | Call to source | test.ps1:6:1:6:2 | y | provenance | | +| test.ps1:7:1:7:2 | z | test.ps1:8:6:8:7 | z | provenance | | | test.ps1:7:6:7:7 | x | test.ps1:7:6:7:11 | ...,... [element 0] | provenance | | | test.ps1:7:6:7:11 | ...,... [element 0] | file://:0:0:0:0 | [summary param] pipeline in microsoft.powershell.utility!;Method[join-string] [element 0] | provenance | | | test.ps1:7:6:7:11 | ...,... [element 0] | test.ps1:7:15:7:25 | Call to join-string | provenance | | | test.ps1:7:6:7:11 | ...,... [element 1] | file://:0:0:0:0 | [summary param] pipeline in microsoft.powershell.utility!;Method[join-string] [element 1] | provenance | | | test.ps1:7:6:7:11 | ...,... [element 1] | test.ps1:7:15:7:25 | Call to join-string | provenance | | | test.ps1:7:10:7:11 | y | test.ps1:7:6:7:11 | ...,... [element 1] | provenance | | -| test.ps1:7:15:7:25 | Call to join-string | test.ps1:8:6:8:7 | z | provenance | | +| test.ps1:7:15:7:25 | Call to join-string | test.ps1:7:1:7:2 | z | provenance | | nodes | file://:0:0:0:0 | [summary param] pipeline in microsoft.powershell.utility!;Method[join-string] [element 0] | semmle.label | [summary param] pipeline in microsoft.powershell.utility!;Method[join-string] [element 0] | | file://:0:0:0:0 | [summary param] pipeline in microsoft.powershell.utility!;Method[join-string] [element 1] | semmle.label | [summary param] pipeline in microsoft.powershell.utility!;Method[join-string] [element 1] | @@ -27,12 +32,17 @@ nodes | file://:0:0:0:0 | [summary] to write: ReturnValue in microsoft.powershell.utility!;Method[join-string] | semmle.label | [summary] to write: ReturnValue in microsoft.powershell.utility!;Method[join-string] | | file://:0:0:0:0 | [summary] to write: ReturnValue in microsoft.powershell.utility!;Method[join-string] | semmle.label | [summary] to write: ReturnValue in microsoft.powershell.utility!;Method[join-string] | | file://:0:0:0:0 | [summary] to write: ReturnValue in system.management.automation.language.codegeneration!;Method[escapesinglequotedstringcontent] | semmle.label | [summary] to write: ReturnValue in system.management.automation.language.codegeneration!;Method[escapesinglequotedstringcontent] | +| test.ps1:1:1:1:2 | x | semmle.label | x | | test.ps1:1:6:1:15 | Call to source | semmle.label | Call to source | +| test.ps1:2:1:2:2 | y | semmle.label | y | | test.ps1:2:6:2:96 | Call to escapesinglequotedstringcontent | semmle.label | Call to escapesinglequotedstringcontent | | test.ps1:2:94:2:95 | x | semmle.label | x | | test.ps1:3:6:3:7 | y | semmle.label | y | +| test.ps1:5:1:5:2 | x | semmle.label | x | | test.ps1:5:6:5:15 | Call to source | semmle.label | Call to source | +| test.ps1:6:1:6:2 | y | semmle.label | y | | test.ps1:6:6:6:15 | Call to source | semmle.label | Call to source | +| test.ps1:7:1:7:2 | z | semmle.label | z | | test.ps1:7:6:7:7 | x | semmle.label | x | | test.ps1:7:6:7:11 | ...,... [element 0] | semmle.label | ...,... [element 0] | | test.ps1:7:6:7:11 | ...,... [element 1] | semmle.label | ...,... [element 1] | diff --git a/powershell/ql/test/library-tests/dataflow/params/test.expected b/powershell/ql/test/library-tests/dataflow/params/test.expected index a14731c90c21..03822d56f0b1 100644 --- a/powershell/ql/test/library-tests/dataflow/params/test.expected +++ b/powershell/ql/test/library-tests/dataflow/params/test.expected @@ -1,77 +1,81 @@ models edges | test.ps1:1:14:1:15 | a | test.ps1:2:10:2:11 | a | provenance | | -| test.ps1:5:6:5:15 | Call to source | test.ps1:6:5:6:6 | x | provenance | | +| test.ps1:5:1:5:2 | x | test.ps1:6:5:6:6 | x | provenance | | +| test.ps1:5:6:5:15 | Call to source | test.ps1:5:1:5:2 | x | provenance | | | test.ps1:6:5:6:6 | x | test.ps1:1:14:1:15 | a | provenance | | | test.ps1:8:20:8:21 | x | test.ps1:9:10:9:11 | x | provenance | | | test.ps1:8:24:8:25 | y | test.ps1:10:10:10:11 | y | provenance | | | test.ps1:8:28:8:29 | z | test.ps1:11:10:11:11 | z | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:18:11:18:16 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:19:22:19:27 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:20:14:20:19 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:21:11:21:16 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:22:22:22:27 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:23:22:23:27 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:24:14:24:19 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:25:11:25:16 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:26:22:26:27 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:27:22:27:27 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:28:14:28:19 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:29:11:29:16 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:30:32:30:37 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:31:32:31:37 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:32:14:32:19 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:33:11:33:16 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:34:32:34:37 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:35:32:35:37 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:36:32:36:37 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:37:24:37:29 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:38:21:38:26 | first | provenance | | -| test.ps1:14:10:14:19 | Call to source | test.ps1:39:32:39:37 | first | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:18:18:18:24 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:19:11:19:17 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:20:21:20:27 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:21:21:21:27 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:22:14:22:20 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:23:11:23:17 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:24:21:24:27 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:25:21:25:27 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:26:14:26:20 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:27:11:27:17 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:28:31:28:37 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:29:21:29:27 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:30:14:30:20 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:31:11:31:17 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:32:31:32:37 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:33:31:33:37 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:34:14:34:20 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:35:24:35:30 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:36:21:36:27 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:37:31:37:37 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:38:31:38:37 | second | provenance | | -| test.ps1:15:11:15:20 | Call to source | test.ps1:39:24:39:30 | second | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:18:26:18:31 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:19:29:19:34 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:20:29:20:34 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:21:29:21:34 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:22:29:22:34 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:23:32:23:37 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:24:32:24:37 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:25:32:25:37 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:26:32:26:37 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:27:32:27:37 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:28:24:28:29 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:29:32:29:37 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:30:25:30:30 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:31:22:31:27 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:32:24:32:29 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:33:21:33:26 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:34:25:34:30 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:35:14:35:19 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:36:14:36:19 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:37:14:37:19 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:38:14:38:19 | third | provenance | | -| test.ps1:16:10:16:19 | Call to source | test.ps1:39:14:39:19 | third | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:18:11:18:16 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:19:22:19:27 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:20:14:20:19 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:21:11:21:16 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:22:22:22:27 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:23:22:23:27 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:24:14:24:19 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:25:11:25:16 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:26:22:26:27 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:27:22:27:27 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:28:14:28:19 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:29:11:29:16 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:30:32:30:37 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:31:32:31:37 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:32:14:32:19 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:33:11:33:16 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:34:32:34:37 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:35:32:35:37 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:36:32:36:37 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:37:24:37:29 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:38:21:38:26 | first | provenance | | +| test.ps1:14:1:14:6 | first | test.ps1:39:32:39:37 | first | provenance | | +| test.ps1:14:10:14:19 | Call to source | test.ps1:14:1:14:6 | first | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:18:18:18:24 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:19:11:19:17 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:20:21:20:27 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:21:21:21:27 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:22:14:22:20 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:23:11:23:17 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:24:21:24:27 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:25:21:25:27 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:26:14:26:20 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:27:11:27:17 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:28:31:28:37 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:29:21:29:27 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:30:14:30:20 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:31:11:31:17 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:32:31:32:37 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:33:31:33:37 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:34:14:34:20 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:35:24:35:30 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:36:21:36:27 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:37:31:37:37 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:38:31:38:37 | second | provenance | | +| test.ps1:15:1:15:7 | second | test.ps1:39:24:39:30 | second | provenance | | +| test.ps1:15:11:15:20 | Call to source | test.ps1:15:1:15:7 | second | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:18:26:18:31 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:19:29:19:34 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:20:29:20:34 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:21:29:21:34 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:22:29:22:34 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:23:32:23:37 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:24:32:24:37 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:25:32:25:37 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:26:32:26:37 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:27:32:27:37 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:28:24:28:29 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:29:32:29:37 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:30:25:30:30 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:31:22:31:27 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:32:24:32:29 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:33:21:33:26 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:34:25:34:30 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:35:14:35:19 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:36:14:36:19 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:37:14:37:19 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:38:14:38:19 | third | provenance | | +| test.ps1:16:1:16:6 | third | test.ps1:39:14:39:19 | third | provenance | | +| test.ps1:16:10:16:19 | Call to source | test.ps1:16:1:16:6 | third | provenance | | | test.ps1:18:11:18:16 | first | test.ps1:8:20:8:21 | x | provenance | | | test.ps1:18:18:18:24 | second | test.ps1:8:24:8:25 | y | provenance | | | test.ps1:18:26:18:31 | third | test.ps1:8:28:8:29 | z | provenance | | @@ -139,11 +143,13 @@ edges | test.ps1:39:24:39:30 | second | test.ps1:8:24:8:25 | y | provenance | | | test.ps1:39:32:39:37 | first | test.ps1:8:20:8:21 | x | provenance | | | test.ps1:43:11:43:20 | userinput | test.ps1:44:10:44:19 | UserInput | provenance | | -| test.ps1:47:10:47:19 | Call to source | test.ps1:48:46:48:51 | input | provenance | | +| test.ps1:47:1:47:6 | input | test.ps1:48:46:48:51 | input | provenance | | +| test.ps1:47:10:47:19 | Call to source | test.ps1:47:1:47:6 | input | provenance | | | test.ps1:48:46:48:51 | input | test.ps1:43:11:43:20 | userinput | provenance | | nodes | test.ps1:1:14:1:15 | a | semmle.label | a | | test.ps1:2:10:2:11 | a | semmle.label | a | +| test.ps1:5:1:5:2 | x | semmle.label | x | | test.ps1:5:6:5:15 | Call to source | semmle.label | Call to source | | test.ps1:6:5:6:6 | x | semmle.label | x | | test.ps1:8:20:8:21 | x | semmle.label | x | @@ -152,8 +158,11 @@ nodes | test.ps1:9:10:9:11 | x | semmle.label | x | | test.ps1:10:10:10:11 | y | semmle.label | y | | test.ps1:11:10:11:11 | z | semmle.label | z | +| test.ps1:14:1:14:6 | first | semmle.label | first | | test.ps1:14:10:14:19 | Call to source | semmle.label | Call to source | +| test.ps1:15:1:15:7 | second | semmle.label | second | | test.ps1:15:11:15:20 | Call to source | semmle.label | Call to source | +| test.ps1:16:1:16:6 | third | semmle.label | third | | test.ps1:16:10:16:19 | Call to source | semmle.label | Call to source | | test.ps1:18:11:18:16 | first | semmle.label | first | | test.ps1:18:18:18:24 | second | semmle.label | second | @@ -223,6 +232,7 @@ nodes | test.ps1:39:32:39:37 | first | semmle.label | first | | test.ps1:43:11:43:20 | userinput | semmle.label | userinput | | test.ps1:44:10:44:19 | UserInput | semmle.label | UserInput | +| test.ps1:47:1:47:6 | input | semmle.label | input | | test.ps1:47:10:47:19 | Call to source | semmle.label | Call to source | | test.ps1:48:46:48:51 | input | semmle.label | input | subpaths diff --git a/powershell/ql/test/library-tests/dataflow/pipeline/test.expected b/powershell/ql/test/library-tests/dataflow/pipeline/test.expected index d446cea80050..385002c5a9a1 100644 --- a/powershell/ql/test/library-tests/dataflow/pipeline/test.expected +++ b/powershell/ql/test/library-tests/dataflow/pipeline/test.expected @@ -1,8 +1,11 @@ models edges -| test.ps1:2:10:2:19 | Call to source | test.ps1:5:5:5:6 | x | provenance | | -| test.ps1:3:10:3:19 | Call to source | test.ps1:6:5:6:6 | y | provenance | | -| test.ps1:4:10:4:19 | Call to source | test.ps1:6:9:6:10 | z | provenance | | +| test.ps1:2:5:2:6 | x | test.ps1:5:5:5:6 | x | provenance | | +| test.ps1:2:10:2:19 | Call to source | test.ps1:2:5:2:6 | x | provenance | | +| test.ps1:3:5:3:6 | y | test.ps1:6:5:6:6 | y | provenance | | +| test.ps1:3:10:3:19 | Call to source | test.ps1:3:5:3:6 | y | provenance | | +| test.ps1:4:5:4:6 | z | test.ps1:6:9:6:10 | z | provenance | | +| test.ps1:4:10:4:19 | Call to source | test.ps1:4:5:4:6 | z | provenance | | | test.ps1:5:5:5:6 | x | test.ps1:17:1:17:7 | Call to produce [unknown index] | provenance | | | test.ps1:6:5:6:6 | y | test.ps1:17:1:17:7 | Call to produce [unknown index] | provenance | | | test.ps1:6:9:6:10 | z | test.ps1:17:1:17:7 | Call to produce [unknown index] | provenance | | @@ -13,8 +16,10 @@ edges | test.ps1:12:5:14:5 | x [element 1] | test.ps1:13:9:13:15 | __pipeline_iterator | provenance | | | test.ps1:12:5:14:5 | x [unknown index] | test.ps1:13:9:13:15 | __pipeline_iterator | provenance | | | test.ps1:17:1:17:7 | Call to produce [unknown index] | test.ps1:10:11:10:43 | x [unknown index] | provenance | | -| test.ps1:19:6:19:15 | Call to source | test.ps1:21:1:21:2 | x | provenance | | -| test.ps1:20:6:20:15 | Call to source | test.ps1:21:5:21:6 | y | provenance | | +| test.ps1:19:1:19:2 | x | test.ps1:21:1:21:2 | x | provenance | | +| test.ps1:19:6:19:15 | Call to source | test.ps1:19:1:19:2 | x | provenance | | +| test.ps1:20:1:20:2 | y | test.ps1:21:5:21:6 | y | provenance | | +| test.ps1:20:6:20:15 | Call to source | test.ps1:20:1:20:2 | y | provenance | | | test.ps1:21:1:21:2 | x | test.ps1:21:1:21:6 | ...,... [element 0] | provenance | | | test.ps1:21:1:21:6 | ...,... [element 0] | test.ps1:10:11:10:43 | x [element 0] | provenance | | | test.ps1:21:1:21:6 | ...,... [element 1] | test.ps1:10:11:10:43 | x [element 1] | provenance | | @@ -23,8 +28,10 @@ edges | test.ps1:23:38:27:1 | [synth] pipeline [element 1] | test.ps1:24:5:26:5 | [synth] pipeline [element 1] | provenance | | | test.ps1:24:5:26:5 | [synth] pipeline [element 0] | test.ps1:25:9:25:15 | __pipeline_iterator | provenance | | | test.ps1:24:5:26:5 | [synth] pipeline [element 1] | test.ps1:25:9:25:15 | __pipeline_iterator | provenance | | -| test.ps1:29:6:29:15 | Call to source | test.ps1:31:1:31:2 | x | provenance | | -| test.ps1:30:6:30:15 | Call to source | test.ps1:31:5:31:6 | y | provenance | | +| test.ps1:29:1:29:2 | x | test.ps1:31:1:31:2 | x | provenance | | +| test.ps1:29:6:29:15 | Call to source | test.ps1:29:1:29:2 | x | provenance | | +| test.ps1:30:1:30:2 | y | test.ps1:31:5:31:6 | y | provenance | | +| test.ps1:30:6:30:15 | Call to source | test.ps1:30:1:30:2 | y | provenance | | | test.ps1:31:1:31:2 | x | test.ps1:31:1:31:6 | ...,... [element 0] | provenance | | | test.ps1:31:1:31:6 | ...,... [element 0] | test.ps1:23:38:27:1 | [synth] pipeline [element 0] | provenance | | | test.ps1:31:1:31:6 | ...,... [element 1] | test.ps1:23:38:27:1 | [synth] pipeline [element 1] | provenance | | @@ -48,8 +55,11 @@ edges | test.ps1:50:88:50:105 | ${...} [element x] | test.ps1:50:72:50:105 | [...]... [element x] | provenance | | | test.ps1:50:94:50:104 | Call to source | test.ps1:50:88:50:105 | ${...} [element x] | provenance | | nodes +| test.ps1:2:5:2:6 | x | semmle.label | x | | test.ps1:2:10:2:19 | Call to source | semmle.label | Call to source | +| test.ps1:3:5:3:6 | y | semmle.label | y | | test.ps1:3:10:3:19 | Call to source | semmle.label | Call to source | +| test.ps1:4:5:4:6 | z | semmle.label | z | | test.ps1:4:10:4:19 | Call to source | semmle.label | Call to source | | test.ps1:5:5:5:6 | x | semmle.label | x | | test.ps1:6:5:6:6 | y | semmle.label | y | @@ -62,7 +72,9 @@ nodes | test.ps1:12:5:14:5 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:13:9:13:15 | __pipeline_iterator | semmle.label | __pipeline_iterator | | test.ps1:17:1:17:7 | Call to produce [unknown index] | semmle.label | Call to produce [unknown index] | +| test.ps1:19:1:19:2 | x | semmle.label | x | | test.ps1:19:6:19:15 | Call to source | semmle.label | Call to source | +| test.ps1:20:1:20:2 | y | semmle.label | y | | test.ps1:20:6:20:15 | Call to source | semmle.label | Call to source | | test.ps1:21:1:21:2 | x | semmle.label | x | | test.ps1:21:1:21:6 | ...,... [element 0] | semmle.label | ...,... [element 0] | @@ -73,7 +85,9 @@ nodes | test.ps1:24:5:26:5 | [synth] pipeline [element 0] | semmle.label | [synth] pipeline [element 0] | | test.ps1:24:5:26:5 | [synth] pipeline [element 1] | semmle.label | [synth] pipeline [element 1] | | test.ps1:25:9:25:15 | __pipeline_iterator | semmle.label | __pipeline_iterator | +| test.ps1:29:1:29:2 | x | semmle.label | x | | test.ps1:29:6:29:15 | Call to source | semmle.label | Call to source | +| test.ps1:30:1:30:2 | y | semmle.label | y | | test.ps1:30:6:30:15 | Call to source | semmle.label | Call to source | | test.ps1:31:1:31:2 | x | semmle.label | x | | test.ps1:31:1:31:6 | ...,... [element 0] | semmle.label | ...,... [element 0] | diff --git a/powershell/ql/test/library-tests/dataflow/returns/test.expected b/powershell/ql/test/library-tests/dataflow/returns/test.expected index fdfb0fcbff00..84607e7dd2e9 100644 --- a/powershell/ql/test/library-tests/dataflow/returns/test.expected +++ b/powershell/ql/test/library-tests/dataflow/returns/test.expected @@ -1,52 +1,69 @@ models edges | test.ps1:2:5:2:14 | Call to source | test.ps1:5:6:5:19 | Call to callsourceonce | provenance | | -| test.ps1:5:6:5:19 | Call to callsourceonce | test.ps1:6:6:6:7 | x | provenance | | +| test.ps1:5:1:5:2 | x | test.ps1:6:6:6:7 | x | provenance | | +| test.ps1:5:6:5:19 | Call to callsourceonce | test.ps1:5:1:5:2 | x | provenance | | | test.ps1:9:5:9:14 | Call to source | test.ps1:13:6:13:20 | Call to callsourcetwice [unknown index] | provenance | | | test.ps1:10:5:10:14 | Call to source | test.ps1:13:6:13:20 | Call to callsourcetwice [unknown index] | provenance | | -| test.ps1:13:6:13:20 | Call to callsourcetwice [unknown index] | test.ps1:15:6:15:7 | x [unknown index] | provenance | | -| test.ps1:13:6:13:20 | Call to callsourcetwice [unknown index] | test.ps1:16:6:16:7 | x [unknown index] | provenance | | +| test.ps1:13:1:13:2 | x [unknown index] | test.ps1:15:6:15:7 | x [unknown index] | provenance | | +| test.ps1:13:1:13:2 | x [unknown index] | test.ps1:16:6:16:7 | x [unknown index] | provenance | | +| test.ps1:13:6:13:20 | Call to callsourcetwice [unknown index] | test.ps1:13:1:13:2 | x [unknown index] | provenance | | | test.ps1:15:6:15:7 | x [unknown index] | test.ps1:15:6:15:10 | ...[...] | provenance | | | test.ps1:16:6:16:7 | x [unknown index] | test.ps1:16:6:16:10 | ...[...] | provenance | | | test.ps1:19:12:19:21 | Call to source | test.ps1:22:6:22:18 | Call to returnsource1 | provenance | | -| test.ps1:22:6:22:18 | Call to returnsource1 | test.ps1:23:6:23:7 | x | provenance | | -| test.ps1:26:10:26:19 | Call to source | test.ps1:27:5:27:6 | x | provenance | | +| test.ps1:22:1:22:2 | x | test.ps1:23:6:23:7 | x | provenance | | +| test.ps1:22:6:22:18 | Call to returnsource1 | test.ps1:22:1:22:2 | x | provenance | | +| test.ps1:26:5:26:6 | x | test.ps1:27:5:27:6 | x | provenance | | +| test.ps1:26:5:26:6 | x | test.ps1:27:5:27:6 | x | provenance | | +| test.ps1:26:10:26:19 | Call to source | test.ps1:26:5:26:6 | x | provenance | | +| test.ps1:26:10:26:19 | Call to source | test.ps1:26:5:26:6 | x | provenance | | | test.ps1:27:5:27:6 | x | test.ps1:32:6:32:18 | Call to returnsource2 [unknown index] | provenance | | -| test.ps1:28:10:28:19 | Call to source | test.ps1:29:12:29:13 | y | provenance | | +| test.ps1:28:5:28:6 | y | test.ps1:29:12:29:13 | y | provenance | | +| test.ps1:28:10:28:19 | Call to source | test.ps1:28:5:28:6 | y | provenance | | | test.ps1:29:12:29:13 | y | test.ps1:32:6:32:18 | Call to returnsource2 [unknown index] | provenance | | -| test.ps1:32:6:32:18 | Call to returnsource2 [unknown index] | test.ps1:33:6:33:7 | x [unknown index] | provenance | | -| test.ps1:32:6:32:18 | Call to returnsource2 [unknown index] | test.ps1:34:6:34:7 | x [unknown index] | provenance | | +| test.ps1:32:1:32:2 | x [unknown index] | test.ps1:33:6:33:7 | x [unknown index] | provenance | | +| test.ps1:32:1:32:2 | x [unknown index] | test.ps1:34:6:34:7 | x [unknown index] | provenance | | +| test.ps1:32:6:32:18 | Call to returnsource2 [unknown index] | test.ps1:32:1:32:2 | x [unknown index] | provenance | | | test.ps1:33:6:33:7 | x [unknown index] | test.ps1:33:6:33:10 | ...[...] | provenance | | | test.ps1:34:6:34:7 | x [unknown index] | test.ps1:34:6:34:10 | ...[...] | provenance | | | test.ps1:38:9:38:18 | Call to source | test.ps1:42:6:42:21 | Call to callsourceinloop [unknown index] | provenance | | -| test.ps1:42:6:42:21 | Call to callsourceinloop [unknown index] | test.ps1:43:6:43:7 | x [unknown index] | provenance | | -| test.ps1:42:6:42:21 | Call to callsourceinloop [unknown index] | test.ps1:44:6:44:7 | x [unknown index] | provenance | | +| test.ps1:42:1:42:2 | x [unknown index] | test.ps1:43:6:43:7 | x [unknown index] | provenance | | +| test.ps1:42:1:42:2 | x [unknown index] | test.ps1:44:6:44:7 | x [unknown index] | provenance | | +| test.ps1:42:6:42:21 | Call to callsourceinloop [unknown index] | test.ps1:42:1:42:2 | x [unknown index] | provenance | | | test.ps1:43:6:43:7 | x [unknown index] | test.ps1:43:6:43:10 | ...[...] | provenance | | | test.ps1:44:6:44:7 | x [unknown index] | test.ps1:44:6:44:10 | ...[...] | provenance | | nodes | test.ps1:2:5:2:14 | Call to source | semmle.label | Call to source | +| test.ps1:5:1:5:2 | x | semmle.label | x | | test.ps1:5:6:5:19 | Call to callsourceonce | semmle.label | Call to callsourceonce | | test.ps1:6:6:6:7 | x | semmle.label | x | | test.ps1:9:5:9:14 | Call to source | semmle.label | Call to source | | test.ps1:10:5:10:14 | Call to source | semmle.label | Call to source | +| test.ps1:13:1:13:2 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:13:6:13:20 | Call to callsourcetwice [unknown index] | semmle.label | Call to callsourcetwice [unknown index] | | test.ps1:15:6:15:7 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:15:6:15:10 | ...[...] | semmle.label | ...[...] | | test.ps1:16:6:16:7 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:16:6:16:10 | ...[...] | semmle.label | ...[...] | | test.ps1:19:12:19:21 | Call to source | semmle.label | Call to source | +| test.ps1:22:1:22:2 | x | semmle.label | x | | test.ps1:22:6:22:18 | Call to returnsource1 | semmle.label | Call to returnsource1 | | test.ps1:23:6:23:7 | x | semmle.label | x | +| test.ps1:26:5:26:6 | x | semmle.label | x | +| test.ps1:26:5:26:6 | x | semmle.label | x | | test.ps1:26:10:26:19 | Call to source | semmle.label | Call to source | | test.ps1:27:5:27:6 | x | semmle.label | x | +| test.ps1:28:5:28:6 | y | semmle.label | y | | test.ps1:28:10:28:19 | Call to source | semmle.label | Call to source | | test.ps1:29:12:29:13 | y | semmle.label | y | +| test.ps1:32:1:32:2 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:32:6:32:18 | Call to returnsource2 [unknown index] | semmle.label | Call to returnsource2 [unknown index] | | test.ps1:33:6:33:7 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:33:6:33:10 | ...[...] | semmle.label | ...[...] | | test.ps1:34:6:34:7 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:34:6:34:10 | ...[...] | semmle.label | ...[...] | | test.ps1:38:9:38:18 | Call to source | semmle.label | Call to source | +| test.ps1:42:1:42:2 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:42:6:42:21 | Call to callsourceinloop [unknown index] | semmle.label | Call to callsourceinloop [unknown index] | | test.ps1:43:6:43:7 | x [unknown index] | semmle.label | x [unknown index] | | test.ps1:43:6:43:10 | ...[...] | semmle.label | ...[...] | diff --git a/powershell/ql/test/library-tests/dataflow/typetracking/test.ps1 b/powershell/ql/test/library-tests/dataflow/typetracking/test.ps1 index 980e84abf42e..e9a36131c400 100644 --- a/powershell/ql/test/library-tests/dataflow/typetracking/test.ps1 +++ b/powershell/ql/test/library-tests/dataflow/typetracking/test.ps1 @@ -7,7 +7,7 @@ class MyClass { $myClass = [MyClass]::new("hello") -Sink $myClass # $ type=MyClass +Sink $myClass # $ type=myclass $withNamedArg = New-Object -TypeName PSObject 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 40bbaeed74a9..4ce3845cc051 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 @@ -6,7 +6,8 @@ edges | test.ps1:27:11:27:20 | userinput | test.ps1:28:38:28:67 | Get-Process -Name $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:45:11:45:20 | userinput | test.ps1:47:5:47:9 | code | provenance | | +| test.ps1:47:5:47:9 | code | 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 | | @@ -17,24 +18,25 @@ edges | test.ps1:129:11:129:20 | userinput | test.ps1:131:28:131: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:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:155:46:155:51 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:156:46:156:51 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:157:46:157:51 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:158:46:158:51 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:159:46:159:51 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:160:46:160:51 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:161:46:161:51 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:163:48:163:53 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:164:48:164:53 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:165:48:165:53 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:166:41:166:46 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:167:41:167:46 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:168:36:168:41 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:169:36:169:41 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:170:36:170:41 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:172:42:172:47 | input | provenance | Src:MaD:0 | -| test.ps1:152:10:152:32 | Call to read-host | test.ps1:173:42:173:47 | input | provenance | Src:MaD:0 | +| test.ps1:152:1:152:6 | input | test.ps1:154:46:154:51 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:155:46:155:51 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:156:46:156:51 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:157:46:157:51 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:158:46:158:51 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:159:46:159:51 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:160:46:160:51 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:161:46:161:51 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:163:48:163:53 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:164:48:164:53 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:165:48:165:53 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:166:41:166:46 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:167:41:167:46 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:168:36:168:41 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:169:36:169:41 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:170:36:170:41 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:172:42:172:47 | input | provenance | | +| test.ps1:152:1:152:6 | input | test.ps1:173:42:173:47 | input | provenance | | +| test.ps1:152:10:152:32 | Call to read-host | test.ps1:152:1:152:6 | input | provenance | Src:MaD:0 | | 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 | | @@ -53,6 +55,14 @@ edges | test.ps1:170:36:170:41 | input | test.ps1:129:11:129: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 | | +| test.ps1:214:5:214:6 | o | test.ps1:217:7:217:10 | $o | provenance | | +| test.ps1:214:10:214:32 | Call to read-host | test.ps1:214:5:214:6 | o | provenance | Src:MaD:0 | +| test.ps1:225:5:225:10 | input | test.ps1:226:5:226:21 | env:bar | provenance | | +| test.ps1:225:5:225:10 | input | test.ps1:226:5:226:21 | env:bar | provenance | | +| test.ps1:225:14:225:36 | Call to read-host | test.ps1:225:5:225:10 | input | provenance | Src:MaD:0 | +| test.ps1:225:14:225:36 | Call to read-host | test.ps1:225:5:225:10 | input | provenance | Src:MaD:0 | +| test.ps1:226:5:226:21 | env:bar | test.ps1:228:5:228:6 | y | provenance | | +| test.ps1:228:5:228:6 | y | test.ps1:229:7:229:10 | $y | 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 | @@ -69,6 +79,7 @@ nodes | 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:47:5:47:9 | code | semmle.label | code | | 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 | @@ -90,6 +101,7 @@ nodes | 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:1:152:6 | input | semmle.label | input | | 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 | @@ -109,6 +121,15 @@ nodes | 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 | +| test.ps1:214:5:214:6 | o | semmle.label | o | +| test.ps1:214:10:214:32 | Call to read-host | semmle.label | Call to read-host | +| test.ps1:217:7:217:10 | $o | semmle.label | $o | +| test.ps1:225:5:225:10 | input | semmle.label | input | +| test.ps1:225:5:225:10 | input | semmle.label | input | +| test.ps1:225:14:225:36 | Call to read-host | semmle.label | Call to read-host | +| test.ps1:226:5:226:21 | env:bar | semmle.label | env:bar | +| test.ps1:228:5:228:6 | y | semmle.label | y | +| test.ps1:229:7:229:10 | $y | semmle.label | $y | subpaths #select | 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 | @@ -129,3 +150,5 @@ subpaths | test.ps1:131:28:131:37 | UserInput | test.ps1:152:10:152:32 | Call to read-host | test.ps1:131:28:131: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 | +| test.ps1:217:7:217:10 | $o | test.ps1:214:10:214:32 | Call to read-host | test.ps1:217:7:217:10 | $o | This command depends on a $@. | test.ps1:214:10:214:32 | Call to read-host | user-provided value | +| test.ps1:229:7:229:10 | $y | test.ps1:225:14:225:36 | Call to read-host | test.ps1:229:7:229:10 | $y | This command depends on a $@. | test.ps1:225:14:225:36 | 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 f956795d6cd0..b69bee0c18b8 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 @@ -207,4 +207,24 @@ function Invoke-InvokeExpressionInjectionSafe4 Invoke-InvokeExpressionInjectionSafe1 -UserInput $input Invoke-InvokeExpressionInjectionSafe2 -UserInput $input Invoke-InvokeExpressionInjectionSafe3 -UserInput $input -Invoke-InvokeExpressionInjectionSafe4 -UserInput $input \ No newline at end of file +Invoke-InvokeExpressionInjectionSafe4 -UserInput $input + +function false-positive-in-call-operator($d) +{ + $o = Read-Host "enter input" + & unzip -o "$o" -d $d # GOOD + + . "$o" # BAD +} + +function flow-through-env-var() { + $x = $env:foo + + . "$x" # GOOD # we don't consider environment vars flow sources + + $input = Read-Host "enter input" + $env:bar = $input + + $y = $env:bar + . "$y" # BAD # but we have flow through them +} \ No newline at end of file diff --git a/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected b/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected index 86a4223d4baa..af5d263afa67 100644 --- a/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected +++ b/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected @@ -1,17 +1,25 @@ edges -| test.ps1:1:14:1:45 | Call to read-host | test.ps1:5:72:5:77 | query | provenance | Src:MaD:0 | -| test.ps1:1:14:1:45 | Call to read-host | test.ps1:9:72:9:77 | query | provenance | Src:MaD:0 | -| test.ps1:1:14:1:45 | Call to read-host | test.ps1:17:24:17:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | provenance | Src:MaD:0 | -| test.ps1:1:14:1:45 | Call to read-host | test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | provenance | Src:MaD:0 | -| test.ps1:1:14:1:45 | Call to read-host | test.ps1:78:13:78:22 | userinput | provenance | Src:MaD:0 | -| test.ps1:72:15:79:1 | ${...} [element Query] | test.ps1:81:15:81:25 | QueryConn2 | provenance | | +| test.ps1:1:1:1:10 | userinput | test.ps1:4:1:4:6 | query | provenance | | +| test.ps1:1:1:1:10 | userinput | test.ps1:8:1:8:6 | query | provenance | | +| test.ps1:1:1:1:10 | userinput | test.ps1:17:24:17:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | provenance | | +| test.ps1:1:1:1:10 | userinput | test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | provenance | | +| test.ps1:1:1:1:10 | userinput | test.ps1:78:13:78:22 | userinput | provenance | | +| test.ps1:1:14:1:45 | Call to read-host | test.ps1:1:1:1:10 | userinput | provenance | Src:MaD:0 | +| test.ps1:4:1:4:6 | query | test.ps1:5:72:5:77 | query | provenance | | +| test.ps1:8:1:8:6 | query | test.ps1:9:72:9:77 | query | provenance | | +| test.ps1:72:1:72:11 | QueryConn2 [element Query] | test.ps1:81:15:81:25 | QueryConn2 | provenance | | +| test.ps1:72:15:79:1 | ${...} [element Query] | test.ps1:72:1:72:11 | QueryConn2 [element Query] | provenance | | | test.ps1:78:13:78:22 | userinput | test.ps1:72:15:79:1 | ${...} [element Query] | provenance | | nodes +| test.ps1:1:1:1:10 | userinput | semmle.label | userinput | | test.ps1:1:14:1:45 | Call to read-host | semmle.label | Call to read-host | +| test.ps1:4:1:4:6 | query | semmle.label | query | | test.ps1:5:72:5:77 | query | semmle.label | query | +| test.ps1:8:1:8:6 | query | semmle.label | query | | test.ps1:9:72:9:77 | query | semmle.label | query | | test.ps1:17:24:17:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | semmle.label | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | | test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | semmle.label | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | +| test.ps1:72:1:72:11 | QueryConn2 [element Query] | semmle.label | QueryConn2 [element Query] | | test.ps1:72:15:79:1 | ${...} [element Query] | semmle.label | ${...} [element Query] | | test.ps1:78:13:78:22 | userinput | semmle.label | userinput | | test.ps1:81:15:81:25 | QueryConn2 | semmle.label | QueryConn2 | diff --git a/powershell/ql/test/query-tests/security/cwe-089/test.ps1 b/powershell/ql/test/query-tests/security/cwe-089/test.ps1 index 265c7110427b..2a4056170353 100644 --- a/powershell/ql/test/query-tests/security/cwe-089/test.ps1 +++ b/powershell/ql/test/query-tests/security/cwe-089/test.ps1 @@ -78,4 +78,32 @@ $QueryConn2 = @{ Query = $userinput } -Invoke-Sqlcmd @QueryConn2 # BAD \ No newline at end of file +Invoke-Sqlcmd @QueryConn2 # BAD + +function TakesTypedParameters([int]$i, [long]$l, [float]$f, [double]$d, [decimal]$dec, [char]$c, [bool]$b, [datetime]$dt) { + $query1 = "SELECT * FROM MyTable WHERE MyColumn = '$i'" + Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query1 # GOOD + + $query2 = "SELECT * FROM MyTable WHERE MyColumn = '$l'" + Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query2 # GOOD + + $query3 = "SELECT * FROM MyTable WHERE MyColumn = '$f'" + Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query3 # GOOD + + $query4 = "SELECT * FROM MyTable WHERE MyColumn = '$d'" + Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query4 # GOOD + + $query5 = "SELECT * FROM MyTable WHERE MyColumn = '$dec'" + Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query5 # GOOD + + $query6 = "SELECT * FROM MyTable WHERE MyColumn = '$c'" + Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query6 # GOOD + + $query7 = "SELECT * FROM MyTable WHERE MyColumn = '$b'" + Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query7 # GOOD + + $query8 = "SELECT * FROM MyTable WHERE MyColumn = '$dt'" + Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query8 # GOOD +} + +TakesTypedParameters $userinput $userinput $userinput $userinput $userinput $userinput $userinput $userinput diff --git a/powershell/ql/test/query-tests/security/cwe-319/UnsafeSMBSettings.expected b/powershell/ql/test/query-tests/security/cwe-319/UnsafeSMBSettings.expected new file mode 100644 index 000000000000..3ad0ddc72207 --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-319/UnsafeSMBSettings.expected @@ -0,0 +1,12 @@ +| test.ps1:5:44:5:47 | None | Unsafe SMB setting | +| test.ps1:7:44:7:49 | SMB210 | Unsafe SMB setting | +| test.ps1:9:41:9:46 | false | Unsafe SMB setting | +| test.ps1:9:73:9:78 | false | Unsafe SMB setting | +| test.ps1:11:47:11:52 | false | Unsafe SMB setting | +| test.ps1:13:39:13:44 | false | Unsafe SMB setting | +| test.ps1:15:39:15:44 | false | Unsafe SMB setting | +| test.ps1:15:65:15:70 | false | Unsafe SMB setting | +| test.ps1:15:88:15:93 | SMB210 | Unsafe SMB setting | +| test.ps1:17:44:17:47 | None | Unsafe SMB setting | +| test.ps1:17:62:17:67 | false | Unsafe SMB setting | +| test.ps1:17:94:17:99 | false | Unsafe SMB setting | diff --git a/powershell/ql/test/query-tests/security/cwe-319/UnsafeSMBSettings.qlref b/powershell/ql/test/query-tests/security/cwe-319/UnsafeSMBSettings.qlref new file mode 100644 index 000000000000..d925516eb6b5 --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-319/UnsafeSMBSettings.qlref @@ -0,0 +1 @@ +queries/security/cwe-319/UnsafeSMBSettings.ql \ No newline at end of file diff --git a/powershell/ql/test/query-tests/security/cwe-319/test.ps1 b/powershell/ql/test/query-tests/security/cwe-319/test.ps1 new file mode 100644 index 000000000000..0ae794115d6b --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-319/test.ps1 @@ -0,0 +1,30 @@ +# https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-ntlm-blocking?tabs=powershell + +#Bad Examples + +Set-SmbServerConfiguration -Smb2DialectMin None + +Set-SmbClientConfiguration -Smb2DialectMin SMB210 + +Set-SmbServerConfiguration -encryptdata $false -rejectunencryptedaccess $false + +Set-SmbClientConfiguration -RequireEncryption $false + +Set-SMbClientConfiguration -BlockNTLM $false + +Set-SMbClientConfiguration -BlockNTLM $false -RequireEncryption $false -Smb2DialectMin SMB210 + +Set-SmbServerConfiguration -Smb2DialectMin None -encryptdata $false -rejectunencryptedaccess $false + +#Good Examples + +Set-SmbServerConfiguration -Smb2DialectMin SMB300 + +Set-SmbClientConfiguration -Smb2DialectMin SMB300 + +Set-SmbServerConfiguration -encryptdata $true -rejectunencryptedaccess $true + +Set-SmbClientConfiguration -RequireEncryption $true + +Set-SMbClientConfiguration -BlockNTLM $true + diff --git a/powershell/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.expected b/powershell/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.expected new file mode 100644 index 000000000000..1f4d0a846c98 --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.expected @@ -0,0 +1,18 @@ +edges +| test.ps1:1:1:1:16 | untrustedBase64 | test.ps1:3:69:3:84 | untrustedBase64 | provenance | | +| test.ps1:1:20:1:47 | Call to read-host | test.ps1:1:1:1:16 | untrustedBase64 | provenance | Src:MaD:0 | +| test.ps1:3:1:3:7 | stream | test.ps1:4:31:4:37 | stream | provenance | | +| test.ps1:3:11:3:86 | Call to new | test.ps1:3:1:3:7 | stream | provenance | | +| test.ps1:3:41:3:85 | Call to frombase64string | test.ps1:3:11:3:86 | Call to new | provenance | Config | +| test.ps1:3:69:3:84 | untrustedBase64 | test.ps1:3:41:3:85 | Call to frombase64string | provenance | Config | +nodes +| test.ps1:1:1:1:16 | untrustedBase64 | semmle.label | untrustedBase64 | +| test.ps1:1:20:1:47 | Call to read-host | semmle.label | Call to read-host | +| test.ps1:3:1:3:7 | stream | semmle.label | stream | +| test.ps1:3:11:3:86 | Call to new | semmle.label | Call to new | +| test.ps1:3:41:3:85 | Call to frombase64string | semmle.label | Call to frombase64string | +| test.ps1:3:69:3:84 | untrustedBase64 | semmle.label | untrustedBase64 | +| test.ps1:4:31:4:37 | stream | semmle.label | stream | +subpaths +#select +| test.ps1:4:31:4:37 | stream | test.ps1:1:20:1:47 | Call to read-host | test.ps1:4:31:4:37 | stream | This unsafe deserializer deserializes on a $@. | test.ps1:1:20:1:47 | Call to read-host | read from stdin | diff --git a/powershell/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.qlref b/powershell/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.qlref new file mode 100644 index 000000000000..abfb453d723d --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.qlref @@ -0,0 +1 @@ +queries/security/cwe-502/UnsafeDeserialization.ql \ No newline at end of file diff --git a/powershell/ql/test/query-tests/security/cwe-502/test.ps1 b/powershell/ql/test/query-tests/security/cwe-502/test.ps1 new file mode 100644 index 000000000000..d35ef352cd38 --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-502/test.ps1 @@ -0,0 +1,4 @@ +$untrustedBase64 = Read-Host "Enter user input" +$formatter = New-Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter +$stream = [System.IO.MemoryStream]::new([Convert]::FromBase64String($untrustedBase64)) +$obj = $formatter.Deserialize($stream)