Auto-merge PR #734 (Chack Agent)

Co-authored-by: HackTricks PEASS New-Check Agent <[email protected]>
This commit is contained in:
SirBroccoliandHackTricks PEASS New-Check Agent authored and GitHub committed 2026-09-28 12:01:41 +00:00
1 parent 6040450d94
commit 38634fc1b4
6 files changed
+583 -4

No files matched your search

@@ -0,0 +1,56 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using winPEAS.Info.ApplicationInfo;
namespace winPEAS.Tests
{
[TestClass]
public class PrivilegedWmiEventConsumerTests
{
[TestMethod]
public void SplitsQuotedAndUnquotedCommandTemplates()
{
WmiCommandParts quoted = PrivilegedWmiEventConsumers.SplitCommandLineTemplate(
"\"C:\\Program Files\\Agent\\agent.exe\" --run \"C:\\Data\\job.xml\"");
WmiCommandParts unquoted = PrivilegedWmiEventConsumers.SplitCommandLineTemplate(
"C:\\Program Files\\Agent\\agent.exe --run");
Assert.AreEqual(@"C:\Program Files\Agent\agent.exe", quoted.Executable);
Assert.AreEqual("--run \"C:\\Data\\job.xml\"", quoted.Arguments);
Assert.AreEqual(@"C:\Program Files\Agent\agent.exe", unquoted.Executable);
Assert.AreEqual("--run", unquoted.Arguments);
}
[TestMethod]
public void FindsInterpreterAndScriptTargetsWithoutReadingFiles()
{
List<string> targets = PrivilegedWmiEventConsumers.GetCommandLineTargetPaths(
@"C:\Windows\System32\cscript.exe",
"C:\\Windows\\System32\\cscript.exe //B \"C:\\ProgramData\\Vendor Agent\\event.vbs\"",
null);
CollectionAssert.Contains(targets, @"C:\Windows\System32\cscript.exe");
CollectionAssert.Contains(targets, @"C:\ProgramData\Vendor Agent\event.vbs");
}
[TestMethod]
public void RejectsDynamicWmiEventPlaceholdersAsFilesystemTargets()
{
List<string> targets = PrivilegedWmiEventConsumers.GetCommandLineTargetPaths(
null,
@"C:\ProgramData\Agent\%TargetEvent.ProcessName%.exe --run",
null);
Assert.AreEqual(0, targets.Count);
}
[TestMethod]
public void NormalizesFullWmiConsumerReferences()
{
string normalized = PrivilegedWmiEventConsumers.NormalizeConsumerReference(
"\\\\HOST\\root\\subscription:CommandLineEventConsumer.Name=\"Updater\"");
Assert.AreEqual("CommandLineEventConsumer.Name=\"Updater\"", normalized);
}
}
}
@@ -102,6 +102,7 @@
<Compile Include="ClfsAuthenticationTests.cs" />
<Compile Include="LapsPasswordExposureTests.cs" />
<Compile Include="PrivilegedScheduledTaskTests.cs" />
<Compile Include="PrivilegedWmiEventConsumerTests.cs" />
<Compile Include="RegistryHiveExposureTests.cs" />
<Compile Include="SccmNetworkAccessAccountTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@@ -8,11 +8,11 @@ namespace winPEAS.Checks
{
internal class ApplicationsInfo : ISystemCheck
{
public string[] MitreAttackIds { get; } = new[] { "T1518", "T1547.001", "T1053.005", "T1068", "T1010", "T1014" };
public string[] MitreAttackIds { get; } = new[] { "T1518", "T1547.001", "T1053.005", "T1546.003", "T1068", "T1010", "T1014" };
public void PrintInfo(bool isDebug)
{
Beaprint.GreatPrint("Applications Information", "T1518,T1547.001,T1053.005,T1068,T1010,T1014");
Beaprint.GreatPrint("Applications Information", "T1518,T1547.001,T1053.005,T1546.003,T1068,T1010,T1014");
new List<Action>
{
@@ -23,6 +23,7 @@ namespace winPEAS.Checks
PrintScheduled,
PrintRecallPolicyConfigurationExposure,
PrintControllableSystemTasks,
PrintPrivilegedWmiEventConsumers,
PrintDeviceDrivers,
}.ForEach(action => CheckRunner.Run(action, isDebug));
}
@@ -341,6 +342,65 @@ namespace winPEAS.Checks
}
}
void PrintPrivilegedWmiEventConsumers()
{
try
{
Beaprint.MainPrint("Writable payloads in privileged WMI event consumers", "T1546.003");
Beaprint.LinkPrint(
"https://learn.microsoft.com/en-us/windows/win32/wmisdk/commandlineeventconsumer",
"Microsoft warns that CommandLineEventConsumer runs as LocalSystem and that unsecured payloads can be replaced. File-backed ActiveScript consumers are checked too.");
PrivilegedWmiEventConsumerReport report = PrivilegedWmiEventConsumers.GetReport();
if (!string.IsNullOrEmpty(report.Error))
{
Beaprint.InfoPrint(" Inspection incomplete: " + report.Error);
}
if (report.Findings.Count == 0 && string.IsNullOrEmpty(report.Error))
{
Beaprint.GoodPrint($" No writable payloads found in {report.BoundConsumersInspected} bound privileged consumer(s).");
}
foreach (PrivilegedWmiEventConsumerFinding finding in report.Findings)
{
Beaprint.BadPrint($" Consumer: {finding.ConsumerName} ({finding.ConsumerType})");
Beaprint.BadPrint($" Writable target: {finding.TargetPath}");
Beaprint.BadPrint($" Access: {finding.AccessReason}");
Beaprint.PrintLineSeparator();
}
if (report.BindingLimitReached)
{
Beaprint.NoColorPrint($" Binding inspection was capped at {PrivilegedWmiEventConsumers.MaxBindings}.");
}
if (report.ConsumerLimitReached)
{
Beaprint.NoColorPrint($" Bound-consumer inspection was capped at {PrivilegedWmiEventConsumers.MaxConsumers}.");
}
if (report.TargetLimitReached)
{
Beaprint.NoColorPrint($" Filesystem probes were capped at {PrivilegedWmiEventConsumers.MaxTargets} targets.");
}
if (report.FindingLimitReached)
{
Beaprint.NoColorPrint($" Findings were capped at {PrivilegedWmiEventConsumers.MaxFindings}.");
}
if (report.TimeLimitReached)
{
Beaprint.NoColorPrint($" Inspection stopped at the safety limit of {PrivilegedWmiEventConsumers.MaxInspectionMilliseconds / 1000} seconds.");
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
}
void PrintRecallPolicyConfigurationExposure()
{
try
@@ -588,7 +588,7 @@ namespace winPEAS.Info.ApplicationInfo
return tokens;
}
private static string ResolveActionPath(string executable, string workingDirectory)
internal static string ResolveActionPath(string executable, string workingDirectory)
{
if (string.IsNullOrEmpty(executable))
{
@@ -616,7 +616,7 @@ namespace winPEAS.Info.ApplicationInfo
}
}
private static string GetWritableTargetReason(string displayPath, ISet<string> unprivilegedSids)
internal static string GetWritableTargetReason(string displayPath, ISet<string> unprivilegedSids)
{
if (!IsPathOnLocalDrive(displayPath) || unprivilegedSids == null || unprivilegedSids.Count == 0)
{
@@ -0,0 +1,461 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Management;
using System.Text.RegularExpressions;
using winPEAS.Helpers;
namespace winPEAS.Info.ApplicationInfo
{
internal sealed class PrivilegedWmiEventConsumerFinding
{
public string ConsumerType { get; set; }
public string ConsumerName { get; set; }
public string TargetPath { get; set; }
public string AccessReason { get; set; }
}
internal sealed class PrivilegedWmiEventConsumerReport
{
public List<PrivilegedWmiEventConsumerFinding> Findings { get; } = new List<PrivilegedWmiEventConsumerFinding>();
public int BindingsInspected { get; set; }
public int BoundConsumersInspected { get; set; }
public int TargetsInspected { get; set; }
public bool BindingLimitReached { get; set; }
public bool ConsumerLimitReached { get; set; }
public bool TargetLimitReached { get; set; }
public bool FindingLimitReached { get; set; }
public bool TimeLimitReached { get; set; }
public string Error { get; set; }
internal Stopwatch InspectionTimer { get; } = Stopwatch.StartNew();
}
internal sealed class WmiCommandParts
{
public string Executable { get; set; }
public string Arguments { get; set; }
}
internal static class PrivilegedWmiEventConsumers
{
internal const int MaxBindings = 512;
internal const int MaxConsumers = 256;
internal const int MaxTargets = 512;
internal const int MaxFindings = 64;
internal const int MaxInspectionMilliseconds = 8000;
private static readonly TimeSpan WmiQueryTimeout = TimeSpan.FromSeconds(2);
internal static PrivilegedWmiEventConsumerReport GetReport()
{
var report = new PrivilegedWmiEventConsumerReport();
try
{
HashSet<string> boundConsumerPaths = GetBoundConsumerPaths(report);
if (boundConsumerPaths == null || ShouldStop(report))
{
return report;
}
HashSet<string> unprivilegedSids = PermissionsHelper.GetUnprivilegedTokenSids();
if (unprivilegedSids == null || unprivilegedSids.Count == 0)
{
report.Error = "Could not resolve low-privilege security identifiers.";
return report;
}
try
{
InspectCommandLineConsumers(boundConsumerPaths, unprivilegedSids, report);
}
catch (Exception ex)
{
AppendError(report, "Could not enumerate command-line consumers: " + ex.Message);
}
if (!ShouldStop(report))
{
try
{
InspectActiveScriptConsumers(boundConsumerPaths, unprivilegedSids, report);
}
catch (Exception ex)
{
AppendError(report, "Could not enumerate script consumers: " + ex.Message);
}
}
}
catch (Exception ex)
{
report.Error = ex.Message;
}
return report;
}
private static HashSet<string> GetBoundConsumerPaths(PrivilegedWmiEventConsumerReport report)
{
var paths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
try
{
using (var searcher = CreateSearcher("SELECT Consumer FROM __FilterToConsumerBinding"))
using (ManagementObjectCollection results = searcher.Get())
{
foreach (ManagementObject binding in results)
{
using (binding)
{
if (report.BindingsInspected >= MaxBindings)
{
report.BindingLimitReached = true;
break;
}
report.BindingsInspected++;
string consumerReference = GetString(binding, "Consumer");
string normalized = NormalizeConsumerReference(consumerReference);
if (!string.IsNullOrEmpty(normalized))
{
paths.Add(normalized);
}
}
if (ShouldStop(report))
{
break;
}
}
}
}
catch (Exception ex)
{
report.Error = "Could not enumerate WMI event-consumer bindings: " + ex.Message;
return null;
}
return paths;
}
private static void InspectCommandLineConsumers(
ISet<string> boundConsumerPaths,
ISet<string> unprivilegedSids,
PrivilegedWmiEventConsumerReport report)
{
using (var searcher = CreateSearcher("SELECT * FROM CommandLineEventConsumer"))
using (ManagementObjectCollection results = searcher.Get())
{
foreach (ManagementObject consumer in results)
{
using (consumer)
{
if (ShouldStopConsumers(report))
{
break;
}
if (!IsBound(consumer, boundConsumerPaths))
{
continue;
}
report.BoundConsumersInspected++;
string name = GetString(consumer, "Name") ?? "<unnamed>";
List<string> targets = GetCommandLineTargetPaths(
GetString(consumer, "ExecutablePath"),
GetString(consumer, "CommandLineTemplate"),
GetString(consumer, "WorkingDirectory"));
InspectTargets("CommandLineEventConsumer", name, targets, unprivilegedSids, report);
}
}
}
}
private static void InspectActiveScriptConsumers(
ISet<string> boundConsumerPaths,
ISet<string> unprivilegedSids,
PrivilegedWmiEventConsumerReport report)
{
using (var searcher = CreateSearcher("SELECT * FROM ActiveScriptEventConsumer"))
using (ManagementObjectCollection results = searcher.Get())
{
foreach (ManagementObject consumer in results)
{
using (consumer)
{
if (ShouldStopConsumers(report))
{
break;
}
if (!IsBound(consumer, boundConsumerPaths))
{
continue;
}
report.BoundConsumersInspected++;
string name = GetString(consumer, "Name") ?? "<unnamed>";
string scriptPath = PrivilegedScheduledTasks.ResolveActionPath(
ExpandAndTrim(GetString(consumer, "ScriptFileName")),
null);
var targets = new List<string>();
if (IsStableTargetPath(scriptPath))
{
targets.Add(scriptPath);
}
InspectTargets("ActiveScriptEventConsumer", name, targets, unprivilegedSids, report);
}
}
}
}
private static void InspectTargets(
string consumerType,
string consumerName,
IEnumerable<string> targets,
ISet<string> unprivilegedSids,
PrivilegedWmiEventConsumerReport report)
{
foreach (string target in targets)
{
if (ShouldStop(report))
{
return;
}
if (report.TargetsInspected >= MaxTargets)
{
report.TargetLimitReached = true;
return;
}
report.TargetsInspected++;
string accessReason = PrivilegedScheduledTasks.GetWritableTargetReason(target, unprivilegedSids);
if (string.IsNullOrEmpty(accessReason))
{
continue;
}
report.Findings.Add(new PrivilegedWmiEventConsumerFinding
{
ConsumerType = consumerType,
ConsumerName = consumerName,
TargetPath = target,
AccessReason = accessReason,
});
if (report.Findings.Count >= MaxFindings)
{
report.FindingLimitReached = true;
return;
}
}
}
internal static List<string> GetCommandLineTargetPaths(
string executablePath,
string commandLineTemplate,
string workingDirectory)
{
var targets = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
string expandedExecutable = ExpandAndTrim(executablePath);
string expandedWorkingDirectory = ExpandAndTrim(workingDirectory);
WmiCommandParts command = SplitCommandLineTemplate(commandLineTemplate);
string effectiveExecutable = !string.IsNullOrEmpty(expandedExecutable)
? expandedExecutable
: command.Executable;
string resolvedExecutable = PrivilegedScheduledTasks.ResolveActionPath(
effectiveExecutable,
expandedWorkingDirectory);
if (IsStableTargetPath(resolvedExecutable))
{
targets.Add(resolvedExecutable);
}
foreach (string referencedPath in PrivilegedScheduledTasks.ExtractReferencedFilePaths(
effectiveExecutable,
command.Arguments,
expandedWorkingDirectory))
{
if (IsStableTargetPath(referencedPath))
{
targets.Add(referencedPath);
}
}
return new List<string>(targets);
}
internal static WmiCommandParts SplitCommandLineTemplate(string commandLineTemplate)
{
string commandLine = ExpandAndTrim(commandLineTemplate);
var result = new WmiCommandParts
{
Executable = string.Empty,
Arguments = string.Empty,
};
if (string.IsNullOrEmpty(commandLine))
{
return result;
}
if (commandLine[0] == '"')
{
int closingQuote = commandLine.IndexOf('"', 1);
if (closingQuote > 1)
{
result.Executable = commandLine.Substring(1, closingQuote - 1);
result.Arguments = commandLine.Substring(closingQuote + 1).TrimStart();
return result;
}
}
Match executableMatch = Regex.Match(
commandLine,
@"^.+?\.(?:exe|com|bat|cmd)(?=\s|$)",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (executableMatch.Success)
{
result.Executable = executableMatch.Value.Trim();
result.Arguments = commandLine.Substring(executableMatch.Length).TrimStart();
return result;
}
int firstSpace = commandLine.IndexOfAny(new[] { ' ', '\t', '\r', '\n' });
if (firstSpace < 0)
{
result.Executable = commandLine;
}
else
{
result.Executable = commandLine.Substring(0, firstSpace);
result.Arguments = commandLine.Substring(firstSpace + 1).TrimStart();
}
return result;
}
internal static string NormalizeConsumerReference(string consumerReference)
{
if (string.IsNullOrWhiteSpace(consumerReference))
{
return null;
}
try
{
return new ManagementPath(consumerReference).RelativePath;
}
catch
{
return consumerReference.Trim();
}
}
private static bool IsBound(ManagementObject consumer, ISet<string> boundConsumerPaths)
{
try
{
if (consumer == null || consumer.Path == null || boundConsumerPaths == null)
{
return false;
}
string relativePath = NormalizeConsumerReference(consumer.Path.Path);
return !string.IsNullOrEmpty(relativePath) && boundConsumerPaths.Contains(relativePath);
}
catch
{
return false;
}
}
private static ManagementObjectSearcher CreateSearcher(string query)
{
var scope = new ManagementScope(@"\\.\root\subscription");
var options = new EnumerationOptions
{
ReturnImmediately = true,
Rewindable = false,
Timeout = WmiQueryTimeout,
};
return new ManagementObjectSearcher(scope, new ObjectQuery(query), options);
}
private static string GetString(ManagementBaseObject instance, string propertyName)
{
try
{
object value = instance[propertyName];
return value == null ? null : value.ToString();
}
catch
{
return null;
}
}
private static string ExpandAndTrim(string value)
{
return string.IsNullOrWhiteSpace(value)
? string.Empty
: Environment.ExpandEnvironmentVariables(value).Trim();
}
private static bool IsStableTargetPath(string path)
{
return !string.IsNullOrWhiteSpace(path) && path.IndexOf('%') < 0;
}
private static void AppendError(PrivilegedWmiEventConsumerReport report, string error)
{
report.Error = string.IsNullOrEmpty(report.Error)
? error
: report.Error + " " + error;
}
private static bool ShouldStopConsumers(PrivilegedWmiEventConsumerReport report)
{
if (report.BoundConsumersInspected >= MaxConsumers)
{
report.ConsumerLimitReached = true;
return true;
}
return ShouldStop(report);
}
private static bool ShouldStop(PrivilegedWmiEventConsumerReport report)
{
if (report == null)
{
return true;
}
if (report.InspectionTimer.ElapsedMilliseconds >= MaxInspectionMilliseconds)
{
report.TimeLimitReached = true;
}
if (report.TargetsInspected >= MaxTargets)
{
report.TargetLimitReached = true;
}
if (report.Findings.Count >= MaxFindings)
{
report.FindingLimitReached = true;
}
return report.TimeLimitReached ||
report.TargetLimitReached ||
report.FindingLimitReached;
}
}
}
@@ -1229,6 +1229,7 @@
<Compile Include="Info\ApplicationInfo\DeviceDrivers.cs" />
<Compile Include="Info\ApplicationInfo\InstalledPackageInventory.cs" />
<Compile Include="Info\ApplicationInfo\PrivilegedScheduledTasks.cs" />
<Compile Include="Info\ApplicationInfo\PrivilegedWmiEventConsumers.cs" />
<Compile Include="Info\ApplicationInfo\RecallPolicyConfiguration.cs" />
<Compile Include="Info\ApplicationInfo\SoapClientProxyAnalyzer.cs" />
<Compile Include="Info\ApplicationInfo\InstalledApps.cs" />