mirror of
https://github.com/peass-ng/PEASS-ng.git
synced 2026-01-06 01:58:07 -08:00
Compare commits
20 Commits
20250904-2
...
update_PEA
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9dc23cf01d | ||
|
|
d75525ebbc | ||
|
|
29d8132d93 | ||
|
|
c16c5de36f | ||
|
|
be3fe91da4 | ||
|
|
b8b4a0fc14 | ||
|
|
7042a182df | ||
|
|
c83eef9cd8 | ||
|
|
e15a1f2e12 | ||
|
|
24e9c54290 | ||
|
|
bdb5c61dad | ||
|
|
ee83c23a74 | ||
|
|
7b36014699 | ||
|
|
6fe8304783 | ||
|
|
262feb9896 | ||
|
|
40cf08af85 | ||
|
|
7c9f431649 | ||
|
|
31bdb339d7 | ||
|
|
bdcebadde0 | ||
|
|
4b3f4aa19e |
@@ -2067,6 +2067,11 @@ search:
|
||||
type: f
|
||||
search_in:
|
||||
- common
|
||||
- name: "private-keys-v1.d/*.key"
|
||||
value:
|
||||
type: f
|
||||
search_in:
|
||||
- common
|
||||
|
||||
- name: "*.gnupg"
|
||||
value:
|
||||
@@ -3949,3 +3954,24 @@ search:
|
||||
type: f
|
||||
search_in:
|
||||
- common
|
||||
|
||||
- name: Crontab-UI
|
||||
value:
|
||||
config:
|
||||
auto_check: True
|
||||
|
||||
files:
|
||||
- name: "crontab.db"
|
||||
value:
|
||||
bad_regex: "-P[[:space:]]+\\S+|--password[[:space:]]+\\S+|[Pp]ass(word)?|[Tt]oken|[Ss]ecret"
|
||||
only_bad_lines: True
|
||||
type: f
|
||||
search_in:
|
||||
- common
|
||||
|
||||
- name: "crontab-ui.service"
|
||||
value:
|
||||
just_list_file: True
|
||||
type: f
|
||||
search_in:
|
||||
- common
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Title: Container - Writable bind mounts without nosuid (SUID risk)
|
||||
# ID: CT_RW_bind_mounts_nosuid
|
||||
# Author: HT Bot
|
||||
# Last Update: 17-09-2025
|
||||
# Description: Detect writable bind-mounted paths inside containers that are not mounted with nosuid.
|
||||
# If the container user is root and the mount is a host bind mount without nosuid, an attacker may
|
||||
# be able to drop a SUID binary on the shared path and execute it from the host to escalate to root
|
||||
# (classic container-to-host breakout via writable bind mount).
|
||||
# License: GNU GPL
|
||||
# Version: 1.0
|
||||
# Functions Used: containerCheck, print_2title, print_list, print_info
|
||||
# Global Variables: $inContainer
|
||||
# Initial Functions: containerCheck
|
||||
# Generated Global Variables: $CT_RW_bind_mounts_matches
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
containerCheck
|
||||
|
||||
if [ "$inContainer" ]; then
|
||||
echo ""
|
||||
print_2title "Container - Writable bind mounts w/o nosuid (SUID persistence risk)"
|
||||
print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation/index.html#writable-bind-mounts"
|
||||
|
||||
if [ -r /proc/self/mountinfo ]; then
|
||||
CT_RW_bind_mounts_matches=$(grep -E "(^| )bind( |$)" /proc/self/mountinfo 2>/dev/null | grep -E "(^|,)rw(,|$)" | grep -v "nosuid" || true)
|
||||
else
|
||||
CT_RW_bind_mounts_matches=$(mount -l 2>/dev/null | grep -E "bind" | grep -E "(^|,)rw(,|$)" | grep -v "nosuid" || true)
|
||||
fi
|
||||
|
||||
if [ -z "$CT_RW_bind_mounts_matches" ]; then
|
||||
print_list "Writable bind mounts without nosuid ............ No"
|
||||
else
|
||||
print_list "Writable bind mounts without nosuid ............ Yes" | sed -${E} "s,Yes,${SED_RED},"
|
||||
echo "$CT_RW_bind_mounts_matches" | sed -${E} "s,/proc/self/mountinfo,${SED_GREEN},"
|
||||
echo ""
|
||||
if [ "$(id -u 2>/dev/null)" = "0" ]; then
|
||||
print_list "Note"; echo ": You are root inside a container and there are writable bind mounts without nosuid." | sed -${E} "s,.*,${SED_RED},"
|
||||
echo " If the path is shared with the host and executable there, you may plant a SUID binary (e.g., copy /bin/bash and chmod 6777)"
|
||||
echo " and execute it from the host to obtain root. Ensure proper authorization before testing."
|
||||
else
|
||||
print_list "Note"; echo ": Current user is not root; if you obtain container root, these mounts may enable host escalation via SUID planting." | sed -${E} "s,.*,${SED_RED},"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
@@ -0,0 +1,126 @@
|
||||
# Title: Processes & Cron & Services & Timers - Crontab UI (root) Misconfiguration
|
||||
# ID: PR_Crontab_UI_misconfig
|
||||
# Author: HT Bot
|
||||
# Last Update: 2025-09-13
|
||||
# Description: Detect Crontab UI service and risky configurations that can lead to privesc:
|
||||
# - Root-run Crontab UI exposed on localhost
|
||||
# - Basic-Auth credentials in systemd Environment= (BASIC_AUTH_USER/PWD)
|
||||
# - Cron DB path (CRON_DB_PATH) and weak permissions / embedded secrets in jobs
|
||||
# License: GNU GPL
|
||||
# Version: 1.0
|
||||
# Functions Used: print_2title, print_info, print_list, echo_not_found
|
||||
# Global Variables: $SEARCH_IN_FOLDER, $SED_RED, $SED_RED_YELLOW, $NC
|
||||
# Initial Functions:
|
||||
# Generated Global Variables: $svc, $state, $user, $envvals, $port, $dbpath, $dbfile, $candidates, $procs, $perms, $basic_user, $basic_pwd, $uprint, $pprint, $dir, $found
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
if ! [ "$SEARCH_IN_FOLDER" ]; then
|
||||
print_2title "Crontab UI (root) misconfiguration checks"
|
||||
print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#scheduledcron-jobs"
|
||||
|
||||
# Collect candidate services referencing crontab-ui
|
||||
candidates=""
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
candidates=$(systemctl list-units --type=service --all 2>/dev/null | awk '{print $1}' | grep -Ei '^crontab-ui\.service$' 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Fallback: grep service files for ExecStart containing crontab-ui
|
||||
if [ -z "$candidates" ]; then
|
||||
for dir in /etc/systemd/system /lib/systemd/system; do
|
||||
[ -d "$dir" ] || continue
|
||||
found=$(grep -RIl "^Exec(Start|StartPre|StartPost)=.*crontab-ui" "$dir" 2>/dev/null | xargs -r -I{} basename {} 2>/dev/null)
|
||||
if [ -n "$found" ]; then
|
||||
candidates=$(printf "%s\n%s" "$candidates" "$found" | sort -u)
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Also flag if the binary exists or a process seems to be running
|
||||
if command -v crontab-ui >/dev/null 2>&1; then
|
||||
print_list "crontab-ui binary found at: $(command -v crontab-ui)"$NC
|
||||
else
|
||||
echo_not_found "crontab-ui"
|
||||
fi
|
||||
|
||||
procs=$(ps aux 2>/dev/null | grep -E "(crontab-ui|node .*crontab-ui)" | grep -v grep)
|
||||
if [ -n "$procs" ]; then
|
||||
print_list "Processes matching crontab-ui? ..................... "$NC
|
||||
printf "%s\n" "$procs"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# If no candidates detected, exit quietly
|
||||
if [ "$candidates" ]; then
|
||||
|
||||
# Iterate candidates and extract interesting data
|
||||
printf "%s\n" "$candidates" | while read -r svc; do
|
||||
[ -n "$svc" ] || continue
|
||||
# Ensure suffix .service if missing
|
||||
case "$svc" in
|
||||
*.service) : ;;
|
||||
*) svc="$svc.service" ;;
|
||||
esac
|
||||
|
||||
state=""
|
||||
user=""
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
state=$(systemctl is-active "$svc" 2>/dev/null)
|
||||
user=$(systemctl show "$svc" -p User 2>/dev/null | cut -d= -f2)
|
||||
fi
|
||||
|
||||
[ -z "$state" ] && state="unknown"
|
||||
[ -z "$user" ] && user="unknown"
|
||||
|
||||
echo "Service: $svc (state: $state, User: $user)" | sed -${E} "s,root,${SED_RED},g"
|
||||
|
||||
# Read Environment from systemd (works even if file unreadable in many setups)
|
||||
envvals=$(systemctl show "$svc" -p Environment 2>/dev/null | cut -d= -f2-)
|
||||
if [ -n "$envvals" ]; then
|
||||
basic_user=$(printf "%s\n" "$envvals" | tr ' ' '\n' | grep -E '^BASIC_AUTH_USER=' | head -n1 | cut -d= -f2-)
|
||||
basic_pwd=$(printf "%s\n" "$envvals" | tr ' ' '\n' | grep -E '^BASIC_AUTH_PWD=' | head -n1 | cut -d= -f2-)
|
||||
dbpath=$(printf "%s\n" "$envvals" | tr ' ' '\n' | grep -E '^CRON_DB_PATH=' | head -n1 | cut -d= -f2-)
|
||||
port=$(printf "%s\n" "$envvals" | tr ' ' '\n' | grep -E '^PORT=' | head -n1 | cut -d= -f2-)
|
||||
|
||||
if [ -n "$basic_user" ] || [ -n "$basic_pwd" ]; then
|
||||
uprint="$basic_user"
|
||||
pprint="$basic_pwd"
|
||||
[ -n "$basic_pwd" ] && pprint="$basic_pwd"
|
||||
echo " └─ Basic-Auth credentials in Environment: user='${uprint}' pwd='${pprint}'" | sed -${E} "s,pwd='[^']*',${SED_RED_YELLOW},g"
|
||||
fi
|
||||
|
||||
if [ -n "$dbpath" ]; then
|
||||
echo " └─ CRON_DB_PATH: $dbpath"
|
||||
fi
|
||||
|
||||
# Check listener bound to localhost
|
||||
[ -z "$port" ] && port=8000
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
if ss -ltn 2>/dev/null | grep -qE "127\.0\.0\.1:${port}[[:space:]]"; then
|
||||
echo " └─ Listener detected on 127.0.0.1:${port} (likely Crontab UI)."
|
||||
fi
|
||||
else
|
||||
if netstat -tnl 2>/dev/null | grep -qE "127\.0\.0\.1:${port}[[:space:]]"; then
|
||||
echo " └─ Listener detected on 127.0.0.1:${port} (likely Crontab UI)."
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we know DB path, try to read crontab.db for obvious secrets and check perms
|
||||
if [ -n "$dbpath" ] && [ -d "$dbpath" ] && [ -r "$dbpath" ]; then
|
||||
dbfile="$dbpath/crontab.db"
|
||||
if [ -f "$dbfile" ]; then
|
||||
perms=$(ls -ld "$dbpath" 2>/dev/null | awk '{print $1, $3, $4}')
|
||||
echo " └─ DB dir perms: $perms"
|
||||
if [ -w "$dbpath" ] || [ -w "$dbfile" ]; then
|
||||
echo " └─ Writable by current user -> potential job injection!" | sed -${E} "s,.*,${SED_RED},g"
|
||||
fi
|
||||
echo " └─ Inspecting $dbfile for embedded secrets in commands (zip -P / --password / pass/token/secret)..."
|
||||
grep -E "-P[[:space:]]+\S+|--password[[:space:]]+\S+|[Pp]ass(word)?|[Tt]oken|[Ss]ecret" "$dbfile" 2>/dev/null | head -n 20 | sed -${E} "s,(${SED_RED_YELLOW}),\1,g"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
# Small linpeas: 1
|
||||
|
||||
|
||||
sudoVB1=" \*|env_keep\W*\+=.*LD_PRELOAD|env_keep\W*\+=.*LD_LIBRARY_PATH|peass{SUDOVB1_HERE}"
|
||||
sudoVB2="peass{SUDOVB2_HERE}"
|
||||
sudoVB1=" \*|env_keep\W*\+=.*LD_PRELOAD|env_keep\W*\+=.*LD_LIBRARY_PATH|env_keep\W*\+=.*BASH_ENV|env_keep\W*\+=.* ENV|peass{SUDOVB1_HERE}"
|
||||
sudoVB2="peass{SUDOVB2_HERE}"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Title: Variables - sudovB
|
||||
# ID: sudovB
|
||||
# Author: Carlos Polop
|
||||
# Last Update: 22-08-2023
|
||||
# Last Update: 04-10-2025
|
||||
# Description: Sudo version bad regex
|
||||
# License: GNU GPL
|
||||
# Version: 1.0
|
||||
@@ -13,4 +13,4 @@
|
||||
# Small linpeas: 1
|
||||
|
||||
|
||||
sudovB="[01].[012345678].[0-9]+|1.9.[01234][^0-9]|1.9.[01234]$|1.9.5p1"
|
||||
sudovB="[01].[012345678].[0-9]+|1.9.[01234][^0-9]|1.9.[01234]$|1.9.5p1|1\.9\.[6-9]|1\.9\.1[0-7]"
|
||||
|
||||
@@ -69,57 +69,62 @@ ECHO.
|
||||
CALL :T_Progress 2
|
||||
|
||||
:ListHotFixes
|
||||
wmic qfe get Caption,Description,HotFixID,InstalledOn | more
|
||||
where wmic >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
wmic qfe get Caption,Description,HotFixID,InstalledOn | more
|
||||
) else (
|
||||
powershell -command "Get-HotFix | Format-Table -AutoSize"
|
||||
)
|
||||
set expl=no
|
||||
for /f "tokens=3-9" %%a in ('systeminfo') do (ECHO."%%a %%b %%c %%d %%e %%f %%g" | findstr /i "2000 XP 2003 2008 vista" && set expl=yes) & (ECHO."%%a %%b %%c %%d %%e %%f %%g" | findstr /i /C:"windows 7" && set expl=yes)
|
||||
IF "%expl%" == "yes" ECHO. [i] Possible exploits (https://github.com/codingo/OSCP-2/blob/master/Windows/WinPrivCheck.bat)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB2592799" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB2592799" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS11-080 patch is NOT installed! (Vulns: XP/SP3,2K3/SP3-afd.sys)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB3143141" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB3143141" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS16-032 patch is NOT installed! (Vulns: 2K8/SP1/2,Vista/SP2,7/SP1-secondary logon)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB2393802" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB2393802" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS11-011 patch is NOT installed! (Vulns: XP/SP2/3,2K3/SP2,2K8/SP2,Vista/SP1/2,7/SP0-WmiTraceMessageVa)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB982799" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB982799" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS10-59 patch is NOT installed! (Vulns: 2K8,Vista,7/SP0-Chimichurri)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB979683" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB979683" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS10-21 patch is NOT installed! (Vulns: 2K/SP4,XP/SP2/3,2K3/SP2,2K8/SP2,Vista/SP0/1/2,7/SP0-Win Kernel)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB2305420" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB2305420" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS10-092 patch is NOT installed! (Vulns: 2K8/SP0/1/2,Vista/SP1/2,7/SP0-Task Sched)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB981957" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB981957" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS10-073 patch is NOT installed! (Vulns: XP/SP2/3,2K3/SP2/2K8/SP2,Vista/SP1/2,7/SP0-Keyboard Layout)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB4013081" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB4013081" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS17-017 patch is NOT installed! (Vulns: 2K8/SP2,Vista/SP2,7/SP1-Registry Hive Loading)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB977165" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB977165" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS10-015 patch is NOT installed! (Vulns: 2K,XP,2K3,2K8,Vista,7-User Mode to Ring)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB941693" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB941693" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS08-025 patch is NOT installed! (Vulns: 2K/SP4,XP/SP2,2K3/SP1/2,2K8/SP0,Vista/SP0/1-win32k.sys)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB920958" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB920958" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS06-049 patch is NOT installed! (Vulns: 2K/SP4-ZwQuerySysInfo)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB914389" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB914389" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS06-030 patch is NOT installed! (Vulns: 2K,XP/SP2-Mrxsmb.sys)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB908523" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB908523" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS05-055 patch is NOT installed! (Vulns: 2K/SP4-APC Data-Free)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB890859" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB890859" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS05-018 patch is NOT installed! (Vulns: 2K/SP3/4,XP/SP1/2-CSRSS)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB842526" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB842526" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS04-019 patch is NOT installed! (Vulns: 2K/SP2/3/4-Utility Manager)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB835732" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB835732" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS04-011 patch is NOT installed! (Vulns: 2K/SP2/3/4,XP/SP0/1-LSASS service BoF)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB841872" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB841872" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS04-020 patch is NOT installed! (Vulns: 2K/SP4-POSIX)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB2975684" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB2975684" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS14-040 patch is NOT installed! (Vulns: 2K3/SP2,2K8/SP2,Vista/SP2,7/SP1-afd.sys Dangling Pointer)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB3136041" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB3136041" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS16-016 patch is NOT installed! (Vulns: 2K8/SP1/2,Vista/SP2,7/SP1-WebDAV to Address)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB3057191" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB3057191" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS15-051 patch is NOT installed! (Vulns: 2K3/SP2,2K8/SP2,Vista/SP2,7/SP1-win32k.sys)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB2989935" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB2989935" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS14-070 patch is NOT installed! (Vulns: 2K3/SP2-TCP/IP)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB2778930" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB2778930" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS13-005 patch is NOT installed! (Vulns: Vista,7,8,2008,2008R2,2012,RT-hwnd_broadcast)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB2850851" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB2850851" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS13-053 patch is NOT installed! (Vulns: 7SP0/SP1_x86-schlamperei)
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB2870008" 1>NUL
|
||||
IF "%expl%" == "yes" wmic qfe get Caption,Description,HotFixID,InstalledOn 2>nul | findstr /C:"KB2870008" 1>NUL
|
||||
IF "%expl%" == "yes" IF errorlevel 1 ECHO.MS13-081 patch is NOT installed! (Vulns: 7SP0/SP1_x86-track_popup_menu)
|
||||
ECHO.
|
||||
CALL :T_Progress 2
|
||||
@@ -197,7 +202,12 @@ CALL :T_Progress 1
|
||||
|
||||
:AVSettings
|
||||
CALL :ColorLine " %E%33m[+]%E%97m Registered Anti-Virus(AV)"
|
||||
WMIC /Node:localhost /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct Get displayName /Format:List | more
|
||||
where wmic >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
WMIC /Node:localhost /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct Get displayName /Format:List | more
|
||||
) else (
|
||||
powershell -command "Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntiVirusProduct | Select-Object -ExpandProperty displayName"
|
||||
)
|
||||
ECHO.Checking for defender whitelisted PATHS
|
||||
reg query "HKLM\SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths" 2>nul
|
||||
CALL :T_Progress 1
|
||||
@@ -226,7 +236,12 @@ CALL :T_Progress 3
|
||||
:MountedDisks
|
||||
CALL :ColorLine " %E%33m[+]%E%97m MOUNTED DISKS"
|
||||
ECHO. [i] Maybe you find something interesting
|
||||
(wmic logicaldisk get caption 2>nul | more) || (fsutil fsinfo drives 2>nul)
|
||||
where wmic >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
wmic logicaldisk get caption | more
|
||||
) else (
|
||||
fsutil fsinfo drives
|
||||
)
|
||||
ECHO.
|
||||
CALL :T_Progress 1
|
||||
|
||||
@@ -273,15 +288,29 @@ tasklist /SVC
|
||||
ECHO.
|
||||
CALL :T_Progress 2
|
||||
ECHO. [i] Checking file permissions of running processes (File backdooring - maybe the same files start automatically when Administrator logs in)
|
||||
for /f "tokens=2 delims='='" %%x in ('wmic process list full^|find /i "executablepath"^|find /i /v "system32"^|find ":"') do (
|
||||
for /f eol^=^"^ delims^=^" %%z in ('ECHO.%%x') do (
|
||||
icacls "%%z" 2>nul | findstr /i "(F) (M) (W) :\\" | findstr /i ":\\ everyone authenticated users todos %username%" && ECHO.
|
||||
where wmic >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
for /f "tokens=2 delims='='" %%x in ('wmic process list full ^|find /i "executablepath"^|find /i /v "system32"^|find ":"') do (
|
||||
for /f eol^=^"^ delims^=^" %%z in ('ECHO.%%x') do (
|
||||
icacls "%%z" 2>nul | findstr /i "(F) (M) (W) :\\" | findstr /i ":\\ everyone authenticated users todos %username%" && ECHO.
|
||||
)
|
||||
)
|
||||
) else (
|
||||
for /f "tokens=*" %%x in ('powershell -command "Get-Process | Where-Object {$_.Path -and $_.Path -notlike '*system32*'} | Select-Object -ExpandProperty Path -Unique"') do (
|
||||
icacls "%%x" 2>nul | findstr /i "(F) (M) (W) :\\" | findstr /i ":\\ everyone authenticated users todos %username%" && ECHO.
|
||||
)
|
||||
)
|
||||
ECHO.
|
||||
ECHO. [i] Checking directory permissions of running processes (DLL injection)
|
||||
for /f "tokens=2 delims='='" %%x in ('wmic process list full^|find /i "executablepath"^|find /i /v "system32"^|find ":"') do for /f eol^=^"^ delims^=^" %%y in ('ECHO.%%x') do (
|
||||
icacls "%%~dpy\" 2>nul | findstr /i "(F) (M) (W) :\\" | findstr /i ":\\ everyone authenticated users todos %username%" && ECHO.
|
||||
where wmic >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
for /f "tokens=2 delims='='" %%x in ('wmic process list full ^|find /i "executablepath"^|find /i /v "system32"^|find ":"') do for /f eol^=^"^ delims^=^" %%y in ('ECHO.%%x') do (
|
||||
icacls "%%~dpy\" 2>nul | findstr /i "(F) (M) (W) :\\" | findstr /i ":\\ everyone authenticated users todos %username%" && ECHO.
|
||||
)
|
||||
) else (
|
||||
for /f "tokens=*" %%x in ('powershell -command "Get-Process | Where-Object {$_.Path -and $_.Path -notlike '*system32*'} | Select-Object -ExpandProperty Path -Unique"') do (
|
||||
for /f "delims=" %%d in ("%%~dpx") do icacls "%%d" 2>nul | findstr /i "(F) (M) (W) :\\" | findstr /i ":\\ everyone authenticated users todos %username%" && ECHO.
|
||||
)
|
||||
)
|
||||
ECHO.
|
||||
CALL :T_Progress 3
|
||||
@@ -452,8 +481,19 @@ ECHO.
|
||||
:ServiceBinaryPermissions
|
||||
CALL :ColorLine " %E%33m[+]%E%97m SERVICE BINARY PERMISSIONS WITH WMIC and ICACLS"
|
||||
ECHO. [?] https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#services
|
||||
for /f "tokens=2 delims='='" %%a in ('cmd.exe /c wmic service list full ^| findstr /i "pathname" ^|findstr /i /v "system32"') do (
|
||||
for /f eol^=^"^ delims^=^" %%b in ("%%a") do icacls "%%b" 2>nul | findstr /i "(F) (M) (W) :\\" | findstr /i ":\\ everyone authenticated users todos usuarios %username%" && ECHO.
|
||||
where wmic >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
for /f "tokens=2 delims='='" %%a in ('cmd.exe /c wmic service list full ^| findstr /i "pathname" ^|findstr /i /v "system32"') do (
|
||||
for /f eol^=^"^ delims^=^" %%b in ("%%a") do icacls "%%b" 2>nul | findstr /i "(F) (M) (W) :\\" | findstr /i ":\\ everyone authenticated users todos usuarios %username%" && ECHO.
|
||||
)
|
||||
) else (
|
||||
for /f "tokens=*" %%a in ('powershell -command "Get-CimInstance -ClassName Win32_Service | Where-Object {$_.PathName -and $_.PathName -notlike '*system32*'} | Select-Object -ExpandProperty PathName"') do (
|
||||
for /f "tokens=1 delims= " %%b in ("%%a") do (
|
||||
set "svcpath=%%b"
|
||||
set "svcpath=!svcpath:~1,-1!"
|
||||
if exist "!svcpath!" icacls "!svcpath!" 2>nul | findstr /i "(F) (M) (W) :\\" | findstr /i ":\\ everyone authenticated users todos usuarios %username%" && ECHO.
|
||||
)
|
||||
)
|
||||
)
|
||||
ECHO.
|
||||
CALL :T_Progress 1
|
||||
@@ -628,16 +668,29 @@ if "%long%" == "true" (
|
||||
ECHO.
|
||||
ECHO. [i] Iterating through the drives
|
||||
ECHO.
|
||||
for /f %%x in ('wmic logicaldisk get name^| more') do (
|
||||
set tdrive=%%x
|
||||
if "!tdrive:~1,2!" == ":" (
|
||||
%%x
|
||||
CALL :ColorLine " %E%33m[+]%E%97m FILES THAT CONTAINS THE WORD PASSWORD WITH EXTENSION: .xml .ini .txt *.cfg *.config"
|
||||
findstr /s/n/m/i password *.xml *.ini *.txt *.cfg *.config 2>nul | findstr /v /i "\\AppData\\Local \\WinSxS ApnDatabase.xml \\UEV\\InboxTemplates \\Microsoft.Windows.Cloud \\Notepad\+\+\\ vmware cortana alphabet \\7-zip\\" 2>nul
|
||||
ECHO.
|
||||
CALL :ColorLine " %E%33m[+]%E%97m FILES WHOSE NAME CONTAINS THE WORD PASS CRED or .config not inside \Windows\"
|
||||
dir /s/b *pass* == *cred* == *.config* == *.cfg 2>nul | findstr /v /i "\\windows\\"
|
||||
ECHO.
|
||||
where wmic >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
for /f %%x in ('wmic logicaldisk get name ^| more') do (
|
||||
set tdrive=%%x
|
||||
if "!tdrive:~1,2!" == ":" (
|
||||
%%x
|
||||
CALL :ColorLine " %E%33m[+]%E%97m FILES THAT CONTAINS THE WORD PASSWORD WITH EXTENSION: .xml .ini .txt *.cfg *.config"
|
||||
findstr /s/n/m/i password *.xml *.ini *.txt *.cfg *.config 2>nul | findstr /v /i "\\AppData\\Local \\WinSxS ApnDatabase.xml \\UEV\\InboxTemplates \\Microsoft.Windows.Cloud \\Notepad\+\+\\ vmware cortana alphabet \\7-zip\\" 2>nul
|
||||
ECHO.
|
||||
CALL :ColorLine " %E%33m[+]%E%97m FILES WHOSE NAME CONTAINS THE WORD PASS CRED or .config not inside \Windows\"
|
||||
dir /s/b *pass* == *cred* == *.config* == *.cfg 2>nul | findstr /v /i "\\windows\\"
|
||||
ECHO.
|
||||
)
|
||||
)
|
||||
) else (
|
||||
for /f %%x in ('powershell -command "Get-PSDrive -PSProvider FileSystem | Where-Object {$_.Root -match ':'} | Select-Object -ExpandProperty Name"') do (
|
||||
%%x:
|
||||
CALL :ColorLine " %E%33m[+]%E%97m FILES THAT CONTAINS THE WORD PASSWORD WITH EXTENSION: .xml .ini .txt *.cfg *.config"
|
||||
findstr /s/n/m/i password *.xml *.ini *.txt *.cfg *.config 2>nul | findstr /v /i "\\AppData\\Local \\WinSxS ApnDatabase.xml \\UEV\\InboxTemplates \\Microsoft.Windows.Cloud \\Notepad\+\+\\ vmware cortana alphabet \\7-zip\\" 2>nul
|
||||
ECHO.
|
||||
CALL :ColorLine " %E%33m[+]%E%97m FILES WHOSE NAME CONTAINS THE WORD PASS CRED or .config not inside \Windows\"
|
||||
dir /s/b *pass* == *cred* == *.config* == *.cfg 2>nul | findstr /v /i "\\windows\\"
|
||||
ECHO.
|
||||
)
|
||||
)
|
||||
CALL :T_Progress 2
|
||||
@@ -666,5 +719,5 @@ EXIT /B
|
||||
|
||||
:ColorLine
|
||||
SET "CurrentLine=%~1"
|
||||
FOR /F "delims=" %%A IN ('FORFILES.EXE /P %~dp0 /M %~nx0 /C "CMD /C ECHO.!CurrentLine!"') DO ECHO.%%A
|
||||
ECHO.!CurrentLine!
|
||||
EXIT /B
|
||||
|
||||
@@ -22,7 +22,7 @@ $url = "https://github.com/peass-ng/PEASS-ng/releases/latest/download/winPEASany
|
||||
# One liner to download and execute winPEASany from memory in a PS shell
|
||||
$wp=[System.Reflection.Assembly]::Load([byte[]](Invoke-WebRequest "$url" -UseBasicParsing | Select-Object -ExpandProperty Content)); [winPEAS.Program]::Main("")
|
||||
|
||||
# The cprevios cmd in 2 lines
|
||||
# The previous cmd in 2 lines
|
||||
$wp=[System.Reflection.Assembly]::Load([byte[]](Invoke-WebRequest "$url" -UseBasicParsing | Select-Object -ExpandProperty Content));
|
||||
[winPEAS.Program]::Main("") #Put inside the quotes the winpeas parameters you want to use
|
||||
|
||||
@@ -279,3 +279,7 @@ If you find any issue, please report it using **[github issues](https://github.c
|
||||
## Advisory
|
||||
|
||||
All the scripts/binaries of the PEAS Suite should be used for authorized penetration testing and/or educational purposes only. Any misuse of this software will not be the responsibility of the author or of any other collaborator. Use it at your own networks and/or with the network owner's permission.
|
||||
- AD ACL opportunities for bloodyAD:
|
||||
- Password reset rights over privileged users and computers (Reset Password extended right or equivalent GenericAll/WriteDacl/WriteOwner/property writes).
|
||||
- Shadow credentials possible where msDS-KeyCredentialLink is writable on user/computer objects.
|
||||
- AD-integrated DNS zones writable by the current principal (zone ACLs) and DnsAdmins membership.
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.DirectoryServices;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using winPEAS.Helpers;
|
||||
using winPEAS.Helpers.Registry;
|
||||
using winPEAS.Info.FilesInfo.Certificates;
|
||||
|
||||
namespace winPEAS.Checks
|
||||
{
|
||||
@@ -17,7 +19,8 @@ namespace winPEAS.Checks
|
||||
new List<Action>
|
||||
{
|
||||
PrintGmsaReadableByCurrentPrincipal,
|
||||
PrintAdcsEsc4LikeTemplates
|
||||
PrintAdcsMisconfigurations,
|
||||
PrintAdAclPrivescCandidates
|
||||
}.ForEach(action => CheckRunner.Run(action, isDebug));
|
||||
}
|
||||
|
||||
@@ -62,6 +65,32 @@ namespace winPEAS.Checks
|
||||
? r.Properties[name][0]?.ToString()
|
||||
: null;
|
||||
}
|
||||
private static Guid? GetAttributeSchemaGuid(string ldapDisplayName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var schemaNC = GetRootDseProp("schemaNamingContext");
|
||||
if (string.IsNullOrEmpty(schemaNC)) return null;
|
||||
|
||||
using (var baseDe = new DirectoryEntry("LDAP://" + schemaNC))
|
||||
using (var ds = new DirectorySearcher(baseDe))
|
||||
{
|
||||
ds.PageSize = 50;
|
||||
ds.Filter = "(&(objectClass=attributeSchema)(lDAPDisplayName=" + ldapDisplayName + "))";
|
||||
ds.PropertiesToLoad.Add("schemaIDGUID");
|
||||
var res = ds.FindOne();
|
||||
if (res == null) return null;
|
||||
var guidBytes = res.Properties["schemaIDGUID"]?.Count > 0 ? res.Properties["schemaIDGUID"][0] as byte[] : null;
|
||||
if (guidBytes == null || guidBytes.Length != 16) return null;
|
||||
return new Guid(guidBytes);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Detect gMSA objects where the current principal (or one of its groups) can retrieve the managed password
|
||||
private void PrintGmsaReadableByCurrentPrincipal()
|
||||
@@ -152,22 +181,91 @@ namespace winPEAS.Checks
|
||||
}
|
||||
}
|
||||
|
||||
// Detect AD CS certificate templates where current principal has dangerous control rights (ESC4-style)
|
||||
private void PrintAdcsEsc4LikeTemplates()
|
||||
// Detect AD CS misconfigurations
|
||||
private void PrintAdcsMisconfigurations()
|
||||
{
|
||||
try
|
||||
{
|
||||
Beaprint.MainPrint("AD CS templates with dangerous ACEs (ESC4)");
|
||||
Beaprint.LinkPrint(
|
||||
"https://book.hacktricks.wiki/en/windows-hardening/active-directory-methodology/ad-certificates.html#esc4",
|
||||
"If you can modify a template (WriteDacl/WriteOwner/GenericAll), you can abuse ESC4");
|
||||
|
||||
Beaprint.MainPrint("AD CS misconfigurations for ESC");
|
||||
Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/active-directory-methodology/ad-certificates.html");
|
||||
|
||||
if (!Checks.IsPartOfDomain)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] Host is not domain-joined. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
Beaprint.InfoPrint("Check for ADCS misconfigurations in the local DC registry");
|
||||
bool IsDomainController = RegistryHelper.GetReg("HKLM", @"SYSTEM\CurrentControlSet\Services\NTDS")?.ValueCount > 0;
|
||||
if (IsDomainController)
|
||||
{
|
||||
// For StrongBinding and CertificateMapping, More details in KB014754 - Registry key information:
|
||||
// https://support.microsoft.com/en-us/topic/kb5014754-certificate-based-authentication-changes-on-windows-domain-controllers-ad2c23b0-15d8-4340-a468-4d4f3b188f16
|
||||
uint? strongBinding = RegistryHelper.GetDwordValue("HKLM", @"SYSTEM\CurrentControlSet\Services\Kdc", "StrongCertificateBindingEnforcement");
|
||||
switch (strongBinding)
|
||||
{
|
||||
case 0:
|
||||
Beaprint.BadPrint(" StrongCertificateBindingEnforcement: 0 — Weak mapping allowed, vulnerable to ESC9.");
|
||||
break;
|
||||
case 2:
|
||||
Beaprint.GoodPrint(" StrongCertificateBindingEnforcement: 2 — Prevents weak UPN/DNS mappings even if SID extension missing, not vulnerable to ESC9.");
|
||||
break;
|
||||
// 1 is default behavior now I think?
|
||||
case 1:
|
||||
default:
|
||||
Beaprint.NoColorPrint($" StrongCertificateBindingEnforcement: {strongBinding} — Allow weak mapping if SID extension missing, may be vulnerable to ESC9.");
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
uint? certMapping = RegistryHelper.GetDwordValue("HKLM", @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL", "CertificateMappingMethods");
|
||||
if (certMapping.HasValue && (certMapping & 0x4) != 0)
|
||||
Beaprint.BadPrint($" CertificateMappingMethods: {certMapping} — Allow UPN-based mapping, vulnerable to ESC10.");
|
||||
else if(certMapping.HasValue && ((certMapping & 0x1) != 0 || (certMapping & 0x2) != 0))
|
||||
Beaprint.NoColorPrint($" CertificateMappingMethods: {certMapping} — Allow weak Subject/Issuer certificate mapping.");
|
||||
// 0x18 (strong mapping) is default behavior if not the flags above I think?
|
||||
else
|
||||
Beaprint.GoodPrint($" CertificateMappingMethods: {certMapping} — Strong Certificate mapping enabled.");
|
||||
|
||||
// We take the Active CA, can they be several?
|
||||
string caName = RegistryHelper.GetRegValue("HKLM", $@"SYSTEM\CurrentControlSet\Services\CertSvc\Configuration", "Active");
|
||||
if (!string.IsNullOrWhiteSpace(caName))
|
||||
{
|
||||
// Obscure Source for InterfaceFlag Enum:
|
||||
// https://www.sysadmins.lv/apidocs/pki/html/T_PKI_CertificateServices_Flags_InterfaceFlagEnum.htm
|
||||
uint? interfaceFlags = RegistryHelper.GetDwordValue("HKLM", $@"SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\{caName}", "InterfaceFlags");
|
||||
if (!interfaceFlags.HasValue || (interfaceFlags & 512) == 0)
|
||||
Beaprint.BadPrint(" IF_ENFORCEENCRYPTICERTREQUEST not set in InterfaceFlags — vulnerable to ESC11.");
|
||||
else
|
||||
Beaprint.GoodPrint(" IF_ENFORCEENCRYPTICERTREQUEST set in InterfaceFlags — not vulnerable to ESC11.");
|
||||
|
||||
string policyModule = RegistryHelper.GetRegValue("HKLM", $@"SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\{caName}\PolicyModules", "Active");
|
||||
if (!string.IsNullOrWhiteSpace(policyModule))
|
||||
{
|
||||
string disableExtensionList = RegistryHelper.GetRegValue("HKLM", $@"SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\{caName}\PolicyModules\{policyModule}", "DisableExtensionList");
|
||||
// zOID_NTDS_CA_SECURITY_EXT (OID 1.3.6.1.4.1.311.25.2)
|
||||
if (disableExtensionList?.Contains("1.3.6.1.4.1.311.25.2") == true)
|
||||
Beaprint.BadPrint(" szOID_NTDS_CA_SECURITY_EXT disabled for the entire CA — vulnerable to ESC16.");
|
||||
else
|
||||
Beaprint.GoodPrint(" szOID_NTDS_CA_SECURITY_EXT not disabled for the CA — not vulnerable to ESC16.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] Policy Module not found. Skipping.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] Certificate Authority not found. Skipping.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] Host is not a domain controller. Skipping ADCS Registry check");
|
||||
}
|
||||
|
||||
// Detect AD CS certificate templates where current principal has dangerous control rights(ESC4 - style)
|
||||
Beaprint.InfoPrint("\nIf you can modify a template (WriteDacl/WriteOwner/GenericAll), you can abuse ESC4");
|
||||
var configNC = GetRootDseProp("configurationNamingContext");
|
||||
if (string.IsNullOrEmpty(configNC))
|
||||
{
|
||||
@@ -270,6 +368,299 @@ namespace winPEAS.Checks
|
||||
{
|
||||
Beaprint.PrintException(ex.Message);
|
||||
}
|
||||
|
||||
// Enumerate quick, high-signal AD ACL opportunities that tools like bloodyAD can abuse
|
||||
// - Password reset rights over privileged users (Reset Password ER or equivalent)
|
||||
// - Shadow credentials (msDS-KeyCredentialLink writable) on users/computers
|
||||
// - AD-Integrated DNS zones where we can write/create records; DnsAdmins membership
|
||||
private void PrintAdAclPrivescCandidates()
|
||||
{
|
||||
try
|
||||
{
|
||||
Beaprint.MainPrint("AD ACL-based escalation opportunities (bloodyAD)");
|
||||
Beaprint.LinkPrint(
|
||||
"https://github.com/CravateRouge/bloodyAD",
|
||||
"Detect objects where you could reset passwords, write shadow credentials, or modify AD‑integrated DNS");
|
||||
|
||||
if (!Checks.IsPartOfDomain)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] Host is not domain-joined. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
var defaultNC = GetRootDseProp("defaultNamingContext");
|
||||
var rootDomainNC = GetRootDseProp("rootDomainNamingContext") ?? defaultNC;
|
||||
if (string.IsNullOrEmpty(defaultNC))
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] Could not resolve defaultNamingContext.");
|
||||
return;
|
||||
}
|
||||
|
||||
var currentSidSet = GetCurrentSidSet();
|
||||
|
||||
// Resolve attribute/extended-right GUIDs we care about
|
||||
var resetPwdGuid = new Guid("00299570-246D-11D0-A768-00AA006E0529");
|
||||
var unicodePwdGuid = GetAttributeSchemaGuid("unicodePwd");
|
||||
var userPasswordGuid = GetAttributeSchemaGuid("userPassword");
|
||||
var kclGuid = GetAttributeSchemaGuid("msDS-KeyCredentialLink");
|
||||
|
||||
// Build a small set of high-value targets
|
||||
var targets = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // dn -> name
|
||||
|
||||
// 1) Members of Domain Admins
|
||||
try
|
||||
{
|
||||
using (var baseDe = new DirectoryEntry("LDAP://" + defaultNC))
|
||||
using (var ds = new DirectorySearcher(baseDe))
|
||||
{
|
||||
ds.Filter = "(&(objectClass=group)(sAMAccountName=Domain Admins))";
|
||||
ds.PropertiesToLoad.Add("distinguishedName");
|
||||
ds.PropertiesToLoad.Add("member");
|
||||
var g = ds.FindOne();
|
||||
if (g != null)
|
||||
{
|
||||
var members = g.Properties["member"];
|
||||
if (members != null)
|
||||
{
|
||||
foreach (var m in members)
|
||||
{
|
||||
var dn = m.ToString();
|
||||
try
|
||||
{
|
||||
using (var de = new DirectoryEntry("LDAP://" + dn))
|
||||
{
|
||||
var name = de.Properties["sAMAccountName"]?.Value as string ?? dn;
|
||||
if (!targets.ContainsKey(dn)) targets.Add(dn, name);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
// 2) AdminSDHolder-protected users (adminCount=1)
|
||||
try
|
||||
{
|
||||
using (var baseDe = new DirectoryEntry("LDAP://" + defaultNC))
|
||||
using (var ds = new DirectorySearcher(baseDe))
|
||||
{
|
||||
ds.PageSize = 300;
|
||||
ds.Filter = "(&(objectClass=user)(objectCategory=person)(adminCount=1))";
|
||||
ds.PropertiesToLoad.Add("distinguishedName");
|
||||
ds.PropertiesToLoad.Add("sAMAccountName");
|
||||
foreach (SearchResult r in ds.FindAll())
|
||||
{
|
||||
var dn = GetProp(r, "distinguishedName");
|
||||
var name = GetProp(r, "sAMAccountName") ?? dn;
|
||||
if (!string.IsNullOrEmpty(dn) && !targets.ContainsKey(dn)) targets.Add(dn, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
// 3) Domain Controllers (computer objects)
|
||||
try
|
||||
{
|
||||
using (var baseDe = new DirectoryEntry("LDAP://" + defaultNC))
|
||||
using (var ds = new DirectorySearcher(baseDe))
|
||||
{
|
||||
ds.PageSize = 100;
|
||||
// UAC bit 8192 = SERVER_TRUST_ACCOUNT
|
||||
ds.Filter = "(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))";
|
||||
ds.PropertiesToLoad.Add("distinguishedName");
|
||||
ds.PropertiesToLoad.Add("sAMAccountName");
|
||||
foreach (SearchResult r in ds.FindAll())
|
||||
{
|
||||
var dn = GetProp(r, "distinguishedName");
|
||||
var name = GetProp(r, "sAMAccountName") ?? dn;
|
||||
if (!string.IsNullOrEmpty(dn) && !targets.ContainsKey(dn)) targets.Add(dn, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
int pwdResetHits = 0, shadowHits = 0;
|
||||
var maxToShow = 10;
|
||||
|
||||
foreach (var kv in targets)
|
||||
{
|
||||
DirectoryEntry de = null;
|
||||
try
|
||||
{
|
||||
de = new DirectoryEntry("LDAP://" + kv.Key);
|
||||
de.Options.SecurityMasks = SecurityMasks.Dacl;
|
||||
de.RefreshCache(new[] { "ntSecurityDescriptor" });
|
||||
}
|
||||
catch
|
||||
{
|
||||
de?.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var sd = de.ObjectSecurity;
|
||||
var rules = sd.GetAccessRules(true, true, typeof(SecurityIdentifier));
|
||||
|
||||
bool canPwdReset = false;
|
||||
bool canShadow = false;
|
||||
var hitRightsPwd = new HashSet<string>();
|
||||
var hitRightsKcl = new HashSet<string>();
|
||||
|
||||
foreach (ActiveDirectoryAccessRule rule in rules)
|
||||
{
|
||||
if (rule.AccessControlType != AccessControlType.Allow) continue;
|
||||
var sid = (rule.IdentityReference as SecurityIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(sid) || !currentSidSet.Contains(sid)) continue;
|
||||
|
||||
var rights = rule.ActiveDirectoryRights;
|
||||
|
||||
// Password reset via ER or powerful rights
|
||||
if (rights.HasFlag(ActiveDirectoryRights.GenericAll))
|
||||
{
|
||||
canPwdReset = true; hitRightsPwd.Add("GenericAll");
|
||||
}
|
||||
if (rights.HasFlag(ActiveDirectoryRights.WriteOwner)) { canPwdReset = true; hitRightsPwd.Add("WriteOwner"); }
|
||||
if (rights.HasFlag(ActiveDirectoryRights.WriteDacl)) { canPwdReset = true; hitRightsPwd.Add("WriteDacl"); }
|
||||
if (rights.HasFlag(ActiveDirectoryRights.ExtendedRight) && rule.ObjectType == resetPwdGuid)
|
||||
{ canPwdReset = true; hitRightsPwd.Add("ResetPassword"); }
|
||||
if (rights.HasFlag(ActiveDirectoryRights.WriteProperty))
|
||||
{
|
||||
if (unicodePwdGuid.HasValue && rule.ObjectType == unicodePwdGuid.Value) { canPwdReset = true; hitRightsPwd.Add("Write unicodePwd"); }
|
||||
if (userPasswordGuid.HasValue && rule.ObjectType == userPasswordGuid.Value) { canPwdReset = true; hitRightsPwd.Add("Write userPassword"); }
|
||||
}
|
||||
|
||||
// Shadow credentials (msDS-KeyCredentialLink)
|
||||
if (rights.HasFlag(ActiveDirectoryRights.GenericAll))
|
||||
{
|
||||
canShadow = true; hitRightsKcl.Add("GenericAll");
|
||||
}
|
||||
if (rights.HasFlag(ActiveDirectoryRights.WriteProperty) && kclGuid.HasValue && rule.ObjectType == kclGuid.Value)
|
||||
{
|
||||
canShadow = true; hitRightsKcl.Add("Write msDS-KeyCredentialLink");
|
||||
}
|
||||
}
|
||||
|
||||
if (canPwdReset)
|
||||
{
|
||||
pwdResetHits++;
|
||||
if (pwdResetHits <= maxToShow)
|
||||
Beaprint.BadPrint($" [PasswordReset] {kv.Value} -> {kv.Key} ({string.Join(", ", hitRightsPwd)})");
|
||||
}
|
||||
if (canShadow)
|
||||
{
|
||||
shadowHits++;
|
||||
if (shadowHits <= maxToShow)
|
||||
Beaprint.BadPrint($" [ShadowCreds] {kv.Value} -> {kv.Key} ({string.Join(", ", hitRightsKcl)})");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally { de?.Dispose(); }
|
||||
}
|
||||
|
||||
if (pwdResetHits == 0) Beaprint.GoodPrint(" No obvious password reset rights over high-value objects found.");
|
||||
else Beaprint.BadPrint($" => {pwdResetHits} potential password reset target(s) over high-value objects.");
|
||||
|
||||
if (shadowHits == 0) Beaprint.GoodPrint(" No writable msDS-KeyCredentialLink found on high-value objects.");
|
||||
else Beaprint.BadPrint($" => {shadowHits} potential shadow credentials target(s) over high-value objects.");
|
||||
|
||||
// DNS: membership + zone ACLs
|
||||
try
|
||||
{
|
||||
using (var baseDe = new DirectoryEntry("LDAP://" + defaultNC))
|
||||
using (var ds = new DirectorySearcher(baseDe))
|
||||
{
|
||||
ds.Filter = "(&(objectClass=group)(sAMAccountName=DnsAdmins))";
|
||||
ds.PropertiesToLoad.Add("objectSid");
|
||||
var res = ds.FindOne();
|
||||
if (res != null && res.Properties["objectSid"].Count > 0)
|
||||
{
|
||||
var sid = new SecurityIdentifier((byte[])res.Properties["objectSid"][0], 0).Value;
|
||||
if (currentSidSet.Contains(sid))
|
||||
Beaprint.BadPrint(" [DNS] Current principal is a member of DnsAdmins.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
int dnsAclHits = 0;
|
||||
foreach (var partition in new[] { $"DC=DomainDnsZones,{defaultNC}", $"DC=ForestDnsZones,{rootDomainNC}" })
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var msDns = new DirectoryEntry("LDAP://CN=MicrosoftDNS," + partition))
|
||||
using (var ds = new DirectorySearcher(msDns))
|
||||
{
|
||||
ds.PageSize = 200;
|
||||
ds.Filter = "(objectClass=dnsZone)";
|
||||
ds.PropertiesToLoad.Add("distinguishedName");
|
||||
ds.PropertiesToLoad.Add("name");
|
||||
|
||||
foreach (SearchResult r in ds.FindAll())
|
||||
{
|
||||
DirectoryEntry zone = null;
|
||||
try
|
||||
{
|
||||
zone = r.GetDirectoryEntry();
|
||||
zone.Options.SecurityMasks = SecurityMasks.Dacl;
|
||||
zone.RefreshCache(new[] { "ntSecurityDescriptor" });
|
||||
}
|
||||
catch
|
||||
{
|
||||
zone?.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bool canWriteZone = false;
|
||||
var sd = zone.ObjectSecurity;
|
||||
var rules = sd.GetAccessRules(true, true, typeof(SecurityIdentifier));
|
||||
foreach (ActiveDirectoryAccessRule rule in rules)
|
||||
{
|
||||
if (rule.AccessControlType != AccessControlType.Allow) continue;
|
||||
var sid = (rule.IdentityReference as SecurityIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(sid) || !currentSidSet.Contains(sid)) continue;
|
||||
|
||||
var rights = rule.ActiveDirectoryRights;
|
||||
if (rights.HasFlag(ActiveDirectoryRights.GenericAll) ||
|
||||
rights.HasFlag(ActiveDirectoryRights.WriteProperty) ||
|
||||
rights.HasFlag(ActiveDirectoryRights.CreateChild) ||
|
||||
rights.HasFlag(ActiveDirectoryRights.DeleteChild))
|
||||
{
|
||||
canWriteZone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (canWriteZone)
|
||||
{
|
||||
dnsAclHits++;
|
||||
var zname = GetProp(r, "name") ?? GetProp(r, "distinguishedName") ?? "<zone>";
|
||||
if (dnsAclHits <= maxToShow)
|
||||
Beaprint.BadPrint($" [DNS] Writable AD‑integrated zone: {zname}");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally { zone?.Dispose(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (dnsAclHits == 0) Beaprint.GoodPrint(" No writable AD‑integrated DNS zones detected for current principal.");
|
||||
else Beaprint.BadPrint($" => {dnsAclHits} AD‑integrated DNS zone(s) appear writable.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.GrayPrint(" [!] Error during AD ACL checks: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user