Merge pull request #815 from peass-ng/fix/writable-executable-audit

Audit writable executable privilege-escalation leads
This commit is contained in:
SirBroccoli authored and GitHub committed 2026-11-06 13:04:35 +01:00
commit d7805b2e9b
4 files changed
+74 -10

No files matched your search

@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using winPEAS.Helpers;
using winPEAS.Info.ApplicationInfo;
@@ -37,12 +40,13 @@ namespace winPEAS.Tests
[TestMethod]
public void WritableInstalledExecutableIsReportedByFilePathOnce()
{
string executable = @"C:\Program Files\Vendor\bin\sample.exe";
string executable = @"C:\Program Files\Vendor.Agent\bin\sample.exe";
var files = new[]
{
new CustomFileInfo("sample.exe", ".exe", executable, 1, false),
new CustomFileInfo("SAMPLE.EXE", ".EXE", executable.ToUpperInvariant(), 1, false),
new CustomFileInfo("read-only.exe", ".exe", @"C:\Program Files\Vendor\read-only.exe", 1, false),
new CustomFileInfo("deny-only.exe", ".exe", @"C:\Program Files\Vendor\deny-only.exe", 1, false),
new CustomFileInfo("notes.txt", ".txt", @"C:\Program Files\Vendor\notes.txt", 1, false),
new CustomFileInfo("directory.exe", ".exe", @"C:\Program Files\Vendor\directory.exe", 0, true)
};
@@ -50,6 +54,8 @@ namespace winPEAS.Tests
var findings = winPEAS.Checks.FilesInfo.FindWritableExecutables(files, path =>
{
checkedPaths.Add(path);
if (path.EndsWith("deny-only.exe"))
return new List<string> { "Users [Deny: AllAccess]" };
return path.Equals(executable, StringComparison.OrdinalIgnoreCase)
? new List<string> { "Users [Allow: AllAccess]" }
: new List<string>();
@@ -58,7 +64,36 @@ namespace winPEAS.Tests
Assert.AreEqual(1, findings.Count);
Assert.AreEqual(executable, findings[0].Key);
Assert.AreEqual("Users [Allow: AllAccess]", findings[0].Value[0]);
Assert.AreEqual(2, checkedPaths.Count);
Assert.AreEqual(3, checkedPaths.Count);
}
[TestMethod]
public void ExactFilePermissionLookupHandlesDottedParentDirectory()
{
string parent = Path.Combine(Path.GetTempPath(), "winpeas-acl." + Guid.NewGuid().ToString("N"));
string executable = Path.Combine(parent, "agent.exe");
try
{
Directory.CreateDirectory(parent);
File.WriteAllText(executable, "fixture");
SecurityIdentifier sid = WindowsIdentity.GetCurrent().User;
FileSecurity security = File.GetAccessControl(executable);
security.AddAccessRule(new FileSystemAccessRule(sid, FileSystemRights.WriteData,
AccessControlType.Allow));
File.SetAccessControl(executable, security);
var sids = new Dictionary<string, string> { { sid.Value, "fixture-user" } };
var permissions = PermissionsHelper.GetPermissionsFile(executable, sids,
PermissionType.WRITEABLE_OR_EQUIVALENT);
Assert.IsTrue(permissions.Any(right => right.Contains("[Allow:")));
var quotedCommandPermissions = PermissionsHelper.GetPermissionsFile(
"\"" + executable + "\" --run", sids, PermissionType.WRITEABLE_OR_EQUIVALENT);
Assert.IsTrue(quotedCommandPermissions.Any(right => right.Contains("[Allow:")));
}
finally
{
if (Directory.Exists(parent)) Directory.Delete(parent, true);
}
}
[TestMethod]
@@ -109,6 +109,7 @@ namespace winPEAS.Checks
{
Beaprint.MainPrint("Installed Applications --Via Program Files/Uninstall registry--", "T1518");
Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#applications", "Check if you can modify installed software");
Beaprint.GrayPrint(" Folder ACLs are review leads. Run filesinfo for executable-level write-ACL candidates; privilege escalation requires privileged execution and effective write access.");
PrintDockerDesktopVersionRisk();
PrintCheckmkAgentVersionRisk();
PrintVeeamBackupVersionRisk();
@@ -116,11 +116,11 @@ namespace winPEAS.Checks
};
public string[] MitreAttackIds { get; } = new[] { "T1083", "T1552.001", "T1552.002", "T1552.004", "T1552.006", "T1003.002", "T1003.004", "T1564.001", "T1574.001", "T1059.004", "T1114.001", "T1218", "T1649" };
public string[] MitreAttackIds { get; } = new[] { "T1083", "T1552.001", "T1552.002", "T1552.004", "T1552.006", "T1003.002", "T1003.004", "T1564.001", "T1574", "T1059.004", "T1114.001", "T1218", "T1649" };
public void PrintInfo(bool isDebug)
{
Beaprint.GreatPrint("Interesting files and registry", "T1083,T1552.001,T1552.002,T1552.004,T1552.006,T1003.002,T1003.004,T1564.001,T1574.001,T1059.004,T1114.001,T1218,T1649");
Beaprint.GreatPrint("Interesting files and registry", "T1083,T1552.001,T1552.002,T1552.004,T1552.006,T1003.002,T1003.004,T1564.001,T1574,T1059.004,T1114.001,T1218,T1649");
new List<Action>
{
@@ -1005,7 +1005,7 @@ namespace winPEAS.Checks
private void PrintExecutablesWithWritePermissions()
{
Beaprint.MainPrint("Searching executable files with write (equivalent) permissions (can be slow)", "T1574.001");
Beaprint.MainPrint("Executable file write-ACL candidates (can be slow)", "T1574");
var systemDrive = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\";
@@ -1038,9 +1038,12 @@ namespace winPEAS.Checks
}
}
foreach (var finding in FindWritableExecutables(files,
var findings = FindWritableExecutables(files,
path => PermissionsHelper.GetPermissionsFile(path, Checks.CurrentUserSiDs,
PermissionType.WRITEABLE_OR_EQUIVALENT)))
PermissionType.WRITEABLE_OR_EQUIVALENT));
if (findings.Count > 0)
Beaprint.GrayPrint(" Confirm effective write access and that a more privileged service, task, installer, or user executes the file before treating it as privilege escalation.");
foreach (var finding in findings)
{
string log = $" File Permissions \"{finding.Key}\": " + string.Join(",", finding.Value);
if (IsPathWithin(finding.Key, currentUserDir))
@@ -1070,7 +1073,7 @@ namespace winPEAS.Checks
try
{
var rights = getPermissions(file.FullPath);
if (rights != null && rights.Count > 0)
if (rights != null && rights.Any(right => right != null && right.Contains("[Allow:")))
findings.Add(new KeyValuePair<string, List<string>>(file.FullPath, rights));
}
catch (Exception) { /* Inaccessible files should not stop the scan. */ }
@@ -92,9 +92,34 @@ namespace winPEAS.Helpers
*/
List<string> results = new List<string>();
path = path.Trim();
if (path == null || path == "")
if (string.IsNullOrWhiteSpace(path))
return results;
path = path.Trim();
// Most callers already have an exact path. Parsing it as a command line
// can truncate paths with dotted parent directories (Vendor.App\bin.exe).
try
{
return GetMyPermissionsF(File.GetAccessControl(path.Trim('"')), SIDs, permissionType);
}
catch
{
// Fall back to extracting a path from command lines and quoted values.
}
if (path.StartsWith("\"", StringComparison.Ordinal))
{
int closingQuote = path.IndexOf('"', 1);
if (closingQuote > 1)
{
try
{
return GetMyPermissionsF(File.GetAccessControl(path.Substring(1, closingQuote - 1)),
SIDs, permissionType);
}
catch { /* Keep the legacy command-line fallback below. */ }
}
}
Match reg_path = Regex.Match(path.ToString(), @"\W*([a-z]:\\[^.]+(\.[a-zA-Z0-9_-]+)?)\W*", RegexOptions.IgnoreCase);
string binaryPath = reg_path.Groups[1].ToString();