From 49db1df468b5bb57fb6a3973fdf37fe772d6a665 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Wed, 19 Nov 2025 18:59:41 +0000 Subject: [PATCH 01/17] =?UTF-8?q?Add=20linpeas=20privilege=20escalation=20?= =?UTF-8?q?checks=20from:=20SupaPwn:=20Hacking=20Our=20Way=20into=20Lovabl?= =?UTF-8?q?e=E2=80=99s=20Office=20and=20Helping=20Secure=20Supabase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- linPEAS/README.md | 4 ++ .../Postgresql_Event_Triggers.sh | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 linPEAS/builder/linpeas_parts/7_software_information/Postgresql_Event_Triggers.sh diff --git a/linPEAS/README.md b/linPEAS/README.md index 620608d..125f471 100755 --- a/linPEAS/README.md +++ b/linPEAS/README.md @@ -98,6 +98,10 @@ The goal of this script is to search for possible **Privilege Escalation Paths** This script doesn't have any dependency. +### Recent privilege-escalation additions + +- Added detection for unsafe PostgreSQL event triggers and custom `postgres_fdw` hooks that temporarily grant SUPERUSER privileges, surfacing SupaPwn-style escalation paths earlier. + It uses **/bin/sh** syntax, so can run in anything supporting `sh` (and the binaries and parameters used). By default, **linpeas won't write anything to disk and won't try to login as any other user using `su`**. diff --git a/linPEAS/builder/linpeas_parts/7_software_information/Postgresql_Event_Triggers.sh b/linPEAS/builder/linpeas_parts/7_software_information/Postgresql_Event_Triggers.sh new file mode 100644 index 0000000..05d76fc --- /dev/null +++ b/linPEAS/builder/linpeas_parts/7_software_information/Postgresql_Event_Triggers.sh @@ -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 "" From 11c0d145615bb4d1848a44582bac3049b8826951 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sat, 22 Nov 2025 18:54:22 +0000 Subject: [PATCH 02/17] 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" From dd220af544dee3e24598b64eea26068ca89197b1 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Thu, 27 Nov 2025 13:44:39 +0000 Subject: [PATCH 03/17] Add winpeas privilege escalation checks from: Metasploit Wrap-Up 11/14/2025 --- winPEAS/winPEASexe/README.md | 2 +- .../winPEASexe/winPEAS/Checks/SystemInfo.cs | 88 ++++++++++++++++--- 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/winPEAS/winPEASexe/README.md b/winPEAS/winPEASexe/README.md index 8dd211c..6787d9f 100755 --- a/winPEAS/winPEASexe/README.md +++ b/winPEAS/winPEASexe/README.md @@ -217,7 +217,7 @@ Once you have installed and activated it you need to: - [x] SCCM - [x] Security Package Credentials - [x] AlwaysInstallElevated - - [x] WSUS + - [x] WSUS (HTTP downgrade + CVE-2025-59287 exposure) - **Browser Information** - [x] Firefox DBs diff --git a/winPEAS/winPEASexe/winPEAS/Checks/SystemInfo.cs b/winPEAS/winPEASexe/winPEAS/Checks/SystemInfo.cs index 344fd13..96f17bd 100644 --- a/winPEAS/winPEASexe/winPEAS/Checks/SystemInfo.cs +++ b/winPEAS/winPEASexe/winPEAS/Checks/SystemInfo.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Management; using System.Reflection; using System.Runtime.InteropServices; using System.Text.RegularExpressions; @@ -561,27 +562,66 @@ namespace winPEAS.Checks { Beaprint.MainPrint("Checking WSUS"); Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#wsus"); - string path = "Software\\Policies\\Microsoft\\Windows\\WindowsUpdate"; - string path2 = "Software\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU"; - string HKLM_WSUS = RegistryHelper.GetRegValue("HKLM", path, "WUServer"); - string using_HKLM_WSUS = RegistryHelper.GetRegValue("HKLM", path2, "UseWUServer"); - if (HKLM_WSUS.Contains("http://")) + string policyPath = "Software\\Policies\\Microsoft\\Windows\\WindowsUpdate"; + string policyAUPath = "Software\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU"; + string wsusPolicyValue = RegistryHelper.GetRegValue("HKLM", policyPath, "WUServer"); + string useWUServerValue = RegistryHelper.GetRegValue("HKLM", policyAUPath, "UseWUServer"); + + if (!string.IsNullOrEmpty(wsusPolicyValue) && wsusPolicyValue.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) { - Beaprint.BadPrint(" WSUS is using http: " + HKLM_WSUS); + Beaprint.BadPrint(" WSUS is using http: " + wsusPolicyValue); Beaprint.InfoPrint("You can test https://github.com/pimps/wsuxploit to escalate privileges"); - if (using_HKLM_WSUS == "1") + if (useWUServerValue == "1") Beaprint.BadPrint(" And UseWUServer is equals to 1, so it is vulnerable!"); - else if (using_HKLM_WSUS == "0") + else if (useWUServerValue == "0") Beaprint.GoodPrint(" But UseWUServer is equals to 0, so it is not vulnerable!"); else - Console.WriteLine(" But UseWUServer is equals to " + using_HKLM_WSUS + ", so it may work or not"); + Console.WriteLine(" But UseWUServer is equals to " + useWUServerValue + ", so it may work or not"); } else { - if (string.IsNullOrEmpty(HKLM_WSUS)) + if (string.IsNullOrEmpty(wsusPolicyValue)) Beaprint.NotFoundPrint(); else - Beaprint.GoodPrint(" WSUS value: " + HKLM_WSUS); + Beaprint.GoodPrint(" WSUS value: " + wsusPolicyValue); + } + + if (!string.IsNullOrEmpty(wsusPolicyValue)) + { + bool clientsForced = useWUServerValue == "1"; + if (clientsForced) + { + Beaprint.BadPrint(" CVE-2025-59287: Clients talk to WSUS at " + wsusPolicyValue + " (UseWUServer=1). Unpatched WSUS allows unauthenticated deserialization to SYSTEM."); + } + else + { + Beaprint.InfoPrint(" CVE-2025-59287: WSUS endpoint discovered at " + wsusPolicyValue + ". Confirm patch level before attempting exploitation."); + if (!string.IsNullOrEmpty(useWUServerValue)) + Beaprint.InfoPrint(" UseWUServer is set to " + useWUServerValue + ", clients may still reach Microsoft Update."); + } + } + + string wsusSetupPath = @"SOFTWARE\Microsoft\Update Services\Server\Setup"; + string wsusVersion = RegistryHelper.GetRegValue("HKLM", wsusSetupPath, "VersionString"); + string wsusInstallPath = RegistryHelper.GetRegValue("HKLM", wsusSetupPath, "InstallPath"); + bool wsusRoleDetected = !string.IsNullOrEmpty(wsusVersion) || !string.IsNullOrEmpty(wsusInstallPath); + + if (TryGetServiceStateAndAccount("WSUSService", out string wsusServiceState, out string wsusServiceAccount)) + { + wsusRoleDetected = true; + string serviceMsg = " WSUSService status: " + wsusServiceState; + if (!string.IsNullOrEmpty(wsusServiceAccount)) + serviceMsg += " (runs as " + wsusServiceAccount + ")"; + Beaprint.BadPrint(serviceMsg); + } + + if (wsusRoleDetected) + { + if (!string.IsNullOrEmpty(wsusVersion)) + Beaprint.BadPrint(" WSUS Server version: " + wsusVersion + " (verify patch level for CVE-2025-59287)."); + if (!string.IsNullOrEmpty(wsusInstallPath)) + Beaprint.InfoPrint(" WSUS install path: " + wsusInstallPath); + Beaprint.BadPrint(" CVE-2025-59287: Local WSUS server exposes an unauthenticated deserialization surface reachable over HTTP(S). Patch or restrict access."); } } catch (Exception ex) @@ -590,6 +630,32 @@ namespace winPEAS.Checks } } + private static bool TryGetServiceStateAndAccount(string serviceName, out string state, out string account) + { + state = string.Empty; + account = string.Empty; + + try + { + string query = $"SELECT Name, State, StartName FROM Win32_Service WHERE Name='{serviceName.Replace("'", "''")}'"; + using (var searcher = new ManagementObjectSearcher(@"root\cimv2", query)) + { + foreach (ManagementObject service in searcher.Get()) + { + state = service["State"]?.ToString() ?? string.Empty; + account = service["StartName"]?.ToString() ?? string.Empty; + return true; + } + } + } + catch (Exception ex) + { + Beaprint.PrintException(ex.Message); + } + + return false; + } + static void PrintKrbRelayUp() { try From e99e64cddff71557c91ed97381688a1f67e43d9d Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sat, 29 Nov 2025 01:41:29 +0000 Subject: [PATCH 04/17] Add linpeas privilege escalation checks from: Metasploit Wrap-Up 11/28/2025 --- linPEAS/README.md | 3 + .../16_IGEL_OS_SUID.sh | 80 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 linPEAS/builder/linpeas_parts/8_interesting_perms_files/16_IGEL_OS_SUID.sh diff --git a/linPEAS/README.md b/linPEAS/README.md index 620608d..5ade213 100755 --- a/linPEAS/README.md +++ b/linPEAS/README.md @@ -102,6 +102,9 @@ It uses **/bin/sh** syntax, so can run in anything supporting `sh` (and the bina By default, **linpeas won't write anything to disk and won't try to login as any other user using `su`**. +LinPEAS keeps expanding vendor-specific coverage; as of 29-Nov-2025 it warns when IGEL OS appliances still ship the SUID `setup`/`date` helpers that allow NetworkManager/systemd configuration hijacking (Metasploit module `linux/local/igel_network_priv_esc`). + + By default linpeas takes around **4 mins** to complete, but It could take from **5 to 10 minutes** to execute all the checks using **-a** parameter *(Recommended option for CTFs)*: - From less than 1 min to 2 mins to make almost all the checks - Almost 1 min to search for possible passwords inside all the accesible files of the system diff --git a/linPEAS/builder/linpeas_parts/8_interesting_perms_files/16_IGEL_OS_SUID.sh b/linPEAS/builder/linpeas_parts/8_interesting_perms_files/16_IGEL_OS_SUID.sh new file mode 100644 index 0000000..9a7d312 --- /dev/null +++ b/linPEAS/builder/linpeas_parts/8_interesting_perms_files/16_IGEL_OS_SUID.sh @@ -0,0 +1,80 @@ +# Title: Interesting Permissions Files - IGEL OS SUID setup/date abuse +# ID: IP_IGEL_OS_SUID +# Author: HT Bot +# Last Update: 29-11-2025 +# Description: Detect IGEL OS environments that expose the SUID-root `setup`/`date` binaries and highlight writable NetworkManager/systemd configs that enable the documented privilege escalation chain (Metasploit linux/local/igel_network_priv_esc). +# License: GNU GPL +# Version: 1.0 +# Functions Used: print_2title, print_info +# Global Variables: $ITALIC, $NC, $SED_GREEN, $SED_RED, $SED_RED_YELLOW, $SUPERFAST +# Initial Functions: +# Generated Global Variables: $igel_markers, $igel_marker_sources, $marker, $igel_suid_hits, $candidate, $writable_nm, $writable_systemd, $unitdir, $tmp_units +# Fat linpeas: 0 +# Small linpeas: 1 + +igel_markers="" +igel_marker_sources="" +if [ -f /etc/os-release ] && grep -qi "igel" /etc/os-release 2>/dev/null; then + igel_markers="Yes" + igel_marker_sources="/etc/os-release" +fi +if [ -f /etc/issue ] && grep -qi "igel" /etc/issue 2>/dev/null; then + igel_markers="Yes" + igel_marker_sources="${igel_marker_sources} /etc/issue" +fi +for marker in /etc/igel /wfs/igel /userhome/.igel /config/sessions/igel; do + if [ -e "$marker" ]; then + igel_markers="Yes" + igel_marker_sources="${igel_marker_sources} $marker" + fi +done + +igel_suid_hits="" +for candidate in /usr/bin/setup /bin/setup /usr/sbin/setup /opt/igel/bin/setup /usr/bin/date /bin/date /usr/lib/igel/date; do + if [ -u "$candidate" ]; then + igel_suid_hits="${igel_suid_hits}$(ls -lah "$candidate" 2>/dev/null)\n" + fi +done + +if [ -n "$igel_markers" ] || [ -n "$igel_suid_hits" ]; then + print_2title "IGEL OS SUID setup/date privilege escalation surface" + print_info "https://www.rapid7.com/blog/post/pt-metasploit-wrap-up-11-28-2025" + if [ -n "$igel_markers" ]; then + echo "Potential IGEL OS detected via: $igel_marker_sources" | sed -${E} "s,.*,${SED_GREEN}," + else + echo "IGEL-specific SUID helpers found but IGEL markers were not detected" | sed -${E} "s,.*,${SED_RED}," + fi + if [ -n "$igel_suid_hits" ]; then + echo "SUID-root helpers exposing configuration primitives:" | sed -${E} "s,.*,${SED_RED_YELLOW}," + printf "%b" "$igel_suid_hits" + else + echo "No SUID setup/date binaries were located (system may be patched)." + fi + + writable_nm="" + writable_systemd="" + if ! [ "$SUPERFAST" ]; then + if [ -d /etc/NetworkManager ]; then + writable_nm=$(find /etc/NetworkManager -maxdepth 3 -type f -writable 2>/dev/null | head -n 25) + fi + for unitdir in /etc/systemd/system /lib/systemd/system /usr/lib/systemd/system; do + if [ -d "$unitdir" ]; then + tmp_units=$(find "$unitdir" -maxdepth 2 -type f -writable 2>/dev/null | head -n 15) + if [ -n "$tmp_units" ]; then + writable_systemd="${writable_systemd}${tmp_units}\n" + fi + fi + done + fi + + if [ -n "$writable_nm" ]; then + echo "Writable NetworkManager profiles/hooks (swap Exec path to your payload):" | sed -${E} "s,.*,${SED_RED_YELLOW}," + echo "$writable_nm" + fi + if [ -n "$writable_systemd" ]; then + echo "Writable systemd unit files (edit ExecStart, then restart via setup/date):" | sed -${E} "s,.*,${SED_RED_YELLOW}," + printf "%b" "$writable_systemd" + fi + printf "$ITALIC Known exploitation chain: Use the SUID setup/date binaries to edit NetworkManager or systemd configs so ExecStart points to your payload, then trigger a service restart via the same helper to run as root (Metasploit linux/local/igel_network_priv_esc).$NC\n" +fi +echo "" From b188ac34b6179b09abe9f89a54d2ae7871a20a7d Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sat, 29 Nov 2025 18:48:21 +0000 Subject: [PATCH 05/17] =?UTF-8?q?Add=20linpeas=20privilege=20escalation=20?= =?UTF-8?q?checks=20from:=20HTB:=20Era=20=E2=80=93=20IDORs,=20PHP=20ssh2.e?= =?UTF-8?q?xec=20Wrapper=20RCE,=20and=20Custom-Signed=20Binary=20Privilege?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../16_Writable_root_execs.sh | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 linPEAS/builder/linpeas_parts/8_interesting_perms_files/16_Writable_root_execs.sh diff --git a/linPEAS/builder/linpeas_parts/8_interesting_perms_files/16_Writable_root_execs.sh b/linPEAS/builder/linpeas_parts/8_interesting_perms_files/16_Writable_root_execs.sh new file mode 100644 index 0000000..1f86216 --- /dev/null +++ b/linPEAS/builder/linpeas_parts/8_interesting_perms_files/16_Writable_root_execs.sh @@ -0,0 +1,36 @@ +# Title: Interesting Permissions Files - Writable root-owned executables +# ID: IP_Writable_root_execs +# Author: HT Bot +# Last Update: 29-11-2025 +# Description: Locate root-owned executables outside home folders that the current user can modify +# License: GNU GPL +# Version: 1.0 +# Functions Used: print_2title, print_info, echo_not_found +# Global Variables: $DEBUG, $IAMROOT, $ROOT_FOLDER, $HOME, $writeVB +# Initial Functions: +# Generated Global Variables: $writable_root_execs +# Fat linpeas: 0 +# Small linpeas: 1 + +if ! [ "$IAMROOT" ]; then + print_2title "Writable root-owned executables I can modify (max 200)" + print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#writable-files" + + writable_root_execs=$( + find "$ROOT_FOLDER" -type f -user root -perm -u=x \ + \( -perm -g=w -o -perm -o=w \) \ + ! -path "/proc/*" ! -path "/sys/*" ! -path "/run/*" ! -path "/dev/*" ! -path "/snap/*" ! -path "$HOME/*" 2>/dev/null \ + | while IFS= read -r f; do + if [ -w "$f" ]; then + ls -l "$f" 2>/dev/null + fi + done | head -n 200 + ) + + if [ "$writable_root_execs" ] || [ "$DEBUG" ]; then + printf "%s\n" "$writable_root_execs" | sed -${E} "s,$writeVB,${SED_RED_YELLOW}," + else + echo_not_found "Writable root-owned executables" + fi + echo "" +fi From 313fe6bef50eea8756e327d9473c5e82be98b84d Mon Sep 17 00:00:00 2001 From: SirBroccoli Date: Sun, 7 Dec 2025 13:21:52 +0100 Subject: [PATCH 06/17] Update README.md --- linPEAS/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/linPEAS/README.md b/linPEAS/README.md index 125f471..620608d 100755 --- a/linPEAS/README.md +++ b/linPEAS/README.md @@ -98,10 +98,6 @@ The goal of this script is to search for possible **Privilege Escalation Paths** This script doesn't have any dependency. -### Recent privilege-escalation additions - -- Added detection for unsafe PostgreSQL event triggers and custom `postgres_fdw` hooks that temporarily grant SUPERUSER privileges, surfacing SupaPwn-style escalation paths earlier. - It uses **/bin/sh** syntax, so can run in anything supporting `sh` (and the binaries and parameters used). By default, **linpeas won't write anything to disk and won't try to login as any other user using `su`**. From 595e021864ea02a1590ce851ab05f8a94d7aff80 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 8 Dec 2025 00:27:02 +0000 Subject: [PATCH 07/17] fix: correct typo of SeDebugPrivilege --- winPEAS/winPEASbat/winPEAS.bat | 2 +- winPEAS/winPEASps1/winPEAS.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/winPEAS/winPEASbat/winPEAS.bat b/winPEAS/winPEASbat/winPEAS.bat index fdf176d..c51ccf2 100755 --- a/winPEAS/winPEASbat/winPEAS.bat +++ b/winPEAS/winPEASbat/winPEAS.bat @@ -405,7 +405,7 @@ CALL :T_Progress 1 :BasicUserInfo CALL :ColorLine "%E%32m[*]%E%97m BASIC USER INFO -ECHO. [i] Check if you are inside the Administrators group or if you have enabled any token that can be use to escalate privileges like SeImpersonatePrivilege, SeAssignPrimaryPrivilege, SeTcbPrivilege, SeBackupPrivilege, SeRestorePrivilege, SeCreateTokenPrivilege, SeLoadDriverPrivilege, SeTakeOwnershipPrivilege, SeDebbugPrivilege +ECHO. [i] Check if you are inside the Administrators group or if you have enabled any token that can be use to escalate privileges like SeImpersonatePrivilege, SeAssignPrimaryPrivilege, SeTcbPrivilege, SeBackupPrivilege, SeRestorePrivilege, SeCreateTokenPrivilege, SeLoadDriverPrivilege, SeTakeOwnershipPrivilege, SeDebugPrivilege ECHO. [?] https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#users--groups ECHO. CALL :ColorLine " %E%33m[+]%E%97m CURRENT USER" diff --git a/winPEAS/winPEASps1/winPEAS.ps1 b/winPEAS/winPEASps1/winPEAS.ps1 index c471666..efd3268 100644 --- a/winPEAS/winPEASps1/winPEAS.ps1 +++ b/winPEAS/winPEASps1/winPEAS.ps1 @@ -1650,7 +1650,7 @@ Write-Host -ForegroundColor Blue "=========|| WHOAMI INFO" Write-Host "" if ($TimeStamp) { TimeElapsed } Write-Host -ForegroundColor Blue "=========|| Check Token access here: https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens.html#abusing-tokens" -ForegroundColor yellow -Write-Host -ForegroundColor Blue "=========|| Check if you are inside the Administrators group or if you have enabled any token that can be use to escalate privileges like SeImpersonatePrivilege, SeAssignPrimaryPrivilege, SeTcbPrivilege, SeBackupPrivilege, SeRestorePrivilege, SeCreateTokenPrivilege, SeLoadDriverPrivilege, SeTakeOwnershipPrivilege, SeDebbugPrivilege" +Write-Host -ForegroundColor Blue "=========|| Check if you are inside the Administrators group or if you have enabled any token that can be use to escalate privileges like SeImpersonatePrivilege, SeAssignPrimaryPrivilege, SeTcbPrivilege, SeBackupPrivilege, SeRestorePrivilege, SeCreateTokenPrivilege, SeLoadDriverPrivilege, SeTakeOwnershipPrivilege, SeDebugPrivilege" Write-Host "https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#users--groups" -ForegroundColor Yellow Start-Process whoami.exe -ArgumentList "/all" -Wait -NoNewWindow From 7e0f678f335ddc50357efd9acc18472f7b60b872 Mon Sep 17 00:00:00 2001 From: compass-dexter <145257008+compass-dexter@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:58:09 +0100 Subject: [PATCH 08/17] fix(linPEAS): grep for AuthorizedKeysFile According to sshd_config(5) this is the correct setting --- linPEAS/builder/linpeas_parts/7_software_information/Ssh.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linPEAS/builder/linpeas_parts/7_software_information/Ssh.sh b/linPEAS/builder/linpeas_parts/7_software_information/Ssh.sh index f456964..7ee753c 100644 --- a/linPEAS/builder/linpeas_parts/7_software_information/Ssh.sh +++ b/linPEAS/builder/linpeas_parts/7_software_information/Ssh.sh @@ -29,7 +29,7 @@ fi peass{SSH} -grep "PermitRootLogin \|ChallengeResponseAuthentication \|PasswordAuthentication \|UsePAM \|Port\|PermitEmptyPasswords\|PubkeyAuthentication\|ListenAddress\|ForwardAgent\|AllowAgentForwarding\|AuthorizedKeysFiles" /etc/ssh/sshd_config 2>/dev/null | grep -v "#" | sed -${E} "s,PermitRootLogin.*es|PermitEmptyPasswords.*es|ChallengeResponseAuthentication.*es|FordwardAgent.*es,${SED_RED}," +grep "PermitRootLogin \|ChallengeResponseAuthentication \|PasswordAuthentication \|UsePAM \|Port\|PermitEmptyPasswords\|PubkeyAuthentication\|ListenAddress\|ForwardAgent\|AllowAgentForwarding\|AuthorizedKeysFile" /etc/ssh/sshd_config 2>/dev/null | grep -v "#" | sed -${E} "s,PermitRootLogin.*es|PermitEmptyPasswords.*es|ChallengeResponseAuthentication.*es|FordwardAgent.*es,${SED_RED}," if ! [ "$SEARCH_IN_FOLDER" ]; then if [ "$TIMEOUT" ]; then From b09bd92116b1fdd2c0ffebb6e98410520a4af446 Mon Sep 17 00:00:00 2001 From: carlospolop Date: Fri, 12 Dec 2025 14:28:17 +0100 Subject: [PATCH 09/17] f --- .github/workflows/CI-master_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI-master_tests.yml b/.github/workflows/CI-master_tests.yml index 62219c9..a20ea15 100644 --- a/.github/workflows/CI-master_tests.yml +++ b/.github/workflows/CI-master_tests.yml @@ -219,7 +219,7 @@ jobs: # Setup go - uses: actions/setup-go@v2 with: - go-version: 1.17.0-rc1 + go-version: 1.17rc1 stable: false - run: go version From 0277e447f00e6cd52938ba2d6849115497527804 Mon Sep 17 00:00:00 2001 From: carlospolop Date: Fri, 12 Dec 2025 16:25:36 +0100 Subject: [PATCH 10/17] f --- .github/workflows/CI-master_tests.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI-master_tests.yml b/.github/workflows/CI-master_tests.yml index a20ea15..842ef3c 100644 --- a/.github/workflows/CI-master_tests.yml +++ b/.github/workflows/CI-master_tests.yml @@ -212,15 +212,14 @@ jobs: steps: # Download repo - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 with: ref: ${{ github.head_ref }} # Setup go - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v6 with: - go-version: 1.17rc1 - stable: false + go-version: '1.23' - run: go version # Build linpeas From 877b9b81ce303831abb258f09e2645724ad8bbdb Mon Sep 17 00:00:00 2001 From: DNR <3779295+DotNetRussell@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:45:02 -0500 Subject: [PATCH 11/17] Fix wording in privilege escalation checklist --- .../builder/linpeas_parts/linpeas_base/0_variables_base.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linPEAS/builder/linpeas_parts/linpeas_base/0_variables_base.sh b/linPEAS/builder/linpeas_parts/linpeas_base/0_variables_base.sh index a79840f..c99df36 100644 --- a/linPEAS/builder/linpeas_parts/linpeas_base/0_variables_base.sh +++ b/linPEAS/builder/linpeas_parts/linpeas_base/0_variables_base.sh @@ -371,7 +371,7 @@ echo "" printf ${BLUE}"Linux Privesc Checklist: ${YELLOW}https://book.hacktricks.wiki/en/linux-hardening/linux-privilege-escalation-checklist.html\n"$NC echo " LEGEND:" | sed "s,LEGEND,${C}[1;4m&${C}[0m," echo " RED/YELLOW: 95% a PE vector" | sed "s,RED/YELLOW,${SED_RED_YELLOW}," -echo " RED: You should take a look to it" | sed "s,RED,${SED_RED}," +echo " RED: You should take a look into it" | sed "s,RED,${SED_RED}," echo " LightCyan: Users with console" | sed "s,LightCyan,${SED_LIGHT_CYAN}," echo " Blue: Users without console & mounted devs" | sed "s,Blue,${SED_BLUE}," echo " Green: Common things (users, groups, SUID/SGID, mounts, .sh scripts, cronjobs) " | sed "s,Green,${SED_GREEN}," @@ -514,4 +514,4 @@ else HOMESEARCH="$HOME $HOMESEARCH" fi fi -GREPHOMESEARCH=$(echo "$HOMESEARCH" | sed 's/ *$//g' | tr " " "|") #Remove ending spaces before putting "|" \ No newline at end of file +GREPHOMESEARCH=$(echo "$HOMESEARCH" | sed 's/ *$//g' | tr " " "|") #Remove ending spaces before putting "|" From 10b087febfe0b758b3624ac8ae1cf105dc6fb5ad Mon Sep 17 00:00:00 2001 From: npc <131228600+Apursuit@users.noreply.github.com> Date: Mon, 15 Dec 2025 20:23:52 +0800 Subject: [PATCH 12/17] Fix su bruteforce false positives on BusyBox systems (bbsuid) Fix su bruteforce false positives on BusyBox systems (bbsuid) --- linPEAS/builder/linpeas_parts/functions/su_try_pwd.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/linPEAS/builder/linpeas_parts/functions/su_try_pwd.sh b/linPEAS/builder/linpeas_parts/functions/su_try_pwd.sh index 306e0f5..f7a868d 100644 --- a/linPEAS/builder/linpeas_parts/functions/su_try_pwd.sh +++ b/linPEAS/builder/linpeas_parts/functions/su_try_pwd.sh @@ -1,7 +1,7 @@ # Title: LinPeasBase - su_try_pwd # ID: su_try_pwd # Author: Carlos Polop -# Last Update: 22-08-2023 +# Last Update: 15-12-2025 # Description: Try to login as user using a password # License: GNU GPL # Version: 1.0 @@ -17,7 +17,7 @@ su_try_pwd(){ BFUSER=$1 PASSWORDTRY=$2 trysu=$(echo "$PASSWORDTRY" | timeout 1 su $BFUSER -c whoami 2>/dev/null) - if [ "$trysu" ]; then + if [ $? -eq 0 ]; then echo " You can login as $BFUSER using password: $PASSWORDTRY" | sed -${E} "s,.*,${SED_RED_YELLOW}," fi -} \ No newline at end of file +} From 4abbf37cc08c8578defd72ce944e664b4d60d5d0 Mon Sep 17 00:00:00 2001 From: JohannesLks Date: Thu, 1 Jan 2026 14:07:08 +0100 Subject: [PATCH 13/17] fix: SSH key regex false positive with ImageMagick mime.xml The regex '-----BEGIN .* PRIVATE KEY.*-----' was matching '-----BEGIN PGP PRIVATE KEY BLOCK-----' in /etc/ImageMagick-6/mime.xml, causing a false positive for SSH keys. Fixed by removing the trailing .* before ----- so the regex now requires the key header to end directly with -----, which excludes PGP key definitions that have 'BLOCK-----' at the end. Tested key types still detected: - RSA PRIVATE KEY - EC PRIVATE KEY - OPENSSH PRIVATE KEY - DSA PRIVATE KEY --- .../linpeas_parts/7_software_information/Ssh.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/linPEAS/builder/linpeas_parts/7_software_information/Ssh.sh b/linPEAS/builder/linpeas_parts/7_software_information/Ssh.sh index 7ee753c..65947a0 100644 --- a/linPEAS/builder/linpeas_parts/7_software_information/Ssh.sh +++ b/linPEAS/builder/linpeas_parts/7_software_information/Ssh.sh @@ -33,17 +33,17 @@ grep "PermitRootLogin \|ChallengeResponseAuthentication \|PasswordAuthentication if ! [ "$SEARCH_IN_FOLDER" ]; then if [ "$TIMEOUT" ]; then - privatekeyfilesetc=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' /etc 2>/dev/null) - privatekeyfileshome=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' $HOMESEARCH 2>/dev/null) - privatekeyfilesroot=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' /root 2>/dev/null) - privatekeyfilesmnt=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' /mnt 2>/dev/null) + privatekeyfilesetc=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' /etc 2>/dev/null) + privatekeyfileshome=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' $HOMESEARCH 2>/dev/null) + privatekeyfilesroot=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' /root 2>/dev/null) + privatekeyfilesmnt=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' /mnt 2>/dev/null) else - privatekeyfilesetc=$(grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' /etc 2>/dev/null) #If there is tons of files linpeas gets frozen here without a timeout - privatekeyfileshome=$(grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' $HOME/.ssh 2>/dev/null) + privatekeyfilesetc=$(grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' /etc 2>/dev/null) #If there is tons of files linpeas gets frozen here without a timeout + privatekeyfileshome=$(grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' $HOME/.ssh 2>/dev/null) fi else # If $SEARCH_IN_FOLDER lets just search for private keys in the whole firmware - privatekeyfilesetc=$(timeout 120 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' "$ROOT_FOLDER" 2>/dev/null) + privatekeyfilesetc=$(timeout 120 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' "$ROOT_FOLDER" 2>/dev/null) fi if [ "$privatekeyfilesetc" ] || [ "$privatekeyfileshome" ] || [ "$privatekeyfilesroot" ] || [ "$privatekeyfilesmnt" ] ; then From 9d35195c56f9f0487640d93bc7be1d39f1e79b8c Mon Sep 17 00:00:00 2001 From: JohannesLks Date: Thu, 1 Jan 2026 22:53:40 +0100 Subject: [PATCH 14/17] fix: Highlight stored credentials in RDCMan.settings RDCMan.settings files can contain encrypted credentials in credentialsProfiles sections. This change enables content inspection to highlight: - credentialsProfiles (indicates stored credentials) - password (encrypted password value) - encryptedPassword (alternative password field) Previously, just_list_file only showed the file path without inspecting contents, causing stored credentials to be missed. --- build_lists/sensitive_files.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_lists/sensitive_files.yaml b/build_lists/sensitive_files.yaml index 0310714..5380830 100644 --- a/build_lists/sensitive_files.yaml +++ b/build_lists/sensitive_files.yaml @@ -3546,7 +3546,7 @@ search: - name: "RDCMan.settings" value: - just_list_file: True + bad_regex: "credentialsProfiles|password|encryptedPassword" type: f search_in: - common From 72dbd9ef28edbaf27f01e4b2361cfd3cd9bbd742 Mon Sep 17 00:00:00 2001 From: Carlos Polop Date: Fri, 16 Jan 2026 17:56:34 +0100 Subject: [PATCH 15/17] Fix PR tests Go setup and update linpeas parts --- .github/workflows/PR-tests.yml | 5 +- .../1_system_information/16_Protections.sh | 78 ++++++++++++++++++- .../1_system_information/17_Kernel_Modules.sh | 20 ++++- .../14_Writable_files_owner_all.sh | 2 +- 4 files changed, 99 insertions(+), 6 deletions(-) diff --git a/.github/workflows/PR-tests.yml b/.github/workflows/PR-tests.yml index 1536b85..5a58e73 100644 --- a/.github/workflows/PR-tests.yml +++ b/.github/workflows/PR-tests.yml @@ -110,10 +110,9 @@ jobs: ref: ${{ github.head_ref }} # Setup go - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v6 with: - go-version: 1.17.0-rc1 - stable: false + go-version: '1.23' - run: go version # Build linpeas diff --git a/linPEAS/builder/linpeas_parts/1_system_information/16_Protections.sh b/linPEAS/builder/linpeas_parts/1_system_information/16_Protections.sh index 50901c9..56e967b 100644 --- a/linPEAS/builder/linpeas_parts/1_system_information/16_Protections.sh +++ b/linPEAS/builder/linpeas_parts/1_system_information/16_Protections.sh @@ -80,10 +80,86 @@ print_list "Seccomp enabled? ............... "$NC print_list "User namespace? ................ "$NC if [ "$(cat /proc/self/uid_map 2>/dev/null)" ]; then echo "enabled" | sed "s,enabled,${SED_GREEN},"; else echo "disabled" | sed "s,disabled,${SED_RED},"; fi +#-- SY) Unprivileged user namespaces +print_list "unpriv_userns_clone? ........... "$NC +unpriv_userns_clone=$(cat /proc/sys/kernel/unprivileged_userns_clone 2>/dev/null) +if [ -z "$unpriv_userns_clone" ]; then + echo_not_found "/proc/sys/kernel/unprivileged_userns_clone" +else + if [ "$unpriv_userns_clone" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_GREEN},"; else echo "$unpriv_userns_clone" | sed -${E} "s,.*,${SED_RED},g"; fi +fi + +#-- SY) Unprivileged eBPF +print_list "unpriv_bpf_disabled? ........... "$NC +unpriv_bpf_disabled=$(cat /proc/sys/kernel/unprivileged_bpf_disabled 2>/dev/null) +if [ -z "$unpriv_bpf_disabled" ]; then + echo_not_found "/proc/sys/kernel/unprivileged_bpf_disabled" +else + if [ "$unpriv_bpf_disabled" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$unpriv_bpf_disabled" | sed -${E} "s,.*,${SED_GREEN},g"; fi +fi + #-- SY) cgroup2 print_list "Cgroup2 enabled? ............... "$NC ([ "$(grep cgroup2 /proc/filesystems 2>/dev/null)" ] && echo "enabled" || echo "disabled") | sed "s,disabled,${SED_RED}," | sed "s,enabled,${SED_GREEN}," +#-- SY) Kernel hardening sysctls +print_list "kptr_restrict? ................. "$NC +kptr_restrict=$(cat /proc/sys/kernel/kptr_restrict 2>/dev/null) +if [ -z "$kptr_restrict" ]; then + echo_not_found "/proc/sys/kernel/kptr_restrict" +else + if [ "$kptr_restrict" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$kptr_restrict" | sed -${E} "s,.*,${SED_GREEN},g"; fi +fi + +print_list "dmesg_restrict? ................ "$NC +dmesg_restrict=$(cat /proc/sys/kernel/dmesg_restrict 2>/dev/null) +if [ -z "$dmesg_restrict" ]; then + echo_not_found "/proc/sys/kernel/dmesg_restrict" +else + if [ "$dmesg_restrict" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$dmesg_restrict" | sed -${E} "s,.*,${SED_GREEN},g"; fi +fi + +print_list "ptrace_scope? .................. "$NC +ptrace_scope=$(cat /proc/sys/kernel/yama/ptrace_scope 2>/dev/null) +if [ -z "$ptrace_scope" ]; then + echo_not_found "/proc/sys/kernel/yama/ptrace_scope" +else + if [ "$ptrace_scope" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$ptrace_scope" | sed -${E} "s,.*,${SED_GREEN},g"; fi +fi + +print_list "perf_event_paranoid? ........... "$NC +perf_event_paranoid=$(cat /proc/sys/kernel/perf_event_paranoid 2>/dev/null) +if [ -z "$perf_event_paranoid" ]; then + echo_not_found "/proc/sys/kernel/perf_event_paranoid" +else + if [ "$perf_event_paranoid" -le 1 ]; then echo "$perf_event_paranoid" | sed -${E} "s,.*,${SED_RED},g"; else echo "$perf_event_paranoid" | sed -${E} "s,.*,${SED_GREEN},g"; fi +fi + +print_list "mmap_min_addr? ................. "$NC +mmap_min_addr=$(cat /proc/sys/vm/mmap_min_addr 2>/dev/null) +if [ -z "$mmap_min_addr" ]; then + echo_not_found "/proc/sys/vm/mmap_min_addr" +else + if [ "$mmap_min_addr" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$mmap_min_addr" | sed -${E} "s,.*,${SED_GREEN},g"; fi +fi + +print_list "lockdown mode? ................. "$NC +if [ -f "/sys/kernel/security/lockdown" ]; then + cat /sys/kernel/security/lockdown 2>/dev/null | sed -${E} "s,none,${SED_RED},g; s,integrity|confidentiality,${SED_GREEN},g" +else + echo_not_found "/sys/kernel/security/lockdown" +fi + +#-- SY) Kernel hardening config flags +print_list "Kernel hardening flags? ........ "$NC +if [ -f "/boot/config-$(uname -r)" ]; then + grep -E 'CONFIG_RANDOMIZE_BASE|CONFIG_STACKPROTECTOR|CONFIG_SLAB_FREELIST_|CONFIG_KASAN' /boot/config-$(uname -r) 2>/dev/null +elif [ -f "/proc/config.gz" ]; then + zcat /proc/config.gz 2>/dev/null | grep -E 'CONFIG_RANDOMIZE_BASE|CONFIG_STACKPROTECTOR|CONFIG_SLAB_FREELIST_|CONFIG_KASAN' +else + echo_not_found "kernel config" +fi + #-- SY) Gatekeeper if [ "$MACPEAS" ]; then print_list "Gatekeeper enabled? .......... "$NC @@ -136,4 +212,4 @@ else if [ "$hypervisorflag" ]; then printf $RED"Yes"$NC; else printf $GREEN"No"$NC; fi fi -echo "" \ No newline at end of file +echo "" diff --git a/linPEAS/builder/linpeas_parts/1_system_information/17_Kernel_Modules.sh b/linPEAS/builder/linpeas_parts/1_system_information/17_Kernel_Modules.sh index 41490d6..9beb7e0 100644 --- a/linPEAS/builder/linpeas_parts/1_system_information/17_Kernel_Modules.sh +++ b/linPEAS/builder/linpeas_parts/1_system_information/17_Kernel_Modules.sh @@ -58,5 +58,23 @@ else echo_not_found "/proc/sys/kernel/modules_disabled" fi +# Check for module signature enforcement +print_3title "Module signature enforcement? " +if [ -f "/proc/sys/kernel/module_sig_enforce" ]; then + if [ "$(cat /proc/sys/kernel/module_sig_enforce)" = "1" ]; then + echo "Enforced" | sed -${E} "s,.*,${SED_GREEN},g" + else + echo "Not enforced" | sed -${E} "s,.*,${SED_RED},g" + fi +elif [ -f "/sys/module/module/parameters/sig_enforce" ]; then + if [ "$(cat /sys/module/module/parameters/sig_enforce)" = "Y" ]; then + echo "Enforced" | sed -${E} "s,.*,${SED_GREEN},g" + else + echo "Not enforced" | sed -${E} "s,.*,${SED_RED},g" + fi +else + echo_not_found "module_sig_enforce" +fi -echo "" \ No newline at end of file + +echo "" diff --git a/linPEAS/builder/linpeas_parts/8_interesting_perms_files/14_Writable_files_owner_all.sh b/linPEAS/builder/linpeas_parts/8_interesting_perms_files/14_Writable_files_owner_all.sh index 894d5bf..a6c0918 100644 --- a/linPEAS/builder/linpeas_parts/8_interesting_perms_files/14_Writable_files_owner_all.sh +++ b/linPEAS/builder/linpeas_parts/8_interesting_perms_files/14_Writable_files_owner_all.sh @@ -17,7 +17,7 @@ if ! [ "$IAMROOT" ]; then print_2title "Interesting writable files owned by me or writable by everyone (not in Home) (max 200)" print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#writable-files" #In the next file, you need to specify type "d" and "f" to avoid fake link files apparently writable by all - obmowbe=$(find $ROOT_FOLDER '(' -type f -or -type d ')' '(' '(' -user $USER ')' -or '(' -perm -o=w ')' ')' ! -path "/proc/*" ! -path "/sys/*" ! -path "$HOME/*" 2>/dev/null | grep -Ev "$notExtensions" | sort | uniq | awk -F/ '{line_init=$0; if (!cont){ cont=0 }; $NF=""; act=$0; if (act == pre){(cont += 1)} else {cont=0}; if (cont < 5){ print line_init; } if (cont == "5"){print "#)You_can_write_even_more_files_inside_last_directory\n"}; pre=act }' | head -n 200) + obmowbe=$(find $ROOT_FOLDER '(' -type f -or -type d ')' '(' '(' -user $USER ')' -or '(' -perm -o=w ')' ')' ! -path "/proc/*" ! -path "/sys/*" ! -path "/dev/*" ! -path "/snap/*" ! -path "$HOME/*" 2>/dev/null | grep -Ev "$notExtensions" | sort | uniq | awk -F/ '{line_init=$0; if (!cont){ cont=0 }; $NF=""; act=$0; if (act == pre){(cont += 1)} else {cont=0}; if (cont < 5){ print line_init; } if (cont == "5"){print "#)You_can_write_even_more_files_inside_last_directory\n"}; pre=act }' | head -n 200) printf "%s\n" "$obmowbe" | while read l; do if echo "$l" | grep -q "You_can_write_even_more_files_inside_last_directory"; then printf $ITALIC"$l\n"$NC; elif echo "$l" | grep -qE "$writeVB"; then From 69371f825e7c07100b4c68f834024028eb2d78da Mon Sep 17 00:00:00 2001 From: Carlos Polop Date: Fri, 16 Jan 2026 18:01:40 +0100 Subject: [PATCH 16/17] Fix GTFOBins list fetch for linpeas builder --- linPEAS/builder/src/linpeasBuilder.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/linPEAS/builder/src/linpeasBuilder.py b/linPEAS/builder/src/linpeasBuilder.py index 40c8b51..e11d5e8 100644 --- a/linPEAS/builder/src/linpeasBuilder.py +++ b/linPEAS/builder/src/linpeasBuilder.py @@ -348,8 +348,25 @@ class LinpeasBuilder: return bin_b64 def __get_gtfobins_lists(self) -> tuple: - r = requests.get("https://github.com/GTFOBins/GTFOBins.github.io/tree/master/_gtfobins") - bins = re.findall(r'_gtfobins/([a-zA-Z0-9_ \-]+).md', r.text) + bins = [] + api_url = "https://api.github.com/repos/GTFOBins/GTFOBins.github.io/contents/_gtfobins?per_page=100" + while api_url: + r = requests.get(api_url, timeout=10) + if not r.ok: + break + data = r.json() + for entry in data: + if entry.get("type") == "file" and entry.get("name"): + bins.append(entry["name"]) + api_url = None + link = r.headers.get("Link", "") + for part in link.split(","): + if 'rel="next"' in part: + api_url = part.split(";")[0].strip().strip("<>") + break + if not bins: + r = requests.get("https://github.com/GTFOBins/GTFOBins.github.io/tree/master/_gtfobins", timeout=10) + bins = re.findall(r'_gtfobins/([a-zA-Z0-9_ \-]+)(?:\\.md)?', r.text) sudoVB = [] suidVB = [] @@ -357,12 +374,12 @@ class LinpeasBuilder: for b in bins: try: - rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}.md", timeout=5) + rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}", timeout=5) except: try: - rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}.md", timeout=5) + rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}", timeout=5) except: - rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}.md", timeout=5) + rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}", timeout=5) if "sudo:" in rb.text: if len(b) <= 3: sudoVB.append("[^a-zA-Z0-9]"+b+"$") # Less false possitives applied to small names From 1d4b748cbcf5f6b13f2cf91e229fcfb03d77c20d Mon Sep 17 00:00:00 2001 From: Carlos Polop Date: Fri, 16 Jan 2026 18:07:04 +0100 Subject: [PATCH 17/17] Fix builder GTFOBins parsing and protections metadata --- .../linpeas_parts/1_system_information/16_Protections.sh | 2 +- linPEAS/builder/src/linpeasBuilder.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/linPEAS/builder/linpeas_parts/1_system_information/16_Protections.sh b/linPEAS/builder/linpeas_parts/1_system_information/16_Protections.sh index 56e967b..95a91b5 100644 --- a/linPEAS/builder/linpeas_parts/1_system_information/16_Protections.sh +++ b/linPEAS/builder/linpeas_parts/1_system_information/16_Protections.sh @@ -30,7 +30,7 @@ # Functions Used: echo_not_found, print_2title, print_list, warn_exec # Global Variables: # Initial Functions: -# Generated Global Variables: $ASLR, $hypervisorflag, $detectedvirt +# Generated Global Variables: $ASLR, $hypervisorflag, $detectedvirt, $unpriv_userns_clone, $perf_event_paranoid, $mmap_min_addr, $ptrace_scope, $dmesg_restrict, $kptr_restrict, $unpriv_bpf_disabled # Fat linpeas: 0 # Small linpeas: 0 diff --git a/linPEAS/builder/src/linpeasBuilder.py b/linPEAS/builder/src/linpeasBuilder.py index e11d5e8..d53eec7 100644 --- a/linPEAS/builder/src/linpeasBuilder.py +++ b/linPEAS/builder/src/linpeasBuilder.py @@ -115,7 +115,7 @@ class LinpeasBuilder: suidVB, sudoVB, capsVB = self.__get_gtfobins_lists() assert len(suidVB) > 185, f"Len suidVB is {len(suidVB)}" assert len(sudoVB) > 250, f"Len sudo is {len(sudoVB)}" - assert len(capsVB) > 10, f"Len suidVB is {len(capsVB)}" + assert len(capsVB) > 2, f"Len capsVB is {len(capsVB)}" self.__replace_mark(SUIDVB1_MARKUP, suidVB[:int(len(suidVB)/2)], "|") self.__replace_mark(SUIDVB2_MARKUP, suidVB[int(len(suidVB)/2):], "|")