mirror of
https://github.com/peass-ng/PEASS-ng.git
synced 2026-01-17 15:32:29 -08:00
Compare commits
61 Commits
20260117-1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d8a14d2ec | ||
|
|
9c49dfd2bb | ||
|
|
9acc2ef61d | ||
|
|
e7663381f2 | ||
|
|
ce5bd84575 | ||
|
|
c447ca993d | ||
|
|
a1aa60faf8 | ||
|
|
e81c436d80 | ||
|
|
f4883f814e | ||
|
|
4a7fb83165 | ||
|
|
ff13e8bebb | ||
|
|
e80425aa3d | ||
|
|
dc5ae45cc3 | ||
|
|
ff21b3dcb9 | ||
|
|
2f44379713 | ||
|
|
2c6cbfa43d | ||
|
|
8ae2667800 | ||
|
|
43a7684621 | ||
|
|
182c8974fd | ||
|
|
7b4a83d51d | ||
|
|
8aa05e13a4 | ||
|
|
4559fd09ea | ||
|
|
4f8a3b3f25 | ||
|
|
0ed7a39a7d | ||
|
|
1cdd473d79 | ||
|
|
0e29450869 | ||
|
|
4255330728 | ||
|
|
8f928f8c5d | ||
|
|
0e8959a6db | ||
|
|
ea787df91c | ||
|
|
c14f9aeb30 | ||
|
|
a86dedb553 | ||
|
|
7e4743d9be | ||
|
|
14aa117a0e | ||
|
|
7016e5a0b4 | ||
|
|
7a5aa8dcae | ||
|
|
34d0f18aad | ||
|
|
49644a9813 | ||
|
|
35eb4f5be7 | ||
|
|
13656ba172 | ||
|
|
8c043b6164 | ||
|
|
aca58f36b9 | ||
|
|
dc7ce70104 | ||
|
|
a5114ef5dd | ||
|
|
ff41d5b0e7 | ||
|
|
fa58c6688b | ||
|
|
3aa04a53fc | ||
|
|
373ed3cce2 | ||
|
|
79d53de4cf | ||
|
|
3a89398e39 | ||
|
|
e77867b2d3 | ||
|
|
be72fecfa8 | ||
|
|
0e52c2feea | ||
|
|
1039cc2eff | ||
|
|
3268701ed6 | ||
|
|
488d388830 | ||
|
|
85aa98a841 | ||
|
|
74521345f6 | ||
|
|
6100bfaceb | ||
|
|
b7b7aebf1c | ||
|
|
6c75f10fae |
@@ -6,6 +6,8 @@
|
||||
|
||||
Check the **Local Linux Privilege Escalation checklist** from **[book.hacktricks.wiki](https://book.hacktricks.wiki/en/linux-hardening/linux-privilege-escalation-checklist.html)**.
|
||||
|
||||
> **Dec 2025 update:** linpeas now inspects Linux kernels for CVE-2025-38352 (POSIX CPU timers race) by combining CONFIG_POSIX_CPU_TIMERS_TASK_WORK state with kernel build information, so you immediately know if publicly available PoCs might succeed.
|
||||
|
||||
[](https://asciinema.org/a/309566)
|
||||
|
||||
## MacPEAS
|
||||
@@ -98,6 +100,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`**.
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# Title: System Information - CVE_2025_38236
|
||||
# ID: SY_CVE_2025_38236
|
||||
# Author: HT Bot
|
||||
# Last Update: 17-12-2025
|
||||
# Description: Detect Linux kernels exposed to CVE-2025-38236 (AF_UNIX MSG_OOB UAF) that allow local privilege escalation:
|
||||
# - Vulnerable scope:
|
||||
# * Linux kernels 6.9+ before commit 32ca245464e1479bfea8592b9db227fdc1641705
|
||||
# * AF_UNIX stream sockets with MSG_OOB enabled (CONFIG_AF_UNIX_OOB or implicit support)
|
||||
# - Exploitation summary:
|
||||
# * send/recv MSG_OOB pattern leaves zero-length SKBs in the receive queue
|
||||
# * manage_oob() skips cleanup, freeing the OOB SKB while u->oob_skb still points to it
|
||||
# * Subsequent recv(MSG_OOB) dereferences the dangling pointer → kernel UAF → LPE
|
||||
# - Mitigations:
|
||||
# * Update to a kernel that includes commit 32ca245464e1479bfea8592b9db227fdc1641705 (or newer)
|
||||
# * Disable CONFIG_AF_UNIX_OOB or block MSG_OOB in sandboxed processes
|
||||
# * Backport vendor fixes or follow Chrome's MSG_OOB filtering approach
|
||||
# License: GNU GPL
|
||||
# Version: 1.0
|
||||
# Functions Used: print_2title, print_info
|
||||
# Global Variables: $MACPEAS, $SED_RED_YELLOW, $SED_GREEN, $E
|
||||
# Initial Functions:
|
||||
# Generated Global Variables: $cve38236_kernel_release, $cve38236_kernel_version, $cve38236_oob_line, $cve38236_unix_line, $cve38236_oob_status, $CVE38236_CONFIG_SOURCE, $cve38236_conf_file, $cve38236_config_key, $cve38236_release, $cve38236_cfg, $cve38236_config_line
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
_cve38236_version_to_number() {
|
||||
if [ -z "$1" ]; then
|
||||
printf '0\n'
|
||||
return
|
||||
fi
|
||||
echo "$1" | awk -F. '{
|
||||
major=$1+0
|
||||
if (NF>=2) minor=$2+0; else minor=0
|
||||
if (NF>=3) patch=$3+0; else patch=0
|
||||
printf "%d\n", (major*1000000)+(minor*1000)+patch
|
||||
}'
|
||||
}
|
||||
|
||||
_cve38236_version_ge() {
|
||||
local v1 v2
|
||||
v1=$(_cve38236_version_to_number "$1")
|
||||
v2=$(_cve38236_version_to_number "$2")
|
||||
[ "$v1" -ge "$v2" ]
|
||||
}
|
||||
|
||||
_cve38236_cat_config_file() {
|
||||
local cve38236_conf_file="$1"
|
||||
if [ -z "$cve38236_conf_file" ] || ! [ -r "$cve38236_conf_file" ]; then
|
||||
return 1
|
||||
fi
|
||||
if printf '%s' "$cve38236_conf_file" | grep -q '\\.gz$'; then
|
||||
if command -v zcat >/dev/null 2>&1; then
|
||||
zcat "$cve38236_conf_file" 2>/dev/null
|
||||
elif command -v gzip >/dev/null 2>&1; then
|
||||
gzip -dc "$cve38236_conf_file" 2>/dev/null
|
||||
else
|
||||
cat "$cve38236_conf_file" 2>/dev/null
|
||||
fi
|
||||
else
|
||||
cat "$cve38236_conf_file" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
_cve38236_read_config_line() {
|
||||
local cve38236_config_key="$1"
|
||||
local cve38236_release cve38236_config_line cve38236_cfg
|
||||
cve38236_release="$(uname -r 2>/dev/null)"
|
||||
for cve38236_cfg in /proc/config.gz \
|
||||
"/boot/config-${cve38236_release}" \
|
||||
"/usr/lib/modules/${cve38236_release}/build/.config" \
|
||||
"/lib/modules/${cve38236_release}/build/.config"; do
|
||||
if [ -r "$cve38236_cfg" ]; then
|
||||
cve38236_config_line=$(_cve38236_cat_config_file "$cve38236_cfg" | grep -E "^(${cve38236_config_key}=|# ${cve38236_config_key} is not set)" | head -n1)
|
||||
if [ -n "$cve38236_config_line" ]; then
|
||||
CVE38236_CONFIG_SOURCE="$cve38236_cfg"
|
||||
printf '%s\n' "$cve38236_config_line"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
if [ ! "$MACPEAS" ]; then
|
||||
cve38236_kernel_release="$(uname -r 2>/dev/null)"
|
||||
cve38236_kernel_version="$(printf '%s' "$cve38236_kernel_release" | sed 's/[^0-9.].*//')"
|
||||
|
||||
if [ -n "$cve38236_kernel_version" ] && _cve38236_version_ge "$cve38236_kernel_version" "6.9.0"; then
|
||||
print_2title "CVE-2025-38236 - AF_UNIX MSG_OOB UAF"
|
||||
|
||||
cve38236_oob_line=$(_cve38236_read_config_line "CONFIG_AF_UNIX_OOB")
|
||||
cve38236_oob_status="unknown"
|
||||
|
||||
if printf '%s' "$cve38236_oob_line" | grep -q '=y\|=m'; then
|
||||
cve38236_oob_status="enabled"
|
||||
elif printf '%s' "$cve38236_oob_line" | grep -q 'not set'; then
|
||||
cve38236_oob_status="disabled"
|
||||
fi
|
||||
|
||||
if [ "$cve38236_oob_status" = "unknown" ]; then
|
||||
cve38236_unix_line=$(_cve38236_read_config_line "CONFIG_UNIX")
|
||||
if printf '%s' "$cve38236_unix_line" | grep -q 'not set'; then
|
||||
cve38236_oob_status="disabled"
|
||||
elif printf '%s' "$cve38236_unix_line" | grep -q '=y\|=m'; then
|
||||
cve38236_oob_status="enabled"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$cve38236_oob_status" = "disabled" ]; then
|
||||
printf 'Kernel %s >= 6.9 but MSG_OOB support is disabled (%s).\n' "$cve38236_kernel_release" "${cve38236_oob_line:-CONFIG_AF_UNIX disabled}" | sed -${E} "s,.*,${SED_GREEN},"
|
||||
print_info "CVE-2025-38236 requires AF_UNIX MSG_OOB; disabling CONFIG_AF_UNIX_OOB/CONFIG_UNIX mitigates it."
|
||||
else
|
||||
printf 'Kernel %s (parsed %s) may be vulnerable to CVE-2025-38236 - AF_UNIX MSG_OOB UAF.\n' "$cve38236_kernel_release" "$cve38236_kernel_version" | sed -${E} "s,.*,${SED_RED_YELLOW},"
|
||||
[ -n "$cve38236_oob_line" ] && print_info "Config hint: $cve38236_oob_line"
|
||||
if [ "$cve38236_oob_status" = "unknown" ]; then
|
||||
print_info "Could not read CONFIG_AF_UNIX_OOB directly; AF_UNIX appears enabled, so assume MSG_OOB reachable."
|
||||
fi
|
||||
print_info "Exploit chain: crafted MSG_OOB send/recv frees the OOB SKB while u->oob_skb still points to it, enabling kernel UAF → arbitrary read/write primitives (Project Zero 2025/08)."
|
||||
print_info "Mitigations: update to a kernel containing commit 32ca245464e1479bfea8592b9db227fdc1641705, disable CONFIG_AF_UNIX_OOB, or filter MSG_OOB in sandbox policies."
|
||||
print_info "Heuristic detection: based solely on uname -r and kernel config; vendor kernels with backported fixes should be verified manually."
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
fi
|
||||
@@ -0,0 +1,183 @@
|
||||
# Title: System Information - CVE_2025_38352
|
||||
# ID: SY_CVE_2025_38352
|
||||
# Author: HT Bot
|
||||
# Last Update: 22-12-2025
|
||||
# Description: Detect Linux kernels that may still be vulnerable to CVE-2025-38352 (race-condition UAF in POSIX CPU timers)
|
||||
# - Highlights kernels built without CONFIG_POSIX_CPU_TIMERS_TASK_WORK
|
||||
# - Flags 6.12.x builds older than the fix commit f90fff1e152dedf52b932240ebbd670d83330eca (first shipped in 6.12.34)
|
||||
# - Provides quick risk scoring so operators can decide whether to attempt the publicly available PoC
|
||||
# - Core requirements for exploitation:
|
||||
# * CONFIG_POSIX_CPU_TIMERS_TASK_WORK disabled (common on 32-bit Android / custom kernels)
|
||||
# * Lack of the upstream exit_state guard in run_posix_cpu_timers()
|
||||
# License: GNU GPL
|
||||
# Version: 1.0
|
||||
# Functions Used: echo_not_found, print_2title, print_list
|
||||
# Global Variables: $E, $SED_GREEN, $SED_RED, $SED_RED_YELLOW, $SED_YELLOW
|
||||
# Initial Functions:
|
||||
# Generated Global Variables: $cve38352_kernel_release, $cve38352_kernel_version_cmp, $cve38352_symbol, $cve38352_task_work_state, $cve38352_config_status, $cve38352_config_source, $cve38352_config_candidates, $cve38352_cfg, $cve38352_line, $cve38352_patch_state, $cve38352_patch_label, $cve38352_fix_tag, $cve38352_last_vuln_tag, $cve38352_risk_msg, $cve38352_risk_color, $cve38352_task_line, $cve38352_patch_line, $cve38352_risk_line
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
cve38352_version_lt(){
|
||||
awk -v v1="$1" -v v2="$2" '
|
||||
function cleannum(val) {
|
||||
gsub(/[^0-9].*/, "", val)
|
||||
if (val == "") {
|
||||
val = 0
|
||||
}
|
||||
return val + 0
|
||||
}
|
||||
BEGIN {
|
||||
n = split(v1, a, ".")
|
||||
m = split(v2, b, ".")
|
||||
max = (n > m ? n : m)
|
||||
for (i = 1; i <= max; i++) {
|
||||
av = (i <= n ? cleannum(a[i]) : 0)
|
||||
bv = (i <= m ? cleannum(b[i]) : 0)
|
||||
if (av < bv) {
|
||||
exit 0
|
||||
}
|
||||
if (av > bv) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
exit 1
|
||||
}'
|
||||
}
|
||||
|
||||
cve38352_sanitize_version(){
|
||||
printf "%s" "$1" | tr '-' '.' | sed 's/[^0-9.].*$//' | sed 's/\.\./\./g' | sed 's/^\.//' | sed 's/\.$//'
|
||||
}
|
||||
|
||||
print_2title "CVE-2025-38352 - POSIX CPU timers race"
|
||||
|
||||
cve38352_kernel_release=$(uname -r 2>/dev/null)
|
||||
if [ -z "$cve38352_kernel_release" ]; then
|
||||
echo_not_found "uname -r"
|
||||
echo ""
|
||||
else
|
||||
|
||||
cve38352_kernel_version_cmp=$(cve38352_sanitize_version "$cve38352_kernel_release")
|
||||
if [ -z "$cve38352_kernel_version_cmp" ]; then
|
||||
cve38352_kernel_version_cmp="unknown"
|
||||
fi
|
||||
|
||||
cve38352_symbol="CONFIG_POSIX_CPU_TIMERS_TASK_WORK"
|
||||
cve38352_task_work_state="unknown"
|
||||
cve38352_config_status="Unknown ($cve38352_symbol not found)"
|
||||
cve38352_config_source=""
|
||||
|
||||
cve38352_config_candidates="/boot/config-$cve38352_kernel_release /proc/config.gz /lib/modules/$cve38352_kernel_release/build/.config /usr/lib/modules/$cve38352_kernel_release/build/.config /usr/src/linux/.config"
|
||||
for cve38352_cfg in $cve38352_config_candidates; do
|
||||
[ -r "$cve38352_cfg" ] || continue
|
||||
if printf "%s" "$cve38352_cfg" | grep -q '\\.gz$'; then
|
||||
cve38352_line=$(gzip -dc "$cve38352_cfg" 2>/dev/null | grep -E "^(# )?$cve38352_symbol" | head -n1)
|
||||
else
|
||||
cve38352_line=$(grep -E "^(# )?$cve38352_symbol" "$cve38352_cfg" 2>/dev/null | head -n1)
|
||||
fi
|
||||
[ -z "$cve38352_line" ] && continue
|
||||
cve38352_config_source="$cve38352_cfg"
|
||||
case "$cve38352_line" in
|
||||
"$cve38352_symbol=y")
|
||||
cve38352_task_work_state="enabled"
|
||||
cve38352_config_status="Enabled (y)"
|
||||
;;
|
||||
"$cve38352_symbol=m")
|
||||
cve38352_task_work_state="enabled"
|
||||
cve38352_config_status="Built as module (m)"
|
||||
;;
|
||||
"$cve38352_symbol=n")
|
||||
cve38352_task_work_state="disabled"
|
||||
cve38352_config_status="Disabled (n)"
|
||||
;;
|
||||
"# $cve38352_symbol is not set")
|
||||
cve38352_task_work_state="disabled"
|
||||
cve38352_config_status="Not set"
|
||||
;;
|
||||
*)
|
||||
cve38352_config_status="Found: $cve38352_line"
|
||||
;;
|
||||
esac
|
||||
break
|
||||
done
|
||||
|
||||
cve38352_patch_state="unknown_branch"
|
||||
cve38352_patch_label="Unable to determine kernel train"
|
||||
cve38352_fix_tag="6.12.34"
|
||||
cve38352_last_vuln_tag="6.12.33"
|
||||
case "$cve38352_kernel_version_cmp" in
|
||||
6.12|6.12.*)
|
||||
if cve38352_version_lt "$cve38352_kernel_version_cmp" "$cve38352_fix_tag"; then
|
||||
cve38352_patch_state="pre_fix"
|
||||
cve38352_patch_label="6.12.x build < $cve38352_fix_tag (last known vulnerable LTS: $cve38352_last_vuln_tag)"
|
||||
else
|
||||
cve38352_patch_state="post_fix"
|
||||
cve38352_patch_label="6.12.x build >= $cve38352_fix_tag (should include fix f90fff1e152d)"
|
||||
fi
|
||||
;;
|
||||
unknown)
|
||||
cve38352_patch_label="Kernel version string could not be parsed"
|
||||
;;
|
||||
*)
|
||||
cve38352_patch_label="Kernel train $cve38352_kernel_version_cmp (verify commit f90fff1e152dedf52b932240ebbd670d83330eca manually)"
|
||||
;;
|
||||
esac
|
||||
|
||||
cve38352_risk_msg="Unknown - missing configuration data"
|
||||
cve38352_risk_color=""
|
||||
if [ "$cve38352_task_work_state" = "enabled" ]; then
|
||||
cve38352_risk_msg="Low - CONFIG_POSIX_CPU_TIMERS_TASK_WORK is enabled"
|
||||
cve38352_risk_color="green"
|
||||
elif [ "$cve38352_task_work_state" = "disabled" ]; then
|
||||
if [ "$cve38352_patch_state" = "pre_fix" ]; then
|
||||
cve38352_risk_msg="High - task_work disabled & kernel predates fix f90fff1e152d"
|
||||
cve38352_risk_color="red"
|
||||
else
|
||||
cve38352_risk_msg="Review - task_work disabled, ensure fix f90fff1e152d is backported"
|
||||
cve38352_risk_color="yellow"
|
||||
fi
|
||||
fi
|
||||
|
||||
print_list "Kernel release ............... $cve38352_kernel_release\n"
|
||||
print_list "Comparable version ........... $cve38352_kernel_version_cmp\n"
|
||||
|
||||
cve38352_task_line="Task_work config ............. $cve38352_config_status"
|
||||
if [ -n "$cve38352_config_source" ]; then
|
||||
cve38352_task_line="$cve38352_task_line (from $cve38352_config_source)"
|
||||
fi
|
||||
cve38352_task_line="$cve38352_task_line\n"
|
||||
if [ "$cve38352_task_work_state" = "disabled" ]; then
|
||||
print_list "$cve38352_task_line" | sed -${E} "s,.*,${SED_RED},"
|
||||
elif [ "$cve38352_task_work_state" = "enabled" ]; then
|
||||
print_list "$cve38352_task_line" | sed -${E} "s,.*,${SED_GREEN},"
|
||||
else
|
||||
print_list "$cve38352_task_line"
|
||||
fi
|
||||
|
||||
cve38352_patch_line="Patch status ................. $cve38352_patch_label\n"
|
||||
if [ "$cve38352_patch_state" = "pre_fix" ]; then
|
||||
print_list "$cve38352_patch_line" | sed -${E} "s,.*,${SED_RED_YELLOW},"
|
||||
elif [ "$cve38352_patch_state" = "post_fix" ]; then
|
||||
print_list "$cve38352_patch_line" | sed -${E} "s,.*,${SED_GREEN},"
|
||||
else
|
||||
print_list "$cve38352_patch_line" | sed -${E} "s,.*,${SED_YELLOW},"
|
||||
fi
|
||||
|
||||
cve38352_risk_line="CVE-2025-38352 risk .......... $cve38352_risk_msg\n"
|
||||
case "$cve38352_risk_color" in
|
||||
red)
|
||||
print_list "$cve38352_risk_line" | sed -${E} "s,.*,${SED_RED_YELLOW},"
|
||||
;;
|
||||
green)
|
||||
print_list "$cve38352_risk_line" | sed -${E} "s,.*,${SED_GREEN},"
|
||||
;;
|
||||
yellow)
|
||||
print_list "$cve38352_risk_line" | sed -${E} "s,.*,${SED_YELLOW},"
|
||||
;;
|
||||
*)
|
||||
print_list "$cve38352_risk_line"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
fi
|
||||
@@ -1,14 +1,14 @@
|
||||
# Title: Cloud - AWS ECS
|
||||
# ID: CL_AWS_ECS
|
||||
# Author: Carlos Polop
|
||||
# Last Update: 22-08-2023
|
||||
# Last Update: 17-01-2026
|
||||
# Description: AWS ECS Enumeration
|
||||
# License: GNU GPL
|
||||
# Version: 1.0
|
||||
# Functions Used: check_aws_ecs, exec_with_jq, print_2title, print_3title
|
||||
# Global Variables: $aws_ecs_metadata_uri, $aws_ecs_service_account_uri, $is_aws_ecs
|
||||
# Initial Functions: check_aws_ecs
|
||||
# Generated Global Variables: $aws_ecs_req
|
||||
# Generated Global Variables: $aws_ecs_req, $aws_exec_env, $ecs_task_metadata, $launch_type, $network_modes, $imds_tool, $imds_token, $imds_roles, $imds_http_code, $ecs_block_line, $ecs_host_line, $iptables_cmd, $docker_rules, $first_role
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
@@ -44,5 +44,146 @@ if [ "$is_aws_ecs" = "Yes" ]; then
|
||||
else
|
||||
echo "I couldn't find AWS_CONTAINER_CREDENTIALS_RELATIVE_URI env var to get IAM role info (the task is running without a task role probably)"
|
||||
fi
|
||||
|
||||
print_3title "ECS task metadata hints"
|
||||
aws_exec_env=$(printenv AWS_EXECUTION_ENV 2>/dev/null)
|
||||
if [ "$aws_exec_env" ]; then
|
||||
printf "AWS_EXECUTION_ENV=%s\n" "$aws_exec_env"
|
||||
fi
|
||||
|
||||
ecs_task_metadata=""
|
||||
if [ "$aws_ecs_metadata_uri" ]; then
|
||||
ecs_task_metadata=$(eval $aws_ecs_req "$aws_ecs_metadata_uri/task" 2>/dev/null)
|
||||
fi
|
||||
|
||||
if [ "$ecs_task_metadata" ]; then
|
||||
launch_type=$(printf "%s" "$ecs_task_metadata" | grep -oE '"LaunchType":"[^"]+"' | head -n 1 | cut -d '"' -f4)
|
||||
if [ "$launch_type" ]; then
|
||||
printf "ECS LaunchType reported: %s\n" "$launch_type"
|
||||
fi
|
||||
network_modes=$(printf "%s" "$ecs_task_metadata" | grep -oE '"NetworkMode":"[^"]+"' | cut -d '"' -f4 | sort -u | tr '\n' ' ')
|
||||
if [ "$network_modes" ]; then
|
||||
printf "Reported NetworkMode(s): %s\n" "$network_modes"
|
||||
fi
|
||||
else
|
||||
echo "Unable to fetch task metadata (check ECS_CONTAINER_METADATA_URI)."
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
print_3title "IMDS reachability from this task"
|
||||
imds_token=""
|
||||
imds_roles=""
|
||||
imds_http_code=""
|
||||
imds_tool=""
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
imds_tool="curl"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
imds_tool="wget"
|
||||
fi
|
||||
|
||||
if [ "$imds_tool" = "curl" ]; then
|
||||
imds_token=$(curl -s --connect-timeout 2 --max-time 2 -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null)
|
||||
if [ "$imds_token" ]; then
|
||||
printf "[!] IMDSv2 token request succeeded (metadata reachable from this task).\n"
|
||||
imds_roles=$(curl -s --connect-timeout 2 --max-time 2 -H "X-aws-ec2-metadata-token: $imds_token" "http://169.254.169.254/latest/meta-data/iam/security-credentials/" 2>/dev/null | tr '\n' ' ')
|
||||
if [ "$imds_roles" ]; then
|
||||
printf " Instance profile role(s) exposed via IMDS: %s\n" "$imds_roles"
|
||||
first_role=$(printf "%s" "$imds_roles" | awk '{print $1}')
|
||||
if [ "$first_role" ]; then
|
||||
printf " Example: curl -H 'X-aws-ec2-metadata-token: <TOKEN>' http://169.254.169.254/latest/meta-data/iam/security-credentials/%s\n" "$first_role"
|
||||
fi
|
||||
else
|
||||
printf " No IAM role names returned (instance profile might be missing).\n"
|
||||
fi
|
||||
else
|
||||
imds_http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 --max-time 2 "http://169.254.169.254/latest/meta-data/" 2>/dev/null)
|
||||
case "$imds_http_code" in
|
||||
000|"")
|
||||
printf "[i] IMDS endpoint did not respond (likely blocked via hop-limit or host firewalling).\n"
|
||||
;;
|
||||
401)
|
||||
printf "[i] IMDS requires v2 tokens but token requests are being blocked (bridge-mode tasks rely on this when hop limit = 1).\n"
|
||||
;;
|
||||
*)
|
||||
printf "[i] IMDS GET returned HTTP %s (investigate host configuration).\n" "$imds_http_code"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
elif [ "$imds_tool" = "wget" ]; then
|
||||
imds_token=$(wget -q -O - --timeout=2 --tries=1 --method=PUT --header="X-aws-ec2-metadata-token-ttl-seconds: 21600" "http://169.254.169.254/latest/api/token" 2>/dev/null)
|
||||
if [ "$imds_token" ]; then
|
||||
printf "[!] IMDSv2 token request succeeded (metadata reachable from this task).\n"
|
||||
imds_roles=$(wget -q -O - --timeout=2 --tries=1 --header="X-aws-ec2-metadata-token: $imds_token" "http://169.254.169.254/latest/meta-data/iam/security-credentials/" 2>/dev/null | tr '\n' ' ')
|
||||
if [ "$imds_roles" ]; then
|
||||
printf " Instance profile role(s) exposed via IMDS: %s\n" "$imds_roles"
|
||||
else
|
||||
printf " No IAM role names returned (instance profile might be missing).\n"
|
||||
fi
|
||||
else
|
||||
wget --server-response -O /dev/null --timeout=2 --tries=1 "http://169.254.169.254/latest/meta-data/" 2>&1 | awk 'BEGIN{code=""} /^ HTTP/{code=$2} END{ if(code!="") { printf("[i] IMDS GET returned HTTP %s (token could not be retrieved).\n", code); } else { print "[i] IMDS endpoint did not respond (likely blocked)."; } }'
|
||||
fi
|
||||
else
|
||||
echo "Neither curl nor wget were found, I can't test IMDS reachability."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
print_3title "ECS agent IMDS settings"
|
||||
if [ -r "/etc/ecs/ecs.config" ]; then
|
||||
ecs_block_line=$(grep -E "^ECS_AWSVPC_BLOCK_IMDS=" /etc/ecs/ecs.config 2>/dev/null | tail -n 1)
|
||||
ecs_host_line=$(grep -E "^ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST=" /etc/ecs/ecs.config 2>/dev/null | tail -n 1)
|
||||
if [ "$ecs_block_line" ]; then
|
||||
printf "%s\n" "$ecs_block_line"
|
||||
if echo "$ecs_block_line" | grep -qi "=true"; then
|
||||
echo " -> awsvpc-mode tasks should be blocked from IMDS by the ECS agent."
|
||||
else
|
||||
echo " -> awsvpc-mode tasks can still reach IMDS (set this to true to block)."
|
||||
fi
|
||||
else
|
||||
echo "ECS_AWSVPC_BLOCK_IMDS not set (awsvpc tasks inherit host IMDS reachability)."
|
||||
fi
|
||||
|
||||
if [ "$ecs_host_line" ]; then
|
||||
printf "%s\n" "$ecs_host_line"
|
||||
if echo "$ecs_host_line" | grep -qi "=false"; then
|
||||
echo " -> Host-network tasks lose IAM task roles but IMDS is blocked."
|
||||
else
|
||||
echo " -> Host-network tasks keep IAM task roles and retain IMDS access."
|
||||
fi
|
||||
else
|
||||
echo "ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST not set (defaults keep IMDS reachable for host-mode tasks)."
|
||||
fi
|
||||
else
|
||||
echo "Cannot read /etc/ecs/ecs.config (file missing or permissions denied)."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
print_3title "DOCKER-USER IMDS filtering"
|
||||
iptables_cmd=""
|
||||
if command -v iptables >/dev/null 2>&1; then
|
||||
iptables_cmd=$(command -v iptables)
|
||||
elif command -v iptables-nft >/dev/null 2>&1; then
|
||||
iptables_cmd=$(command -v iptables-nft)
|
||||
fi
|
||||
|
||||
if [ "$iptables_cmd" ]; then
|
||||
docker_rules=$($iptables_cmd -S DOCKER-USER 2>/dev/null)
|
||||
if [ $? -eq 0 ]; then
|
||||
if [ "$docker_rules" ]; then
|
||||
echo "$docker_rules"
|
||||
else
|
||||
echo "(DOCKER-USER chain exists but no rules were found)"
|
||||
fi
|
||||
if echo "$docker_rules" | grep -q "169\\.254\\.169\\.254"; then
|
||||
echo " -> IMDS traffic is explicitly filtered before Docker NAT."
|
||||
else
|
||||
echo " -> No DOCKER-USER rule drops 169.254.169.254 traffic (bridge tasks rely on hop limit or host firewalling)."
|
||||
fi
|
||||
else
|
||||
echo "Unable to read DOCKER-USER chain (missing chain or insufficient permissions)."
|
||||
fi
|
||||
else
|
||||
echo "iptables binary not found; cannot inspect DOCKER-USER chain."
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
@@ -12,5 +12,4 @@
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
|
||||
sudoB="$(whoami)|ALL:ALL|ALL : ALL|ALL|env_keep|NOPASSWD|SETENV|/apache2|/cryptsetup|/mount"
|
||||
sudoB="$(whoami)|ALL:ALL|ALL : ALL|ALL|env_keep|NOPASSWD|SETENV|/apache2|/cryptsetup|/mount|/restic|--password-command|--password-file|-o ProxyCommand|-o PreferredAuthentications"
|
||||
@@ -76,22 +76,13 @@ 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.
|
||||
- Flag legacy/expired-signed kernel drivers (e.g., ValleyRAT's kernelquick) and their registry-controlled stealth configuration so you can spot kernel-level persistence.
|
||||
|
||||
- Flag installed OEM utilities such as ASUS DriverHub, MSI Center, Acer Control Centre and Razer Synapse 4, highlighting writable updater folders and world-accessible pipes tied to recent CVEs.
|
||||
|
||||
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).
|
||||
|
||||
The tool is based on **[SeatBelt](https://github.com/GhostPack/Seatbelt)**.
|
||||
|
||||
### New (AD-aware) checks
|
||||
|
||||
- Active Directory quick checks now include:
|
||||
- gMSA readable managed passwords: enumerate msDS-GroupManagedServiceAccount objects and report those where the current user/group is allowed to retrieve the managed password (PrincipalsAllowedToRetrieveManagedPassword).
|
||||
- AD object control surfaces: parse ACLs for high-value objects plus a sampled set of users/groups/computers and flag when the current security principal already has GenericAll/GenericWrite/WriteDacl/WriteOwner or attribute-specific rights (SPN, UAC, msDS-AllowedToActOnBehalfOfOtherIdentity, sidHistory, member, unicodePwd, replication) that can be abused for password resets, Kerberoasting, delegation/RBCD, DCSync, or stealth persistence.
|
||||
- AD CS (ESC4) hygiene: enumerate published certificate templates and highlight templates where the current user/group has dangerous control rights (GenericAll/WriteDacl/WriteOwner/WriteProperty/ExtendedRight) that could allow template abuse (e.g., ESC4 -> ESC1).
|
||||
|
||||
These checks are lightweight, read-only, and only run when the host is domain-joined.
|
||||
|
||||
|
||||
## Where are my COLORS?!?!?!
|
||||
|
||||
@@ -155,6 +146,7 @@ Once you have installed and activated it you need to:
|
||||
- [x] Applocker Configuration & bypass suggestions
|
||||
- [x] Printers
|
||||
- [x] Named Pipes
|
||||
- [x] Named Pipe ACL abuse candidates
|
||||
- [x] AMSI Providers
|
||||
- [x] SysMon
|
||||
- [x] .NET Versions
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.DirectoryServices;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
@@ -20,6 +21,7 @@ namespace winPEAS.Checks
|
||||
new List<Action>
|
||||
{
|
||||
PrintGmsaReadableByCurrentPrincipal,
|
||||
PrintKerberoastableServiceAccounts,
|
||||
PrintAdObjectControlPaths,
|
||||
PrintAdcsMisconfigurations
|
||||
}.ForEach(action => CheckRunner.Run(action, isDebug));
|
||||
@@ -751,6 +753,37 @@ namespace winPEAS.Checks
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintKerberoastableServiceAccounts()
|
||||
{
|
||||
try
|
||||
{
|
||||
Beaprint.MainPrint("Kerberoasting / service ticket risks");
|
||||
Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/active-directory-methodology/kerberoast.html",
|
||||
"Enumerate weak SPN accounts and legacy Kerberos crypto");
|
||||
|
||||
if (!Checks.IsPartOfDomain)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] Host is not domain-joined. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
var defaultNC = GetRootDseProp("defaultNamingContext");
|
||||
if (string.IsNullOrEmpty(defaultNC))
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] Could not resolve defaultNamingContext.");
|
||||
return;
|
||||
}
|
||||
|
||||
PrintDomainKerberosDefaults(defaultNC);
|
||||
EnumerateKerberoastCandidates(defaultNC);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] Kerberoasting check failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Detect AD CS misconfigurations
|
||||
private void PrintAdcsMisconfigurations()
|
||||
{
|
||||
@@ -939,5 +972,420 @@ namespace winPEAS.Checks
|
||||
Beaprint.PrintException(ex.Message);
|
||||
}
|
||||
}
|
||||
private void PrintDomainKerberosDefaults(string defaultNc)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var domainEntry = new DirectoryEntry("LDAP://" + defaultNc))
|
||||
{
|
||||
var encValue = GetDirectoryEntryInt(domainEntry, "msDS-DefaultSupportedEncryptionTypes");
|
||||
if (encValue.HasValue)
|
||||
{
|
||||
var desc = DescribeEncTypes(encValue);
|
||||
if (IsRc4Allowed(encValue))
|
||||
Beaprint.BadPrint($" Domain default supported encryption types: {desc} — RC4/NT hash tickets allowed.");
|
||||
else
|
||||
Beaprint.GoodPrint($" Domain default supported encryption types: {desc} — RC4 disabled.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] Domain default supported encryption types not set (legacy compatibility defaults to RC4).");
|
||||
}
|
||||
}
|
||||
|
||||
using (var baseDe = new DirectoryEntry("LDAP://" + defaultNc))
|
||||
using (var ds = new DirectorySearcher(baseDe))
|
||||
{
|
||||
ds.Filter = "(&(objectClass=user)(sAMAccountName=krbtgt))";
|
||||
ds.PropertiesToLoad.Add("msDS-SupportedEncryptionTypes");
|
||||
var result = ds.FindOne();
|
||||
if (result != null)
|
||||
{
|
||||
var encValue = GetIntProp(result, "msDS-SupportedEncryptionTypes");
|
||||
if (encValue.HasValue)
|
||||
{
|
||||
var desc = DescribeEncTypes(encValue);
|
||||
if (IsRc4Allowed(encValue))
|
||||
Beaprint.BadPrint($" krbtgt supports: {desc} — RC4 TGTs can still be issued.");
|
||||
else
|
||||
Beaprint.GoodPrint($" krbtgt supports: {desc}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] krbtgt enc types inherit domain defaults (unspecified).");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] Unable to query Kerberos defaults: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnumerateKerberoastCandidates(string defaultNc)
|
||||
{
|
||||
int checkedAccounts = 0;
|
||||
int highTotal = 0;
|
||||
int mediumTotal = 0;
|
||||
var high = new List<KerberoastCandidate>();
|
||||
var medium = new List<KerberoastCandidate>();
|
||||
|
||||
try
|
||||
{
|
||||
using (var baseDe = new DirectoryEntry("LDAP://" + defaultNc))
|
||||
using (var ds = new DirectorySearcher(baseDe))
|
||||
{
|
||||
ds.PageSize = 500;
|
||||
ds.Filter = "(servicePrincipalName=*)";
|
||||
ds.PropertiesToLoad.Add("sAMAccountName");
|
||||
ds.PropertiesToLoad.Add("displayName");
|
||||
ds.PropertiesToLoad.Add("distinguishedName");
|
||||
ds.PropertiesToLoad.Add("servicePrincipalName");
|
||||
ds.PropertiesToLoad.Add("msDS-SupportedEncryptionTypes");
|
||||
ds.PropertiesToLoad.Add("userAccountControl");
|
||||
ds.PropertiesToLoad.Add("pwdLastSet");
|
||||
ds.PropertiesToLoad.Add("memberOf");
|
||||
ds.PropertiesToLoad.Add("objectClass");
|
||||
|
||||
foreach (SearchResult r in ds.FindAll())
|
||||
{
|
||||
checkedAccounts++;
|
||||
var candidate = BuildKerberoastCandidate(r);
|
||||
if (candidate == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidate.IsHighRisk)
|
||||
{
|
||||
highTotal++;
|
||||
if (high.Count < 15) high.Add(candidate);
|
||||
}
|
||||
else
|
||||
{
|
||||
mediumTotal++;
|
||||
if (medium.Count < 12) medium.Add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Beaprint.InfoPrint($"Checked {checkedAccounts} SPN-bearing accounts. High-risk RC4/privileged targets: {highTotal}, long-lived AES-only targets: {mediumTotal}.");
|
||||
|
||||
if (highTotal == 0 && mediumTotal == 0)
|
||||
{
|
||||
Beaprint.GoodPrint(" No obvious Kerberoastable service accounts detected with current visibility.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (high.Count > 0)
|
||||
{
|
||||
Beaprint.BadPrint(" [!] RC4-enabled or privileged SPN accounts:");
|
||||
foreach (var c in high)
|
||||
{
|
||||
Beaprint.ColorPrint($" - {c.Label} | SPNs: {c.SpnSummary} | Enc: {c.Encryption} | {c.Reason}", Beaprint.LRED);
|
||||
}
|
||||
if (highTotal > high.Count)
|
||||
{
|
||||
Beaprint.GrayPrint($" ... {highTotal - high.Count} additional high-risk accounts omitted.");
|
||||
}
|
||||
}
|
||||
|
||||
if (medium.Count > 0)
|
||||
{
|
||||
Beaprint.ColorPrint(" [~] Long-lived SPN accounts (still Kerberoastable via AES tickets):", Beaprint.YELLOW);
|
||||
foreach (var c in medium)
|
||||
{
|
||||
Beaprint.ColorPrint($" - {c.Label} | SPNs: {c.SpnSummary} | Enc: {c.Encryption} | {c.Reason}", Beaprint.YELLOW);
|
||||
}
|
||||
if (mediumTotal > medium.Count)
|
||||
{
|
||||
Beaprint.GrayPrint($" ... {mediumTotal - medium.Count} additional medium-risk accounts omitted.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] LDAP error while enumerating SPNs: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private KerberoastCandidate BuildKerberoastCandidate(SearchResult r)
|
||||
{
|
||||
var sam = GetProp(r, "sAMAccountName");
|
||||
var displayName = GetProp(r, "displayName");
|
||||
var dn = GetProp(r, "distinguishedName");
|
||||
|
||||
if (IsComputerObject(r) || IsManagedServiceAccount(r))
|
||||
return null;
|
||||
|
||||
var uac = GetIntProp(r, "userAccountControl");
|
||||
if (uac.HasValue && (uac.Value & 0x2) != 0)
|
||||
return null;
|
||||
|
||||
var encValue = GetIntProp(r, "msDS-SupportedEncryptionTypes");
|
||||
bool rc4Allowed = IsRc4Allowed(encValue);
|
||||
bool aesPresent = HasAes(encValue);
|
||||
bool passwordNeverExpires = uac.HasValue && (uac.Value & 0x10000) != 0;
|
||||
DateTime? pwdLastSet = GetFileTimeProp(r, "pwdLastSet");
|
||||
bool stalePassword = pwdLastSet.HasValue && pwdLastSet.Value < DateTime.UtcNow.AddDays(-365);
|
||||
var privilegeHits = GetPrivilegedGroups(r);
|
||||
var reasons = new List<string>();
|
||||
|
||||
if (rc4Allowed)
|
||||
reasons.Add("RC4 allowed");
|
||||
else if (!aesPresent)
|
||||
reasons.Add("No AES flag");
|
||||
if (passwordNeverExpires)
|
||||
reasons.Add("PasswordNeverExpires");
|
||||
if (stalePassword)
|
||||
reasons.Add("PwdLastSet " + pwdLastSet.Value.ToString("yyyy-MM-dd"));
|
||||
if (privilegeHits.Count > 0)
|
||||
reasons.Add("Privileged: " + string.Join("/", privilegeHits));
|
||||
|
||||
if (reasons.Count == 0)
|
||||
return null;
|
||||
|
||||
bool isHigh = rc4Allowed || privilegeHits.Count > 0;
|
||||
if (!isHigh && !(passwordNeverExpires || stalePassword))
|
||||
return null;
|
||||
|
||||
var label = !string.IsNullOrEmpty(sam) ? sam : dn;
|
||||
if (!string.IsNullOrEmpty(displayName) && !string.Equals(displayName, sam, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
label = string.IsNullOrEmpty(sam) ? displayName : $"{sam} ({displayName})";
|
||||
}
|
||||
|
||||
return new KerberoastCandidate
|
||||
{
|
||||
Label = label ?? "<unknown>",
|
||||
SpnSummary = BuildSpnSummary(r),
|
||||
Encryption = DescribeEncTypes(encValue),
|
||||
Reason = string.Join("; ", reasons),
|
||||
IsHighRisk = isHigh
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildSpnSummary(SearchResult r)
|
||||
{
|
||||
if (!r.Properties.Contains("servicePrincipalName") || r.Properties["servicePrincipalName"].Count == 0)
|
||||
return "<none>";
|
||||
|
||||
var values = r.Properties["servicePrincipalName"];
|
||||
var list = new List<string>();
|
||||
int limit = values.Count < 3 ? values.Count : 3;
|
||||
for (int i = 0; i < limit; i++)
|
||||
{
|
||||
var spn = values[i]?.ToString();
|
||||
if (!string.IsNullOrEmpty(spn))
|
||||
list.Add(spn);
|
||||
}
|
||||
|
||||
string summary = list.Count > 0 ? string.Join(", ", list) : "<none>";
|
||||
if (values.Count > limit)
|
||||
summary += $" (+{values.Count - limit} more)";
|
||||
return summary;
|
||||
}
|
||||
|
||||
private static List<string> GetPrivilegedGroups(SearchResult r)
|
||||
{
|
||||
var hits = new List<string>();
|
||||
if (!r.Properties.Contains("memberOf"))
|
||||
return hits;
|
||||
|
||||
var memberships = r.Properties["memberOf"];
|
||||
foreach (var membership in memberships)
|
||||
{
|
||||
var cn = ExtractCn(membership?.ToString());
|
||||
if (string.IsNullOrEmpty(cn))
|
||||
continue;
|
||||
|
||||
foreach (var keyword in PrivilegedGroupKeywords)
|
||||
{
|
||||
if (cn.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
if (!hits.Contains(cn))
|
||||
hits.Add(cn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
private static string ExtractCn(string dn)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dn))
|
||||
return null;
|
||||
|
||||
var parts = dn.Split(',');
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var trimmed = part.Trim();
|
||||
if (trimmed.StartsWith("CN=", StringComparison.OrdinalIgnoreCase))
|
||||
return trimmed.Substring(3);
|
||||
}
|
||||
return dn;
|
||||
}
|
||||
|
||||
private static bool IsComputerObject(SearchResult r)
|
||||
{
|
||||
return HasObjectClass(r, "computer");
|
||||
}
|
||||
|
||||
private static bool IsManagedServiceAccount(SearchResult r)
|
||||
{
|
||||
return HasObjectClass(r, "msDS-ManagedServiceAccount") || HasObjectClass(r, "msDS-GroupManagedServiceAccount");
|
||||
}
|
||||
|
||||
private static bool HasObjectClass(SearchResult r, string className)
|
||||
{
|
||||
if (!r.Properties.Contains("objectClass"))
|
||||
return false;
|
||||
|
||||
foreach (var val in r.Properties["objectClass"])
|
||||
{
|
||||
if (string.Equals(val?.ToString(), className, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static DateTime? GetFileTimeProp(SearchResult r, string propName)
|
||||
{
|
||||
if (!r.Properties.Contains(propName) || r.Properties[propName].Count == 0)
|
||||
return null;
|
||||
return ConvertFileTime(r.Properties[propName][0]);
|
||||
}
|
||||
|
||||
private static DateTime? ConvertFileTime(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
try
|
||||
{
|
||||
if (value is long longVal)
|
||||
{
|
||||
if (longVal <= 0) return null;
|
||||
return DateTime.FromFileTimeUtc(longVal);
|
||||
}
|
||||
|
||||
if (value is IConvertible convertible)
|
||||
{
|
||||
long converted = convertible.ToInt64(null);
|
||||
if (converted > 0)
|
||||
return DateTime.FromFileTimeUtc(converted);
|
||||
}
|
||||
|
||||
var type = value.GetType();
|
||||
var highProp = type.GetProperty("HighPart", BindingFlags.Public | BindingFlags.Instance);
|
||||
var lowProp = type.GetProperty("LowPart", BindingFlags.Public | BindingFlags.Instance);
|
||||
if (highProp != null && lowProp != null)
|
||||
{
|
||||
int high = Convert.ToInt32(highProp.GetValue(value, null));
|
||||
int low = Convert.ToInt32(lowProp.GetValue(value, null));
|
||||
long fileTime = ((long)high << 32) | (uint)low;
|
||||
if (fileTime > 0)
|
||||
return DateTime.FromFileTimeUtc(fileTime);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? GetIntProp(SearchResult r, string name)
|
||||
{
|
||||
if (!r.Properties.Contains(name) || r.Properties[name].Count == 0)
|
||||
return null;
|
||||
return ConvertToNullableInt(r.Properties[name][0]);
|
||||
}
|
||||
|
||||
private static int? GetDirectoryEntryInt(DirectoryEntry entry, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ConvertToNullableInt(entry.Properties[name]?.Value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int? ConvertToNullableInt(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
if (value is int intValue)
|
||||
return intValue;
|
||||
if (value is long longValue)
|
||||
return unchecked((int)longValue);
|
||||
if (int.TryParse(value.ToString(), out var parsed))
|
||||
return parsed;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsRc4Allowed(int? encValue)
|
||||
{
|
||||
if (!encValue.HasValue || encValue.Value == 0)
|
||||
return true;
|
||||
return (encValue.Value & EncFlagRc4) != 0;
|
||||
}
|
||||
|
||||
private static bool HasAes(int? encValue)
|
||||
{
|
||||
if (!encValue.HasValue)
|
||||
return false;
|
||||
return (encValue.Value & (EncFlagAes128 | EncFlagAes256)) != 0;
|
||||
}
|
||||
|
||||
private static string DescribeEncTypes(int? encValue)
|
||||
{
|
||||
if (!encValue.HasValue || encValue.Value == 0)
|
||||
return "Unspecified (inherits defaults / RC4 compatible)";
|
||||
|
||||
var parts = new List<string>();
|
||||
if ((encValue.Value & EncFlagDesCrc) != 0) parts.Add("DES-CBC-CRC");
|
||||
if ((encValue.Value & EncFlagDesMd5) != 0) parts.Add("DES-CBC-MD5");
|
||||
if ((encValue.Value & EncFlagRc4) != 0) parts.Add("RC4-HMAC");
|
||||
if ((encValue.Value & EncFlagAes128) != 0) parts.Add("AES128");
|
||||
if ((encValue.Value & EncFlagAes256) != 0) parts.Add("AES256");
|
||||
if ((encValue.Value & 0x20) != 0) parts.Add("FAST");
|
||||
if (parts.Count == 0) parts.Add($"0x{encValue.Value:X}");
|
||||
return string.Join(", ", parts);
|
||||
}
|
||||
|
||||
private class KerberoastCandidate
|
||||
{
|
||||
public string Label { get; set; }
|
||||
public string SpnSummary { get; set; }
|
||||
public string Encryption { get; set; }
|
||||
public string Reason { get; set; }
|
||||
public bool IsHighRisk { get; set; }
|
||||
}
|
||||
|
||||
private static readonly string[] PrivilegedGroupKeywords = new[]
|
||||
{
|
||||
"Domain Admin",
|
||||
"Enterprise Admin",
|
||||
"Administrators",
|
||||
"Exchange",
|
||||
"Schema Admin",
|
||||
"Account Operator",
|
||||
"Server Operator",
|
||||
"Backup Operator",
|
||||
"DnsAdmin"
|
||||
};
|
||||
|
||||
private const int EncFlagDesCrc = 0x1;
|
||||
private const int EncFlagDesMd5 = 0x2;
|
||||
private const int EncFlagRc4 = 0x4;
|
||||
private const int EncFlagAes128 = 0x8;
|
||||
private const int EncFlagAes256 = 0x10;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +88,13 @@ namespace winPEAS.Checks
|
||||
new SystemCheck("userinfo", new UserInfo()),
|
||||
new SystemCheck("processinfo", new ProcessInfo()),
|
||||
new SystemCheck("servicesinfo", new ServicesInfo()),
|
||||
new SystemCheck("soapclientinfo", new SoapClientInfo()),
|
||||
new SystemCheck("applicationsinfo", new ApplicationsInfo()),
|
||||
new SystemCheck("networkinfo", new NetworkInfo()),
|
||||
new SystemCheck("activedirectoryinfo", new ActiveDirectoryInfo()),
|
||||
new SystemCheck("cloudinfo", new CloudInfo()),
|
||||
new SystemCheck("windowscreds", new WindowsCreds()),
|
||||
new SystemCheck("registryinfo", new RegistryInfo()),
|
||||
new SystemCheck("browserinfo", new BrowserInfo()),
|
||||
new SystemCheck("filesinfo", new FilesInfo()),
|
||||
new SystemCheck("fileanalysis", new FileAnalysis()),
|
||||
|
||||
141
winPEAS/winPEASexe/winPEAS/Checks/RegistryInfo.cs
Normal file
141
winPEAS/winPEASexe/winPEAS/Checks/RegistryInfo.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using winPEAS.Helpers;
|
||||
using winPEAS.Helpers.Registry;
|
||||
|
||||
namespace winPEAS.Checks
|
||||
{
|
||||
internal class RegistryInfo : ISystemCheck
|
||||
{
|
||||
private const string TypingInsightsRelativePath = @"Software\Microsoft\Input\TypingInsights";
|
||||
|
||||
private static readonly string[] KnownWritableSystemKeyCandidates = new[]
|
||||
{
|
||||
@"SOFTWARE\Microsoft\CoreShell",
|
||||
@"SOFTWARE\Microsoft\DRM",
|
||||
@"SOFTWARE\Microsoft\Input\Locales",
|
||||
@"SOFTWARE\Microsoft\Input\Settings",
|
||||
@"SOFTWARE\Microsoft\Shell\Oobe",
|
||||
@"SOFTWARE\Microsoft\Shell\Session",
|
||||
@"SOFTWARE\Microsoft\Tracing",
|
||||
@"SOFTWARE\Microsoft\Windows\UpdateApi",
|
||||
@"SOFTWARE\Microsoft\WindowsUpdate\UX",
|
||||
@"SOFTWARE\WOW6432Node\Microsoft\DRM",
|
||||
@"SOFTWARE\WOW6432Node\Microsoft\Tracing",
|
||||
@"SYSTEM\Software\Microsoft\TIP",
|
||||
@"SYSTEM\ControlSet001\Control\Cryptography\WebSignIn\Navigation",
|
||||
@"SYSTEM\ControlSet001\Control\MUI\StringCacheSettings",
|
||||
@"SYSTEM\ControlSet001\Control\USB\AutomaticSurpriseRemoval",
|
||||
@"SYSTEM\ControlSet001\Services\BTAGService\Parameters\Settings",
|
||||
};
|
||||
|
||||
private static readonly string[] ScanBasePaths = new[]
|
||||
{
|
||||
@"SOFTWARE\Microsoft",
|
||||
@"SOFTWARE\WOW6432Node\Microsoft",
|
||||
@"SYSTEM\CurrentControlSet\Services",
|
||||
@"SYSTEM\CurrentControlSet\Control",
|
||||
@"SYSTEM\ControlSet001\Control",
|
||||
};
|
||||
|
||||
public void PrintInfo(bool isDebug)
|
||||
{
|
||||
Beaprint.GreatPrint("Registry permissions for hive exploitation");
|
||||
|
||||
new List<Action>
|
||||
{
|
||||
PrintTypingInsightsPermissions,
|
||||
PrintKnownSystemWritableKeys,
|
||||
PrintHeuristicWritableKeys,
|
||||
}.ForEach(action => CheckRunner.Run(action, isDebug));
|
||||
}
|
||||
|
||||
private void PrintTypingInsightsPermissions()
|
||||
{
|
||||
Beaprint.MainPrint("Cross-user TypingInsights key (HKCU/HKU)");
|
||||
|
||||
var matches = new List<RegistryWritableKeyInfo>();
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (RegistryAclScanner.TryGetWritableKey("HKCU", TypingInsightsRelativePath, out var currentUserKey))
|
||||
{
|
||||
if (seen.Add(currentUserKey.FullPath))
|
||||
{
|
||||
matches.Add(currentUserKey);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sid in RegistryHelper.GetUserSIDs())
|
||||
{
|
||||
if (string.IsNullOrEmpty(sid) || sid.Equals(".DEFAULT", StringComparison.OrdinalIgnoreCase) || sid.EndsWith("_Classes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string relativePath = $"{sid}\\{TypingInsightsRelativePath}";
|
||||
if (RegistryAclScanner.TryGetWritableKey("HKU", relativePath, out var info) && seen.Add(info.FullPath))
|
||||
{
|
||||
matches.Add(info);
|
||||
}
|
||||
}
|
||||
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] TypingInsights key does not grant write access to low-privileged groups.");
|
||||
return;
|
||||
}
|
||||
|
||||
PrintEntries(matches);
|
||||
Beaprint.LinkPrint("https://projectzero.google/2025/05/the-windows-registry-adventure-8-exploitation.html", "Writable TypingInsights enables cross-user hive tampering and DoS.");
|
||||
}
|
||||
|
||||
private void PrintKnownSystemWritableKeys()
|
||||
{
|
||||
Beaprint.MainPrint("Known HKLM descendants writable by standard users");
|
||||
|
||||
var matches = new List<RegistryWritableKeyInfo>();
|
||||
foreach (var path in KnownWritableSystemKeyCandidates)
|
||||
{
|
||||
if (RegistryAclScanner.TryGetWritableKey("HKLM", path, out var info))
|
||||
{
|
||||
matches.Add(info);
|
||||
}
|
||||
}
|
||||
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] None of the tracked HKLM keys are writable by low-privileged groups.");
|
||||
return;
|
||||
}
|
||||
|
||||
PrintEntries(matches);
|
||||
}
|
||||
|
||||
private void PrintHeuristicWritableKeys()
|
||||
{
|
||||
Beaprint.MainPrint("Sample of additional writable HKLM keys (depth-limited scan)");
|
||||
|
||||
var matches = RegistryAclScanner.ScanWritableKeys("HKLM", ScanBasePaths, maxDepth: 3, maxResults: 25);
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] No additional writable HKLM keys were found within the sampled paths.");
|
||||
return;
|
||||
}
|
||||
|
||||
PrintEntries(matches);
|
||||
Beaprint.GrayPrint(" [*] Showing up to 25 entries from the sampled paths to avoid noisy output.");
|
||||
}
|
||||
|
||||
private static void PrintEntries(IEnumerable<RegistryWritableKeyInfo> entries)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var principals = string.Join(", ", entry.Principals);
|
||||
var rights = entry.Rights.Count > 0 ? string.Join(", ", entry.Rights.Distinct(StringComparer.OrdinalIgnoreCase)) : "Write access";
|
||||
var displayPath = string.IsNullOrEmpty(entry.FullPath) ? $"{entry.Hive}\\{entry.RelativePath}" : entry.FullPath;
|
||||
Beaprint.BadPrint($" [!] {displayPath} -> {principals} ({rights})");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ namespace winPEAS.Checks
|
||||
PrintModifiableServices,
|
||||
PrintWritableRegServices,
|
||||
PrintPathDllHijacking,
|
||||
PrintOemPrivilegedUtilities,
|
||||
PrintLegacySignedKernelDrivers,
|
||||
PrintKernelQuickIndicators,
|
||||
}.ForEach(action => CheckRunner.Run(action, isDebug));
|
||||
@@ -210,6 +211,51 @@ namespace winPEAS.Checks
|
||||
}
|
||||
}
|
||||
|
||||
void PrintOemPrivilegedUtilities()
|
||||
{
|
||||
try
|
||||
{
|
||||
Beaprint.MainPrint("OEM privileged utilities & risky components");
|
||||
var findings = OemSoftwareHelper.GetPotentiallyVulnerableComponents(Checks.CurrentUserSiDs);
|
||||
|
||||
if (findings.Count == 0)
|
||||
{
|
||||
Beaprint.GoodPrint(" None of the supported OEM utilities were detected.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var finding in findings)
|
||||
{
|
||||
bool hasCves = finding.Cves != null && finding.Cves.Length > 0;
|
||||
string cveSuffix = hasCves ? $" ({string.Join(", ", finding.Cves)})" : string.Empty;
|
||||
Beaprint.BadPrint($" {finding.Name}{cveSuffix}");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(finding.Description))
|
||||
{
|
||||
Beaprint.GrayPrint($" {finding.Description}");
|
||||
}
|
||||
|
||||
foreach (var evidence in finding.Evidence)
|
||||
{
|
||||
string message = $" - {evidence.Message}";
|
||||
if (evidence.Highlight)
|
||||
{
|
||||
Beaprint.BadPrint(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Beaprint.GrayPrint(message);
|
||||
}
|
||||
}
|
||||
|
||||
Beaprint.PrintLineSeparator();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.PrintException(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
void PrintLegacySignedKernelDrivers()
|
||||
{
|
||||
@@ -352,4 +398,3 @@ namespace winPEAS.Checks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
88
winPEAS/winPEASexe/winPEAS/Checks/SoapClientInfo.cs
Normal file
88
winPEAS/winPEASexe/winPEAS/Checks/SoapClientInfo.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using winPEAS.Helpers;
|
||||
using winPEAS.Info.ApplicationInfo;
|
||||
|
||||
namespace winPEAS.Checks
|
||||
{
|
||||
internal class SoapClientInfo : ISystemCheck
|
||||
{
|
||||
public void PrintInfo(bool isDebug)
|
||||
{
|
||||
Beaprint.GreatPrint(".NET SOAP Client Proxies (SOAPwn)");
|
||||
|
||||
CheckRunner.Run(PrintSoapClientFindings, isDebug);
|
||||
}
|
||||
|
||||
private static void PrintSoapClientFindings()
|
||||
{
|
||||
try
|
||||
{
|
||||
Beaprint.MainPrint("Potential SOAPwn / HttpWebClientProtocol abuse surfaces");
|
||||
Beaprint.LinkPrint(
|
||||
"https://labs.watchtowr.com/soapwn-pwning-net-framework-applications-through-http-client-proxies-and-wsdl/",
|
||||
"Look for .NET services that let attackers control SoapHttpClientProtocol URLs or WSDL imports to coerce NTLM or drop files.");
|
||||
|
||||
List<SoapClientProxyFinding> findings = SoapClientProxyAnalyzer.CollectFindings();
|
||||
if (findings.Count == 0)
|
||||
{
|
||||
Beaprint.NotFoundPrint();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (SoapClientProxyFinding finding in findings)
|
||||
{
|
||||
string severity = finding.BinaryIndicators.Contains("ServiceDescriptionImporter")
|
||||
? "Dynamic WSDL import"
|
||||
: "SOAP proxy usage";
|
||||
|
||||
Beaprint.BadPrint($" [{severity}] {finding.BinaryPath}");
|
||||
|
||||
foreach (SoapClientProxyInstance instance in finding.Instances)
|
||||
{
|
||||
string instanceInfo = $" -> {instance.SourceType}: {instance.Name}";
|
||||
if (!string.IsNullOrEmpty(instance.Account))
|
||||
{
|
||||
instanceInfo += $" ({instance.Account})";
|
||||
}
|
||||
if (!string.IsNullOrEmpty(instance.Extra))
|
||||
{
|
||||
instanceInfo += $" | {instance.Extra}";
|
||||
}
|
||||
|
||||
Beaprint.GrayPrint(instanceInfo);
|
||||
}
|
||||
|
||||
if (finding.BinaryIndicators.Count > 0)
|
||||
{
|
||||
Beaprint.BadPrint(" Binary indicators: " + string.Join(", ", finding.BinaryIndicators));
|
||||
}
|
||||
|
||||
if (finding.ConfigIndicators.Count > 0)
|
||||
{
|
||||
string configLabel = string.IsNullOrEmpty(finding.ConfigPath)
|
||||
? "Config indicators"
|
||||
: $"Config indicators ({finding.ConfigPath})";
|
||||
Beaprint.BadPrint(" " + configLabel + ": " + string.Join(", ", finding.ConfigIndicators));
|
||||
}
|
||||
|
||||
if (finding.BinaryScanFailed)
|
||||
{
|
||||
Beaprint.GrayPrint(" (Binary scan skipped due to access/size limits)");
|
||||
}
|
||||
|
||||
if (finding.ConfigScanFailed)
|
||||
{
|
||||
Beaprint.GrayPrint(" (Unable to read config file)");
|
||||
}
|
||||
|
||||
Beaprint.PrintLineSeparator();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.PrintException(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,7 @@ namespace winPEAS.Checks
|
||||
PrintKrbRelayUp,
|
||||
PrintInsideContainer,
|
||||
PrintAlwaysInstallElevated,
|
||||
PrintObjectManagerRaceAmplification,
|
||||
PrintLSAInfo,
|
||||
PrintNtlmSettings,
|
||||
PrintLocalGroupPolicy,
|
||||
@@ -89,6 +90,7 @@ namespace winPEAS.Checks
|
||||
AppLockerHelper.PrintAppLockerPolicy,
|
||||
PrintPrintersWMIInfo,
|
||||
PrintNamedPipes,
|
||||
PrintNamedPipeAbuseCandidates,
|
||||
PrintAMSIProviders,
|
||||
PrintSysmon,
|
||||
PrintDotNetVersions
|
||||
@@ -733,6 +735,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");
|
||||
@@ -857,6 +884,48 @@ namespace winPEAS.Checks
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void PrintNamedPipeAbuseCandidates()
|
||||
{
|
||||
Beaprint.MainPrint("Named Pipes with Low-Priv Write Access to Privileged Servers");
|
||||
|
||||
try
|
||||
{
|
||||
var candidates = NamedPipeSecurityAnalyzer.GetNamedPipeAbuseCandidates().ToList();
|
||||
|
||||
if (!candidates.Any())
|
||||
{
|
||||
Beaprint.NoColorPrint(" No risky named pipe ACLs were found.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
var aclSummary = candidate.LowPrivilegeAces.Any()
|
||||
? string.Join("; ", candidate.LowPrivilegeAces.Select(ace =>
|
||||
$"{ace.Principal} [{ace.RightsDescription}]").Where(s => !string.IsNullOrEmpty(s)))
|
||||
: "Unknown";
|
||||
|
||||
var serverSummary = candidate.Processes.Any()
|
||||
? string.Join("; ", candidate.Processes.Select(proc =>
|
||||
$"{proc.ProcessName} (PID {proc.Pid}, {proc.UserName ?? proc.UserSid})"))
|
||||
: "No privileged handles observed (service idle or access denied)";
|
||||
|
||||
var color = candidate.HasPrivilegedServer ? Beaprint.ansi_color_bad : Beaprint.ansi_color_yellow;
|
||||
|
||||
Beaprint.ColorPrint($" \\\\.\\pipe\\{candidate.Name}", color);
|
||||
Beaprint.NoColorPrint($" Low-priv ACLs : {aclSummary}");
|
||||
Beaprint.NoColorPrint($" Observed owners: {serverSummary}");
|
||||
Beaprint.NoColorPrint($" SDDL : {candidate.Sddl}");
|
||||
Beaprint.PrintLineSeparator();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.PrintException(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintAMSIProviders()
|
||||
{
|
||||
Beaprint.MainPrint("Enumerating AMSI registered providers");
|
||||
|
||||
@@ -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<Dictionary<string, string>> 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<Dictionary<string, string>> flaggedSessions = new List<Dictionary<string, string>>();
|
||||
foreach (Dictionary<string, string> 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<string, string> 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
|
||||
|
||||
@@ -132,6 +132,7 @@ namespace winPEAS.Helpers
|
||||
Console.WriteLine(LCYAN + " activedirectoryinfo" + GRAY + " Quick AD checks (gMSA readable passwords, AD CS template rights)" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " cloudinfo" + GRAY + " Enumerate cloud information" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " windowscreds" + GRAY + " Search windows credentials" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " registryinfo" + GRAY + " Flag writable HKLM/HKU keys that enable hive tampering" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " browserinfo" + GRAY + " Search browser information" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " filesinfo" + GRAY + " Search generic files that can contains credentials" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " fileanalysis" + GRAY + " [NOT RUN BY DEFAULT] Search specific files that can contains credentials and for regexes inside files. Might take several minutes." + NOCOLOR);
|
||||
|
||||
@@ -24,36 +24,51 @@ namespace winPEAS.Helpers
|
||||
////////////////////////////////////
|
||||
/////// MISC - Files & Paths ///////
|
||||
////////////////////////////////////
|
||||
public static bool CheckIfDotNet(string path)
|
||||
public static bool CheckIfDotNet(string path, bool ignoreCompanyName = false)
|
||||
{
|
||||
bool isDotNet = false;
|
||||
FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(path);
|
||||
string companyName = myFileVersionInfo.CompanyName;
|
||||
if ((string.IsNullOrEmpty(companyName)) ||
|
||||
(!Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase)))
|
||||
string companyName = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(path);
|
||||
companyName = myFileVersionInfo.CompanyName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Unable to read version information, continue with assembly inspection
|
||||
}
|
||||
|
||||
bool shouldInspectAssembly = ignoreCompanyName ||
|
||||
(string.IsNullOrEmpty(companyName)) ||
|
||||
(!Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase));
|
||||
|
||||
if (!shouldInspectAssembly)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AssemblyName.GetAssemblyName(path);
|
||||
isDotNet = true;
|
||||
}
|
||||
catch (System.IO.FileNotFoundException)
|
||||
{
|
||||
// System.Console.WriteLine("The file cannot be found.");
|
||||
}
|
||||
catch (System.BadImageFormatException exception)
|
||||
{
|
||||
if (Regex.IsMatch(exception.Message,
|
||||
".*This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.*",
|
||||
RegexOptions.IgnoreCase))
|
||||
{
|
||||
AssemblyName myAssemblyName = AssemblyName.GetAssemblyName(path);
|
||||
isDotNet = true;
|
||||
}
|
||||
catch (System.IO.FileNotFoundException)
|
||||
{
|
||||
// System.Console.WriteLine("The file cannot be found.");
|
||||
}
|
||||
catch (System.BadImageFormatException exception)
|
||||
{
|
||||
if (Regex.IsMatch(exception.Message,
|
||||
".*This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.*",
|
||||
RegexOptions.IgnoreCase))
|
||||
{
|
||||
isDotNet = true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// System.Console.WriteLine("The assembly has already been loaded.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// System.Console.WriteLine("The assembly has already been loaded.");
|
||||
}
|
||||
|
||||
return isDotNet;
|
||||
|
||||
34
winPEAS/winPEASexe/winPEAS/Helpers/ObjectManagerHelper.cs
Normal file
34
winPEAS/winPEASexe/winPEAS/Helpers/ObjectManagerHelper.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using winPEAS.Helpers;
|
||||
|
||||
namespace winPEAS.Helpers.Registry
|
||||
{
|
||||
internal class RegistryWritableKeyInfo
|
||||
{
|
||||
public string Hive { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public string FullPath { get; set; }
|
||||
public List<string> Principals { get; set; } = new List<string>();
|
||||
public List<string> Rights { get; set; } = new List<string>();
|
||||
}
|
||||
|
||||
internal static class RegistryAclScanner
|
||||
{
|
||||
private static readonly Dictionary<string, string> LowPrivSidMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null).Value, "BUILTIN\\Users" },
|
||||
{ new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null).Value, "Authenticated Users" },
|
||||
{ new SecurityIdentifier(WellKnownSidType.WorldSid, null).Value, "Everyone" },
|
||||
{ new SecurityIdentifier(WellKnownSidType.InteractiveSid, null).Value, "Interactive" },
|
||||
{ new SecurityIdentifier(WellKnownSidType.BuiltinGuestsSid, null).Value, "BUILTIN\\Guests" },
|
||||
};
|
||||
|
||||
public static bool TryGetWritableKey(string hive, string relativePath, out RegistryWritableKeyInfo info)
|
||||
{
|
||||
info = null;
|
||||
using (var key = OpenKey(hive, relativePath))
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryCollectWritableInfo(hive, relativePath, key, out info);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<RegistryWritableKeyInfo> ScanWritableKeys(string hive, IEnumerable<string> basePaths, int maxDepth, int maxResults)
|
||||
{
|
||||
var results = new List<RegistryWritableKeyInfo>();
|
||||
var seenPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var basePath in basePaths ?? Enumerable.Empty<string>())
|
||||
{
|
||||
if (results.Count >= maxResults)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
using (var key = OpenKey(hive, basePath))
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Traverse(hive, key, basePath, 0, maxDepth, maxResults, seenPaths, results);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static void Traverse(string hive, RegistryKey currentKey, string currentPath, int depth, int maxDepth, int maxResults, HashSet<string> seenPaths, List<RegistryWritableKeyInfo> results)
|
||||
{
|
||||
if (currentKey == null || results.Count >= maxResults)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryCollectWritableInfo(hive, currentPath, currentKey, out var info))
|
||||
{
|
||||
if (seenPaths.Add(info.FullPath))
|
||||
{
|
||||
results.Add(info);
|
||||
}
|
||||
|
||||
if (results.Count >= maxResults)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth >= maxDepth)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] subKeys;
|
||||
try
|
||||
{
|
||||
subKeys = currentKey.GetSubKeyNames();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var subKeyName in subKeys)
|
||||
{
|
||||
if (results.Count >= maxResults)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var childKey = currentKey.OpenSubKey(subKeyName))
|
||||
{
|
||||
if (childKey == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string childPath = string.IsNullOrEmpty(currentPath) ? subKeyName : $"{currentPath}\\{subKeyName}";
|
||||
Traverse(hive, childKey, childPath, depth + 1, maxDepth, maxResults, seenPaths, results);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore keys we cannot open
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryCollectWritableInfo(string hive, string relativePath, RegistryKey key, out RegistryWritableKeyInfo info)
|
||||
{
|
||||
info = null;
|
||||
|
||||
try
|
||||
{
|
||||
var acl = key.GetAccessControl(AccessControlSections.Access);
|
||||
|
||||
var principals = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var rights = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (RegistryAccessRule rule in acl.GetAccessRules(true, true, typeof(SecurityIdentifier)))
|
||||
{
|
||||
if (rule.AccessControlType != AccessControlType.Allow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var sid = rule.IdentityReference as SecurityIdentifier ?? rule.IdentityReference.Translate(typeof(SecurityIdentifier)) as SecurityIdentifier;
|
||||
if (sid == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!LowPrivSidMap.TryGetValue(sid.Value, out var label))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string interestingRight = PermissionsHelper.PermInt2Str((int)rule.RegistryRights, PermissionType.WRITEABLE_OR_EQUIVALENT_REG);
|
||||
if (string.IsNullOrEmpty(interestingRight))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
principals.Add($"{label} ({sid.Value})");
|
||||
rights.Add(interestingRight);
|
||||
}
|
||||
|
||||
if (principals.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string normalizedRelativePath = relativePath ?? string.Empty;
|
||||
string fullPath = string.IsNullOrEmpty(normalizedRelativePath) ? key.Name : $"{hive}\\{normalizedRelativePath}";
|
||||
|
||||
info = new RegistryWritableKeyInfo
|
||||
{
|
||||
Hive = hive,
|
||||
RelativePath = normalizedRelativePath,
|
||||
FullPath = fullPath,
|
||||
Principals = principals.ToList(),
|
||||
Rights = rights.ToList(),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static RegistryKey OpenKey(string hive, string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RegistryKey baseKey = hive switch
|
||||
{
|
||||
"HKLM" => Microsoft.Win32.Registry.LocalMachine,
|
||||
"HKCU" => Microsoft.Win32.Registry.CurrentUser,
|
||||
"HKU" => Microsoft.Win32.Registry.Users,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return baseKey?.OpenSubKey(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Text;
|
||||
using winPEAS.Helpers;
|
||||
using winPEAS.Info.ProcessInfo;
|
||||
|
||||
namespace winPEAS.Info.ApplicationInfo
|
||||
{
|
||||
internal class SoapClientProxyInstance
|
||||
{
|
||||
public string SourceType { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Account { get; set; }
|
||||
public string Extra { get; set; }
|
||||
}
|
||||
|
||||
internal class SoapClientProxyFinding
|
||||
{
|
||||
public string BinaryPath { get; set; }
|
||||
public List<SoapClientProxyInstance> Instances { get; } = new List<SoapClientProxyInstance>();
|
||||
public HashSet<string> BinaryIndicators { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
public HashSet<string> ConfigIndicators { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
public string ConfigPath { get; set; }
|
||||
public bool BinaryScanFailed { get; set; }
|
||||
public bool ConfigScanFailed { get; set; }
|
||||
}
|
||||
|
||||
internal static class SoapClientProxyAnalyzer
|
||||
{
|
||||
private class SoapClientProxyCandidate
|
||||
{
|
||||
public string BinaryPath { get; set; }
|
||||
public string SourceType { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Account { get; set; }
|
||||
public string Extra { get; set; }
|
||||
}
|
||||
|
||||
private static readonly string[] BinaryIndicatorStrings = new[]
|
||||
{
|
||||
"SoapHttpClientProtocol",
|
||||
"HttpWebClientProtocol",
|
||||
"DiscoveryClientProtocol",
|
||||
"HttpSimpleClientProtocol",
|
||||
"HttpGetClientProtocol",
|
||||
"HttpPostClientProtocol",
|
||||
"ServiceDescriptionImporter",
|
||||
"System.Web.Services.Description.ServiceDescriptionImporter",
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> ConfigIndicatorMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "soap:address", "soap:address element present" },
|
||||
{ "soap12:address", "soap12:address element present" },
|
||||
{ "?wsdl", "?wsdl reference" },
|
||||
{ "<wsdl:", "WSDL schema embedded in config" },
|
||||
{ "servicedescriptionimporter", "ServiceDescriptionImporter referenced in config" },
|
||||
{ "system.web.services.description", "System.Web.Services.Description namespace referenced" },
|
||||
{ "new-webserviceproxy", "PowerShell New-WebServiceProxy referenced" },
|
||||
{ "file://", "file:// scheme referenced" },
|
||||
};
|
||||
|
||||
private const long MaxBinaryScanSize = 200 * 1024 * 1024; // 200MB
|
||||
private static readonly object DotNetCacheLock = new object();
|
||||
private static readonly Dictionary<string, bool> DotNetCache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public static List<SoapClientProxyFinding> CollectFindings()
|
||||
{
|
||||
var findings = new Dictionary<string, SoapClientProxyFinding>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var candidate in EnumerateServiceCandidates().Concat(EnumerateProcessCandidates()))
|
||||
{
|
||||
if (string.IsNullOrEmpty(candidate.BinaryPath) || !File.Exists(candidate.BinaryPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!findings.TryGetValue(candidate.BinaryPath, out var finding))
|
||||
{
|
||||
finding = new SoapClientProxyFinding
|
||||
{
|
||||
BinaryPath = candidate.BinaryPath,
|
||||
};
|
||||
|
||||
findings.Add(candidate.BinaryPath, finding);
|
||||
}
|
||||
|
||||
finding.Instances.Add(new SoapClientProxyInstance
|
||||
{
|
||||
SourceType = candidate.SourceType,
|
||||
Name = candidate.Name,
|
||||
Account = string.IsNullOrEmpty(candidate.Account) ? "Unknown" : candidate.Account,
|
||||
Extra = candidate.Extra ?? string.Empty,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var finding in findings.Values)
|
||||
{
|
||||
ScanBinaryIndicators(finding);
|
||||
ScanConfigIndicators(finding);
|
||||
}
|
||||
|
||||
return findings.Values
|
||||
.Where(f => f.BinaryIndicators.Count > 0 || f.ConfigIndicators.Count > 0)
|
||||
.OrderByDescending(f => f.BinaryIndicators.Contains("ServiceDescriptionImporter"))
|
||||
.ThenBy(f => f.BinaryPath, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<SoapClientProxyCandidate> EnumerateServiceCandidates()
|
||||
{
|
||||
var results = new List<SoapClientProxyCandidate>();
|
||||
try
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher(@"root\\cimv2", "SELECT Name, DisplayName, PathName, StartName FROM Win32_Service"))
|
||||
using (var services = searcher.Get())
|
||||
{
|
||||
foreach (ManagementObject service in services)
|
||||
{
|
||||
string pathName = service["PathName"]?.ToString();
|
||||
string binaryPath = MyUtils.GetExecutableFromPath(pathName ?? string.Empty);
|
||||
if (string.IsNullOrEmpty(binaryPath) || !File.Exists(binaryPath))
|
||||
continue;
|
||||
|
||||
if (!IsDotNetBinary(binaryPath))
|
||||
continue;
|
||||
|
||||
results.Add(new SoapClientProxyCandidate
|
||||
{
|
||||
BinaryPath = binaryPath,
|
||||
SourceType = "Service",
|
||||
Name = service["Name"]?.ToString() ?? string.Empty,
|
||||
Account = service["StartName"]?.ToString() ?? string.Empty,
|
||||
Extra = service["DisplayName"]?.ToString() ?? string.Empty,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.GrayPrint("Error while enumerating services for SOAP client analysis: " + ex.Message);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static IEnumerable<SoapClientProxyCandidate> EnumerateProcessCandidates()
|
||||
{
|
||||
var results = new List<SoapClientProxyCandidate>();
|
||||
try
|
||||
{
|
||||
List<Dictionary<string, string>> processes = ProcessesInfo.GetProcInfo();
|
||||
foreach (var proc in processes)
|
||||
{
|
||||
string path = proc.ContainsKey("ExecutablePath") ? proc["ExecutablePath"] : string.Empty;
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
||||
continue;
|
||||
|
||||
if (!IsDotNetBinary(path))
|
||||
continue;
|
||||
|
||||
string owner = proc.ContainsKey("Owner") ? proc["Owner"] : string.Empty;
|
||||
if (!IsInterestingProcessOwner(owner))
|
||||
continue;
|
||||
|
||||
results.Add(new SoapClientProxyCandidate
|
||||
{
|
||||
BinaryPath = path,
|
||||
SourceType = "Process",
|
||||
Name = proc.ContainsKey("Name") ? proc["Name"] : string.Empty,
|
||||
Account = owner,
|
||||
Extra = proc.ContainsKey("ProcessID") ? $"PID {proc["ProcessID"]}" : string.Empty,
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.GrayPrint("Error while enumerating processes for SOAP client analysis: " + ex.Message);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static bool IsInterestingProcessOwner(string owner)
|
||||
{
|
||||
if (string.IsNullOrEmpty(owner))
|
||||
return true;
|
||||
|
||||
string normalizedOwner = owner;
|
||||
if (owner.Contains("\\"))
|
||||
{
|
||||
normalizedOwner = owner.Split('\\').Last();
|
||||
}
|
||||
|
||||
return !normalizedOwner.Equals(Environment.UserName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsDotNetBinary(string path)
|
||||
{
|
||||
lock (DotNetCacheLock)
|
||||
{
|
||||
if (DotNetCache.TryGetValue(path, out bool cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
try
|
||||
{
|
||||
result = MyUtils.CheckIfDotNet(path, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
DotNetCache[path] = result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ScanBinaryIndicators(SoapClientProxyFinding finding)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileInfo fi = new FileInfo(finding.BinaryPath);
|
||||
if (!fi.Exists || fi.Length == 0)
|
||||
return;
|
||||
|
||||
if (fi.Length > MaxBinaryScanSize)
|
||||
{
|
||||
finding.BinaryScanFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var indicator in BinaryIndicatorStrings)
|
||||
{
|
||||
if (FileContainsString(finding.BinaryPath, indicator))
|
||||
{
|
||||
finding.BinaryIndicators.Add(indicator);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finding.BinaryScanFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ScanConfigIndicators(SoapClientProxyFinding finding)
|
||||
{
|
||||
string configPath = GetConfigPath(finding.BinaryPath);
|
||||
if (!string.IsNullOrEmpty(configPath) && File.Exists(configPath))
|
||||
{
|
||||
finding.ConfigPath = configPath;
|
||||
try
|
||||
{
|
||||
string content = File.ReadAllText(configPath);
|
||||
foreach (var kvp in ConfigIndicatorMap)
|
||||
{
|
||||
if (content.IndexOf(kvp.Key, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
finding.ConfigIndicators.Add(kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finding.ConfigScanFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
string directory = Path.GetDirectoryName(finding.BinaryPath);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
try
|
||||
{
|
||||
var wsdlFiles = Directory.GetFiles(directory, "*.wsdl", SearchOption.TopDirectoryOnly);
|
||||
if (wsdlFiles.Length > 0)
|
||||
{
|
||||
finding.ConfigIndicators.Add($"Found {wsdlFiles.Length} WSDL file(s) next to binary");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetConfigPath(string binaryPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(binaryPath))
|
||||
return string.Empty;
|
||||
|
||||
string candidate = binaryPath + ".config";
|
||||
return File.Exists(candidate) ? candidate : string.Empty;
|
||||
}
|
||||
|
||||
private static bool FileContainsString(string path, string value)
|
||||
{
|
||||
const int bufferSize = 64 * 1024;
|
||||
byte[] pattern = Encoding.UTF8.GetBytes(value);
|
||||
if (pattern.Length == 0)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
|
||||
{
|
||||
byte[] buffer = new byte[bufferSize + pattern.Length];
|
||||
int bufferLen = 0;
|
||||
int bytesRead;
|
||||
while ((bytesRead = fs.Read(buffer, bufferLen, bufferSize)) > 0)
|
||||
{
|
||||
int total = bufferLen + bytesRead;
|
||||
if (IndexOf(buffer, total, pattern) >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pattern.Length > 1)
|
||||
{
|
||||
bufferLen = Math.Min(pattern.Length - 1, total);
|
||||
Buffer.BlockCopy(buffer, total - bufferLen, buffer, 0, bufferLen);
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferLen = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int IndexOf(byte[] buffer, int bufferLength, byte[] pattern)
|
||||
{
|
||||
int limit = bufferLength - pattern.Length;
|
||||
if (limit < 0)
|
||||
return -1;
|
||||
|
||||
for (int i = 0; i <= limit; i++)
|
||||
{
|
||||
bool match = true;
|
||||
for (int j = 0; j < pattern.Length; j++)
|
||||
{
|
||||
if (buffer[i + j] != pattern[j])
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using System.ServiceProcess;
|
||||
using winPEAS.Helpers;
|
||||
|
||||
namespace winPEAS.Info.ServicesInfo
|
||||
{
|
||||
internal static class OemSoftwareHelper
|
||||
{
|
||||
internal static List<OemSoftwareFinding> GetPotentiallyVulnerableComponents(Dictionary<string, string> currentUserSids)
|
||||
{
|
||||
var findings = new List<OemSoftwareFinding>();
|
||||
var services = GetServiceSnapshot();
|
||||
var processes = GetProcessSnapshot();
|
||||
|
||||
foreach (var definition in GetDefinitions())
|
||||
{
|
||||
var finding = new OemSoftwareFinding
|
||||
{
|
||||
Name = definition.Name,
|
||||
Description = definition.Description,
|
||||
Cves = definition.Cves,
|
||||
};
|
||||
|
||||
AppendServiceEvidence(definition, services, finding);
|
||||
AppendProcessEvidence(definition, processes, finding);
|
||||
AppendPathEvidence(definition, currentUserSids, finding);
|
||||
AppendPipeEvidence(definition, finding);
|
||||
|
||||
if (finding.Evidence.Count > 0)
|
||||
{
|
||||
findings.Add(finding);
|
||||
}
|
||||
}
|
||||
|
||||
return findings;
|
||||
}
|
||||
|
||||
private static void AppendServiceEvidence(OemComponentDefinition definition, List<ServiceSnapshot> services, OemSoftwareFinding finding)
|
||||
{
|
||||
if (definition.ServiceHints == null || definition.ServiceHints.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var serviceHint in definition.ServiceHints)
|
||||
{
|
||||
foreach (var service in services)
|
||||
{
|
||||
if (ContainsIgnoreCase(service.Name, serviceHint) ||
|
||||
ContainsIgnoreCase(service.DisplayName, serviceHint))
|
||||
{
|
||||
finding.Evidence.Add(new OemEvidence
|
||||
{
|
||||
EvidenceType = "service",
|
||||
Highlight = true,
|
||||
Message = $"Service '{service.Name}' (Display: {service.DisplayName}) matches indicator '{serviceHint}'"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendProcessEvidence(OemComponentDefinition definition, List<ProcessSnapshot> processes, OemSoftwareFinding finding)
|
||||
{
|
||||
if (definition.ProcessHints == null || definition.ProcessHints.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var processHint in definition.ProcessHints)
|
||||
{
|
||||
foreach (var process in processes)
|
||||
{
|
||||
bool matchesName = ContainsIgnoreCase(process.Name, processHint);
|
||||
bool matchesPath = ContainsIgnoreCase(process.FullPath, processHint);
|
||||
|
||||
if (matchesName || matchesPath)
|
||||
{
|
||||
var location = string.IsNullOrWhiteSpace(process.FullPath) ? "Unknown" : process.FullPath;
|
||||
finding.Evidence.Add(new OemEvidence
|
||||
{
|
||||
EvidenceType = "process",
|
||||
Highlight = true,
|
||||
Message = $"Process '{process.Name}' (Path: {location}) matches indicator '{processHint}'"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendPathEvidence(OemComponentDefinition definition, Dictionary<string, string> currentUserSids, OemSoftwareFinding finding)
|
||||
{
|
||||
if ((definition.DirectoryHints == null || definition.DirectoryHints.Length == 0) &&
|
||||
(definition.FileHints == null || definition.FileHints.Length == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (definition.DirectoryHints != null)
|
||||
{
|
||||
foreach (var dirHint in definition.DirectoryHints)
|
||||
{
|
||||
var expandedPath = ExpandPath(dirHint.Path);
|
||||
if (!Directory.Exists(expandedPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var permissions = PermissionsHelper.GetPermissionsFolder(expandedPath, currentUserSids, PermissionType.WRITEABLE_OR_EQUIVALENT);
|
||||
bool isWritable = permissions.Count > 0;
|
||||
|
||||
finding.Evidence.Add(new OemEvidence
|
||||
{
|
||||
EvidenceType = "path",
|
||||
Highlight = isWritable,
|
||||
Message = BuildPathMessage(expandedPath, dirHint.Description, isWritable, permissions)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.FileHints != null)
|
||||
{
|
||||
foreach (var fileHint in definition.FileHints)
|
||||
{
|
||||
var expandedPath = ExpandPath(fileHint);
|
||||
if (!File.Exists(expandedPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var permissions = PermissionsHelper.GetPermissionsFile(expandedPath, currentUserSids, PermissionType.WRITEABLE_OR_EQUIVALENT);
|
||||
bool isWritable = permissions.Count > 0;
|
||||
|
||||
finding.Evidence.Add(new OemEvidence
|
||||
{
|
||||
EvidenceType = "file",
|
||||
Highlight = isWritable,
|
||||
Message = BuildPathMessage(expandedPath, "file", isWritable, permissions)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendPipeEvidence(OemComponentDefinition definition, OemSoftwareFinding finding)
|
||||
{
|
||||
if (definition.PipeHints == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pipeHint in definition.PipeHints)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = $"\\\\.\\pipe\\{pipeHint.Name}";
|
||||
var security = File.GetAccessControl(path);
|
||||
string sddl = security.GetSecurityDescriptorSddlForm(AccessControlSections.All);
|
||||
string identity = string.Empty;
|
||||
string rights = string.Empty;
|
||||
bool worldWritable = false;
|
||||
|
||||
if (pipeHint.CheckWorldWritable)
|
||||
{
|
||||
worldWritable = HasWorldWritableAce(security, out identity, out rights);
|
||||
}
|
||||
|
||||
string details = worldWritable
|
||||
? $"Named pipe '{pipeHint.Name}' ({pipeHint.Description}) is writable by {identity} ({rights})."
|
||||
: $"Named pipe '{pipeHint.Name}' ({pipeHint.Description}) present. SDDL: {sddl}";
|
||||
|
||||
finding.Evidence.Add(new OemEvidence
|
||||
{
|
||||
EvidenceType = "pipe",
|
||||
Highlight = worldWritable,
|
||||
Message = details
|
||||
});
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// Pipe not present.
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
// Pipe namespace not accessible.
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Best effort: pipes might disappear during enumeration or deny access.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ServiceSnapshot> GetServiceSnapshot()
|
||||
{
|
||||
var services = new List<ServiceSnapshot>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var service in ServiceController.GetServices())
|
||||
{
|
||||
services.Add(new ServiceSnapshot
|
||||
{
|
||||
Name = service.ServiceName ?? string.Empty,
|
||||
DisplayName = service.DisplayName ?? string.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore - this is best effort.
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static List<ProcessSnapshot> GetProcessSnapshot()
|
||||
{
|
||||
var processes = new List<ProcessSnapshot>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var process in Process.GetProcesses())
|
||||
{
|
||||
string fullPath = string.Empty;
|
||||
try
|
||||
{
|
||||
fullPath = process.MainModule?.FileName ?? string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Access denied or 64-bit vs 32-bit mismatch.
|
||||
}
|
||||
|
||||
processes.Add(new ProcessSnapshot
|
||||
{
|
||||
Name = process.ProcessName ?? string.Empty,
|
||||
FullPath = fullPath ?? string.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore - enumeration is best effort.
|
||||
}
|
||||
|
||||
return processes;
|
||||
}
|
||||
|
||||
private static string ExpandPath(string rawPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawPath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var expanded = Environment.ExpandEnvironmentVariables(rawPath);
|
||||
return expanded.Trim().Trim('"');
|
||||
}
|
||||
|
||||
private static string BuildPathMessage(string path, string description, bool isWritable, List<string> permissions)
|
||||
{
|
||||
string descriptor = string.IsNullOrWhiteSpace(description) ? "" : $" ({description})";
|
||||
if (isWritable)
|
||||
{
|
||||
return $"Path '{path}'{descriptor} is writable by current user: {string.Join(", ", permissions)}";
|
||||
}
|
||||
|
||||
return $"Path '{path}'{descriptor} detected.";
|
||||
}
|
||||
|
||||
private static bool ContainsIgnoreCase(string value, string toFind)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(toFind))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return value.IndexOf(toFind, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
private static bool HasWorldWritableAce(FileSecurity security, out string identity, out string rights)
|
||||
{
|
||||
identity = string.Empty;
|
||||
rights = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var rules = security.GetAccessRules(true, true, typeof(SecurityIdentifier));
|
||||
foreach (FileSystemAccessRule rule in rules)
|
||||
{
|
||||
if (rule.AccessControlType != AccessControlType.Allow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rule.IdentityReference is SecurityIdentifier sid)
|
||||
{
|
||||
bool isWorld = sid.IsWellKnown(WellKnownSidType.WorldSid);
|
||||
bool isAuthenticated = sid.IsWellKnown(WellKnownSidType.AuthenticatedUserSid);
|
||||
|
||||
if (!isWorld && !isAuthenticated)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FileSystemRights interestingRights =
|
||||
FileSystemRights.FullControl |
|
||||
FileSystemRights.Modify |
|
||||
FileSystemRights.Write |
|
||||
FileSystemRights.WriteData |
|
||||
FileSystemRights.CreateFiles |
|
||||
FileSystemRights.ChangePermissions;
|
||||
|
||||
if ((rule.FileSystemRights & interestingRights) != 0)
|
||||
{
|
||||
identity = isWorld ? "Everyone" : "Authenticated Users";
|
||||
rights = rule.FileSystemRights.ToString();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore parsing issues.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerable<OemComponentDefinition> GetDefinitions()
|
||||
{
|
||||
return new List<OemComponentDefinition>
|
||||
{
|
||||
new OemComponentDefinition
|
||||
{
|
||||
Name = "ASUS DriverHub",
|
||||
Description = "Local web API exposed by ADU.exe allowed bypassing origin/url validation and signature checks.",
|
||||
Cves = new[] { "CVE-2025-3462", "CVE-2025-3463" },
|
||||
ServiceHints = new[] { "asusdriverhub", "asus driverhub" },
|
||||
ProcessHints = new[] { "adu", "asusdriverhub" },
|
||||
DirectoryHints = new[]
|
||||
{
|
||||
new PathHint { Path = "%ProgramFiles%\\ASUS\\AsusDriverHub", Description = "Program Files" },
|
||||
new PathHint { Path = "%ProgramFiles(x86)%\\ASUS\\AsusDriverHub", Description = "Program Files (x86)" },
|
||||
new PathHint { Path = "%ProgramData%\\ASUS\\AsusDriverHub\\SupportTemp", Description = "SupportTemp updater staging" }
|
||||
},
|
||||
FileHints = new[]
|
||||
{
|
||||
"%ProgramData%\\ASUS\\AsusDriverHub\\SupportTemp\\Installer.json"
|
||||
}
|
||||
},
|
||||
new OemComponentDefinition
|
||||
{
|
||||
Name = "MSI Center",
|
||||
Description = "MSI.CentralServer.exe exposed TCP commands with TOCTOU and signature bypass issues.",
|
||||
Cves = new[] { "CVE-2025-27812", "CVE-2025-27813" },
|
||||
ServiceHints = new[] { "msi.center", "msi centralserver" },
|
||||
ProcessHints = new[] { "msi.centralserver", "msi center" },
|
||||
DirectoryHints = new[]
|
||||
{
|
||||
new PathHint { Path = "%ProgramFiles%\\MSI\\MSI Center", Description = "Main installation" },
|
||||
new PathHint { Path = "%ProgramFiles(x86)%\\MSI\\MSI Center", Description = "Main installation (x86)" },
|
||||
new PathHint { Path = "%ProgramData%\\MSI\\MSI Center", Description = "Shared data" },
|
||||
new PathHint { Path = "%ProgramData%\\MSI Center SDK", Description = "SDK temp copy location" }
|
||||
}
|
||||
},
|
||||
new OemComponentDefinition
|
||||
{
|
||||
Name = "Acer Control Centre",
|
||||
Description = "ACCSvc.exe exposes treadstone_service_LightMode named pipe with weak impersonation controls.",
|
||||
Cves = new[] { "CVE-2025-5491" },
|
||||
ServiceHints = new[] { "accsvc", "acer control" },
|
||||
ProcessHints = new[] { "accsvc", "accstd" },
|
||||
DirectoryHints = new[]
|
||||
{
|
||||
new PathHint { Path = "%ProgramFiles%\\Acer\\Care Center", Description = "Install directory" },
|
||||
new PathHint { Path = "%ProgramFiles(x86)%\\Acer\\Care Center", Description = "Install directory (x86)" }
|
||||
},
|
||||
PipeHints = new[]
|
||||
{
|
||||
new PipeHint { Name = "treadstone_service_LightMode", Description = "Command dispatcher", CheckWorldWritable = true }
|
||||
}
|
||||
},
|
||||
new OemComponentDefinition
|
||||
{
|
||||
Name = "Razer Synapse 4 Elevation Service",
|
||||
Description = "razer_elevation_service.exe exposes COM elevation helpers that allowed arbitrary process launch.",
|
||||
Cves = new[] { "CVE-2025-27811" },
|
||||
ServiceHints = new[] { "razer_elevation_service" },
|
||||
ProcessHints = new[] { "razer_elevation_service" },
|
||||
DirectoryHints = new[]
|
||||
{
|
||||
new PathHint { Path = "%ProgramFiles%\\Razer\\RazerAppEngine", Description = "Razer App Engine" },
|
||||
new PathHint { Path = "%ProgramFiles(x86)%\\Razer\\RazerAppEngine", Description = "Razer App Engine (x86)" }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class ServiceSnapshot
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
}
|
||||
|
||||
private class ProcessSnapshot
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FullPath { get; set; }
|
||||
}
|
||||
|
||||
private class OemComponentDefinition
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string[] Cves { get; set; } = Array.Empty<string>();
|
||||
public string[] ServiceHints { get; set; } = Array.Empty<string>();
|
||||
public string[] ProcessHints { get; set; } = Array.Empty<string>();
|
||||
public PathHint[] DirectoryHints { get; set; } = Array.Empty<PathHint>();
|
||||
public string[] FileHints { get; set; } = Array.Empty<string>();
|
||||
public PipeHint[] PipeHints { get; set; } = Array.Empty<PipeHint>();
|
||||
}
|
||||
|
||||
private class PathHint
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string Description { get; set; }
|
||||
}
|
||||
|
||||
private class PipeHint
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public bool CheckWorldWritable { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
internal class OemSoftwareFinding
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string[] Cves { get; set; } = Array.Empty<string>();
|
||||
public List<OemEvidence> Evidence { get; } = new List<OemEvidence>();
|
||||
}
|
||||
|
||||
internal class OemEvidence
|
||||
{
|
||||
public string EvidenceType { get; set; }
|
||||
public string Message { get; set; }
|
||||
public bool Highlight { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using winPEAS.Helpers;
|
||||
using winPEAS.Native;
|
||||
|
||||
namespace winPEAS.Info.SystemInfo.NamedPipes
|
||||
{
|
||||
internal static class NamedPipeSecurityAnalyzer
|
||||
{
|
||||
private const string DeviceNamedPipePrefix = @"\Device\NamedPipe\";
|
||||
private static readonly char[] CandidateSeparators = { '\\', '/', '-', ':', '(' };
|
||||
|
||||
private static readonly HashSet<string> LowPrivSidSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"S-1-1-0", // Everyone
|
||||
"S-1-5-11", // Authenticated Users
|
||||
"S-1-5-32-545", // Users
|
||||
"S-1-5-32-546", // Guests
|
||||
"S-1-5-32-547", // Power Users
|
||||
"S-1-5-32-554", // Pre-Windows 2000 Compatible Access
|
||||
"S-1-5-32-555", // Remote Desktop Users
|
||||
"S-1-5-32-558", // Performance Log Users
|
||||
"S-1-5-32-559", // Performance Monitor Users
|
||||
"S-1-5-32-562", // Distributed COM Users
|
||||
"S-1-5-32-569", // Remote Management Users
|
||||
"S-1-5-4", // Interactive
|
||||
"S-1-5-2", // Network
|
||||
"S-1-5-1", // Dialup
|
||||
"S-1-5-7" // Anonymous Logon
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> LowPrivPrincipalKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"everyone",
|
||||
"authenticated users",
|
||||
"users",
|
||||
"guests",
|
||||
"power users",
|
||||
"remote desktop users",
|
||||
"remote management users",
|
||||
"distributed com users",
|
||||
"anonymous logon",
|
||||
"interactive",
|
||||
"network",
|
||||
"local",
|
||||
"batch",
|
||||
"iis_iusrs"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> PrivilegedSidSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"S-1-5-18", // SYSTEM
|
||||
"S-1-5-19", // LOCAL SERVICE
|
||||
"S-1-5-20", // NETWORK SERVICE
|
||||
"S-1-5-32-544" // Administrators
|
||||
};
|
||||
|
||||
private static readonly (string Label, FileSystemRights Right)[] DangerousRightsMap = new[]
|
||||
{
|
||||
("FullControl", FileSystemRights.FullControl),
|
||||
("Modify", FileSystemRights.Modify),
|
||||
("Write", FileSystemRights.Write),
|
||||
("WriteData", FileSystemRights.WriteData),
|
||||
("AppendData", FileSystemRights.AppendData),
|
||||
("CreateFiles", FileSystemRights.CreateFiles),
|
||||
("CreateDirectories", FileSystemRights.CreateDirectories),
|
||||
("WriteAttributes", FileSystemRights.WriteAttributes),
|
||||
("WriteExtendedAttributes", FileSystemRights.WriteExtendedAttributes),
|
||||
("Delete", FileSystemRights.Delete),
|
||||
("ChangePermissions", FileSystemRights.ChangePermissions),
|
||||
("TakeOwnership", FileSystemRights.TakeOwnership)
|
||||
};
|
||||
|
||||
public static IEnumerable<NamedPipeSecurityIssue> GetNamedPipeAbuseCandidates()
|
||||
{
|
||||
var insecurePipes = DiscoverInsecurePipes();
|
||||
if (!insecurePipes.Any())
|
||||
{
|
||||
return Enumerable.Empty<NamedPipeSecurityIssue>();
|
||||
}
|
||||
|
||||
AttachProcesses(insecurePipes);
|
||||
|
||||
return insecurePipes.Values
|
||||
.Where(issue => issue.LowPrivilegeAces.Any())
|
||||
.OrderByDescending(issue => issue.HasPrivilegedServer)
|
||||
.ThenBy(issue => issue.Name)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static Dictionary<string, NamedPipeSecurityIssue> DiscoverInsecurePipes()
|
||||
{
|
||||
var result = new Dictionary<string, NamedPipeSecurityIssue>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var pipe in NamedPipes.GetNamedPipeInfos())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pipe.Sddl) || pipe.Sddl.Equals("ERROR", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var descriptor = new RawSecurityDescriptor(pipe.Sddl);
|
||||
if (descriptor.DiscretionaryAcl == null)
|
||||
continue;
|
||||
|
||||
foreach (GenericAce ace in descriptor.DiscretionaryAcl)
|
||||
{
|
||||
if (!(ace is CommonAce commonAce))
|
||||
continue;
|
||||
|
||||
var sid = commonAce.SecurityIdentifier;
|
||||
if (sid == null || !IsLowPrivilegePrincipal(sid))
|
||||
continue;
|
||||
|
||||
if (!HasDangerousWriteRights(commonAce.AccessMask))
|
||||
continue;
|
||||
|
||||
var rights = DescribeRights(commonAce.AccessMask).ToList();
|
||||
if (!rights.Any())
|
||||
continue;
|
||||
|
||||
if (!result.TryGetValue(pipe.Name, out var issue))
|
||||
{
|
||||
issue = new NamedPipeSecurityIssue(pipe.Name, pipe.Sddl, NormalizePipeName(pipe.Name));
|
||||
result[pipe.Name] = issue;
|
||||
}
|
||||
|
||||
var account = ResolveSidToName(sid);
|
||||
issue.AddLowPrivPrincipal(account, sid.Value, rights);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore malformed SDDL strings
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void AttachProcesses(Dictionary<string, NamedPipeSecurityIssue> insecurePipes)
|
||||
{
|
||||
if (!insecurePipes.Any())
|
||||
return;
|
||||
|
||||
var lookup = BuildLookup(insecurePipes.Values);
|
||||
if (!lookup.Any())
|
||||
return;
|
||||
|
||||
List<HandlesHelper.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> handles;
|
||||
try
|
||||
{
|
||||
handles = HandlesHelper.GetAllHandlers();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentProcess = Kernel32.GetCurrentProcess();
|
||||
var processCache = new Dictionary<int, NamedPipeProcessInfo>();
|
||||
|
||||
foreach (var handle in handles)
|
||||
{
|
||||
IntPtr processHandle = IntPtr.Zero;
|
||||
IntPtr duplicatedHandle = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
int pid = GetPid(handle);
|
||||
if (pid <= 0)
|
||||
continue;
|
||||
|
||||
processHandle = Kernel32.OpenProcess(
|
||||
HandlesHelper.ProcessAccessFlags.DupHandle | HandlesHelper.ProcessAccessFlags.QueryLimitedInformation,
|
||||
false,
|
||||
pid);
|
||||
|
||||
if (processHandle == IntPtr.Zero)
|
||||
continue;
|
||||
|
||||
if (!Kernel32.DuplicateHandle(processHandle, handle.HandleValue, currentProcess, out duplicatedHandle, 0, false, HandlesHelper.DUPLICATE_SAME_ACCESS))
|
||||
continue;
|
||||
|
||||
var typeName = HandlesHelper.GetObjectType(duplicatedHandle);
|
||||
if (!string.Equals(typeName, "File", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var objectName = HandlesHelper.GetObjectName(duplicatedHandle);
|
||||
if (string.IsNullOrEmpty(objectName) || !objectName.StartsWith(DeviceNamedPipePrefix, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var normalizedHandleName = NormalizePipeName(objectName.Substring(DeviceNamedPipePrefix.Length));
|
||||
var candidates = GetCandidateKeys(normalizedHandleName);
|
||||
|
||||
bool matched = false;
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (!lookup.TryGetValue(candidate, out var matchedIssues))
|
||||
continue;
|
||||
|
||||
if (!processCache.TryGetValue(pid, out var processInfo))
|
||||
{
|
||||
var raw = HandlesHelper.getProcInfoById(pid);
|
||||
processInfo = new NamedPipeProcessInfo(raw.pid, raw.name, raw.userName, raw.userSid, IsHighPrivilegeAccount(raw.userSid, raw.userName));
|
||||
processCache[pid] = processInfo;
|
||||
}
|
||||
|
||||
foreach (var issue in matchedIssues)
|
||||
{
|
||||
issue.AddProcess(processInfo);
|
||||
}
|
||||
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!matched)
|
||||
continue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore per-handle failures
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (duplicatedHandle != IntPtr.Zero)
|
||||
{
|
||||
Kernel32.CloseHandle(duplicatedHandle);
|
||||
}
|
||||
if (processHandle != IntPtr.Zero)
|
||||
{
|
||||
Kernel32.CloseHandle(processHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, List<NamedPipeSecurityIssue>> BuildLookup(IEnumerable<NamedPipeSecurityIssue> issues)
|
||||
{
|
||||
var lookup = new Dictionary<string, List<NamedPipeSecurityIssue>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var issue in issues)
|
||||
{
|
||||
foreach (var key in GetCandidateKeys(issue.NormalizedName))
|
||||
{
|
||||
if (!lookup.TryGetValue(key, out var list))
|
||||
{
|
||||
list = new List<NamedPipeSecurityIssue>();
|
||||
lookup[key] = list;
|
||||
}
|
||||
|
||||
if (!list.Contains(issue))
|
||||
{
|
||||
list.Add(issue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetCandidateKeys(string normalizedName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(normalizedName))
|
||||
return Array.Empty<string>();
|
||||
|
||||
var candidates = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
normalizedName
|
||||
};
|
||||
|
||||
foreach (var separator in CandidateSeparators)
|
||||
{
|
||||
var idx = normalizedName.IndexOf(separator);
|
||||
if (idx > 0)
|
||||
{
|
||||
candidates.Add(normalizedName.Substring(0, idx));
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private static string NormalizePipeName(string rawName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawName))
|
||||
return string.Empty;
|
||||
|
||||
var normalized = rawName.Replace('/', '\\').Trim();
|
||||
while (normalized.StartsWith("\\", StringComparison.Ordinal))
|
||||
{
|
||||
normalized = normalized.Substring(1);
|
||||
}
|
||||
|
||||
return normalized.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static bool HasDangerousWriteRights(int accessMask)
|
||||
{
|
||||
var rights = (FileSystemRights)accessMask;
|
||||
foreach (var entry in DangerousRightsMap)
|
||||
{
|
||||
if ((rights & entry.Right) == entry.Right)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> DescribeRights(int accessMask)
|
||||
{
|
||||
var rights = (FileSystemRights)accessMask;
|
||||
var descriptions = new List<string>();
|
||||
|
||||
foreach (var entry in DangerousRightsMap)
|
||||
{
|
||||
if ((rights & entry.Right) == entry.Right)
|
||||
{
|
||||
descriptions.Add(entry.Label);
|
||||
if (entry.Right == FileSystemRights.FullControl)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!descriptions.Any())
|
||||
{
|
||||
descriptions.Add($"0x{accessMask:x}");
|
||||
}
|
||||
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
private static bool IsLowPrivilegePrincipal(SecurityIdentifier sid)
|
||||
{
|
||||
if (sid == null)
|
||||
return false;
|
||||
|
||||
if (LowPrivSidSet.Contains(sid.Value))
|
||||
return true;
|
||||
|
||||
var accountName = ResolveSidToName(sid);
|
||||
if (string.IsNullOrEmpty(accountName))
|
||||
return false;
|
||||
|
||||
return LowPrivPrincipalKeywords.Any(keyword => accountName.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
}
|
||||
|
||||
private static string ResolveSidToName(SecurityIdentifier sid)
|
||||
{
|
||||
if (sid == null)
|
||||
return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
return sid.Translate(typeof(NTAccount)).Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return sid.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsHighPrivilegeAccount(string sid, string userName)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(sid))
|
||||
{
|
||||
if (PrivilegedSidSet.Contains(sid))
|
||||
return true;
|
||||
|
||||
if (sid.StartsWith("S-1-5-80-", StringComparison.OrdinalIgnoreCase)) // Service SID
|
||||
return true;
|
||||
|
||||
if (sid.StartsWith("S-1-5-82-", StringComparison.OrdinalIgnoreCase)) // AppPool / service-like SIDs
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(userName))
|
||||
{
|
||||
if (string.Equals(userName, HandlesHelper.elevatedProcess, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
var normalized = userName.ToUpperInvariant();
|
||||
if (normalized.Contains("SYSTEM") || normalized.Contains("LOCAL SERVICE") || normalized.Contains("NETWORK SERVICE"))
|
||||
return true;
|
||||
|
||||
if (normalized.StartsWith("NT SERVICE\\", StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
if (normalized.EndsWith("$", StringComparison.Ordinal) && normalized.Contains("\\"))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int GetPid(HandlesHelper.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handle)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
if (IntPtr.Size == 4)
|
||||
{
|
||||
return (int)handle.UniqueProcessId.ToUInt32();
|
||||
}
|
||||
|
||||
return (int)handle.UniqueProcessId.ToUInt64();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class NamedPipeSecurityIssue
|
||||
{
|
||||
private readonly Dictionary<string, NamedPipePrincipalAccess> _principalAccess = new Dictionary<string, NamedPipePrincipalAccess>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<int, NamedPipeProcessInfo> _processes = new Dictionary<int, NamedPipeProcessInfo>();
|
||||
|
||||
public NamedPipeSecurityIssue(string name, string sddl, string normalizedName)
|
||||
{
|
||||
Name = name;
|
||||
Sddl = sddl;
|
||||
NormalizedName = normalizedName;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string Sddl { get; }
|
||||
public string NormalizedName { get; }
|
||||
|
||||
public IReadOnlyCollection<NamedPipePrincipalAccess> LowPrivilegeAces => _principalAccess.Values;
|
||||
public IReadOnlyCollection<NamedPipeProcessInfo> Processes => _processes.Values;
|
||||
public bool HasPrivilegedServer => _processes.Values.Any(process => process.IsHighPrivilege);
|
||||
|
||||
public void AddLowPrivPrincipal(string principal, string sid, IEnumerable<string> rights)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sid))
|
||||
return;
|
||||
|
||||
if (!_principalAccess.TryGetValue(sid, out var access))
|
||||
{
|
||||
access = new NamedPipePrincipalAccess(principal, sid);
|
||||
_principalAccess[sid] = access;
|
||||
}
|
||||
|
||||
access.AddRights(rights);
|
||||
}
|
||||
|
||||
public void AddProcess(NamedPipeProcessInfo process)
|
||||
{
|
||||
if (process == null)
|
||||
return;
|
||||
|
||||
if (!_processes.ContainsKey(process.Pid))
|
||||
{
|
||||
_processes[process.Pid] = process;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class NamedPipePrincipalAccess
|
||||
{
|
||||
private readonly HashSet<string> _rights = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public NamedPipePrincipalAccess(string principal, string sid)
|
||||
{
|
||||
Principal = principal;
|
||||
Sid = sid;
|
||||
}
|
||||
|
||||
public string Principal { get; }
|
||||
public string Sid { get; }
|
||||
public string RightsDescription => _rights.Count == 0 ? string.Empty : string.Join("|", _rights.OrderBy(r => r));
|
||||
public IEnumerable<string> Rights => _rights;
|
||||
|
||||
public void AddRights(IEnumerable<string> rights)
|
||||
{
|
||||
if (rights == null)
|
||||
return;
|
||||
|
||||
foreach (var right in rights)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(right))
|
||||
{
|
||||
_rights.Add(right.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class NamedPipeProcessInfo
|
||||
{
|
||||
public NamedPipeProcessInfo(int pid, string processName, string userName, string userSid, bool isHighPrivilege)
|
||||
{
|
||||
Pid = pid;
|
||||
ProcessName = processName;
|
||||
UserName = userName;
|
||||
UserSid = userSid;
|
||||
IsHighPrivilege = isHighPrivilege;
|
||||
}
|
||||
|
||||
public int Pid { get; }
|
||||
public string ProcessName { get; }
|
||||
public string UserName { get; }
|
||||
public string UserSid { get; }
|
||||
public bool IsHighPrivilege { get; }
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,20 @@ namespace winPEAS.Info.UserInfo
|
||||
{
|
||||
class UserInfoHelper
|
||||
{
|
||||
private static readonly Dictionary<string, bool> _highPrivAccountCache = new Dictionary<string, bool>(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<string> 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
|
||||
{
|
||||
|
||||
@@ -1197,9 +1197,11 @@
|
||||
<Compile Include="Checks\NetworkInfo.cs" />
|
||||
<Compile Include="Checks\ProcessInfo.cs" />
|
||||
<Compile Include="Checks\ServicesInfo.cs" />
|
||||
<Compile Include="Checks\SoapClientInfo.cs" />
|
||||
<Compile Include="Checks\SystemInfo.cs" />
|
||||
<Compile Include="Checks\UserInfo.cs" />
|
||||
<Compile Include="Checks\WindowsCreds.cs" />
|
||||
<Compile Include="Checks\RegistryInfo.cs" />
|
||||
<Compile Include="Helpers\AppLocker\AppLockerHelper.cs" />
|
||||
<Compile Include="Helpers\AppLocker\AppLockerRules.cs" />
|
||||
<Compile Include="Helpers\AppLocker\IAppIdPolicyHandler.cs" />
|
||||
@@ -1223,6 +1225,7 @@
|
||||
<Compile Include="Info\ApplicationInfo\ApplicationInfoHelper.cs" />
|
||||
<Compile Include="Info\ApplicationInfo\AutoRuns.cs" />
|
||||
<Compile Include="Info\ApplicationInfo\DeviceDrivers.cs" />
|
||||
<Compile Include="Info\ApplicationInfo\SoapClientProxyAnalyzer.cs" />
|
||||
<Compile Include="Info\ApplicationInfo\InstalledApps.cs" />
|
||||
<Compile Include="Helpers\Beaprint.cs" />
|
||||
<Compile Include="Info\CloudInfo\AWSInfo.cs" />
|
||||
@@ -1291,6 +1294,7 @@
|
||||
<Compile Include="Info\SystemInfo\GroupPolicy\GroupPolicy.cs" />
|
||||
<Compile Include="Info\SystemInfo\GroupPolicy\LocalGroupPolicyInfo.cs" />
|
||||
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipeInfo.cs" />
|
||||
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipeSecurityAnalyzer.cs" />
|
||||
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipes.cs" />
|
||||
<Compile Include="Info\SystemInfo\Ntlm\Ntlm.cs" />
|
||||
<Compile Include="Info\SystemInfo\Ntlm\NtlmSettingsInfo.cs" />
|
||||
@@ -1359,6 +1363,7 @@
|
||||
<Compile Include="KnownFileCreds\Vault\Structs\VAULT_ITEM_WIN8.cs" />
|
||||
<Compile Include="KnownFileCreds\Vault\VaultCli.cs" />
|
||||
<Compile Include="Helpers\MyUtils.cs" />
|
||||
<Compile Include="Helpers\ObjectManagerHelper.cs" />
|
||||
<Compile Include="Info\UserInfo\SAM\Enums.cs" />
|
||||
<Compile Include="Info\UserInfo\SAM\SamServer.cs" />
|
||||
<Compile Include="Info\UserInfo\SAM\Structs.cs" />
|
||||
@@ -1458,12 +1463,14 @@
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Info\ServicesInfo\ServicesInfoHelper.cs" />
|
||||
<Compile Include="Info\ServicesInfo\OemSoftwareHelper.cs" />
|
||||
<Compile Include="Info\SystemInfo\SystemInfo.cs" />
|
||||
<Compile Include="Info\UserInfo\UserInfoHelper.cs" />
|
||||
<Compile Include="Helpers\DomainHelper.cs" />
|
||||
<Compile Include="Helpers\CheckRunner.cs" />
|
||||
<Compile Include="Helpers\ReflectionHelper.cs" />
|
||||
<Compile Include="Helpers\Registry\RegistryHelper.cs" />
|
||||
<Compile Include="Helpers\Registry\RegistryAclScanner.cs" />
|
||||
<Compile Include="Helpers\Search\SearchHelper.cs" />
|
||||
<Compile Include="Wifi\Wifi.cs" />
|
||||
<Compile Include="Wifi\NativeWifiApi\Interop.cs" />
|
||||
|
||||
Reference in New Issue
Block a user