public static void MeasureInvokeExpression(string ScriptBlock) { Token[] token; ParseError[] error; ScriptBlockAst ScriptBlockAst = Parser.ParseInput(ScriptBlock, out token, out error); Func <Ast, bool> predicate = delegate(Ast ast) { var derpVar = new VariableExpressionAst(ast.Extent, "Derp", splatted: false); CommandAst targetAst = new CommandAst(ast.Extent, new[] { derpVar }, TokenKind.Unknown, Enumerable.Empty <RedirectionAst>()); if (targetAst != null) { if (targetAst.CommandElements[0].Extent.Text.In(false, new List <string> { "iex", "invoke-expression" })) { return(true); } } return(false); }; var foundNode = ScriptBlockAst.Find(predicate, true); if (foundNode != null) { Console.WriteLine("[+] Possible injection vulnerability found"); Console.WriteLine(String.Format(@"Possible script injection risk via the Invoke-Expression cmdlet. Untrusted input can cause arbitrary PowerShell expressions to be run. Variables may be used directly for dynamic parameter arguments, splatting can be used for dynamic parameter names, and the invocation operator can be used for dynamic command names. If content escaping is truly needed, PowerShell has several valid quote characters, so [System.Management.Automation.Language.CodeGeneration]::Escape* should be used. RuleName = InjectionRisk.InvokeExpression Severity = Warning", foundNode.Extent )); } }
public static void MeasureMethodInjection(string ScriptBlock) { Token[] token; ParseError[] error; ScriptBlockAst ScriptBlockAst = Parser.ParseInput(ScriptBlock, out token, out error); Func <Ast, bool> predicate = delegate(Ast ast) { var derpVar = new VariableExpressionAst(ast.Extent, "Derp", splatted: false); var derpVar2 = new StringConstantExpressionAst(ast.Extent, "Derpvr2", StringConstantType.BareWord); InvokeMemberExpressionAst targetAst = new InvokeMemberExpressionAst(ast.Extent, derpVar, derpVar2, Enumerable.Empty <ExpressionAst>(), @static: false); if (targetAst != null) { if (targetAst.Member is ConstantExpressionAst) { return(true); } } return(false); }; var foundNode = ScriptBlockAst.Find(predicate, true); if (foundNode != null) { Console.WriteLine("[+] Possible injection vulnerability found"); Console.WriteLine(String.Format(@"Possible property access injection via dynamic member access. Untrusted input can cause arbitrary static properties to be accessed: RuleName = InjectionRisk.MethodInjection Severity = Warning", foundNode.Extent)); } }
public static void MeasureCommandInjection(string ScriptBlock) { Token[] token; ParseError[] error; ScriptBlockAst ScriptBlockAst = Parser.ParseInput(ScriptBlock, out token, out error); Func <Ast, bool> predicate = delegate(Ast ast) { var derpVar = new VariableExpressionAst(ast.Extent, "Derp", splatted: false); CommandAst targetAst = new CommandAst(ast.Extent, new[] { derpVar }, TokenKind.Unknown, Enumerable.Empty <RedirectionAst>()); if (targetAst != null) { if (targetAst.CommandElements[0].Extent.Text.In(false, new List <string> { "cmd", "powershell" })) { var commandInvoked = targetAst.CommandElements[1]; for (int parameterPosition = 1; parameterPosition < targetAst.CommandElements.Count; parameterPosition++) { if (targetAst.CommandElements[parameterPosition].Extent.Text.In(false, new List <string> { "/c", "/k", "command", "-c", "-enc" })) { commandInvoked = targetAst.CommandElements[parameterPosition + 1]; break; } } if (commandInvoked is ExpandableStringExpressionAst) { return(true); } } } return(false); }; var foundNode = ScriptBlockAst.Find(predicate, true); if (foundNode != null) { Console.WriteLine("[+] Possible injection vulnerability found"); Console.WriteLine(String.Format(@"Possible command injection risk via calling cmd.exe or powershell.exe. Untrusted input can cause arbitrary commands to be run. Input should be provided as variable input directly (such as 'cmd /c ping $destination', rather than within an expandable string. The PowerShell.AddCommand().AddParameter() APIs should be used instead. RuleName = InjectionRisk.CommandInjection Severity = Warning", foundNode.Extent)); } }
public static void MeasureForeachObjectInjection(string ScriptBlock) { Token[] token; ParseError[] error; ScriptBlockAst ScriptBlockAst = Parser.ParseInput(ScriptBlock, out token, out error); Func <Ast, bool> predicate = delegate(Ast ast) { var derpVar = new VariableExpressionAst(ast.Extent, "Derp", splatted: false); CommandAst targetAst = new CommandAst(ast.Extent, new[] { derpVar }, TokenKind.Unknown, Enumerable.Empty <RedirectionAst>()); if (targetAst != null) { if (targetAst.CommandElements[0].Extent.Text.In(false, new List <string> { "foreach", "%" })) { var memberInvoked = targetAst.CommandElements[1]; for (int parameterPosition = 1; parameterPosition < targetAst.CommandElements.Count; parameterPosition++) { if (targetAst.CommandElements[parameterPosition].Extent.Text.In(false, new List <string> { "process", "membername" })) { memberInvoked = targetAst.CommandElements[parameterPosition + 1]; break; } } if (memberInvoked is ConstantExpressionAst && memberInvoked is ScriptBlockExpressionAst) { return(true); } } } return(false); }; var foundNode = ScriptBlockAst.Find(predicate, true); if (foundNode != null) { Console.WriteLine("[+] Possible injection vulnerability found"); Console.WriteLine(String.Format(@"Possible property access injection via Foreach-Object. Untrusted input can cause arbitrary properties /methods to be accessed: RuleName = InjectionRisk.ForeachObjectInjection Severity = Warning", foundNode.Extent)); } }
//public static void MeasureAddType(string ScriptBlock) //{ // Token[] token; // ParseError[] error; // ScriptBlockAst ScriptBlockAst = Parser.ParseInput(ScriptBlock, out token, out error); // // Func<Ast, bool> predicate = delegate (Ast ast) // { // var derpVar = new VariableExpressionAst(ast.Extent, "Derp", splatted: false); // CommandAst targetAst = new CommandAst(ast.Extent, new[] { derpVar }, TokenKind.Unknown, Enumerable.Empty<RedirectionAst>()); // if (targetAst.CommandElements[0].Extent.Text == "Add-Type") // { // var addTypeParameters = StaticParameterBinder.BindCommand(targetAst); // var typeDefinitionParameter = addTypeParameters.BoundParameters.TypeDefinition; // if (typeDefinitionParameter.ConstantValue) // { // if (addTypeParameters.BoundParameters.TypeDefinition.ValueSystem.Management.Automation.Language.VariableExpressionAst) // { // var variableName = addTypeParameters.BoundParameters.TypeDefinition.Value.VariablePath.UserPath; // var constantAssignmentForVariable = ScriptBlockAst.FindAll(tempvar => tempvar is Ast, true); // if (assignmentAst && assignmentAst.Left.VariablePath.UserPath == variableName && assignmentAst.Right.ExpressionSystem.Management.Automation.Language.ConstantExpressionAst) // { // return true; // } // if (constantAssignmentForVariable != null) // { // return false; // } // else // { // return true; // } // } // return true; // } // } // return false; // }; // // var foundNode = ScriptBlockAst.Find(predicate, true); // if (foundNode != null) // { // Console.WriteLine("[+] Possible injection vulnerability found"); // Console.WriteLine(String.Format(@"Possible code injection risk via the Add-Type cmdlet. Untrusted input can cause arbitrary Win32 code to be run.. //RuleName = InjectionRisk.AddType //Severity = Warning", foundNode.Extent // )); // } //} public static void MeasureDangerousMethod(string ScriptBlock) { Token[] token; ParseError[] error; ScriptBlockAst ScriptBlockAst = Parser.ParseInput(ScriptBlock, out token, out error); Func <Ast, bool> predicate = delegate(Ast ast) { var derpVar = new VariableExpressionAst(ast.Extent, "Derp", splatted: false); var derpVar2 = new StringConstantExpressionAst(ast.Extent, "derbvar2", StringConstantType.BareWord); InvokeMemberExpressionAst targetAst = new InvokeMemberExpressionAst(ast.Extent, derpVar, derpVar2, Enumerable.Empty <ExpressionAst>(), false); if (targetAst != null) { if (targetAst.Member.Extent.Text.In(false, new List <string> { "invokescript", "createnestedpipeline", "addscript", "newscriptblock", "expandstring" })) { return(true); } if (targetAst.Member.Extent.Text.In(false, new List <string> { "create" }) && targetAst.Expression.Extent.Text.In(false, new List <string> { "scriptblock" })) { return(true); } } return(false); }; var foundNode = ScriptBlockAst.Find(predicate, true); if (foundNode != null) { Console.WriteLine("[+] Possible injection vulnerability found"); Console.WriteLine(String.Format(@"Possible script injection risk via the a dangerous method. Untrusted input can cause arbitrary PowerShell expressions to be run. The PowerShell.AddCommand().AddParameter() APIs should be used instead. RuleName = {1} Severity = Warning", foundNode.Extent, foundNode.Extent.Text) ); } }
public static void MeasureUnsafeEscaping(string ScriptBlock) { Token[] token; ParseError[] error; ScriptBlockAst ScriptBlockAst = Parser.ParseInput(ScriptBlock, out token, out error); Func <Ast, bool> predicate = delegate(Ast ast) { var leftVariable = new VariableExpressionAst(ast.Extent, "herp", splatted: false); var rightVariable = new VariableExpressionAst(ast.Extent, "derp", splatted: false); var targetAst = new BinaryExpressionAst(ast.Extent, leftVariable, TokenKind.Ieq, rightVariable, ast.Extent); if (targetAst != null) { if (targetAst.Operator.In(false, new List <string> { "replace" }) && targetAst.Right.Extent.Text.In(false, new List <string> { "`", "'" })) { return(true); } } return(false); }; var foundNode = ScriptBlockAst.Find(predicate, true); if (foundNode != null) { Console.WriteLine("[+] Possible injection vulnerability found"); Console.WriteLine(@"Possible unsafe use of input escaping. Variables may be used directly for dynamic parameter arguments, splatting can be used for dynamic parameter names, and the invocation operator can be used for dynamic command names. If content escaping is truly needed, PowerShell has several valid quote characters, so the [System.Management.Automation.Language.CodeGeneration]::Escape* should be used instead RuleName = InjectionRisk.UnsafeEscaping Severity = Warning"); } }
/// <summary> /// Get the module name from the error extent /// /// If a parser encounters Import-DSCResource -ModuleName SomeModule /// and if SomeModule is not present in any of the PSModulePaths, the /// parser throws ModuleNotFoundDuringParse Error. We correlate the /// error message with extent to extract the module name as the error /// record doesn't provide direct access to the missing module name. /// </summary> /// <param name="error">Parse error</param> /// <param name="ast">AST of the script that contians the parse error</param> /// <param name="moduleVersion">Specifc version of the required module</param> /// <returns>The name of the module that caused the parser to throw the error. Returns null if it cannot extract the module name.</returns> public static IEnumerable <string> GetModuleNameFromErrorExtent(ParseError error, ScriptBlockAst ast, out Version moduleVersion) { moduleVersion = null; ThrowIfNull(error, "error"); ThrowIfNull(ast, "ast"); var statement = ast.Find(x => x.Extent.Equals(error.Extent), true); var dynamicKywdAst = statement as DynamicKeywordStatementAst; if (dynamicKywdAst == null) { return(null); } // check if the command name is import-dscmodule // right now we handle only the following forms // 1. Import-DSCResourceModule -ModuleName somemodule // 2. Import-DSCResourceModule -ModuleName somemodule1 -ModuleVersion major.minor.patch.build // 3. Import-DSCResourceModule -ModuleName somemodule1,somemodule2 var dscKeywordAst = dynamicKywdAst.CommandElements[0] as StringConstantExpressionAst; if (dscKeywordAst == null || !dscKeywordAst.Value.Equals("Import-DscResource", StringComparison.OrdinalIgnoreCase)) { return(null); } // find a parameter named modulename int positionOfModuleNameParamter = 0; int positionOfModuleVersionParameter = 0; for (int i = 1; i < dynamicKywdAst.CommandElements.Count; i++) { var paramAst = dynamicKywdAst.CommandElements[i] as CommandParameterAst; // TODO match the initial letters only if (paramAst != null && paramAst.ParameterName.Equals("ModuleName", StringComparison.OrdinalIgnoreCase)) { if (i == dynamicKywdAst.CommandElements.Count) { // command was Save-DscDependency ... -ModuleName -> module name missing return(null); } positionOfModuleNameParamter = i + 1; continue; } if (paramAst != null && paramAst.ParameterName.Equals("ModuleVersion", StringComparison.OrdinalIgnoreCase)) { if (i == dynamicKywdAst.CommandElements.Count) { // command was Save-DscDependency ... -ModuleVersion -> module version missing return(null); } positionOfModuleVersionParameter = i + 1; continue; } } var modules = new List <string>(); var paramValAst = dynamicKywdAst.CommandElements[positionOfModuleNameParamter]; // import-dscresource -ModuleName module1 if (paramValAst is StringConstantExpressionAst paramValStrConstExprAst) { modules.Add(paramValStrConstExprAst.Value); // import-dscresource -ModuleName module1 -ModuleVersion major.minor.patch.build var versionParameterAst = dynamicKywdAst.CommandElements[positionOfModuleVersionParameter] as StringConstantExpressionAst; if (versionParameterAst != null) { Version.TryParse(versionParameterAst.Value, out moduleVersion); // ignore return value since a module version of null means no version } return(modules); } // Import-DscResource –ModuleName @{ModuleName="module1";ModuleVersion="1.2.3.4"} //var paramValAstHashtableAst = paramValAst.Find(oneAst => oneAst is HashtableAst, true) as HashtableAst; if (paramValAst.Find(oneAst => oneAst is HashtableAst, true) is HashtableAst paramValAstHashtableAst) { var moduleNameTuple = paramValAstHashtableAst.KeyValuePairs.SingleOrDefault(x => x.Item1.Extent.Text.Equals("ModuleName")); var moduleName = moduleNameTuple.Item2.Find(astt => astt is StringConstantExpressionAst, true) as StringConstantExpressionAst; if (moduleName == null) { return(null); } modules.Add(moduleName.Value); var moduleVersionTuple = paramValAstHashtableAst.KeyValuePairs.SingleOrDefault(x => x.Item1.Extent.Text.Equals("ModuleVersion")); var moduleVersionAst = moduleVersionTuple.Item2.Find(astt => astt is StringConstantExpressionAst, true) as StringConstantExpressionAst; Version.TryParse(moduleVersionAst.Value, out moduleVersion); return(modules); } // import-dscresource -ModuleName module1,module2 if (paramValAst is ArrayLiteralAst paramValArrLtrlAst) { foreach (var elem in paramValArrLtrlAst.Elements) { var elemStrConstExprAst = elem as StringConstantExpressionAst; if (elemStrConstExprAst != null) { modules.Add(elemStrConstExprAst.Value); } } if (modules.Count == 0) { return(null); } return(modules); } return(null); }
/// <summary> /// Get the module name from the error extent /// /// If a parser encounters Import-DSCResource -ModuleName SomeModule /// and if SomeModule is not present in any of the PSModulePaths, the /// parser throws ModuleNotFoundDuringParse Error. We correlate the /// error message with extent to extract the module name as the error /// record doesn't provide direct access to the missing module name. /// </summary> /// <param name="error">Parse error</param> /// <param name="ast">AST of the script that contians the parse error</param> /// <returns>The name of the module that caused the parser to throw the error. Returns null if it cannot extract the module name.</returns> public static IEnumerable <string> GetModuleNameFromErrorExtent(ParseError error, ScriptBlockAst ast) { ThrowIfNull(error, "error"); ThrowIfNull(ast, "ast"); var statement = ast.Find(x => x.Extent.Equals(error.Extent), true); var dynamicKywdAst = statement as DynamicKeywordStatementAst; if (dynamicKywdAst == null) { return(null); } // check if the command name is import-dscmodule // right now we handle only the following forms // 1. Import-DSCResourceModule -ModuleName somemodule // 2. Import-DSCResourceModule -ModuleName somemodule1,somemodule2 if (dynamicKywdAst.CommandElements.Count < 3) { return(null); } var dscKeywordAst = dynamicKywdAst.CommandElements[0] as StringConstantExpressionAst; if (dscKeywordAst == null || !dscKeywordAst.Value.Equals("Import-DscResource", StringComparison.OrdinalIgnoreCase)) { return(null); } // find a parameter named modulename int k; for (k = 1; k < dynamicKywdAst.CommandElements.Count; k++) { var paramAst = dynamicKywdAst.CommandElements[1] as CommandParameterAst; // TODO match the initial letters only if (paramAst == null || !paramAst.ParameterName.Equals("ModuleName", StringComparison.OrdinalIgnoreCase)) { continue; } break; } if (k == dynamicKywdAst.CommandElements.Count) { // cannot find modulename return(null); } var modules = new List <string>(); // k < count - 1, because only -ModuleName throws parse error and hence not possible var paramValAst = dynamicKywdAst.CommandElements[++k]; // import-dscresource -ModuleName module1 var paramValStrConstExprAst = paramValAst as StringConstantExpressionAst; if (paramValStrConstExprAst != null) { modules.Add(paramValStrConstExprAst.Value); return(modules); } // import-dscresource -ModuleName module1,module2 var paramValArrLtrlAst = paramValAst as ArrayLiteralAst; if (paramValArrLtrlAst != null) { foreach (var elem in paramValArrLtrlAst.Elements) { var elemStrConstExprAst = elem as StringConstantExpressionAst; if (elemStrConstExprAst != null) { modules.Add(elemStrConstExprAst.Value); } } if (modules.Count == 0) { return(null); } return(modules); } return(null); }