Compare commits

..

1 Commits

48 changed files with 247 additions and 4906 deletions

View File

@@ -1,18 +0,0 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"decision": {
"type": "string",
"enum": ["merge", "comment"]
},
"message": {
"type": "string"
},
"confidence": {
"type": "string",
"enum": ["low", "medium", "high"]
}
},
"required": ["decision", "message", "confidence"]
}

26
.github/workflows/CI-PR_from_dev.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: CI-PR_from_dev
on:
push:
branches:
- winpeas_dev
- linpeas_dev
workflow_dispatch:
jobs:
create_pull_request:
runs-on: ubuntu-latest
steps:
# checkout
- name: Checkout
uses: actions/checkout@v2
# PR
- name: Pull Request
uses: repo-sync/pull-request@v2
with:
destination_branch: "master"
github_token: ${{ secrets.PULL_REQUEST_TOKEN }}

View File

@@ -212,14 +212,15 @@ jobs:
steps:
# Download repo
- uses: actions/checkout@v5
- uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
# Setup go
- uses: actions/setup-go@v6
- uses: actions/setup-go@v2
with:
go-version: '1.23'
go-version: 1.17.0-rc1
stable: false
- run: go version
# Build linpeas

View File

@@ -110,9 +110,10 @@ jobs:
ref: ${{ github.head_ref }}
# Setup go
- uses: actions/setup-go@v6
- uses: actions/setup-go@v2
with:
go-version: '1.23'
go-version: 1.17.0-rc1
stable: false
- run: go version
# Build linpeas

View File

@@ -1,113 +0,0 @@
name: Codex PR Triage
on:
pull_request:
types: [opened]
jobs:
codex_triage:
if: ${{ github.event.pull_request.user.login == 'carlospolop' }}
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
outputs:
decision: ${{ steps.parse.outputs.decision }}
message: ${{ steps.parse.outputs.message }}
steps:
- name: Checkout PR merge ref
uses: actions/checkout@v5
with:
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: Pre-fetch base and head refs
run: |
git fetch --no-tags origin \
${{ github.event.pull_request.base.ref }} \
+refs/pull/${{ github.event.pull_request.number }}/head
- name: Run Codex
id: run_codex
uses: openai/codex-action@v1
with:
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
output-schema-file: .github/codex/pr-merge-schema.json
model: gpt-5.2-codex
prompt: |
You are reviewing PR #${{ github.event.pull_request.number }} for ${{ github.repository }}.
Decide whether to merge or comment. Merge only if all of the following are true:
- Changes are simple and safe (no DoS, no long operations, no backdoors).
- Changes follow common PEASS syntax and style without breaking anything and add useful checks or value.
- Changes simplify code or add new useful checks without breaking anything.
If you don't have any doubts, and all the previous conditions are met, decide to merge.
If you have serious doubts, choose "comment" and include your doubts or questions.
If you decide to merge, include a short rationale.
Pull request title and body:
----
${{ github.event.pull_request.title }}
${{ github.event.pull_request.body }}
Review ONLY the changes introduced by the PR:
git log --oneline ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}
Output JSON only, following the provided schema.
- name: Parse Codex decision
id: parse
env:
CODEX_MESSAGE: ${{ steps.run_codex.outputs.final-message }}
run: |
python3 - <<'PY'
import json
import os
data = json.loads(os.environ.get('CODEX_MESSAGE', '') or '{}')
decision = data.get('decision', 'comment')
message = data.get('message', '').strip() or 'Codex did not provide details.'
with open(os.environ['GITHUB_OUTPUT'], 'a') as handle:
handle.write(f"decision={decision}\n")
handle.write("message<<EOF\n")
handle.write(message + "\n")
handle.write("EOF\n")
PY
merge_or_comment:
runs-on: ubuntu-latest
needs: codex_triage
if: ${{ needs.codex_triage.outputs.decision != '' }}
permissions:
contents: write
pull-requests: write
steps:
- name: Merge PR when approved
if: ${{ needs.codex_triage.outputs.decision == 'merge' }}
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
gh api \
-X PUT \
-H "Accept: application/vnd.github+json" \
/repos/${{ github.repository }}/pulls/${PR_NUMBER}/merge \
-f merge_method=squash \
-f commit_title="Auto-merge PR #${PR_NUMBER} (Codex)"
- name: Comment with doubts
if: ${{ needs.codex_triage.outputs.decision == 'comment' }}
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
CODEX_MESSAGE: ${{ needs.codex_triage.outputs.message }}
with:
github-token: ${{ github.token }}
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(process.env.PR_NUMBER),
body: process.env.CODEX_MESSAGE,
});

View File

