Compare commits

..

9 Commits

Author SHA1 Message Date
SirBroccoli
94e84dec91 Merge pull request #521 from peass-ng/update_PEASS-winpeas-HackTheBox_Mirage__Chaining_NFS_Leak_20251122_183905
[WINPEAS] Add privilege escalation check: HackTheBox Mirage Chaining NFS Leaks, Dy...
2025-12-07 13:23:17 +01:00
SirBroccoli
ac80ce3a9a Merge pull request #520 from peass-ng/update_PEASS-linpeas-SupaPwn__Hacking_Our_Way_into_Lovabl_20251119_184112
[LINPEAS] Add privilege escalation check: SupaPwn Hacking Our Way into Lovable’s O...
2025-12-07 13:22:12 +01:00
SirBroccoli
313fe6bef5 Update README.md 2025-12-07 13:21:52 +01:00
HackTricks News Bot
11c0d14561 Add winpeas privilege escalation checks from: HackTheBox Mirage: Chaining NFS Leaks, Dynamic DNS Abuse, NATS Credential Theft, 2025-11-22 18:54:22 +00:00
HackTricks News Bot
49db1df468 Add linpeas privilege escalation checks from: SupaPwn: Hacking Our Way into Lovable’s Office and Helping Secure Supabase 2025-11-19 18:59:41 +00:00
SirBroccoli
80318c5005 Merge pull request #514 from moscowchill/bat-pr
Fix ANSI escape codes displaying as literal text in winPEAS.bat
2025-11-15 15:45:38 +01:00
SirBroccoli
7af6c33d39 Merge pull request #513 from sttlr/patch-1
Fix: LinPEASS doesn't run via metasploit module
2025-11-15 15:44:50 +01:00
moscow chill
336c53a163 Fix ANSI escape codes displaying as literal text in winPEAS.bat
The script was setting E=0x1B[ as a literal string instead of the actual
ESC character (ASCII 27), causing color codes to display as text like
"0x1B[33m[+]0x1B[97m" instead of rendering as colors.

Changed the SetOnce subroutine to properly capture the ESC character using
the 'prompt $E' technique before building the ANSI escape sequence prefix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 20:16:34 +01:00
Max K.
6877f39193 Fix: LinPEASS doesn't run via metasploit module
If you set "WINPEASS" to "false" - it's a string, and therefore "true". So it would run WinPEASS anyway.

The fix converts value of the variable to string before comparing it.
2025-10-28 13:19:03 +02:00
7 changed files with 411 additions and 327 deletions

View File

@@ -0,0 +1,72 @@
# Title: Software Information - PostgreSQL Event Triggers
# ID: SI_Postgresql_Event_Triggers
# Author: HT Bot
# Last Update: 19-11-2025
# Description: Detect unsafe PostgreSQL event triggers and postgres_fdw custom scripts that grant temporary SUPERUSER
# License: GNU GPL
# Version: 1.0
# Functions Used: echo_not_found, print_2title, print_info
# Global Variables: $DEBUG, $E, $SED_GREEN, $SED_RED, $SED_YELLOW, $TIMEOUT
# Initial Functions:
# Generated Global Variables: $psql_bin, $psql_evt_output, $psql_evt_status, $psql_evt_err_line, $postgres_fdw_dirs, $postgres_fdw_hits, $old_ifs, $evtname, $enabled, $owner, $owner_is_super, $func, $func_owner, $func_owner_is_super, $IFS
# Fat linpeas: 0
# Small linpeas: 1
if [ "$DEBUG" ] || { [ "$TIMEOUT" ] && [ "$(command -v psql 2>/dev/null || echo -n '')" ]; }; then
print_2title "PostgreSQL event trigger ownership & postgres_fdw hooks"
print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#postgresql-event-triggers"
psql_bin="$(command -v psql 2>/dev/null || echo -n '')"
if [ "$TIMEOUT" ] && [ "$psql_bin" ]; then
psql_evt_output="$($TIMEOUT 5 "$psql_bin" -w -X -q -A -t -d postgres -c "WITH evt AS ( SELECT e.evtname, e.evtenabled, pg_get_userbyid(e.evtowner) AS trig_owner, tr.rolsuper AS trig_owner_super, n.nspname || '.' || p.proname AS function_name, pg_get_userbyid(p.proowner) AS func_owner, fr.rolsuper AS func_owner_super FROM pg_event_trigger e JOIN pg_proc p ON e.evtfoid = p.oid JOIN pg_namespace n ON p.pronamespace = n.oid LEFT JOIN pg_roles tr ON tr.oid = e.evtowner LEFT JOIN pg_roles fr ON fr.oid = p.proowner ) SELECT evtname || '|' || evtenabled || '|' || COALESCE(trig_owner,'?') || '|' || COALESCE(CASE WHEN trig_owner_super THEN 'yes' ELSE 'no' END,'unknown') || '|' || function_name || '|' || COALESCE(func_owner,'?') || '|' || COALESCE(CASE WHEN func_owner_super THEN 'yes' ELSE 'no' END,'unknown') FROM evt WHERE COALESCE(trig_owner_super,false) = false OR COALESCE(func_owner_super,false) = false;" 2>&1)"
psql_evt_status=$?
if [ $psql_evt_status -eq 0 ]; then
if [ "$psql_evt_output" ]; then
echo "Non-superuser-owned event triggers were found (trigger|enabled?|owner|owner_is_super|function|function_owner|fn_owner_is_super):" | sed -${E} "s,.*,${SED_RED},"
printf "%s\n" "$psql_evt_output" | while IFS='|' read evtname enabled owner owner_is_super func func_owner func_owner_is_super; do
case "$enabled" in
O) enabled="enabled" ;;
D) enabled="disabled" ;;
*) enabled="status_$enabled" ;;
esac
echo " - $evtname ($enabled) uses $func owned by $func_owner (superuser:$func_owner_is_super); trigger owner: $owner (superuser:$owner_is_super)" | sed -${E} "s,superuser:no,${SED_RED},g"
done
else
echo "No event triggers owned by non-superusers were returned." | sed -${E} "s,.*,${SED_GREEN},"
fi
else
psql_evt_err_line=$(printf '%s\n' "$psql_evt_output" | head -n1)
echo "Could not query pg_event_trigger (psql exit $psql_evt_status): $psql_evt_err_line" | sed -${E} "s,.*,${SED_YELLOW},"
fi
else
if ! [ "$TIMEOUT" ]; then
echo_not_found "timeout"
fi
if ! [ "$psql_bin" ]; then
echo_not_found "psql"
fi
fi
postgres_fdw_dirs="/etc/postgresql /var/lib/postgresql /var/lib/postgres /usr/lib/postgresql /usr/local/lib/postgresql /opt/supabase /opt/postgres /srv/postgres"
postgres_fdw_hits=""
for d in $postgres_fdw_dirs; do
if [ -d "$d" ]; then
old_ifs="$IFS"
IFS="\n"
for f in $(find "$d" -maxdepth 5 -type f \( -name '*postgres_fdw*.sql' -o -name '*postgres_fdw*.psql' -o -name 'after-create.sql' \) 2>/dev/null); do
if [ -f "$f" ] && grep -qiE "alter[[:space:]]+role[[:space:]]+postgres[[:space:]]+superuser" "$f" 2>/dev/null; then
postgres_fdw_hits="$postgres_fdw_hits\n$f"
fi
done
IFS="$old_ifs"
fi
done
if [ "$postgres_fdw_hits" ]; then
echo "Detected postgres_fdw custom scripts granting postgres SUPERUSER (check for SupaPwn-style window):" | sed -${E} "s,.*,${SED_RED},"
printf "%s\n" "$postgres_fdw_hits" | sed "s,^, - ,"
fi
fi
echo ""

