From 11c0d145615bb4d1848a44582bac3049b8826951 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sat, 22 Nov 2025 18:54:22 +0000 Subject: [PATCH] Add winpeas privilege escalation checks from: HackTheBox Mirage: Chaining NFS Leaks, Dynamic DNS Abuse, NATS Credential Theft, --- winPEAS/winPEASps1/README.md | 8 + winPEAS/winPEASps1/winPEAS.ps1 | 327 +++++++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+) diff --git a/winPEAS/winPEASps1/README.md b/winPEAS/winPEASps1/README.md index 6475870..adaf43d 100755 --- a/winPEAS/winPEASps1/README.md +++ b/winPEAS/winPEASps1/README.md @@ -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. diff --git a/winPEAS/winPEASps1/winPEAS.ps1 b/winPEAS/winPEASps1/winPEAS.ps1 index 6ab50c3..c471666 100644 --- a/winPEAS/winPEASps1/winPEAS.ps1 +++ b/winPEAS/winPEASps1/winPEAS.ps1 @@ -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"