@@ -1,136 +0,0 @@
name: PR Failure Codex Dispatch
on:
workflow_run:
workflows: ["PR-tests"]
types: [completed]
jobs:
codex_on_failure:
if: >
${{ github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.pull_requests &&
github.event.workflow_run.pull_requests[0].user.login == 'carlospolop' &&
!startsWith(github.event.workflow_run.head_commit.message, 'Fix CI failures for PR #') }}
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
actions: read
steps:
- name: Comment on PR with failure info
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
RUN_URL: ${{ github.event.workflow_run.html_url }}
WORKFLOW_NAME: ${{ github.event.workflow_run.name }}
with:
github-token: ${{ github.token }}
script: |
const prNumber = Number(process.env.PR_NUMBER);
const body = `PR #${prNumber} had a failing workflow "${process.env.WORKFLOW_NAME}".\n\nRun: ${process.env.RUN_URL}\n\nLaunching Codex to attempt a fix.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
- name: Checkout PR head
uses: actions/checkout@v5
with:
repository: ${{ github.event.workflow_run.head_repository.full_name }}
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0
persist-credentials: true
- name: Configure git author
run: |
git config user.name "codex-action"
git config user.email "codex-action@users.noreply.github.com"
- name: Fetch failure summary
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.event.workflow_run.id }}
run: |
gh api -H "Accept: application/vnd.github+json" \
/repos/${{ github.repository }}/actions/runs/$RUN_ID/jobs \
--paginate > /tmp/jobs.json
python3 - <<'PY'
import json
data = json.load(open('/tmp/jobs.json'))
lines = []
for job in data.get('jobs', []):
if job.get('conclusion') == 'failure':
lines.append(f"Job: {job.get('name')} (id {job.get('id')})")
lines.append(f"URL: {job.get('html_url')}")
for step in job.get('steps', []):
if step.get('conclusion') == 'failure':
lines.append(f" Step: {step.get('name')}")
lines.append("")
summary = "\n".join(lines).strip() or "No failing job details found."
with open('codex_failure_summary.txt', 'w') as handle:
handle.write(summary)
PY
- name: Create Codex prompt
env:
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
RUN_URL: ${{ github.event.workflow_run.html_url }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
run: |
{
echo "You are fixing CI failures for PR #${PR_NUMBER} in ${{ github.repository }}."
echo "The failing workflow run is: ${RUN_URL}"
echo "The PR branch is: ${HEAD_BRANCH}"
echo ""
echo "Failure summary:"
cat codex_failure_summary.txt
echo ""
echo "Please identify the cause, apply a easy, simple and minimal fix, and update files accordingly."
echo "Run any fast checks you can locally (no network)."
echo "Leave the repo in a state ready to commit as when you finish, it'll be automatically committed and pushed."
} > codex_prompt.txt
- name: Run Codex
id: run_codex
uses: openai/codex-action@v1
with:
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
prompt-file: codex_prompt.txt
sandbox: workspace-write
model: gpt-5.2-codex
- name: Commit and push if changed
env:
TARGET_BRANCH: ${{ github.event.workflow_run.head_branch }}
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
run: |
if git diff --quiet; then
echo "No changes to commit."
exit 0
fi
git add -A
git commit -m "Fix CI failures for PR #${PR_NUMBER}"
git push origin HEAD:${TARGET_BRANCH}
- name: Comment with Codex result
if: steps.run_codex.outputs.final-message != ''
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
CODEX_MESSAGE: ${{ steps.run_codex.outputs.final-message }}
with:
github-token: ${{ github.token }}
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(process.env.PR_NUMBER),
body: process.env.CODEX_MESSAGE,
});

View File

@@ -28,7 +28,7 @@ Check the **[parsers](./parsers/)** directory to **transform PEASS outputs to JS
If you are a **PEASS & Hacktricks enthusiast**, you can get your hands now on **our [custom swag](https://peass.creator-spring.com/) and show how much you like our projects!**
You can also, join the 💬 [Discord group](https://discord.gg/hRep4RUj7f) or the [telegram group](https://t.me/peass) to learn about the latest news in cybersecurity and meet other cybersecurity enthusiasts, or follow me on Twitter 🐦 [@hacktricks_live](https://twitter.com/hacktricks_live).
You can also, join the 💬 [Discord group](https://discord.gg/hRep4RUj7f) or the [telegram group](https://t.me/peass) to learn about latest news in cybersecurity and meet other cybersecurity enthusiasts, or follow me on Twitter 🐦 [@hacktricks_live](https://twitter.com/hacktricks_live).
## Let's improve PEASS together
@@ -37,3 +37,4 @@ If you want to **add something** and have **any cool idea** related to this proj
## Advisory
All the scripts/binaries of the PEAS suite should be used for authorized penetration testing and/or educational purposes only. Any misuse of this software will not be the responsibility of the author or of any other collaborator. Use it at your own machines and/or with the owner's permission.

View File

@@ -3352,7 +3352,7 @@ search:
- name: "credentials.xml"
value:
bad_regex: "secret.*|password.*|token.*|SecretKey.*|credentialId.*"
bad_regex: "secret.*|password.*"
remove_empty_lines: True
type: f
search_in:
@@ -3360,7 +3360,7 @@ search:
- name: "config.xml"
value:
bad_regex: "secret.*|password.*|token.*|SecretKey.*|credentialId.*"
bad_regex: "secret.*|password.*"
only_bad_lines: True
type: f
search_in:
@@ -3546,7 +3546,7 @@ search:
- name: "RDCMan.settings"
value:
bad_regex: "credentialsProfiles|password|encryptedPassword"
just_list_file: True
type: f
search_in:
- common

View File

@@ -1,7 +0,0 @@
Job: Build_and_test_linpeas_pr (id 60731895947)
URL: https://github.com/peass-ng/PEASS-ng/actions/runs/21120092167/job/60731895947
Step: Build linpeas
Job: Build_and_test_macpeas_pr (id 60731895952)
URL: https://github.com/peass-ng/PEASS-ng/actions/runs/21120092167/job/60731895952
Step: Build macpeas

View File

@@ -1,15 +0,0 @@
You are fixing CI failures for PR #551 in peass-ng/PEASS-ng.
The failing workflow run is: https://github.com/peass-ng/PEASS-ng/actions/runs/21120092167
The PR branch is: codex-pr-failure-test-1
Failure summary:
Job: Build_and_test_linpeas_pr (id 60731895947)
URL: https://github.com/peass-ng/PEASS-ng/actions/runs/21120092167/job/60731895947
Step: Build linpeas
Job: Build_and_test_macpeas_pr (id 60731895952)
URL: https://github.com/peass-ng/PEASS-ng/actions/runs/21120092167/job/60731895952
Step: Build macpeas
Please identify the cause, apply a easy, simple and minimal fix, and update files accordingly.
Run any fast checks you can locally (no network).
Leave the repo in a state ready to commit as when you finish, it'll be automatically committed and pushed.

View File

@@ -6,8 +6,6 @@
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.
[![asciicast](https://asciinema.org/a/250532.png)](https://asciinema.org/a/309566)
## MacPEAS
@@ -100,17 +98,10 @@ The goal of this script is to search for possible **Privilege Escalation Paths**
This script doesn't have any dependency.
### Recent updates
- **Dec 2025**: Added detection for sudo configurations that expose restic's `--password-command` helper, a common privilege escalation vector observed in real environments.
It uses **/bin/sh** syntax, so can run in anything supporting `sh` (and the binaries and parameters used).
By default, **linpeas won't write anything to disk and won't try to login as any other user using `su`**.
LinPEAS keeps expanding vendor-specific coverage; as of 29-Nov-2025 it warns when IGEL OS appliances still ship the SUID `setup`/`date` helpers that allow NetworkManager/systemd configuration hijacking (Metasploit module `linux/local/igel_network_priv_esc`).
By default linpeas takes around **4 mins** to complete, but It could take from **5 to 10 minutes** to execute all the checks using **-a** parameter *(Recommended option for CTFs)*:
- From less than 1 min to 2 mins to make almost all the checks
- Almost 1 min to search for possible passwords inside all the accesible files of the system

View File

@@ -30,7 +30,7 @@
# Functions Used: echo_not_found, print_2title, print_list, warn_exec
# Global Variables:
# Initial Functions:
# Generated Global Variables: $ASLR, $hypervisorflag, $detectedvirt, $unpriv_userns_clone, $perf_event_paranoid, $mmap_min_addr, $ptrace_scope, $dmesg_restrict, $kptr_restrict, $unpriv_bpf_disabled
# Generated Global Variables: $ASLR, $hypervisorflag, $detectedvirt
# Fat linpeas: 0
# Small linpeas: 0
@@ -80,86 +80,10 @@ print_list "Seccomp enabled? ............... "$NC
print_list "User namespace? ................ "$NC
if [ "$(cat /proc/self/uid_map 2>/dev/null)" ]; then echo "enabled" | sed "s,enabled,${SED_GREEN},"; else echo "disabled" | sed "s,disabled,${SED_RED},"; fi
#-- SY) Unprivileged user namespaces
print_list "unpriv_userns_clone? ........... "$NC
unpriv_userns_clone=$(cat /proc/sys/kernel/unprivileged_userns_clone 2>/dev/null)
if [ -z "$unpriv_userns_clone" ]; then
echo_not_found "/proc/sys/kernel/unprivileged_userns_clone"
else
if [ "$unpriv_userns_clone" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_GREEN},"; else echo "$unpriv_userns_clone" | sed -${E} "s,.*,${SED_RED},g"; fi
fi
#-- SY) Unprivileged eBPF
print_list "unpriv_bpf_disabled? ........... "$NC
unpriv_bpf_disabled=$(cat /proc/sys/kernel/unprivileged_bpf_disabled 2>/dev/null)
if [ -z "$unpriv_bpf_disabled" ]; then
echo_not_found "/proc/sys/kernel/unprivileged_bpf_disabled"
else
if [ "$unpriv_bpf_disabled" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$unpriv_bpf_disabled" | sed -${E} "s,.*,${SED_GREEN},g"; fi
fi
#-- SY) cgroup2
print_list "Cgroup2 enabled? ............... "$NC
([ "$(grep cgroup2 /proc/filesystems 2>/dev/null)" ] && echo "enabled" || echo "disabled") | sed "s,disabled,${SED_RED}," | sed "s,enabled,${SED_GREEN},"
#-- SY) Kernel hardening sysctls
print_list "kptr_restrict? ................. "$NC
kptr_restrict=$(cat /proc/sys/kernel/kptr_restrict 2>/dev/null)
if [ -z "$kptr_restrict" ]; then
echo_not_found "/proc/sys/kernel/kptr_restrict"
else
if [ "$kptr_restrict" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$kptr_restrict" | sed -${E} "s,.*,${SED_GREEN},g"; fi
fi
print_list "dmesg_restrict? ................ "$NC
dmesg_restrict=$(cat /proc/sys/kernel/dmesg_restrict 2>/dev/null)
if [ -z "$dmesg_restrict" ]; then
echo_not_found "/proc/sys/kernel/dmesg_restrict"
else
if [ "$dmesg_restrict" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$dmesg_restrict" | sed -${E} "s,.*,${SED_GREEN},g"; fi
fi
print_list "ptrace_scope? .................. "$NC
ptrace_scope=$(cat /proc/sys/kernel/yama/ptrace_scope 2>/dev/null)
if [ -z "$ptrace_scope" ]; then
echo_not_found "/proc/sys/kernel/yama/ptrace_scope"
else
if [ "$ptrace_scope" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$ptrace_scope" | sed -${E} "s,.*,${SED_GREEN},g"; fi
fi
print_list "perf_event_paranoid? ........... "$NC
perf_event_paranoid=$(cat /proc/sys/kernel/perf_event_paranoid 2>/dev/null)
if [ -z "$perf_event_paranoid" ]; then
echo_not_found "/proc/sys/kernel/perf_event_paranoid"
else
if [ "$perf_event_paranoid" -le 1 ]; then echo "$perf_event_paranoid" | sed -${E} "s,.*,${SED_RED},g"; else echo "$perf_event_paranoid" | sed -${E} "s,.*,${SED_GREEN},g"; fi
fi
print_list "mmap_min_addr? ................. "$NC
mmap_min_addr=$(cat /proc/sys/vm/mmap_min_addr 2>/dev/null)
if [ -z "$mmap_min_addr" ]; then
echo_not_found "/proc/sys/vm/mmap_min_addr"
else
if [ "$mmap_min_addr" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$mmap_min_addr" | sed -${E} "s,.*,${SED_GREEN},g"; fi
fi
print_list "lockdown mode? ................. "$NC
if [ -f "/sys/kernel/security/lockdown" ]; then
cat /sys/kernel/security/lockdown 2>/dev/null | sed -${E} "s,none,${SED_RED},g; s,integrity|confidentiality,${SED_GREEN},g"
else
echo_not_found "/sys/kernel/security/lockdown"
fi
#-- SY) Kernel hardening config flags
print_list "Kernel hardening flags? ........ "$NC
if [ -f "/boot/config-$(uname -r)" ]; then
grep -E 'CONFIG_RANDOMIZE_BASE|CONFIG_STACKPROTECTOR|CONFIG_SLAB_FREELIST_|CONFIG_KASAN' /boot/config-$(uname -r) 2>/dev/null
elif [ -f "/proc/config.gz" ]; then
zcat /proc/config.gz 2>/dev/null | grep -E 'CONFIG_RANDOMIZE_BASE|CONFIG_STACKPROTECTOR|CONFIG_SLAB_FREELIST_|CONFIG_KASAN'
else
echo_not_found "kernel config"
fi
#-- SY) Gatekeeper
if [ "$MACPEAS" ]; then
print_list "Gatekeeper enabled? .......... "$NC
@@ -212,4 +136,4 @@ else
if [ "$hypervisorflag" ]; then printf $RED"Yes"$NC; else printf $GREEN"No"$NC; fi
fi
echo ""
echo ""

View File

@@ -58,23 +58,5 @@ else
echo_not_found "/proc/sys/kernel/modules_disabled"
fi
# Check for module signature enforcement
print_3title "Module signature enforcement? "
if [ -f "/proc/sys/kernel/module_sig_enforce" ]; then
if [ "$(cat /proc/sys/kernel/module_sig_enforce)" = "1" ]; then
echo "Enforced" | sed -${E} "s,.*,${SED_GREEN},g"
else
echo "Not enforced" | sed -${E} "s,.*,${SED_RED},g"
fi
elif [ -f "/sys/module/module/parameters/sig_enforce" ]; then
if [ "$(cat /sys/module/module/parameters/sig_enforce)" = "Y" ]; then
echo "Enforced" | sed -${E} "s,.*,${SED_GREEN},g"
else
echo "Not enforced" | sed -${E} "s,.*,${SED_RED},g"
fi
else
echo_not_found "module_sig_enforce"
fi
echo ""
echo ""

View File

@@ -1,126 +0,0 @@
# 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

View File

@@ -1,183 +0,0 @@
# 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

View File

@@ -1,14 +1,14 @@
# Title: Cloud - AWS ECS
# ID: CL_AWS_ECS
# Author: Carlos Polop
# Last Update: 17-01-2026
# Last Update: 22-08-2023
# 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, $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
# Generated Global Variables: $aws_ecs_req
# Fat linpeas: 0
# Small linpeas: 1
@@ -44,146 +44,5 @@ 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 ""
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
fi

View File

@@ -0,0 +1,129 @@
# Title: Software Information - Imunify360/Ai-Bolit RCE (<=32.7.4.0)
# ID: SI_Imunify360_AiBolit
# Author: HT Bot
# Last Update: 13-11-2025
# Description: Detect Imunify360/Ai-Bolit presence, version and risky execution flags related to the deobfuscation RCE fixed in v32.7.4.0
# License: GNU GPL
# Version: 1.0
# Functions Used: print_2title, print_3title, print_info
# Global Variables: $DEBUG, $HOME
# Initial Functions:
# Generated Global Variables: $ai_bolit_version, $ai_bolit_vuln, $imunify_pkgs, $ai_bolit_installed, $ps_matches, $units, $writable_webroot, $risk, $vmin, $deobf_refs
# Fat linpeas: 0
# Small linpeas: 1
# Quick detector for Imunify360 / Ai-Bolit installation
ai_bolit_installed=""
for p in \
/opt/ai-bolit \
/opt/ai-bolit/wrapper \
/usr/bin/imunify-antivirus \
/usr/bin/imunify360-agent \
/opt/imunify* \
/usr/share/imunify*; do
[ -e "$p" ] && ai_bolit_installed=1 && break
done
# Also consider it present if any package is installed
imunify_pkgs=$( (rpm -qa 2>/dev/null | grep -Ei '^(imunify|imunify360|imunify-antivirus)'; dpkg -l 2>/dev/null | grep -Ei 'imunify|imunify360') 2>/dev/null )
if [ "$imunify_pkgs" ] && [ -z "$ai_bolit_installed" ]; then ai_bolit_installed=1; fi
if [ "$ai_bolit_installed" ] || [ "$DEBUG" ]; then
print_2title "Imunify360/Ai-Bolit RCE (<=32.7.4.0) exposure check"
# Show installed packages
if [ "$imunify_pkgs" ]; then
print_info "Installed Imunify packages (package manager):"
printf "%s\n" "$imunify_pkgs"
echo ""
fi
# Try to obtain Ai-Bolit version from common locations (do not execute third-party binaries)
ai_bolit_version=""
if [ -r "/opt/ai-bolit/VERSION" ]; then
ai_bolit_version=$(head -n1 /opt/ai-bolit/VERSION 2>/dev/null | tr -d ' \t\r')
elif [ -r "/opt/ai-bolit/version" ]; then
ai_bolit_version=$(head -n1 /opt/ai-bolit/version 2>/dev/null | tr -d ' \t\r')
fi
if [ "$ai_bolit_version" ]; then
printf "Ai-Bolit version: %s\n" "$ai_bolit_version"
else
printf "Ai-Bolit version: unknown (could not read /opt/ai-bolit/VERSION)\n"
fi
# Determine if version is vulnerable (< 32.7.4.0)
ai_bolit_vuln=""
if [ "$ai_bolit_version" ]; then
vmin=$(printf '%s\n' "$ai_bolit_version" "32.7.4.0" | sort -V | head -n1)
if [ "$vmin" = "$ai_bolit_version" ] && [ "$ai_bolit_version" != "32.7.4.0" ]; then
ai_bolit_vuln=1
fi
else
# If we cannot read the version but the product is present, assume unknown/possibly vulnerable
ai_bolit_vuln="unknown"
fi
# Look for running processes that may invoke Ai-Bolit or Imunify and check for --deobfuscate and privileges
ps_matches=$(ps -eo user:12,pid,cmd 2>/dev/null | grep -Ei '(ai-bolit|imunify|scanner\.py)' | grep -v grep)
if [ "$ps_matches" ]; then
print_info "Running Imunify/Ai-Bolit related processes:"
# Highlight --deobfuscate and root user
printf "%s\n" "$ps_matches" \
| sed -${E} "s, --deobfuscate, ${SED_RED}," \
| sed -${E} "s,^root,${SED_RED},"
echo ""
fi
# Check systemd units and whether --deobfuscate is in ExecStart
if command -v systemctl >/dev/null 2>&1; then
units=$(systemctl list-units --type=service --all --no-pager 2>/dev/null | grep -Ei '(imunify|ai-bolit)' | awk '{print $1}' | sort -u)
if [ "$units" ]; then
print_info "Systemd service definitions (grep ExecStart/User):"
for u in $units; do
echo "[Unit] $u"
systemctl cat "$u" 2>/dev/null | grep -E '^(User=|Group=|ExecStart=)' \
| sed -${E} "s, --deobfuscate, ${SED_RED}," \
| sed -${E} "s,^User=\s*root,${SED_RED},"
done
echo ""
fi
fi
# Wrapper/orchestrator hint: check common source paths for the --deobfuscate flag (bounded search)
deobf_refs=$(grep -RIl --max-depth=4 --binary-files=without-match -E "--deobfuscate" \
/opt/imunify* /usr/share/imunify* /opt/ai-bolit* 2>/dev/null | head -n 5)
if [ "$deobf_refs" ]; then
print_info "Files referencing --deobfuscate (first hits):"
printf "%s\n" "$deobf_refs"
echo ""
fi
# Simple heuristic: can the current user write to common website roots?
writable_webroot=""
for w in "$HOME/public_html" "$HOME/www" "$HOME/html" "$HOME/htdocs" "$HOME/public_www"; do
if [ -d "$w" ] && [ -w "$w" ]; then writable_webroot=1; echo "Writable webroot detected: $w" | sed -${E} "s,.*,${SED_YELLOW},"; fi
done
[ "$writable_webroot" ] && echo ""
# Final risk summary
risk="LOW"
if [ "$ai_bolit_vuln" = "1" ]; then
risk="MEDIUM"
elif [ "$ai_bolit_vuln" = "unknown" ]; then
risk="MEDIUM (version unknown)"
fi
if [ "$ps_matches" ] && echo "$ps_matches" | grep -q -- "--deobfuscate" && echo "$ps_matches" | awk '{print $1}' | grep -q '^root$'; then
if [ "$ai_bolit_vuln" ]; then risk="HIGH"; fi
fi
print_3title "Ai-Bolit deobfuscation RCE exposure: RISK = $risk"
if [ "$ai_bolit_vuln" = "1" ]; then
echo "Detected Ai-Bolit < 32.7.4.0. Update to >= 32.7.4.0 or later." | sed -${E} "s,.*,${SED_RED},"
elif [ "$ai_bolit_vuln" = "unknown" ]; then
echo "Ai-Bolit present but version unknown. Verify patch level (>= 32.7.4.0)." | sed -${E} "s,.*,${SED_YELLOW},"
fi
echo "If wrapper/services run with --deobfuscate as root, a low-privileged user who can place PHP files in scanned paths may achieve code execution via the scanner." | sed -${E} "s,.*,${SED_YELLOW},"
echo ""
fi

View File

@@ -1,72 +0,0 @@
# Title: Software Information - PostgreSQL Event Triggers
# ID: SI_Postgresql_Event_Triggers
# Author: HT Bot
# Last Update: 19-11-2025
# Description: Detect unsafe PostgreSQL event triggers and postgres_fdw custom scripts that grant temporary SUPERUSER
# License: GNU GPL
# Version: 1.0
# Functions Used: echo_not_found, print_2title, print_info
# Global Variables: $DEBUG, $E, $SED_GREEN, $SED_RED, $SED_YELLOW, $TIMEOUT
# Initial Functions:
# Generated Global Variables: $psql_bin, $psql_evt_output, $psql_evt_status, $psql_evt_err_line, $postgres_fdw_dirs, $postgres_fdw_hits, $old_ifs, $evtname, $enabled, $owner, $owner_is_super, $func, $func_owner, $func_owner_is_super, $IFS
# Fat linpeas: 0
# Small linpeas: 1
if [ "$DEBUG" ] || { [ "$TIMEOUT" ] && [ "$(command -v psql 2>/dev/null || echo -n '')" ]; }; then
print_2title "PostgreSQL event trigger ownership & postgres_fdw hooks"
print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#postgresql-event-triggers"
psql_bin="$(command -v psql 2>/dev/null || echo -n '')"
if [ "$TIMEOUT" ] && [ "$psql_bin" ]; then
psql_evt_output="$($TIMEOUT 5 "$psql_bin" -w -X -q -A -t -d postgres -c "WITH evt AS ( SELECT e.evtname, e.evtenabled, pg_get_userbyid(e.evtowner) AS trig_owner, tr.rolsuper AS trig_owner_super, n.nspname || '.' || p.proname AS function_name, pg_get_userbyid(p.proowner) AS func_owner, fr.rolsuper AS func_owner_super FROM pg_event_trigger e JOIN pg_proc p ON e.evtfoid = p.oid JOIN pg_namespace n ON p.pronamespace = n.oid LEFT JOIN pg_roles tr ON tr.oid = e.evtowner LEFT JOIN pg_roles fr ON fr.oid = p.proowner ) SELECT evtname || '|' || evtenabled || '|' || COALESCE(trig_owner,'?') || '|' || COALESCE(CASE WHEN trig_owner_super THEN 'yes' ELSE 'no' END,'unknown') || '|' || function_name || '|' || COALESCE(func_owner,'?') || '|' || COALESCE(CASE WHEN func_owner_super THEN 'yes' ELSE 'no' END,'unknown') FROM evt WHERE COALESCE(trig_owner_super,false) = false OR COALESCE(func_owner_super,false) = false;" 2>&1)"
psql_evt_status=$?
if [ $psql_evt_status -eq 0 ]; then
if [ "$psql_evt_output" ]; then
echo "Non-superuser-owned event triggers were found (trigger|enabled?|owner|owner_is_super|function|function_owner|fn_owner_is_super):" | sed -${E} "s,.*,${SED_RED},"
printf "%s\n" "$psql_evt_output" | while IFS='|' read evtname enabled owner owner_is_super func func_owner func_owner_is_super; do
case "$enabled" in
O) enabled="enabled" ;;
D) enabled="disabled" ;;
*) enabled="status_$enabled" ;;
esac
echo " - $evtname ($enabled) uses $func owned by $func_owner (superuser:$func_owner_is_super); trigger owner: $owner (superuser:$owner_is_super)" | sed -${E} "s,superuser:no,${SED_RED},g"
done
else
echo "No event triggers owned by non-superusers were returned." | sed -${E} "s,.*,${SED_GREEN},"
fi
else
psql_evt_err_line=$(printf '%s\n' "$psql_evt_output" | head -n1)
echo "Could not query pg_event_trigger (psql exit $psql_evt_status): $psql_evt_err_line" | sed -${E} "s,.*,${SED_YELLOW},"
fi
else
if ! [ "$TIMEOUT" ]; then
echo_not_found "timeout"
fi
if ! [ "$psql_bin" ]; then
echo_not_found "psql"
fi
fi
postgres_fdw_dirs="/etc/postgresql /var/lib/postgresql /var/lib/postgres /usr/lib/postgresql /usr/local/lib/postgresql /opt/supabase /opt/postgres /srv/postgres"
postgres_fdw_hits=""
for d in $postgres_fdw_dirs; do
if [ -d "$d" ]; then
old_ifs="$IFS"
IFS="\n"
for f in $(find "$d" -maxdepth 5 -type f \( -name '*postgres_fdw*.sql' -o -name '*postgres_fdw*.psql' -o -name 'after-create.sql' \) 2>/dev/null); do
if [ -f "$f" ] && grep -qiE "alter[[:space:]]+role[[:space:]]+postgres[[:space:]]+superuser" "$f" 2>/dev/null; then
postgres_fdw_hits="$postgres_fdw_hits\n$f"
fi
done
IFS="$old_ifs"
fi
done
if [ "$postgres_fdw_hits" ]; then
echo "Detected postgres_fdw custom scripts granting postgres SUPERUSER (check for SupaPwn-style window):" | sed -${E} "s,.*,${SED_RED},"
printf "%s\n" "$postgres_fdw_hits" | sed "s,^, - ,"
fi
fi
echo ""

View File

@@ -29,21 +29,21 @@ fi
peass{SSH}
grep "PermitRootLogin \|ChallengeResponseAuthentication \|PasswordAuthentication \|UsePAM \|Port\|PermitEmptyPasswords\|PubkeyAuthentication\|ListenAddress\|ForwardAgent\|AllowAgentForwarding\|AuthorizedKeysFile" /etc/ssh/sshd_config 2>/dev/null | grep -v "#" | sed -${E} "s,PermitRootLogin.*es|PermitEmptyPasswords.*es|ChallengeResponseAuthentication.*es|FordwardAgent.*es,${SED_RED},"
grep "PermitRootLogin \|ChallengeResponseAuthentication \|PasswordAuthentication \|UsePAM \|Port\|PermitEmptyPasswords\|PubkeyAuthentication\|ListenAddress\|ForwardAgent\|AllowAgentForwarding\|AuthorizedKeysFiles" /etc/ssh/sshd_config 2>/dev/null | grep -v "#" | sed -${E} "s,PermitRootLogin.*es|PermitEmptyPasswords.*es|ChallengeResponseAuthentication.*es|FordwardAgent.*es,${SED_RED},"
if ! [ "$SEARCH_IN_FOLDER" ]; then
if [ "$TIMEOUT" ]; then
privatekeyfilesetc=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' /etc 2>/dev/null)
privatekeyfileshome=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' $HOMESEARCH 2>/dev/null)
privatekeyfilesroot=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' /root 2>/dev/null)
privatekeyfilesmnt=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' /mnt 2>/dev/null)
privatekeyfilesetc=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' /etc 2>/dev/null)
privatekeyfileshome=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' $HOMESEARCH 2>/dev/null)
privatekeyfilesroot=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' /root 2>/dev/null)
privatekeyfilesmnt=$(timeout 40 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' /mnt 2>/dev/null)
else
privatekeyfilesetc=$(grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' /etc 2>/dev/null) #If there is tons of files linpeas gets frozen here without a timeout
privatekeyfileshome=$(grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' $HOME/.ssh 2>/dev/null)
privatekeyfilesetc=$(grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' /etc 2>/dev/null) #If there is tons of files linpeas gets frozen here without a timeout
privatekeyfileshome=$(grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' $HOME/.ssh 2>/dev/null)
fi
else
# If $SEARCH_IN_FOLDER lets just search for private keys in the whole firmware
privatekeyfilesetc=$(timeout 120 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY\-\-\-\-\-' "$ROOT_FOLDER" 2>/dev/null)
privatekeyfilesetc=$(timeout 120 grep -rl '\-\-\-\-\-BEGIN .* PRIVATE KEY.*\-\-\-\-\-' "$ROOT_FOLDER" 2>/dev/null)
fi
if [ "$privatekeyfilesetc" ] || [ "$privatekeyfileshome" ] || [ "$privatekeyfilesroot" ] || [ "$privatekeyfilesmnt" ] ; then

View File

@@ -17,7 +17,7 @@ if ! [ "$IAMROOT" ]; then
print_2title "Interesting writable files owned by me or writable by everyone (not in Home) (max 200)"
print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#writable-files"
#In the next file, you need to specify type "d" and "f" to avoid fake link files apparently writable by all
obmowbe=$(find $ROOT_FOLDER '(' -type f -or -type d ')' '(' '(' -user $USER ')' -or '(' -perm -o=w ')' ')' ! -path "/proc/*" ! -path "/sys/*" ! -path "/dev/*" ! -path "/snap/*" ! -path "$HOME/*" 2>/dev/null | grep -Ev "$notExtensions" | sort | uniq | awk -F/ '{line_init=$0; if (!cont){ cont=0 }; $NF=""; act=$0; if (act == pre){(cont += 1)} else {cont=0}; if (cont < 5){ print line_init; } if (cont == "5"){print "#)You_can_write_even_more_files_inside_last_directory\n"}; pre=act }' | head -n 200)
obmowbe=$(find $ROOT_FOLDER '(' -type f -or -type d ')' '(' '(' -user $USER ')' -or '(' -perm -o=w ')' ')' ! -path "/proc/*" ! -path "/sys/*" ! -path "$HOME/*" 2>/dev/null | grep -Ev "$notExtensions" | sort | uniq | awk -F/ '{line_init=$0; if (!cont){ cont=0 }; $NF=""; act=$0; if (act == pre){(cont += 1)} else {cont=0}; if (cont < 5){ print line_init; } if (cont == "5"){print "#)You_can_write_even_more_files_inside_last_directory\n"}; pre=act }' | head -n 200)
printf "%s\n" "$obmowbe" | while read l; do
if echo "$l" | grep -q "You_can_write_even_more_files_inside_last_directory"; then printf $ITALIC"$l\n"$NC;
elif echo "$l" | grep -qE "$writeVB"; then

View File

@@ -1,80 +0,0 @@
# Title: Interesting Permissions Files - IGEL OS SUID setup/date abuse
# ID: IP_IGEL_OS_SUID
# Author: HT Bot
# Last Update: 29-11-2025
# Description: Detect IGEL OS environments that expose the SUID-root `setup`/`date` binaries and highlight writable NetworkManager/systemd configs that enable the documented privilege escalation chain (Metasploit linux/local/igel_network_priv_esc).
# License: GNU GPL
# Version: 1.0
# Functions Used: print_2title, print_info
# Global Variables: $ITALIC, $NC, $SED_GREEN, $SED_RED, $SED_RED_YELLOW, $SUPERFAST
# Initial Functions:
# Generated Global Variables: $igel_markers, $igel_marker_sources, $marker, $igel_suid_hits, $candidate, $writable_nm, $writable_systemd, $unitdir, $tmp_units
# Fat linpeas: 0
# Small linpeas: 1
igel_markers=""
igel_marker_sources=""
if [ -f /etc/os-release ] && grep -qi "igel" /etc/os-release 2>/dev/null; then
igel_markers="Yes"
igel_marker_sources="/etc/os-release"
fi
if [ -f /etc/issue ] && grep -qi "igel" /etc/issue 2>/dev/null; then
igel_markers="Yes"
igel_marker_sources="${igel_marker_sources} /etc/issue"
fi
for marker in /etc/igel /wfs/igel /userhome/.igel /config/sessions/igel; do
if [ -e "$marker" ]; then
igel_markers="Yes"
igel_marker_sources="${igel_marker_sources} $marker"
fi
done
igel_suid_hits=""
for candidate in /usr/bin/setup /bin/setup /usr/sbin/setup /opt/igel/bin/setup /usr/bin/date /bin/date /usr/lib/igel/date; do
if [ -u "$candidate" ]; then
igel_suid_hits="${igel_suid_hits}$(ls -lah "$candidate" 2>/dev/null)\n"
fi
done
if [ -n "$igel_markers" ] || [ -n "$igel_suid_hits" ]; then
print_2title "IGEL OS SUID setup/date privilege escalation surface"
print_info "https://www.rapid7.com/blog/post/pt-metasploit-wrap-up-11-28-2025"
if [ -n "$igel_markers" ]; then
echo "Potential IGEL OS detected via: $igel_marker_sources" | sed -${E} "s,.*,${SED_GREEN},"
else
echo "IGEL-specific SUID helpers found but IGEL markers were not detected" | sed -${E} "s,.*,${SED_RED},"
fi
if [ -n "$igel_suid_hits" ]; then
echo "SUID-root helpers exposing configuration primitives:" | sed -${E} "s,.*,${SED_RED_YELLOW},"
printf "%b" "$igel_suid_hits"
else
echo "No SUID setup/date binaries were located (system may be patched)."
fi
writable_nm=""
writable_systemd=""
if ! [ "$SUPERFAST" ]; then
if [ -d /etc/NetworkManager ]; then
writable_nm=$(find /etc/NetworkManager -maxdepth 3 -type f -writable 2>/dev/null | head -n 25)
fi
for unitdir in /etc/systemd/system /lib/systemd/system /usr/lib/systemd/system; do
if [ -d "$unitdir" ]; then
tmp_units=$(find "$unitdir" -maxdepth 2 -type f -writable 2>/dev/null | head -n 15)
if [ -n "$tmp_units" ]; then
writable_systemd="${writable_systemd}${tmp_units}\n"
fi
fi
done
fi
if [ -n "$writable_nm" ]; then
echo "Writable NetworkManager profiles/hooks (swap Exec path to your payload):" | sed -${E} "s,.*,${SED_RED_YELLOW},"
echo "$writable_nm"
fi
if [ -n "$writable_systemd" ]; then
echo "Writable systemd unit files (edit ExecStart, then restart via setup/date):" | sed -${E} "s,.*,${SED_RED_YELLOW},"
printf "%b" "$writable_systemd"
fi
printf "$ITALIC Known exploitation chain: Use the SUID setup/date binaries to edit NetworkManager or systemd configs so ExecStart points to your payload, then trigger a service restart via the same helper to run as root (Metasploit linux/local/igel_network_priv_esc).$NC\n"
fi
echo ""

View File

@@ -1,36 +0,0 @@
# Title: Interesting Permissions Files - Writable root-owned executables
# ID: IP_Writable_root_execs
# Author: HT Bot
# Last Update: 29-11-2025
# Description: Locate root-owned executables outside home folders that the current user can modify
# License: GNU GPL
# Version: 1.0
# Functions Used: print_2title, print_info, echo_not_found
# Global Variables: $DEBUG, $IAMROOT, $ROOT_FOLDER, $HOME, $writeVB
# Initial Functions:
# Generated Global Variables: $writable_root_execs
# Fat linpeas: 0
# Small linpeas: 1
if ! [ "$IAMROOT" ]; then
print_2title "Writable root-owned executables I can modify (max 200)"
print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#writable-files"
writable_root_execs=$(
find "$ROOT_FOLDER" -type f -user root -perm -u=x \
\( -perm -g=w -o -perm -o=w \) \
! -path "/proc/*" ! -path "/sys/*" ! -path "/run/*" ! -path "/dev/*" ! -path "/snap/*" ! -path "$HOME/*" 2>/dev/null \
| while IFS= read -r f; do
if [ -w "$f" ]; then
ls -l "$f" 2>/dev/null
fi
done | head -n 200
)
if [ "$writable_root_execs" ] || [ "$DEBUG" ]; then
printf "%s\n" "$writable_root_execs" | sed -${E} "s,$writeVB,${SED_RED_YELLOW},"
else
echo_not_found "Writable root-owned executables"
fi
echo ""
fi

View File

@@ -1,7 +1,7 @@
# Title: LinPeasBase - su_try_pwd
# ID: su_try_pwd
# Author: Carlos Polop
# Last Update: 15-12-2025
# Last Update: 22-08-2023
# Description: Try to login as user using a password
# License: GNU GPL
# Version: 1.0
@@ -17,7 +17,7 @@ su_try_pwd(){
BFUSER=$1
PASSWORDTRY=$2
trysu=$(echo "$PASSWORDTRY" | timeout 1 su $BFUSER -c whoami 2>/dev/null)
if [ $? -eq 0 ]; then
if [ "$trysu" ]; then
echo " You can login as $BFUSER using password: $PASSWORDTRY" | sed -${E} "s,.*,${SED_RED_YELLOW},"
fi
}
}

View File

@@ -371,7 +371,7 @@ echo ""
printf ${BLUE}"Linux Privesc Checklist: ${YELLOW}https://book.hacktricks.wiki/en/linux-hardening/linux-privilege-escalation-checklist.html\n"$NC
echo " LEGEND:" | sed "s,LEGEND,${C}[1;4m&${C}[0m,"
echo " RED/YELLOW: 95% a PE vector" | sed "s,RED/YELLOW,${SED_RED_YELLOW},"
echo " RED: You should take a look into it" | sed "s,RED,${SED_RED},"
echo " RED: You should take a look to it" | sed "s,RED,${SED_RED},"
echo " LightCyan: Users with console" | sed "s,LightCyan,${SED_LIGHT_CYAN},"
echo " Blue: Users without console & mounted devs" | sed "s,Blue,${SED_BLUE},"
echo " Green: Common things (users, groups, SUID/SGID, mounts, .sh scripts, cronjobs) " | sed "s,Green,${SED_GREEN},"
@@ -514,4 +514,4 @@ else
HOMESEARCH="$HOME $HOMESEARCH"
fi
fi
GREPHOMESEARCH=$(echo "$HOMESEARCH" | sed 's/ *$//g' | tr " " "|") #Remove ending spaces before putting "|"
GREPHOMESEARCH=$(echo "$HOMESEARCH" | sed 's/ *$//g' | tr " " "|") #Remove ending spaces before putting "|"

View File

@@ -12,4 +12,5 @@
# Fat linpeas: 0
# Small linpeas: 1
sudoB="$(whoami)|ALL:ALL|ALL : ALL|ALL|env_keep|NOPASSWD|SETENV|/apache2|/cryptsetup|/mount|/restic|--password-command|--password-file|-o ProxyCommand|-o PreferredAuthentications"
sudoB="$(whoami)|ALL:ALL|ALL : ALL|ALL|env_keep|NOPASSWD|SETENV|/apache2|/cryptsetup|/mount"

View File

@@ -115,7 +115,7 @@ class LinpeasBuilder:
suidVB, sudoVB, capsVB = self.__get_gtfobins_lists()
assert len(suidVB) > 185, f"Len suidVB is {len(suidVB)}"
assert len(sudoVB) > 250, f"Len sudo is {len(sudoVB)}"
assert len(capsVB) > 2, f"Len capsVB is {len(capsVB)}"
assert len(capsVB) > 10, f"Len suidVB is {len(capsVB)}"
self.__replace_mark(SUIDVB1_MARKUP, suidVB[:int(len(suidVB)/2)], "|")
self.__replace_mark(SUIDVB2_MARKUP, suidVB[int(len(suidVB)/2):], "|")
@@ -348,25 +348,8 @@ class LinpeasBuilder:
return bin_b64
def __get_gtfobins_lists(self) -> tuple:
bins = []
api_url = "https://api.github.com/repos/GTFOBins/GTFOBins.github.io/contents/_gtfobins?per_page=100"
while api_url:
r = requests.get(api_url, timeout=10)
if not r.ok:
break
data = r.json()
for entry in data:
if entry.get("type") == "file" and entry.get("name"):
bins.append(entry["name"])
api_url = None
link = r.headers.get("Link", "")
for part in link.split(","):
if 'rel="next"' in part:
api_url = part.split(";")[0].strip().strip("<>")
break
if not bins:
r = requests.get("https://github.com/GTFOBins/GTFOBins.github.io/tree/master/_gtfobins", timeout=10)
bins = re.findall(r'_gtfobins/([a-zA-Z0-9_ \-]+)(?:\\.md)?', r.text)
r = requests.get("https://github.com/GTFOBins/GTFOBins.github.io/tree/master/_gtfobins")
bins = re.findall(r'_gtfobins/([a-zA-Z0-9_ \-]+).md', r.text)
sudoVB = []
suidVB = []
@@ -374,12 +357,12 @@ class LinpeasBuilder:
for b in bins:
try:
rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}", timeout=5)
rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}.md", timeout=5)
except:
try:
rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}", timeout=5)
rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}.md", timeout=5)
except:
rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}", timeout=5)
rb = requests.get(f"https://raw.githubusercontent.com/GTFOBins/GTFOBins.github.io/master/_gtfobins/{b}.md", timeout=5)
if "sudo:" in rb.text:
if len(b) <= 3:
sudoVB.append("[^a-zA-Z0-9]"+b+"$") # Less false possitives applied to small names

View File

@@ -270,7 +270,7 @@ class MetasploitModule < Msf::Post
if datastore['CUSTOM_URL'] != ""
url_peass = datastore['CUSTOM_URL']
else
url_peass = datastore['WINPEASS'].to_s.strip.downcase == 'true' ? "https://github.com/peass-ng/PEASS-ng/releases/latest/download/winPEASany_ofs.exe" : "https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh"
url_peass = datastore['WINPEASS'] ? "https://github.com/peass-ng/PEASS-ng/releases/latest/download/winPEASany_ofs.exe" : "https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh"
end
# If URL is set, check if it is a valid URL or local file
if url_peass.include?("http://") || url_peass.include?("https://")

View File

@@ -405,7 +405,7 @@ CALL :T_Progress 1
:BasicUserInfo
CALL :ColorLine "%E%32m[*]%E%97m BASIC USER INFO
ECHO. [i] Check if you are inside the Administrators group or if you have enabled any token that can be use to escalate privileges like SeImpersonatePrivilege, SeAssignPrimaryPrivilege, SeTcbPrivilege, SeBackupPrivilege, SeRestorePrivilege, SeCreateTokenPrivilege, SeLoadDriverPrivilege, SeTakeOwnershipPrivilege, SeDebugPrivilege
ECHO. [i] Check if you are inside the Administrators group or if you have enabled any token that can be use to escalate privileges like SeImpersonatePrivilege, SeAssignPrimaryPrivilege, SeTcbPrivilege, SeBackupPrivilege, SeRestorePrivilege, SeCreateTokenPrivilege, SeLoadDriverPrivilege, SeTakeOwnershipPrivilege, SeDebbugPrivilege
ECHO. [?] https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#users--groups
ECHO.
CALL :ColorLine " %E%33m[+]%E%97m CURRENT USER"
@@ -707,8 +707,7 @@ EXIT /B
:SetOnce
REM :: ANSI escape character is set once below - for ColorLine Subroutine
for /F %%a in ('echo prompt $E ^| cmd') do set "ESC=%%a"
SET "E=%ESC%["
SET "E=0x1B["
SET "PercentageTrack=0"
EXIT /B

View File

@@ -77,12 +77,19 @@ 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 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 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?!?!?!
@@ -146,7 +153,6 @@ 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
@@ -211,7 +217,7 @@ Once you have installed and activated it you need to:
- [x] SCCM
- [x] Security Package Credentials
- [x] AlwaysInstallElevated
- [x] WSUS (HTTP downgrade + CVE-2025-59287 exposure)
- [x] WSUS
- **Browser Information**
- [x] Firefox DBs

File diff suppressed because it is too large Load Diff

View File

@@ -88,13 +88,11 @@ 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()),

View File

@@ -1,141 +0,0 @@
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})");
}
}
}
}

View File

@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using winPEAS.Helpers;
using winPEAS.Helpers.Registry;
using winPEAS.Info.ServicesInfo;
namespace winPEAS.Checks
@@ -36,9 +34,6 @@ namespace winPEAS.Checks
PrintModifiableServices,
PrintWritableRegServices,
PrintPathDllHijacking,
PrintOemPrivilegedUtilities,
PrintLegacySignedKernelDrivers,
PrintKernelQuickIndicators,
}.ForEach(action => CheckRunner.Run(action, isDebug));
}
@@ -211,190 +206,5 @@ 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()
{
try
{
Beaprint.MainPrint("Kernel drivers with weak/legacy signatures");
Beaprint.LinkPrint("https://research.checkpoint.com/2025/cracking-valleyrat-from-builder-secrets-to-kernel-rootkits/",
"Legacy cross-signed drivers (pre-July-2015) can still grant kernel execution on modern Windows");
List<ServicesInfoHelper.KernelDriverInfo> drivers = ServicesInfoHelper.GetKernelDriverInfos();
if (drivers.Count == 0)
{
Beaprint.InfoPrint(" Unable to enumerate kernel services");
return;
}
var suspiciousDrivers = drivers.Where(d => d.Signature != null && (!d.Signature.IsSigned || d.Signature.IsLegacyExpired))
.OrderBy(d => d.Name)
.ToList();
if (suspiciousDrivers.Count == 0)
{
Beaprint.InfoPrint(" No unsigned or legacy-signed kernel drivers detected");
return;
}
foreach (var driver in suspiciousDrivers)
{
var signature = driver.Signature ?? new ServicesInfoHelper.KernelDriverSignatureInfo();
List<string> reasons = new List<string>();
if (!signature.IsSigned)
{
reasons.Add("unsigned or signature missing");
}
else if (signature.IsLegacyExpired)
{
reasons.Add("signed with certificate that expired before 29-Jul-2015 (legacy exception)");
}
if (!string.IsNullOrEmpty(driver.StartMode) &&
(driver.StartMode.Equals("System", StringComparison.OrdinalIgnoreCase) ||
driver.StartMode.Equals("Boot", StringComparison.OrdinalIgnoreCase)))
{
reasons.Add($"loads at early boot (Start={driver.StartMode})");
}
if (string.Equals(driver.Name, "kernelquick", StringComparison.OrdinalIgnoreCase))
{
reasons.Add("service name matches ValleyRAT rootkit loader");
}
string reason = reasons.Count > 0 ? string.Join("; ", reasons) : "Potentially risky driver";
string signatureLine = signature.IsSigned
? $"Subject: {signature.Subject}; Issuer: {signature.Issuer}; Valid: {FormatDate(signature.NotBefore)} - {FormatDate(signature.NotAfter)}"
: $"Signature issue: {signature.Error ?? "Unsigned"}";
Beaprint.BadPrint($" {driver.Name} ({driver.DisplayName})");
Beaprint.NoColorPrint($" Path : {driver.PathName}");
Beaprint.NoColorPrint($" Start/State: {driver.StartMode}/{driver.State}");
Beaprint.NoColorPrint($" Reason : {reason}");
Beaprint.NoColorPrint($" Signature : {signatureLine}");
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
}
void PrintKernelQuickIndicators()
{
try
{
Beaprint.MainPrint("KernelQuick / ValleyRAT rootkit indicators");
bool found = false;
Dictionary<string, object> serviceValues = RegistryHelper.GetRegValues("HKLM", @"SYSTEM\\CurrentControlSet\\Services\\kernelquick");
if (serviceValues != null)
{
found = true;
string imagePath = serviceValues.ContainsKey("ImagePath") ? serviceValues["ImagePath"].ToString() : "Unknown";
string start = serviceValues.ContainsKey("Start") ? serviceValues["Start"].ToString() : "Unknown";
Beaprint.BadPrint(" Service HKLM\\SYSTEM\\CurrentControlSet\\Services\\kernelquick present");
Beaprint.NoColorPrint($" ImagePath : {imagePath}");
Beaprint.NoColorPrint($" Start : {start}");
}
foreach (var path in new[] { @"SOFTWARE\\KernelQuick", @"SOFTWARE\\WOW6432Node\\KernelQuick", @"SYSTEM\\CurrentControlSet\\Services\\kernelquick" })
{
Dictionary<string, object> values = RegistryHelper.GetRegValues("HKLM", path);
if (values == null)
continue;
var kernelQuickValues = values.Where(k => k.Key.StartsWith("KernelQuick_", StringComparison.OrdinalIgnoreCase)).ToList();
if (kernelQuickValues.Count == 0)
continue;
found = true;
Beaprint.BadPrint($" Registry values under HKLM\\{path}");
foreach (var kv in kernelQuickValues)
{
string displayValue = kv.Value is byte[] bytes ? $"(binary) {bytes.Length} bytes" : string.Format("{0}", kv.Value);
Beaprint.NoColorPrint($" {kv.Key} = {displayValue}");
}
}
Dictionary<string, object> ipdatesValues = RegistryHelper.GetRegValues("HKLM", @"SOFTWARE\\IpDates");
if (ipdatesValues != null)
{
found = true;
Beaprint.BadPrint(" Possible kernel shellcode staging key HKLM\\SOFTWARE\\IpDates");
foreach (var kv in ipdatesValues)
{
string displayValue = kv.Value is byte[] bytes ? $"(binary) {bytes.Length} bytes" : string.Format("{0}", kv.Value);
Beaprint.NoColorPrint($" {kv.Key} = {displayValue}");
}
}
if (!found)
{
Beaprint.InfoPrint(" No KernelQuick-specific registry indicators were found");
}
else
{
Beaprint.LinkPrint("https://research.checkpoint.com/2025/cracking-valleyrat-from-builder-secrets-to-kernel-rootkits/",
"KernelQuick_* values and HKLM\\SOFTWARE\\IpDates are used by the ValleyRAT rootkit to hide files and stage APC payloads");
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
}
private string FormatDate(DateTime? dateTime)
{
return dateTime.HasValue ? dateTime.Value.ToString("yyyy-MM-dd HH:mm") : "n/a";
}
}
}

View File

@@ -1,88 +0,0 @@
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);
}
}
}
}

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
@@ -82,7 +81,6 @@ namespace winPEAS.Checks
PrintKrbRelayUp,
PrintInsideContainer,
PrintAlwaysInstallElevated,
PrintObjectManagerRaceAmplification,
PrintLSAInfo,
PrintNtlmSettings,
PrintLocalGroupPolicy,
@@ -90,7 +88,6 @@ namespace winPEAS.Checks
AppLockerHelper.PrintAppLockerPolicy,
PrintPrintersWMIInfo,
PrintNamedPipes,
PrintNamedPipeAbuseCandidates,
PrintAMSIProviders,
PrintSysmon,
PrintDotNetVersions
@@ -564,66 +561,27 @@ namespace winPEAS.Checks
{
Beaprint.MainPrint("Checking WSUS");
Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#wsus");
string policyPath = "Software\\Policies\\Microsoft\\Windows\\WindowsUpdate";
string policyAUPath = "Software\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU";
string wsusPolicyValue = RegistryHelper.GetRegValue("HKLM", policyPath, "WUServer");
string useWUServerValue = RegistryHelper.GetRegValue("HKLM", policyAUPath, "UseWUServer");
if (!string.IsNullOrEmpty(wsusPolicyValue) && wsusPolicyValue.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
string path = "Software\\Policies\\Microsoft\\Windows\\WindowsUpdate";
string path2 = "Software\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU";
string HKLM_WSUS = RegistryHelper.GetRegValue("HKLM", path, "WUServer");
string using_HKLM_WSUS = RegistryHelper.GetRegValue("HKLM", path2, "UseWUServer");
if (HKLM_WSUS.Contains("http://"))
{
Beaprint.BadPrint(" WSUS is using http: " + wsusPolicyValue);
Beaprint.BadPrint(" WSUS is using http: " + HKLM_WSUS);
Beaprint.InfoPrint("You can test https://github.com/pimps/wsuxploit to escalate privileges");
if (useWUServerValue == "1")
if (using_HKLM_WSUS == "1")
Beaprint.BadPrint(" And UseWUServer is equals to 1, so it is vulnerable!");
else if (useWUServerValue == "0")
else if (using_HKLM_WSUS == "0")
Beaprint.GoodPrint(" But UseWUServer is equals to 0, so it is not vulnerable!");
else
Console.WriteLine(" But UseWUServer is equals to " + useWUServerValue + ", so it may work or not");
Console.WriteLine(" But UseWUServer is equals to " + using_HKLM_WSUS + ", so it may work or not");
}
else
{
if (string.IsNullOrEmpty(wsusPolicyValue))
if (string.IsNullOrEmpty(HKLM_WSUS))
Beaprint.NotFoundPrint();
else
Beaprint.GoodPrint(" WSUS value: " + wsusPolicyValue);
}
if (!string.IsNullOrEmpty(wsusPolicyValue))
{
bool clientsForced = useWUServerValue == "1";
if (clientsForced)
{
Beaprint.BadPrint(" CVE-2025-59287: Clients talk to WSUS at " + wsusPolicyValue + " (UseWUServer=1). Unpatched WSUS allows unauthenticated deserialization to SYSTEM.");
}
else
{
Beaprint.InfoPrint(" CVE-2025-59287: WSUS endpoint discovered at " + wsusPolicyValue + ". Confirm patch level before attempting exploitation.");
if (!string.IsNullOrEmpty(useWUServerValue))
Beaprint.InfoPrint(" UseWUServer is set to " + useWUServerValue + ", clients may still reach Microsoft Update.");
}
}
string wsusSetupPath = @"SOFTWARE\Microsoft\Update Services\Server\Setup";
string wsusVersion = RegistryHelper.GetRegValue("HKLM", wsusSetupPath, "VersionString");
string wsusInstallPath = RegistryHelper.GetRegValue("HKLM", wsusSetupPath, "InstallPath");
bool wsusRoleDetected = !string.IsNullOrEmpty(wsusVersion) || !string.IsNullOrEmpty(wsusInstallPath);
if (TryGetServiceStateAndAccount("WSUSService", out string wsusServiceState, out string wsusServiceAccount))
{
wsusRoleDetected = true;
string serviceMsg = " WSUSService status: " + wsusServiceState;
if (!string.IsNullOrEmpty(wsusServiceAccount))
serviceMsg += " (runs as " + wsusServiceAccount + ")";
Beaprint.BadPrint(serviceMsg);
}
if (wsusRoleDetected)
{
if (!string.IsNullOrEmpty(wsusVersion))
Beaprint.BadPrint(" WSUS Server version: " + wsusVersion + " (verify patch level for CVE-2025-59287).");
if (!string.IsNullOrEmpty(wsusInstallPath))
Beaprint.InfoPrint(" WSUS install path: " + wsusInstallPath);
Beaprint.BadPrint(" CVE-2025-59287: Local WSUS server exposes an unauthenticated deserialization surface reachable over HTTP(S). Patch or restrict access.");
Beaprint.GoodPrint(" WSUS value: " + HKLM_WSUS);
}
}
catch (Exception ex)
@@ -632,32 +590,6 @@ namespace winPEAS.Checks
}
}
private static bool TryGetServiceStateAndAccount(string serviceName, out string state, out string account)
{
state = string.Empty;
account = string.Empty;
try
{
string query = $"SELECT Name, State, StartName FROM Win32_Service WHERE Name='{serviceName.Replace("'", "''")}'";
using (var searcher = new ManagementObjectSearcher(@"root\cimv2", query))
{
foreach (ManagementObject service in searcher.Get())
{
state = service["State"]?.ToString() ?? string.Empty;
account = service["StartName"]?.ToString() ?? string.Empty;
return true;
}
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
return false;
}
static void PrintKrbRelayUp()
{
try
@@ -735,31 +667,6 @@ 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");
@@ -884,48 +791,6 @@ 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");

View File

@@ -156,63 +156,15 @@ 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,-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");
string format = " {0,-10}{1,-15}{2,-15}{3,-25}{4,-10}{5}";
string header = string.Format(format, "SessID", "pSessionName", "pUserName", "pDomainName", "State", "SourceIP");
Beaprint.GrayPrint(header);
var colors = ColorsU();
List<Dictionary<string, string>> flaggedSessions = new List<Dictionary<string, string>>();
foreach (Dictionary<string, string> rdpSes in rdp_sessions)
{
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.");
Beaprint.AnsiPrint(string.Format(format, rdpSes["SessionID"], rdpSes["pSessionName"], rdpSes["pUserName"], rdpSes["pDomainName"], rdpSes["State"], rdpSes["SourceIP"]), ColorsU());
}
}
else

View File

@@ -132,7 +132,6 @@ 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);

View File

@@ -24,51 +24,36 @@ namespace winPEAS.Helpers
////////////////////////////////////
/////// MISC - Files & Paths ///////
////////////////////////////////////
public static bool CheckIfDotNet(string path, bool ignoreCompanyName = false)
public static bool CheckIfDotNet(string path)
{
bool isDotNet = false;
string companyName = string.Empty;
try
FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(path);
string companyName = myFileVersionInfo.CompanyName;
if ((string.IsNullOrEmpty(companyName)) ||
(!Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase)))
{
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))
try
{
AssemblyName myAssemblyName = AssemblyName.GetAssemblyName(path);
isDotNet = true;
}
}
catch
{
// System.Console.WriteLine("The assembly has already been loaded.");
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.");
}
}
return isDotNet;

View File

@@ -1,34 +0,0 @@
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;
}
}
}
}

View File

@@ -1,221 +0,0 @@
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;
}
}
}
}

View File

@@ -1,369 +0,0 @@
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;
}
}
}

View File

@@ -1,458 +0,0 @@
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; }
}
}

View File

@@ -2,14 +2,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.ServiceProcess;
using System.Text.RegularExpressions;
using winPEAS.Helpers;
@@ -279,109 +276,6 @@ namespace winPEAS.Info.ServicesInfo
}
private static readonly DateTime LegacyDriverCutoff = new DateTime(2015, 7, 29);
public static List<KernelDriverInfo> GetKernelDriverInfos()
{
List<KernelDriverInfo> drivers = new List<KernelDriverInfo>();
try
{
using (ManagementObjectSearcher wmiData = new ManagementObjectSearcher(@"root\cimv2", "SELECT Name,DisplayName,PathName,StartMode,State,ServiceType FROM win32_service"))
{
using (ManagementObjectCollection data = wmiData.Get())
{
foreach (ManagementObject result in data)
{
string serviceType = GetStringOrEmpty(result["ServiceType"]);
if (string.IsNullOrEmpty(serviceType) || !serviceType.ToLowerInvariant().Contains("kernel driver"))
continue;
string binaryPath = MyUtils.ReconstructExecPath(GetStringOrEmpty(result["PathName"]));
drivers.Add(new KernelDriverInfo
{
Name = GetStringOrEmpty(result["Name"]),
DisplayName = GetStringOrEmpty(result["DisplayName"]),
StartMode = GetStringOrEmpty(result["StartMode"]),
State = GetStringOrEmpty(result["State"]),
PathName = binaryPath,
Signature = GetDriverSignatureInfo(binaryPath)
});
}
}
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
return drivers;
}
private static KernelDriverSignatureInfo GetDriverSignatureInfo(string binaryPath)
{
KernelDriverSignatureInfo info = new KernelDriverSignatureInfo
{
FilePath = binaryPath,
IsSigned = false
};
if (string.IsNullOrEmpty(binaryPath) || !File.Exists(binaryPath))
{
info.Error = "Binary not found";
return info;
}
try
{
using (var baseCertificate = X509Certificate.CreateFromSignedFile(binaryPath))
using (var certificate = new X509Certificate2(baseCertificate))
{
info.IsSigned = true;
info.Subject = certificate.Subject;
info.Issuer = certificate.Issuer;
info.NotBefore = certificate.NotBefore;
info.NotAfter = certificate.NotAfter;
info.IsLegacyExpired = certificate.NotAfter < LegacyDriverCutoff;
}
}
catch (CryptographicException cryptoEx)
{
info.Error = cryptoEx.Message;
}
catch (Exception ex)
{
info.Error = ex.Message;
}
return info;
}
internal class KernelDriverInfo
{
public string Name { get; set; }
public string DisplayName { get; set; }
public string PathName { get; set; }
public string StartMode { get; set; }
public string State { get; set; }
public KernelDriverSignatureInfo Signature { get; set; }
}
internal class KernelDriverSignatureInfo
{
public string FilePath { get; set; }
public bool IsSigned { get; set; }
public string Subject { get; set; }
public string Issuer { get; set; }
public DateTime? NotBefore { get; set; }
public DateTime? NotAfter { get; set; }
public bool IsLegacyExpired { get; set; }
public string Error { get; set; }
}
//////////////////////////////////////
//////// PATH DLL Hijacking /////////
//////////////////////////////////////

View File

@@ -1,508 +0,0 @@
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; }
}
}

View File

@@ -16,20 +16,6 @@ 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
@@ -105,65 +91,6 @@ 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
{

View File

@@ -1197,11 +1197,9 @@
<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" />
@@ -1225,7 +1223,6 @@
<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" />
@@ -1294,7 +1291,6 @@
<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" />
@@ -1363,7 +1359,6 @@
<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" />
@@ -1463,14 +1458,12 @@
<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" />

View File

@@ -19,14 +19,6 @@ Download the **[latest releas from here](https://github.com/peass-ng/PEASS-ng/re
powershell "IEX(New-Object Net.WebClient).downloadString('https://raw.githubusercontent.com/peass-ng/PEASS-ng/master/winPEAS/winPEASps1/winPEAS.ps1')"
```
## Recent Updates
- Added Active Directory awareness checks to highlight Kerberos-only environments (NTLM restrictions) and time skew issues before attempting ticket-based attacks.
- winPEAS.ps1 now reviews AD-integrated DNS ACLs to flag zones where low-privileged users can register/modify records (dynamic DNS hijack risk).
- Enumerates high-value SPN accounts and weak gMSA password readers so you can immediately target Kerberoastable admins or abused service accounts.
- Surfaces Schannel certificate mapping settings to warn about ESC10-style certificate abuse opportunities when UPN mapping is enabled.
## Advisory
All the scripts/binaries of the PEAS Suite should be used for authorized penetration testing and/or educational purposes only. Any misuse of this software will not be the responsibility of the author or of any other collaborator. Use it at your own networks and/or with the network owner's permission.