View File

@@ -270,7 +270,7 @@ class MetasploitModule < Msf::Post
if datastore['CUSTOM_URL'] != ""
url_peass = datastore['CUSTOM_URL']
else
url_peass = datastore['WINPEASS'] ? "https://github.com/peass-ng/PEASS-ng/releases/latest/download/winPEASany_ofs.exe" : "https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh"
url_peass = datastore['WINPEASS'].to_s.strip.downcase == 'true' ? "https://github.com/peass-ng/PEASS-ng/releases/latest/download/winPEASany_ofs.exe" : "https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh"
end
# If URL is set, check if it is a valid URL or local file
if url_peass.include?("http://") || url_peass.include?("https://")

View File

@@ -707,7 +707,8 @@ EXIT /B
:SetOnce
REM :: ANSI escape character is set once below - for ColorLine Subroutine
SET "E=0x1B["
for /F %%a in ('echo prompt $E ^| cmd') do set "ESC=%%a"
SET "E=%ESC%["
SET "PercentageTrack=0"
EXIT /B

View File

@@ -279,7 +279,3 @@ If you find any issue, please report it using **[github issues](https://github.c
## Advisory
All the scripts/binaries of the PEAS Suite should be used for authorized penetration testing and/or educational purposes only. Any misuse of this software will not be the responsibility of the author or of any other collaborator. Use it at your own networks and/or with the network owner's permission.
- AD ACL opportunities for bloodyAD:
- Password reset rights over privileged users and computers (Reset Password extended right or equivalent GenericAll/WriteDacl/WriteOwner/property writes).
- Shadow credentials possible where msDS-KeyCredentialLink is writable on user/computer objects.
- AD-integrated DNS zones writable by the current principal (zone ACLs) and DnsAdmins membership.

View File

@@ -19,8 +19,7 @@ namespace winPEAS.Checks
new List<Action>
{
PrintGmsaReadableByCurrentPrincipal,
PrintAdcsMisconfigurations,
PrintAdAclPrivescCandidates
PrintAdcsMisconfigurations
}.ForEach(action => CheckRunner.Run(action, isDebug));
}
@@ -65,32 +64,6 @@ namespace winPEAS.Checks
? r.Properties[name][0]?.ToString()
: null;
}
private static Guid? GetAttributeSchemaGuid(string ldapDisplayName)
{
try
{
var schemaNC = GetRootDseProp("schemaNamingContext");
if (string.IsNullOrEmpty(schemaNC)) return null;
using (var baseDe = new DirectoryEntry("LDAP://" + schemaNC))
using (var ds = new DirectorySearcher(baseDe))
{
ds.PageSize = 50;
ds.Filter = "(&(objectClass=attributeSchema)(lDAPDisplayName=" + ldapDisplayName + "))";
ds.PropertiesToLoad.Add("schemaIDGUID");
var res = ds.FindOne();
if (res == null) return null;
var guidBytes = res.Properties["schemaIDGUID"]?.Count > 0 ? res.Properties["schemaIDGUID"][0] as byte[] : null;
if (guidBytes == null || guidBytes.Length != 16) return null;
return new Guid(guidBytes);
}
}
catch
{
return null;
}
}
// Detect gMSA objects where the current principal (or one of its groups) can retrieve the managed password
private void PrintGmsaReadableByCurrentPrincipal()
@@ -368,299 +341,6 @@ namespace winPEAS.Checks
{
Beaprint.PrintException(ex.Message);
}
// Enumerate quick, high-signal AD ACL opportunities that tools like bloodyAD can abuse
// - Password reset rights over privileged users (Reset Password ER or equivalent)
// - Shadow credentials (msDS-KeyCredentialLink writable) on users/computers
// - AD-Integrated DNS zones where we can write/create records; DnsAdmins membership
private void PrintAdAclPrivescCandidates()
{
try
{
Beaprint.MainPrint("AD ACL-based escalation opportunities (bloodyAD)");
Beaprint.LinkPrint(
"https://github.com/CravateRouge/bloodyAD",
"Detect objects where you could reset passwords, write shadow credentials, or modify ADintegrated DNS");
if (!Checks.IsPartOfDomain)
{
Beaprint.GrayPrint(" [-] Host is not domain-joined. Skipping.");
return;
}
var defaultNC = GetRootDseProp("defaultNamingContext");
var rootDomainNC = GetRootDseProp("rootDomainNamingContext") ?? defaultNC;
if (string.IsNullOrEmpty(defaultNC))
{
Beaprint.GrayPrint(" [-] Could not resolve defaultNamingContext.");
return;
}
var currentSidSet = GetCurrentSidSet();
// Resolve attribute/extended-right GUIDs we care about
var resetPwdGuid = new Guid("00299570-246D-11D0-A768-00AA006E0529");
var unicodePwdGuid = GetAttributeSchemaGuid("unicodePwd");
var userPasswordGuid = GetAttributeSchemaGuid("userPassword");
var kclGuid = GetAttributeSchemaGuid("msDS-KeyCredentialLink");
// Build a small set of high-value targets
var targets = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // dn -> name
// 1) Members of Domain Admins
try
{
using (var baseDe = new DirectoryEntry("LDAP://" + defaultNC))
using (var ds = new DirectorySearcher(baseDe))
{
ds.Filter = "(&(objectClass=group)(sAMAccountName=Domain Admins))";
ds.PropertiesToLoad.Add("distinguishedName");
ds.PropertiesToLoad.Add("member");
var g = ds.FindOne();
if (g != null)
{
var members = g.Properties["member"];
if (members != null)
{
foreach (var m in members)
{
var dn = m.ToString();
try
{
using (var de = new DirectoryEntry("LDAP://" + dn))
{
var name = de.Properties["sAMAccountName"]?.Value as string ?? dn;
if (!targets.ContainsKey(dn)) targets.Add(dn, name);
}
}
catch { }
}
}
}
}
}
catch { }
// 2) AdminSDHolder-protected users (adminCount=1)
try
{
using (var baseDe = new DirectoryEntry("LDAP://" + defaultNC))
using (var ds = new DirectorySearcher(baseDe))
{
ds.PageSize = 300;
ds.Filter = "(&(objectClass=user)(objectCategory=person)(adminCount=1))";
ds.PropertiesToLoad.Add("distinguishedName");
ds.PropertiesToLoad.Add("sAMAccountName");
foreach (SearchResult r in ds.FindAll())
{
var dn = GetProp(r, "distinguishedName");
var name = GetProp(r, "sAMAccountName") ?? dn;
if (!string.IsNullOrEmpty(dn) && !targets.ContainsKey(dn)) targets.Add(dn, name);
}
}
}
catch { }
// 3) Domain Controllers (computer objects)
try
{
using (var baseDe = new DirectoryEntry("LDAP://" + defaultNC))
using (var ds = new DirectorySearcher(baseDe))
{
ds.PageSize = 100;
// UAC bit 8192 = SERVER_TRUST_ACCOUNT
ds.Filter = "(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))";
ds.PropertiesToLoad.Add("distinguishedName");
ds.PropertiesToLoad.Add("sAMAccountName");
foreach (SearchResult r in ds.FindAll())
{
var dn = GetProp(r, "distinguishedName");
var name = GetProp(r, "sAMAccountName") ?? dn;
if (!string.IsNullOrEmpty(dn) && !targets.ContainsKey(dn)) targets.Add(dn, name);
}
}
}
catch { }
int pwdResetHits = 0, shadowHits = 0;
var maxToShow = 10;
foreach (var kv in targets)
{
DirectoryEntry de = null;
try
{
de = new DirectoryEntry("LDAP://" + kv.Key);
de.Options.SecurityMasks = SecurityMasks.Dacl;
de.RefreshCache(new[] { "ntSecurityDescriptor" });
}
catch
{
de?.Dispose();
continue;
}
try
{
var sd = de.ObjectSecurity;
var rules = sd.GetAccessRules(true, true, typeof(SecurityIdentifier));
bool canPwdReset = false;
bool canShadow = false;
var hitRightsPwd = new HashSet<string>();
var hitRightsKcl = new HashSet<string>();
foreach (ActiveDirectoryAccessRule rule in rules)
{
if (rule.AccessControlType != AccessControlType.Allow) continue;
var sid = (rule.IdentityReference as SecurityIdentifier)?.Value;
if (string.IsNullOrEmpty(sid) || !currentSidSet.Contains(sid)) continue;
var rights = rule.ActiveDirectoryRights;
// Password reset via ER or powerful rights
if (rights.HasFlag(ActiveDirectoryRights.GenericAll))
{
canPwdReset = true; hitRightsPwd.Add("GenericAll");
}
if (rights.HasFlag(ActiveDirectoryRights.WriteOwner)) { canPwdReset = true; hitRightsPwd.Add("WriteOwner"); }
if (rights.HasFlag(ActiveDirectoryRights.WriteDacl)) { canPwdReset = true; hitRightsPwd.Add("WriteDacl"); }
if (rights.HasFlag(ActiveDirectoryRights.ExtendedRight) && rule.ObjectType == resetPwdGuid)
{ canPwdReset = true; hitRightsPwd.Add("ResetPassword"); }
if (rights.HasFlag(ActiveDirectoryRights.WriteProperty))
{
if (unicodePwdGuid.HasValue && rule.ObjectType == unicodePwdGuid.Value) { canPwdReset = true; hitRightsPwd.Add("Write unicodePwd"); }
if (userPasswordGuid.HasValue && rule.ObjectType == userPasswordGuid.Value) { canPwdReset = true; hitRightsPwd.Add("Write userPassword"); }
}
// Shadow credentials (msDS-KeyCredentialLink)
if (rights.HasFlag(ActiveDirectoryRights.GenericAll))
{
canShadow = true; hitRightsKcl.Add("GenericAll");
}
if (rights.HasFlag(ActiveDirectoryRights.WriteProperty) && kclGuid.HasValue && rule.ObjectType == kclGuid.Value)
{
canShadow = true; hitRightsKcl.Add("Write msDS-KeyCredentialLink");
}
}
if (canPwdReset)
{
pwdResetHits++;
if (pwdResetHits <= maxToShow)
Beaprint.BadPrint($" [PasswordReset] {kv.Value} -> {kv.Key} ({string.Join(", ", hitRightsPwd)})");
}
if (canShadow)
{
shadowHits++;
if (shadowHits <= maxToShow)
Beaprint.BadPrint($" [ShadowCreds] {kv.Value} -> {kv.Key} ({string.Join(", ", hitRightsKcl)})");
}
}
catch { }
finally { de?.Dispose(); }
}
if (pwdResetHits == 0) Beaprint.GoodPrint(" No obvious password reset rights over high-value objects found.");
else Beaprint.BadPrint($" => {pwdResetHits} potential password reset target(s) over high-value objects.");
if (shadowHits == 0) Beaprint.GoodPrint(" No writable msDS-KeyCredentialLink found on high-value objects.");
else Beaprint.BadPrint($" => {shadowHits} potential shadow credentials target(s) over high-value objects.");
// DNS: membership + zone ACLs
try
{
using (var baseDe = new DirectoryEntry("LDAP://" + defaultNC))
using (var ds = new DirectorySearcher(baseDe))
{
ds.Filter = "(&(objectClass=group)(sAMAccountName=DnsAdmins))";
ds.PropertiesToLoad.Add("objectSid");
var res = ds.FindOne();
if (res != null && res.Properties["objectSid"].Count > 0)
{
var sid = new SecurityIdentifier((byte[])res.Properties["objectSid"][0], 0).Value;
if (currentSidSet.Contains(sid))
Beaprint.BadPrint(" [DNS] Current principal is a member of DnsAdmins.");
}
}
}
catch { }
int dnsAclHits = 0;
foreach (var partition in new[] { $"DC=DomainDnsZones,{defaultNC}", $"DC=ForestDnsZones,{rootDomainNC}" })
{
try
{
using (var msDns = new DirectoryEntry("LDAP://CN=MicrosoftDNS," + partition))
using (var ds = new DirectorySearcher(msDns))
{
ds.PageSize = 200;
ds.Filter = "(objectClass=dnsZone)";
ds.PropertiesToLoad.Add("distinguishedName");
ds.PropertiesToLoad.Add("name");
foreach (SearchResult r in ds.FindAll())
{
DirectoryEntry zone = null;
try
{
zone = r.GetDirectoryEntry();
zone.Options.SecurityMasks = SecurityMasks.Dacl;
zone.RefreshCache(new[] { "ntSecurityDescriptor" });
}
catch
{
zone?.Dispose();
continue;
}
try
{
bool canWriteZone = false;
var sd = zone.ObjectSecurity;
var rules = sd.GetAccessRules(true, true, typeof(SecurityIdentifier));
foreach (ActiveDirectoryAccessRule rule in rules)
{
if (rule.AccessControlType != AccessControlType.Allow) continue;
var sid = (rule.IdentityReference as SecurityIdentifier)?.Value;
if (string.IsNullOrEmpty(sid) || !currentSidSet.Contains(sid)) continue;
var rights = rule.ActiveDirectoryRights;
if (rights.HasFlag(ActiveDirectoryRights.GenericAll) ||
rights.HasFlag(ActiveDirectoryRights.WriteProperty) ||
rights.HasFlag(ActiveDirectoryRights.CreateChild) ||
rights.HasFlag(ActiveDirectoryRights.DeleteChild))
{
canWriteZone = true;
break;
}
}
if (canWriteZone)
{
dnsAclHits++;
var zname = GetProp(r, "name") ?? GetProp(r, "distinguishedName") ?? "<zone>";
if (dnsAclHits <= maxToShow)
Beaprint.BadPrint($" [DNS] Writable ADintegrated zone: {zname}");
}
}
catch { }
finally { zone?.Dispose(); }
}
}
}
catch { }
}
if (dnsAclHits == 0) Beaprint.GoodPrint(" No writable ADintegrated DNS zones detected for current principal.");
else Beaprint.BadPrint($" => {dnsAclHits} ADintegrated DNS zone(s) appear writable.");
}
catch (Exception ex)
{
Beaprint.GrayPrint(" [!] Error during AD ACL checks: " + ex.Message);
}
}
}
}
}

