From 74521345f62daddea361f3afc1e28dafe3bc75af Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sat, 13 Dec 2025 18:41:50 +0000 Subject: [PATCH 1/9] Add linpeas privilege escalation checks from: HTB WhiteRabbit: n8n HMAC Forgery, SQL Injection, restic Abuse, and Time-Seeded --- linPEAS/README.md | 4 ++ .../6_users_information/19_Sudo_restic.sh | 71 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 linPEAS/builder/linpeas_parts/6_users_information/19_Sudo_restic.sh diff --git a/linPEAS/README.md b/linPEAS/README.md index 620608d..27de3de 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 updates + +- **Dec 2025**: Added detection for sudo configurations that expose restic's `--password-command` helper, a common privilege escalation vector observed in real environments. + 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/6_users_information/19_Sudo_restic.sh b/linPEAS/builder/linpeas_parts/6_users_information/19_Sudo_restic.sh new file mode 100644 index 0000000..e141db0 --- /dev/null +++ b/linPEAS/builder/linpeas_parts/6_users_information/19_Sudo_restic.sh @@ -0,0 +1,71 @@ +# Title: Users Information - Sudo restic password-command abuse +# ID: UG_Sudo_restic +# Author: HT Bot +# Last Update: 13-12-2025 +# Description: Detect sudo configurations that allow abusing restic --password-command for privilege escalation +# License: GNU GPL +# Version: 1.0 +# Functions Used: echo_not_found, print_2title, print_info +# Global Variables: $PASSWORD +# Initial Functions: +# Generated Global Variables: $restic_bin, $restic_sudo_found, $sudo_no_pw_output, $sudo_with_pw_output, $matches, $sudo_file, $block, $origin +# Fat linpeas: 0 +# Small linpeas: 1 + + +print_2title "Checking sudo restic --password-command exposure" +print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#sudo-and-suid" + +restic_bin="$(command -v restic 2>/dev/null)" +if [ -n "$restic_bin" ]; then + echo "restic binary found at: $restic_bin" | sed -${E} "s,.*,${SED_LIGHT_CYAN},g" +else + echo "restic binary not found in PATH (still checking sudoers rules)" | sed -${E} "s,.*,${SED_YELLOW},g" +fi + +restic_sudo_found="" + +check_restic_entries() { + local block="$1" + local origin="$2" + + if [ -n "$block" ]; then + local matches + matches="$(printf '%s\n' "$block" | grep -i "restic" 2>/dev/null)" + if [ -n "$matches" ]; then + restic_sudo_found=1 + echo "$origin" | sed -${E} "s,.*,${SED_LIGHT_CYAN},g" + printf '%s\n' "$matches" | sed -${E} "s,.*,${SED_RED_YELLOW},g" + fi + fi +} + +sudo_no_pw_output="$(sudo -n -l 2>/dev/null)" +check_restic_entries "$sudo_no_pw_output" "Matches in 'sudo -n -l'" + +if [ -n "$PASSWORD" ]; then + sudo_with_pw_output="$(echo "$PASSWORD" | timeout 1 sudo -S -l 2>/dev/null)" + check_restic_entries "$sudo_with_pw_output" "Matches in 'sudo -l' using provided password" +fi + +if [ -r "/etc/sudoers" ]; then + check_restic_entries "$(grep -v '^#' /etc/sudoers 2>/dev/null)" "Matches in /etc/sudoers" +fi + +if [ -d "/etc/sudoers.d" ]; then + for sudo_file in /etc/sudoers.d/*; do + [ -f "$sudo_file" ] || continue + check_restic_entries "$(grep -v '^#' "$sudo_file" 2>/dev/null)" "Matches in $sudo_file" + done +fi + +if [ -n "$restic_sudo_found" ]; then + echo "" + echo "restic's --password-command runs as the sudo target user (root)." | sed -${E} "s,.*,${SED_RED},g" + echo "Example: sudo restic check --password-command 'cp /bin/bash /tmp/restic-root && chmod 6777 /tmp/restic-root'" | sed -${E} "s,.*,${SED_RED_YELLOW},g" + echo "Then execute /tmp/restic-root -p for a root shell." | sed -${E} "s,.*,${SED_RED_YELLOW},g" +else + echo_not_found "sudo restic" +fi + +echo "" From 85aa98a84177b2ff28b5bc4056d995a938287724 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Tue, 16 Dec 2025 19:11:20 +0000 Subject: [PATCH 2/9] Add winpeas privilege escalation checks from: Inside Ink Dragon: Revealing the Relay Network and Inner Workings of a Stealthy --- winPEAS/winPEASexe/README.md | 1 + winPEAS/winPEASexe/winPEAS/Checks/UserInfo.cs | 54 +++++++++++++- .../winPEAS/Info/UserInfo/UserInfoHelper.cs | 73 +++++++++++++++++++ 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/winPEAS/winPEASexe/README.md b/winPEAS/winPEASexe/README.md index 8dd211c..877b514 100755 --- a/winPEAS/winPEASexe/README.md +++ b/winPEAS/winPEASexe/README.md @@ -76,6 +76,7 @@ The goal of this project is to search for possible **Privilege Escalation Paths* New in this version: - Detect potential GPO abuse by flagging writable SYSVOL paths for GPOs applied to the current host and by highlighting membership in the "Group Policy Creator Owners" group. +- Highlight disconnected high-privilege RDP sessions so you can plan LSASS/token theft when admins leave idle sessions (Ink Dragon-style escalation). It should take only a **few seconds** to execute almost all the checks and **some seconds/minutes during the lasts checks searching for known filenames** that could contain passwords (the time depened on the number of files in your home folder). By default only **some** filenames that could contain credentials are searched, you can use the **searchall** parameter to search all the list (this could will add some minutes). diff --git a/winPEAS/winPEASexe/winPEAS/Checks/UserInfo.cs b/winPEAS/winPEASexe/winPEAS/Checks/UserInfo.cs index 350e525..0804ff4 100644 --- a/winPEAS/winPEASexe/winPEAS/Checks/UserInfo.cs +++ b/winPEAS/winPEASexe/winPEAS/Checks/UserInfo.cs @@ -156,15 +156,63 @@ namespace winPEAS.Checks try { Beaprint.MainPrint("RDP Sessions"); + Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/credentials-mgmt/rdp-sessions", "Disconnected high-privilege RDP sessions keep reusable tokens inside LSASS."); List> rdp_sessions = UserInfoHelper.GetRDPSessions(); if (rdp_sessions.Count > 0) { - string format = " {0,-10}{1,-15}{2,-15}{3,-25}{4,-10}{5}"; - string header = string.Format(format, "SessID", "pSessionName", "pUserName", "pDomainName", "State", "SourceIP"); + string format = " {0,-8}{1,-15}{2,-20}{3,-22}{4,-15}{5,-18}{6,-10}"; + string header = string.Format(format, "SessID", "Session", "User", "Domain", "State", "SourceIP", "HighPriv"); Beaprint.GrayPrint(header); + var colors = ColorsU(); + List> flaggedSessions = new List>(); foreach (Dictionary rdpSes in rdp_sessions) { - Beaprint.AnsiPrint(string.Format(format, rdpSes["SessionID"], rdpSes["pSessionName"], rdpSes["pUserName"], rdpSes["pDomainName"], rdpSes["State"], rdpSes["SourceIP"]), ColorsU()); + rdpSes.TryGetValue("SessionID", out string sessionId); + rdpSes.TryGetValue("pSessionName", out string sessionName); + rdpSes.TryGetValue("pUserName", out string userName); + rdpSes.TryGetValue("pDomainName", out string domainName); + rdpSes.TryGetValue("State", out string state); + rdpSes.TryGetValue("SourceIP", out string sourceIp); + + sessionId = sessionId ?? string.Empty; + sessionName = sessionName ?? string.Empty; + userName = userName ?? string.Empty; + domainName = domainName ?? string.Empty; + state = state ?? string.Empty; + sourceIp = sourceIp ?? string.Empty; + + bool isHighPriv = UserInfoHelper.IsHighPrivilegeAccount(userName, domainName); + string highPrivLabel = isHighPriv ? "Yes" : "No"; + rdpSes["HighPriv"] = highPrivLabel; + + if (isHighPriv && string.Equals(state, "Disconnected", StringComparison.OrdinalIgnoreCase)) + { + flaggedSessions.Add(rdpSes); + } + + Beaprint.AnsiPrint(string.Format(format, sessionId, sessionName, userName, domainName, state, sourceIp, highPrivLabel), colors); + } + + if (flaggedSessions.Count > 0) + { + Beaprint.BadPrint(" [!] Disconnected high-privilege RDP sessions detected. Their credentials/tokens stay in LSASS until the user signs out."); + foreach (Dictionary session in flaggedSessions) + { + session.TryGetValue("pDomainName", out string flaggedDomain); + session.TryGetValue("pUserName", out string flaggedUser); + session.TryGetValue("SessionID", out string flaggedSessionId); + session.TryGetValue("SourceIP", out string flaggedIp); + + flaggedDomain = flaggedDomain ?? string.Empty; + flaggedUser = flaggedUser ?? string.Empty; + flaggedSessionId = flaggedSessionId ?? string.Empty; + flaggedIp = flaggedIp ?? string.Empty; + + string userDisplay = string.Format("{0}\\{1}", flaggedDomain, flaggedUser).Trim('\\'); + string source = string.IsNullOrEmpty(flaggedIp) ? "local" : flaggedIp; + Beaprint.BadPrint(string.Format(" -> Session {0} ({1}) from {2}", flaggedSessionId, userDisplay, source)); + } + Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/credentials-mgmt/rdp-sessions", "Dump LSASS / steal tokens (e.g., comsvcs.dll, LsaLogonSessions, custom SSPs) to reuse those privileges."); } } else diff --git a/winPEAS/winPEASexe/winPEAS/Info/UserInfo/UserInfoHelper.cs b/winPEAS/winPEASexe/winPEAS/Info/UserInfo/UserInfoHelper.cs index 06de121..1a2e3bd 100644 --- a/winPEAS/winPEASexe/winPEAS/Info/UserInfo/UserInfoHelper.cs +++ b/winPEAS/winPEASexe/winPEAS/Info/UserInfo/UserInfoHelper.cs @@ -16,6 +16,20 @@ namespace winPEAS.Info.UserInfo { class UserInfoHelper { + private static readonly Dictionary _highPrivAccountCache = new Dictionary(StringComparer.OrdinalIgnoreCase); + private static readonly string[] _highPrivGroupIndicators = new string[] + { + "administrators", + "domain admins", + "enterprise admins", + "schema admins", + "server operators", + "account operators", + "backup operators", + "dnsadmins", + "hyper-v administrators" + }; + // https://stackoverflow.com/questions/5247798/get-list-of-local-computer-usernames-in-windows @@ -91,6 +105,65 @@ namespace winPEAS.Info.UserInfo return oPrincipalContext; } + public static bool IsHighPrivilegeAccount(string userName, string domain) + { + if (string.IsNullOrWhiteSpace(userName)) + { + return false; + } + + string cacheKey = ($"{domain}\\{userName}").Trim('\\'); + if (_highPrivAccountCache.TryGetValue(cacheKey, out bool cached)) + { + return cached; + } + + bool isHighPriv = false; + try + { + string resolvedDomain = string.IsNullOrWhiteSpace(domain) ? Checks.Checks.CurrentUserDomainName : domain; + List groups = User.GetUserGroups(userName, resolvedDomain); + foreach (string group in groups) + { + if (IsHighPrivilegeGroup(group)) + { + isHighPriv = true; + break; + } + } + } + catch (Exception ex) + { + Beaprint.GrayPrint(string.Format(" [-] Unable to resolve groups for {0}\\{1}: {2}", domain, userName, ex.Message)); + } + + if (!isHighPriv) + { + isHighPriv = string.Equals(userName, "administrator", StringComparison.OrdinalIgnoreCase) || userName.StartsWith("admin", StringComparison.OrdinalIgnoreCase); + } + + _highPrivAccountCache[cacheKey] = isHighPriv; + return isHighPriv; + } + + private static bool IsHighPrivilegeGroup(string groupName) + { + if (string.IsNullOrWhiteSpace(groupName)) + { + return false; + } + + foreach (string indicator in _highPrivGroupIndicators) + { + if (groupName.IndexOf(indicator, StringComparison.OrdinalIgnoreCase) >= 0) + { + return true; + } + } + + return false; + } + //From Seatbelt public enum WTS_CONNECTSTATE_CLASS { From 488d38883040162d84980ff231bb6f95fea801c2 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Wed, 17 Dec 2025 01:34:41 +0000 Subject: [PATCH 3/9] Add winpeas privilege escalation checks from: Windows Exploitation Technique: Amplifying Race Windows via Slow Object Manager --- winPEAS/winPEASexe/README.md | 2 ++ .../winPEASexe/winPEAS/Checks/SystemInfo.cs | 26 ++++++++++++++ .../winPEAS/Helpers/ObjectManagerHelper.cs | 34 +++++++++++++++++++ winPEAS/winPEASexe/winPEAS/winPEAS.csproj | 1 + 4 files changed, 63 insertions(+) create mode 100644 winPEAS/winPEASexe/winPEAS/Helpers/ObjectManagerHelper.cs diff --git a/winPEAS/winPEASexe/README.md b/winPEAS/winPEASexe/README.md index 8dd211c..91f7ef5 100755 --- a/winPEAS/winPEASexe/README.md +++ b/winPEAS/winPEASexe/README.md @@ -77,6 +77,8 @@ The goal of this project is to search for possible **Privilege Escalation Paths* New in this version: - Detect potential GPO abuse by flagging writable SYSVOL paths for GPOs applied to the current host and by highlighting membership in the "Group Policy Creator Owners" group. +- Added Object Manager race-window amplification guidance (Project Zero 2025): winPEAS now checks if the current user can create named objects under \\BaseNamedObjects and reminds you how to build extremely long names/deep directory chains to stretch kernel race windows. + It should take only a **few seconds** to execute almost all the checks and **some seconds/minutes during the lasts checks searching for known filenames** that could contain passwords (the time depened on the number of files in your home folder). By default only **some** filenames that could contain credentials are searched, you can use the **searchall** parameter to search all the list (this could will add some minutes). diff --git a/winPEAS/winPEASexe/winPEAS/Checks/SystemInfo.cs b/winPEAS/winPEASexe/winPEAS/Checks/SystemInfo.cs index 344fd13..e72b31d 100644 --- a/winPEAS/winPEASexe/winPEAS/Checks/SystemInfo.cs +++ b/winPEAS/winPEASexe/winPEAS/Checks/SystemInfo.cs @@ -81,6 +81,7 @@ namespace winPEAS.Checks PrintKrbRelayUp, PrintInsideContainer, PrintAlwaysInstallElevated, + PrintObjectManagerRaceAmplification, PrintLSAInfo, PrintNtlmSettings, PrintLocalGroupPolicy, @@ -667,6 +668,31 @@ namespace winPEAS.Checks } } + static void PrintObjectManagerRaceAmplification() + { + try + { + Beaprint.MainPrint("Object Manager race-window amplification primitives"); + Beaprint.LinkPrint("https://projectzero.google/2025/12/windows-exploitation-techniques.html", "Project Zero write-up:"); + + if (ObjectManagerHelper.TryCreateSessionEvent(out var objectName, out var error)) + { + Beaprint.BadPrint($" Created a test named event ({objectName}) under \\BaseNamedObjects."); + Beaprint.InfoPrint(" -> Low-privileged users can slow NtOpen*/NtCreate* lookups using ~32k-character names or ~16k-level directory chains."); + Beaprint.InfoPrint(" -> Point attacker-controlled symbolic links to the slow path to stretch kernel race windows."); + Beaprint.InfoPrint(" -> Use this whenever a bug follows check -> NtOpenX -> privileged action patterns."); + } + else + { + Beaprint.InfoPrint($" Could not create a test event under \\BaseNamedObjects ({error}). The namespace might be locked down."); + } + } + catch (Exception ex) + { + Beaprint.PrintException(ex.Message); + } + } + private static void PrintNtlmSettings() { Beaprint.MainPrint($"Enumerating NTLM Settings"); diff --git a/winPEAS/winPEASexe/winPEAS/Helpers/ObjectManagerHelper.cs b/winPEAS/winPEASexe/winPEAS/Helpers/ObjectManagerHelper.cs new file mode 100644 index 0000000..fdc6df1 --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/Helpers/ObjectManagerHelper.cs @@ -0,0 +1,34 @@ +using System; +using System.Diagnostics; +using System.Threading; + +namespace winPEAS.Helpers +{ + internal static class ObjectManagerHelper + { + public static bool TryCreateSessionEvent(out string objectName, out string error) + { + objectName = $"PEAS_OMNS_{Process.GetCurrentProcess().Id}_{Guid.NewGuid():N}"; + error = string.Empty; + + try + { + using (var handle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, objectName, out var createdNew)) + { + if (!createdNew) + { + error = "A test event with the generated name already existed."; + return false; + } + } + + return true; + } + catch (Exception ex) + { + error = ex.Message; + return false; + } + } + } +} diff --git a/winPEAS/winPEASexe/winPEAS/winPEAS.csproj b/winPEAS/winPEASexe/winPEAS/winPEAS.csproj index 4259210..a499eee 100644 --- a/winPEAS/winPEASexe/winPEAS/winPEAS.csproj +++ b/winPEAS/winPEASexe/winPEAS/winPEAS.csproj @@ -1359,6 +1359,7 @@ + From 2c6cbfa43dfad62af675551c4ca3fc34ca07ee2b Mon Sep 17 00:00:00 2001 From: SirBroccoli Date: Sat, 17 Jan 2026 15:32:29 +0100 Subject: [PATCH 8/9] Updating sudoB.sh with variables information --- linPEAS/builder/linpeas_parts/variables/sudoB.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/linPEAS/builder/linpeas_parts/variables/sudoB.sh b/linPEAS/builder/linpeas_parts/variables/sudoB.sh index bb2362c..39f17dd 100644 --- a/linPEAS/builder/linpeas_parts/variables/sudoB.sh +++ b/linPEAS/builder/linpeas_parts/variables/sudoB.sh @@ -12,5 +12,4 @@ # Fat linpeas: 0 # Small linpeas: 1 - -sudoB="$(whoami)|ALL:ALL|ALL : ALL|ALL|env_keep|NOPASSWD|SETENV|/apache2|/cryptsetup|/mount" \ No newline at end of file +sudoB="$(whoami)|ALL:ALL|ALL : ALL|ALL|env_keep|NOPASSWD|SETENV|/apache2|/cryptsetup|/mount|/restic|--password-command|--password-file|-o ProxyCommand|-o PreferredAuthentications" \ No newline at end of file From ff21b3dcb95209944346a5c6eca99c315318a437 Mon Sep 17 00:00:00 2001 From: SirBroccoli Date: Sat, 17 Jan 2026 15:34:34 +0100 Subject: [PATCH 9/9] Delete linPEAS/builder/linpeas_parts/6_users_information/19_Sudo_restic.sh --- .../6_users_information/19_Sudo_restic.sh | 71 ------------------- 1 file changed, 71 deletions(-) delete mode 100644 linPEAS/builder/linpeas_parts/6_users_information/19_Sudo_restic.sh diff --git a/linPEAS/builder/linpeas_parts/6_users_information/19_Sudo_restic.sh b/linPEAS/builder/linpeas_parts/6_users_information/19_Sudo_restic.sh deleted file mode 100644 index e141db0..0000000 --- a/linPEAS/builder/linpeas_parts/6_users_information/19_Sudo_restic.sh +++ /dev/null @@ -1,71 +0,0 @@ -# Title: Users Information - Sudo restic password-command abuse -# ID: UG_Sudo_restic -# Author: HT Bot -# Last Update: 13-12-2025 -# Description: Detect sudo configurations that allow abusing restic --password-command for privilege escalation -# License: GNU GPL -# Version: 1.0 -# Functions Used: echo_not_found, print_2title, print_info -# Global Variables: $PASSWORD -# Initial Functions: -# Generated Global Variables: $restic_bin, $restic_sudo_found, $sudo_no_pw_output, $sudo_with_pw_output, $matches, $sudo_file, $block, $origin -# Fat linpeas: 0 -# Small linpeas: 1 - - -print_2title "Checking sudo restic --password-command exposure" -print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#sudo-and-suid" - -restic_bin="$(command -v restic 2>/dev/null)" -if [ -n "$restic_bin" ]; then - echo "restic binary found at: $restic_bin" | sed -${E} "s,.*,${SED_LIGHT_CYAN},g" -else - echo "restic binary not found in PATH (still checking sudoers rules)" | sed -${E} "s,.*,${SED_YELLOW},g" -fi - -restic_sudo_found="" - -check_restic_entries() { - local block="$1" - local origin="$2" - - if [ -n "$block" ]; then - local matches - matches="$(printf '%s\n' "$block" | grep -i "restic" 2>/dev/null)" - if [ -n "$matches" ]; then - restic_sudo_found=1 - echo "$origin" | sed -${E} "s,.*,${SED_LIGHT_CYAN},g" - printf '%s\n' "$matches" | sed -${E} "s,.*,${SED_RED_YELLOW},g" - fi - fi -} - -sudo_no_pw_output="$(sudo -n -l 2>/dev/null)" -check_restic_entries "$sudo_no_pw_output" "Matches in 'sudo -n -l'" - -if [ -n "$PASSWORD" ]; then - sudo_with_pw_output="$(echo "$PASSWORD" | timeout 1 sudo -S -l 2>/dev/null)" - check_restic_entries "$sudo_with_pw_output" "Matches in 'sudo -l' using provided password" -fi - -if [ -r "/etc/sudoers" ]; then - check_restic_entries "$(grep -v '^#' /etc/sudoers 2>/dev/null)" "Matches in /etc/sudoers" -fi - -if [ -d "/etc/sudoers.d" ]; then - for sudo_file in /etc/sudoers.d/*; do - [ -f "$sudo_file" ] || continue - check_restic_entries "$(grep -v '^#' "$sudo_file" 2>/dev/null)" "Matches in $sudo_file" - done -fi - -if [ -n "$restic_sudo_found" ]; then - echo "" - echo "restic's --password-command runs as the sudo target user (root)." | sed -${E} "s,.*,${SED_RED},g" - echo "Example: sudo restic check --password-command 'cp /bin/bash /tmp/restic-root && chmod 6777 /tmp/restic-root'" | sed -${E} "s,.*,${SED_RED_YELLOW},g" - echo "Then execute /tmp/restic-root -p for a root shell." | sed -${E} "s,.*,${SED_RED_YELLOW},g" -else - echo_not_found "sudo restic" -fi - -echo ""