View File

@@ -148,244 +148,6 @@ function Get-ClipBoardText {
}
}
function Get-DomainContext {
try {
return [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()
}
catch {
return $null
}
}
function Convert-SidToName {
param(
$SidInput
)
if ($null -eq $SidInput) { return $null }
try {
if ($SidInput -is [System.Security.Principal.SecurityIdentifier]) {
$sidObject = $SidInput
}
else {
$sidObject = New-Object System.Security.Principal.SecurityIdentifier($SidInput)
}
return $sidObject.Translate([System.Security.Principal.NTAccount]).Value
}
catch {
try { return $sidObject.Value }
catch { return [string]$SidInput }
}
}
function Get-WeakDnsUpdateFindings {
param(
[System.DirectoryServices.ActiveDirectory.Domain]$DomainContext
)
if (-not $DomainContext) { return @() }
$domainDN = $DomainContext.GetDirectoryEntry().distinguishedName
$forestDN = $DomainContext.Forest.RootDomain.GetDirectoryEntry().distinguishedName
$paths = @(
"LDAP://CN=MicrosoftDNS,DC=DomainDnsZones,$domainDN",
"LDAP://CN=MicrosoftDNS,DC=ForestDnsZones,$forestDN",
"LDAP://CN=MicrosoftDNS,$domainDN"
)
$weakPatterns = @(
"authenticated users",
"everyone",
"domain users"
)
$dangerousRights = @("GenericAll", "GenericWrite", "CreateChild", "WriteProperty", "WriteDacl", "WriteOwner")
$findings = @()
foreach ($path in $paths) {
try {
$container = New-Object System.DirectoryServices.DirectoryEntry($path)
$null = $container.NativeGuid
}
catch { continue }
$searcher = New-Object System.DirectoryServices.DirectorySearcher($container)
$searcher.Filter = "(objectClass=dnsZone)"
$searcher.PageSize = 500
$results = $searcher.FindAll()
foreach ($result in $results) {
try {
$zoneEntry = $result.GetDirectoryEntry()
$zoneEntry.Options.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl
$sd = $zoneEntry.ObjectSecurity
foreach ($ace in $sd.Access) {
if ($ace.AccessControlType -ne 'Allow') { continue }
$principal = Convert-SidToName $ace.IdentityReference
if (-not $principal) { continue }
$principalLower = $principal.ToLower()
if (-not ($weakPatterns | Where-Object { $principalLower -like "*${_}*" })) { continue }
$rights = $ace.ActiveDirectoryRights.ToString()
if (-not ($dangerousRights | Where-Object { $rights -like "*${_}*" })) { continue }
$findings += [pscustomobject]@{
Zone = $zoneEntry.Properties["name"].Value
Partition = $path.Split(',')[1]
Principal = $principal
Rights = $rights
}
}
}
catch { continue }
}
}
return ($findings | Sort-Object Zone, Principal -Unique)
}
function Get-GmsaReadersReport {
param(
[System.DirectoryServices.ActiveDirectory.Domain]$DomainContext
)
if (-not $DomainContext) { return @() }
$domainDN = $DomainContext.GetDirectoryEntry().distinguishedName
try {
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$domainDN")
$searcher.Filter = "(&(objectClass=msDS-GroupManagedServiceAccount))"
$searcher.PageSize = 500
[void]$searcher.PropertiesToLoad.Add("sAMAccountName")
[void]$searcher.PropertiesToLoad.Add("msDS-GroupMSAMembership")
$results = $searcher.FindAll()
}
catch { return @() }
$report = @()
foreach ($result in $results) {
$name = $result.Properties["samaccountname"]
$blobs = $result.Properties["msds-groupmsamembership"]
if (-not $blobs) { continue }
$principals = @()
foreach ($blob in $blobs) {
try {
$raw = New-Object System.Security.AccessControl.RawSecurityDescriptor (, $blob)
foreach ($ace in $raw.DiscretionaryAcl) {
$sid = Convert-SidToName $ace.SecurityIdentifier
if ($sid) { $principals += $sid }
}
}
catch { continue }
}
if ($principals.Count -eq 0) { continue }
$principals = $principals | Sort-Object -Unique
$weak = $principals | Where-Object { $_ -match 'Domain Users|Authenticated Users|Everyone' }
$report += [pscustomobject]@{
Account = ($name | Select-Object -First 1)
Allowed = ($principals -join ", ")
WeakPrincipals = if ($weak) { $weak -join ", " } else { "" }
}
}
return $report
}
function Get-PrivilegedSpnTargets {
param(
[System.DirectoryServices.ActiveDirectory.Domain]$DomainContext
)
if (-not $DomainContext) { return @() }
$domainDN = $DomainContext.GetDirectoryEntry().distinguishedName
$keywords = @(
"Domain Admin",
"Enterprise Admin",
"Administrators",
"Exchange",
"IT_",
"Schema Admin",
"Account Operator",
"Server Operator",
"Backup Operator",
"DnsAdmin"
)
try {
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$domainDN")
$searcher.Filter = "(&(objectClass=user)(servicePrincipalName=*))"
$searcher.PageSize = 500
[void]$searcher.PropertiesToLoad.Add("sAMAccountName")
[void]$searcher.PropertiesToLoad.Add("memberOf")
$results = $searcher.FindAll()
}
catch { return @() }
$findings = @()
foreach ($res in $results) {
$groups = $res.Properties["memberof"]
if (-not $groups) { continue }
$matchedGroups = @()
foreach ($group in $groups) {
$cn = ($group -split ',')[0] -replace '^CN=',''
if ($keywords | Where-Object { $cn -like "*${_}*" }) {
$matchedGroups += $cn
}
}
if ($matchedGroups.Count -gt 0) {
$findings += [pscustomobject]@{
User = ($res.Properties["samaccountname"] | Select-Object -First 1)
Groups = ($matchedGroups | Sort-Object -Unique) -join ', '
}
}
}
return ($findings | Sort-Object User | Select-Object -First 12)
}
function Get-NtlmPolicySummary {
try {
$msv = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0' -ErrorAction Stop
}
catch { return $null }
$lsa = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' -ErrorAction SilentlyContinue
return [pscustomobject]@{
RestrictReceiving = $msv.RestrictReceivingNTLMTraffic
RestrictSending = $msv.RestrictSendingNTLMTraffic
LmCompatibility = if ($lsa) { $lsa.LmCompatibilityLevel } else { $null }
}
}
function Get-TimeSkewInfo {
param(
[System.DirectoryServices.ActiveDirectory.Domain]$DomainContext
)
if (-not $DomainContext) { return $null }
try {
$pdc = $DomainContext.PdcRoleOwner.Name
}
catch { return $null }
try {
$stripchart = w32tm /stripchart /computer:$pdc /dataonly /samples:3 2>$null
$sample = $stripchart | Where-Object { $_ -match ',' } | Select-Object -Last 1
if (-not $sample) { return $null }
$parts = $sample.Split(',')
if ($parts.Count -lt 2) { return $null }
$offsetString = $parts[1].Trim().TrimEnd('s')
[double]$offsetSeconds = 0
if (-not [double]::TryParse($offsetString, [ref]$offsetSeconds)) { return $null }
return [pscustomobject]@{
Source = $pdc
OffsetSeconds = $offsetSeconds
RawSample = $sample
}
}
catch {
return $null
}
}
function Get-AdcsSchannelInfo {
$info = [ordered]@{
MappingValue = $null
UpnMapping = $false
ServiceState = $null
}
try {
$schannel = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL' -Name 'CertificateMappingMethods' -ErrorAction Stop
$info.MappingValue = $schannel.CertificateMappingMethods
if (($schannel.CertificateMappingMethods -band 0x4) -eq 0x4) { $info.UpnMapping = $true }
}
catch { }
$svc = Get-Service -Name certsrv -ErrorAction SilentlyContinue
if ($svc) { $info.ServiceState = $svc.Status }
return [pscustomobject]$info
}
function Search-Excel {
[cmdletbinding()]
Param (
@@ -1464,95 +1226,6 @@ Write-Host -ForegroundColor Blue "=========|| LISTENING PORTS"
Start-Process NETSTAT.EXE -ArgumentList "-ano" -Wait -NoNewWindow
######################## ACTIVE DIRECTORY / IDENTITY MISCONFIG CHECKS ########################
Write-Host ""
if ($TimeStamp) { TimeElapsed }
Write-Host -ForegroundColor Blue "=========|| ACTIVE DIRECTORY / IDENTITY MISCONFIG CHECKS"
$domainContext = Get-DomainContext
if (-not $domainContext) {
Write-Host "Host appears to be in a workgroup or the AD context could not be resolved. Skipping domain-specific checks." -ForegroundColor DarkGray
}
else {
$ntlmStatus = Get-NtlmPolicySummary
if ($ntlmStatus) {
$recvValue = if ($ntlmStatus.RestrictReceiving -ne $null) { [int]$ntlmStatus.RestrictReceiving } else { -1 }
$sendValue = if ($ntlmStatus.RestrictSending -ne $null) { [int]$ntlmStatus.RestrictSending } else { -1 }
$lmValue = if ($ntlmStatus.LmCompatibility -ne $null) { [int]$ntlmStatus.LmCompatibility } else { -1 }
$ntlmMsg = "Receiving:{0} Sending:{1} LMCompat:{2}" -f $recvValue, $sendValue, $lmValue
if ($recvValue -ge 1 -or $sendValue -ge 1 -or $lmValue -ge 5) {
Write-Host "[!] NTLM is restricted/disabled ($ntlmMsg). Expect Kerberos-only auth paths (sync time before Kerberoasting)." -ForegroundColor Yellow
}
else {
Write-Host "[i] NTLM restrictions appear relaxed ($ntlmMsg)."
}
}
$timeSkew = Get-TimeSkewInfo -DomainContext $domainContext
if ($timeSkew) {
$offsetAbs = [math]::Abs($timeSkew.OffsetSeconds)
$timeMsg = "Offset vs {0}: {1:N3}s (sample: {2})" -f $timeSkew.Source, $timeSkew.OffsetSeconds, $timeSkew.RawSample.Trim()
if ($offsetAbs -gt 5) {
Write-Host "[!] Significant Kerberos time skew detected - $timeMsg" -ForegroundColor Yellow
}
else {
Write-Host "[i] Kerberos time offset looks OK - $timeMsg"
}
}
$dnsFindings = @(Get-WeakDnsUpdateFindings -DomainContext $domainContext)
if ($dnsFindings.Count -gt 0) {
Write-Host "[!] AD-integrated DNS zones allow low-priv principals to write records (dynamic DNS hijack / service MITM risk)." -ForegroundColor Yellow
$dnsFindings | Format-Table Zone,Partition,Principal,Rights -AutoSize | Out-String | Write-Host
}
else {
Write-Host "[i] No obvious insecure dynamic DNS ACLs found with current privileges."
}
$spnFindings = @(Get-PrivilegedSpnTargets -DomainContext $domainContext)
if ($spnFindings.Count -gt 0) {
Write-Host "[!] High-value SPN accounts identified (prime Kerberoast targets):" -ForegroundColor Yellow
$spnFindings | Format-Table User,Groups -AutoSize | Out-String | Write-Host
}
else {
Write-Host "[i] No privileged SPN users detected via quick LDAP search."
}
$gmsaReport = @(Get-GmsaReadersReport -DomainContext $domainContext)
if ($gmsaReport.Count -gt 0) {
$weakGmsa = $gmsaReport | Where-Object { $_.WeakPrincipals -ne "" }
if ($weakGmsa) {
Write-Host "[!] gMSA passwords readable by low-priv groups/principals: " -ForegroundColor Yellow
$weakGmsa | Select-Object Account, WeakPrincipals | Format-Table -AutoSize | Out-String | Write-Host
}
else {
Write-Host "[i] gMSA accounts discovered (review allowed readers below)."
$gmsaReport | Select-Object Account, Allowed | Sort-Object Account | Select-Object -First 5 | Format-Table -Wrap | Out-String | Write-Host
}
}
else {
Write-Host "[i] No gMSA objects found via LDAP."
}
$adcsInfo = Get-AdcsSchannelInfo
if ($adcsInfo.MappingValue -ne $null) {
$hex = ('0x{0:X}' -f [int]$adcsInfo.MappingValue)
if ($adcsInfo.UpnMapping) {
Write-Host ("[!] Schannel CertificateMappingMethods={0} (UPN mapping allowed) - ESC10 certificate abuse possible if you can edit another user's UPN." -f $hex) -ForegroundColor Yellow
}
else {
Write-Host ("[i] Schannel CertificateMappingMethods={0} (UPN mapping flag not set)." -f $hex)
}
if ($adcsInfo.ServiceState) {
Write-Host ("[i] AD CS service state: {0}" -f $adcsInfo.ServiceState)
}
}
else {
Write-Host "[i] Could not read Schannel certificate mapping configuration." -ForegroundColor DarkGray
}
}
Write-Host ""
if ($TimeStamp) { TimeElapsed }
Write-Host -ForegroundColor Blue "=========|| ARP Table"
@@ -1650,7 +1323,7 @@ Write-Host -ForegroundColor Blue "=========|| WHOAMI INFO"
Write-Host ""
if ($TimeStamp) { TimeElapsed }
Write-Host -ForegroundColor Blue "=========|| Check Token access here: https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens.html#abusing-tokens" -ForegroundColor yellow
Write-Host -ForegroundColor Blue "=========|| Check if you are inside the Administrators group or if you have enabled any token that can be use to escalate privileges like SeImpersonatePrivilege, SeAssignPrimaryPrivilege, SeTcbPrivilege, SeBackupPrivilege, SeRestorePrivilege, SeCreateTokenPrivilege, SeLoadDriverPrivilege, SeTakeOwnershipPrivilege, SeDebugPrivilege"
Write-Host -ForegroundColor Blue "=========|| Check if you are inside the Administrators group or if you have enabled any token that can be use to escalate privileges like SeImpersonatePrivilege, SeAssignPrimaryPrivilege, SeTcbPrivilege, SeBackupPrivilege, SeRestorePrivilege, SeCreateTokenPrivilege, SeLoadDriverPrivilege, SeTakeOwnershipPrivilege, SeDebbugPrivilege"
Write-Host "https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#users--groups" -ForegroundColor Yellow
Start-Process whoami.exe -ArgumentList "/all" -Wait -NoNewWindow