View File

@@ -19,6 +19,14 @@ Download the **[latest releas from here](https://github.com/peass-ng/PEASS-ng/re
powershell "IEX(New-Object Net.WebClient).downloadString('https://raw.githubusercontent.com/peass-ng/PEASS-ng/master/winPEAS/winPEASps1/winPEAS.ps1')"
```
## Recent Updates
- Added Active Directory awareness checks to highlight Kerberos-only environments (NTLM restrictions) and time skew issues before attempting ticket-based attacks.
- winPEAS.ps1 now reviews AD-integrated DNS ACLs to flag zones where low-privileged users can register/modify records (dynamic DNS hijack risk).
- Enumerates high-value SPN accounts and weak gMSA password readers so you can immediately target Kerberoastable admins or abused service accounts.
- Surfaces Schannel certificate mapping settings to warn about ESC10-style certificate abuse opportunities when UPN mapping is enabled.
## Advisory
All the scripts/binaries of the PEAS Suite should be used for authorized penetration testing and/or educational purposes only. Any misuse of this software will not be the responsibility of the author or of any other collaborator. Use it at your own networks and/or with the network owner's permission.

View File

@@ -148,6 +148,244 @@ function Get-ClipBoardText {
}
}
function Get-DomainContext {
try {
return [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()
}
catch {
return $null
}
}
function Convert-SidToName {
param(
$SidInput
)
if ($null -eq $SidInput) { return $null }
try {
if ($SidInput -is [System.Security.Principal.SecurityIdentifier]) {
$sidObject = $SidInput
}
else {
$sidObject = New-Object System.Security.Principal.SecurityIdentifier($SidInput)
}
return $sidObject.Translate([System.Security.Principal.NTAccount]).Value
}
catch {
try { return $sidObject.Value }
catch { return [string]$SidInput }
}
}
function Get-WeakDnsUpdateFindings {
param(
[System.DirectoryServices.ActiveDirectory.Domain]$DomainContext
)
if (-not $DomainContext) { return @() }
$domainDN = $DomainContext.GetDirectoryEntry().distinguishedName
$forestDN = $DomainContext.Forest.RootDomain.GetDirectoryEntry().distinguishedName
$paths = @(
"LDAP://CN=MicrosoftDNS,DC=DomainDnsZones,$domainDN",
"LDAP://CN=MicrosoftDNS,DC=ForestDnsZones,$forestDN",
"LDAP://CN=MicrosoftDNS,$domainDN"
)
$weakPatterns = @(
"authenticated users",
"everyone",
"domain users"
)
$dangerousRights = @("GenericAll", "GenericWrite", "CreateChild", "WriteProperty", "WriteDacl", "WriteOwner")
$findings = @()
foreach ($path in $paths) {
try {
$container = New-Object System.DirectoryServices.DirectoryEntry($path)
$null = $container.NativeGuid
}
catch { continue }
$searcher = New-Object System.DirectoryServices.DirectorySearcher($container)
$searcher.Filter = "(objectClass=dnsZone)"
$searcher.PageSize = 500
$results = $searcher.FindAll()
foreach ($result in $results) {
try {
$zoneEntry = $result.GetDirectoryEntry()
$zoneEntry.Options.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl
$sd = $zoneEntry.ObjectSecurity
foreach ($ace in $sd.Access) {
if ($ace.AccessControlType -ne 'Allow') { continue }
$principal = Convert-SidToName $ace.IdentityReference
if (-not $principal) { continue }
$principalLower = $principal.ToLower()
if (-not ($weakPatterns | Where-Object { $principalLower -like "*${_}*" })) { continue }
$rights = $ace.ActiveDirectoryRights.ToString()
if (-not ($dangerousRights | Where-Object { $rights -like "*${_}*" })) { continue }
$findings += [pscustomobject]@{
Zone = $zoneEntry.Properties["name"].Value
Partition = $path.Split(',')[1]
Principal = $principal
Rights = $rights
}
}
}
catch { continue }
}
}
return ($findings | Sort-Object Zone, Principal -Unique)
}
function Get-GmsaReadersReport {
param(
[System.DirectoryServices.ActiveDirectory.Domain]$DomainContext
)
if (-not $DomainContext) { return @() }
$domainDN = $DomainContext.GetDirectoryEntry().distinguishedName
try {
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$domainDN")
$searcher.Filter = "(&(objectClass=msDS-GroupManagedServiceAccount))"
$searcher.PageSize = 500
[void]$searcher.PropertiesToLoad.Add("sAMAccountName")
[void]$searcher.PropertiesToLoad.Add("msDS-GroupMSAMembership")
$results = $searcher.FindAll()
}
catch { return @() }
$report = @()
foreach ($result in $results) {
$name = $result.Properties["samaccountname"]
$blobs = $result.Properties["msds-groupmsamembership"]
if (-not $blobs) { continue }
$principals = @()
foreach ($blob in $blobs) {
try {
$raw = New-Object System.Security.AccessControl.RawSecurityDescriptor (, $blob)
foreach ($ace in $raw.DiscretionaryAcl) {
$sid = Convert-SidToName $ace.SecurityIdentifier
if ($sid) { $principals += $sid }
}
}
catch { continue }
}
if ($principals.Count -eq 0) { continue }
$principals = $principals | Sort-Object -Unique
$weak = $principals | Where-Object { $_ -match 'Domain Users|Authenticated Users|Everyone' }
$report += [pscustomobject]@{
Account = ($name | Select-Object -First 1)
Allowed = ($principals -join ", ")
WeakPrincipals = if ($weak) { $weak -join ", " } else { "" }
}
}
return $report
}
function Get-PrivilegedSpnTargets {
param(
[System.DirectoryServices.ActiveDirectory.Domain]$DomainContext
)
if (-not $DomainContext) { return @() }
$domainDN = $DomainContext.GetDirectoryEntry().distinguishedName
$keywords = @(
"Domain Admin",
"Enterprise Admin",
"Administrators",
"Exchange",
"IT_",
"Schema Admin",
"Account Operator",
"Server Operator",
"Backup Operator",
"DnsAdmin"
)
try {
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$domainDN")
$searcher.Filter = "(&(objectClass=user)(servicePrincipalName=*))"
$searcher.PageSize = 500
[void]$searcher.PropertiesToLoad.Add("sAMAccountName")
[void]$searcher.PropertiesToLoad.Add("memberOf")
$results = $searcher.FindAll()
}
catch { return @() }
$findings = @()
foreach ($res in $results) {
$groups = $res.Properties["memberof"]
if (-not $groups) { continue }
$matchedGroups = @()
foreach ($group in $groups) {
$cn = ($group -split ',')[0] -replace '^CN=',''
if ($keywords | Where-Object { $cn -like "*${_}*" }) {
$matchedGroups += $cn
}
}
if ($matchedGroups.Count -gt 0) {
$findings += [pscustomobject]@{
User = ($res.Properties["samaccountname"] | Select-Object -First 1)
Groups = ($matchedGroups | Sort-Object -Unique) -join ', '
}
}
}
return ($findings | Sort-Object User | Select-Object -First 12)
}
function Get-NtlmPolicySummary {
try {
$msv = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0' -ErrorAction Stop
}
catch { return $null }
$lsa = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' -ErrorAction SilentlyContinue
return [pscustomobject]@{
RestrictReceiving = $msv.RestrictReceivingNTLMTraffic
RestrictSending = $msv.RestrictSendingNTLMTraffic
LmCompatibility = if ($lsa) { $lsa.LmCompatibilityLevel } else { $null }
}
}
function Get-TimeSkewInfo {
param(
[System.DirectoryServices.ActiveDirectory.Domain]$DomainContext
)
if (-not $DomainContext) { return $null }
try {
$pdc = $DomainContext.PdcRoleOwner.Name
}
catch { return $null }
try {
$stripchart = w32tm /stripchart /computer:$pdc /dataonly /samples:3 2>$null
$sample = $stripchart | Where-Object { $_ -match ',' } | Select-Object -Last 1
if (-not $sample) { return $null }
$parts = $sample.Split(',')
if ($parts.Count -lt 2) { return $null }
$offsetString = $parts[1].Trim().TrimEnd('s')
[double]$offsetSeconds = 0
if (-not [double]::TryParse($offsetString, [ref]$offsetSeconds)) { return $null }
return [pscustomobject]@{
Source = $pdc
OffsetSeconds = $offsetSeconds
RawSample = $sample
}
}
catch {
return $null
}
}
function Get-AdcsSchannelInfo {
$info = [ordered]@{
MappingValue = $null
UpnMapping = $false
ServiceState = $null
}
try {
$schannel = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL' -Name 'CertificateMappingMethods' -ErrorAction Stop
$info.MappingValue = $schannel.CertificateMappingMethods
if (($schannel.CertificateMappingMethods -band 0x4) -eq 0x4) { $info.UpnMapping = $true }
}
catch { }
$svc = Get-Service -Name certsrv -ErrorAction SilentlyContinue
if ($svc) { $info.ServiceState = $svc.Status }
return [pscustomobject]$info
}
function Search-Excel {
[cmdletbinding()]
Param (
@@ -1226,6 +1464,95 @@ Write-Host -ForegroundColor Blue "=========|| LISTENING PORTS"
Start-Process NETSTAT.EXE -ArgumentList "-ano" -Wait -NoNewWindow
######################## ACTIVE DIRECTORY / IDENTITY MISCONFIG CHECKS ########################
Write-Host ""
if ($TimeStamp) { TimeElapsed }
Write-Host -ForegroundColor Blue "=========|| ACTIVE DIRECTORY / IDENTITY MISCONFIG CHECKS"
$domainContext = Get-DomainContext
if (-not $domainContext) {
Write-Host "Host appears to be in a workgroup or the AD context could not be resolved. Skipping domain-specific checks." -ForegroundColor DarkGray
}
else {
$ntlmStatus = Get-NtlmPolicySummary
if ($ntlmStatus) {
$recvValue = if ($ntlmStatus.RestrictReceiving -ne $null) { [int]$ntlmStatus.RestrictReceiving } else { -1 }
$sendValue = if ($ntlmStatus.RestrictSending -ne $null) { [int]$ntlmStatus.RestrictSending } else { -1 }
$lmValue = if ($ntlmStatus.LmCompatibility -ne $null) { [int]$ntlmStatus.LmCompatibility } else { -1 }
$ntlmMsg = "Receiving:{0} Sending:{1} LMCompat:{2}" -f $recvValue, $sendValue, $lmValue
if ($recvValue -ge 1 -or $sendValue -ge 1 -or $lmValue -ge 5) {
Write-Host "[!] NTLM is restricted/disabled ($ntlmMsg). Expect Kerberos-only auth paths (sync time before Kerberoasting)." -ForegroundColor Yellow
}
else {
Write-Host "[i] NTLM restrictions appear relaxed ($ntlmMsg)."
}
}
$timeSkew = Get-TimeSkewInfo -DomainContext $domainContext
if ($timeSkew) {
$offsetAbs = [math]::Abs($timeSkew.OffsetSeconds)
$timeMsg = "Offset vs {0}: {1:N3}s (sample: {2})" -f $timeSkew.Source, $timeSkew.OffsetSeconds, $timeSkew.RawSample.Trim()
if ($offsetAbs -gt 5) {
Write-Host "[!] Significant Kerberos time skew detected - $timeMsg" -ForegroundColor Yellow
}
else {
Write-Host "[i] Kerberos time offset looks OK - $timeMsg"
}
}
$dnsFindings = @(Get-WeakDnsUpdateFindings -DomainContext $domainContext)
if ($dnsFindings.Count -gt 0) {
Write-Host "[!] AD-integrated DNS zones allow low-priv principals to write records (dynamic DNS hijack / service MITM risk)." -ForegroundColor Yellow
$dnsFindings | Format-Table Zone,Partition,Principal,Rights -AutoSize | Out-String | Write-Host
}
else {
Write-Host "[i] No obvious insecure dynamic DNS ACLs found with current privileges."
}
$spnFindings = @(Get-PrivilegedSpnTargets -DomainContext $domainContext)
if ($spnFindings.Count -gt 0) {
Write-Host "[!] High-value SPN accounts identified (prime Kerberoast targets):" -ForegroundColor Yellow
$spnFindings | Format-Table User,Groups -AutoSize | Out-String | Write-Host
}
else {
Write-Host "[i] No privileged SPN users detected via quick LDAP search."
}
$gmsaReport = @(Get-GmsaReadersReport -DomainContext $domainContext)
if ($gmsaReport.Count -gt 0) {
$weakGmsa = $gmsaReport | Where-Object { $_.WeakPrincipals -ne "" }
if ($weakGmsa) {
Write-Host "[!] gMSA passwords readable by low-priv groups/principals: " -ForegroundColor Yellow
$weakGmsa | Select-Object Account, WeakPrincipals | Format-Table -AutoSize | Out-String | Write-Host
}
else {
Write-Host "[i] gMSA accounts discovered (review allowed readers below)."
$gmsaReport | Select-Object Account, Allowed | Sort-Object Account | Select-Object -First 5 | Format-Table -Wrap | Out-String | Write-Host
}
}
else {
Write-Host "[i] No gMSA objects found via LDAP."
}
$adcsInfo = Get-AdcsSchannelInfo
if ($adcsInfo.MappingValue -ne $null) {
$hex = ('0x{0:X}' -f [int]$adcsInfo.MappingValue)
if ($adcsInfo.UpnMapping) {
Write-Host ("[!] Schannel CertificateMappingMethods={0} (UPN mapping allowed) - ESC10 certificate abuse possible if you can edit another user's UPN." -f $hex) -ForegroundColor Yellow
}
else {
Write-Host ("[i] Schannel CertificateMappingMethods={0} (UPN mapping flag not set)." -f $hex)
}
if ($adcsInfo.ServiceState) {
Write-Host ("[i] AD CS service state: {0}" -f $adcsInfo.ServiceState)
}
}
else {
Write-Host "[i] Could not read Schannel certificate mapping configuration." -ForegroundColor DarkGray
}
}
Write-Host ""
if ($TimeStamp) { TimeElapsed }
Write-Host -ForegroundColor Blue "=========|| ARP Table"