Compare commits

..

62 Commits

Author SHA1 Message Date
Carlos Polop
1b449d862a Doc tweak for Codex merge test 2026-01-18 23:58:37 +01:00
Carlos Polop
44f265f2d3 new actions 2026-01-18 23:54:04 +01:00
Carlos Polop
2561b3c928 f 2026-01-17 17:46:36 +01:00
Carlos Polop
e7663381f2 Merge master into PR 529 and resolve ServicesInfo conflict 2026-01-17 15:52:44 +01:00
SirBroccoli
a1aa60faf8 Merge pull request #544 from peass-ng/update_PEASS-winpeas-Kerberoasting__Low-Tech__High-Impact_20251229_013424
[WINPEAS] Add privilege escalation check: Kerberoasting Low-Tech, High-Impact Atta...
2026-01-17 15:42:18 +01:00
SirBroccoli
e81c436d80 Merge branch 'master' into update_PEASS-winpeas-Kerberoasting__Low-Tech__High-Impact_20251229_013424 2026-01-17 15:42:10 +01:00
SirBroccoli
f4883f814e Merge pull request #543 from peass-ng/update_PEASS-linpeas-CVE-2025-38352___In-the-wild_Android_20251222_130932
[LINPEAS] Add privilege escalation check: CVE-2025-38352 – In-the-wild Android Ker...
2026-01-17 15:38:51 +01:00
SirBroccoli
4a7fb83165 Merge pull request #541 from peass-ng/update_PEASS-linpeas-From_Chrome_Renderer_Code_Execution__20251217_020557
[LINPEAS] Add privilege escalation check: From Chrome Renderer Code Execution to L...
2026-01-17 15:36:28 +01:00
SirBroccoli
ff13e8bebb Merge pull request #534 from peass-ng/update_PEASS-winpeas-SOAPwn__Pwning__NET_Framework_Applic_20251211_184735
[WINPEAS] Add privilege escalation check: SOAPwn Pwning .NET Framework Application...
2026-01-17 15:35:26 +01:00
SirBroccoli
e80425aa3d Merge branch 'master' into update_PEASS-winpeas-SOAPwn__Pwning__NET_Framework_Applic_20251211_184735 2026-01-17 15:35:19 +01:00
SirBroccoli
dc5ae45cc3 Merge pull request #535 from peass-ng/update_PEASS-linpeas-HTB_WhiteRabbit__n8n_HMAC_Forgery__S_20251213_183617
[LINPEAS] Add privilege escalation check: HTB WhiteRabbit n8n HMAC Forgery, SQL In...
2026-01-17 15:35:02 +01:00
SirBroccoli
ff21b3dcb9 Delete linPEAS/builder/linpeas_parts/6_users_information/19_Sudo_restic.sh 2026-01-17 15:34:34 +01:00
SirBroccoli
2c6cbfa43d Updating sudoB.sh with variables information 2026-01-17 15:32:29 +01:00
SirBroccoli
8ae2667800 Merge pull request #539 from peass-ng/update_PEASS-winpeas-Windows_Exploitation_Technique__Ampl_20251217_012647
[WINPEAS] Add privilege escalation check: Windows Exploitation Technique Amplifyin...
2026-01-17 15:30:39 +01:00
SirBroccoli
43a7684621 Merge branch 'master' into update_PEASS-winpeas-Windows_Exploitation_Technique__Ampl_20251217_012647 2026-01-17 15:30:32 +01:00
SirBroccoli
182c8974fd Merge pull request #538 from peass-ng/update_PEASS-winpeas-Inside_Ink_Dragon__Revealing_the_Rel_20251216_185841
[WINPEAS] Add privilege escalation check: Inside Ink Dragon Revealing the Relay Ne...
2026-01-17 15:29:36 +01:00
SirBroccoli
7b4a83d51d Merge branch 'master' into update_PEASS-winpeas-Inside_Ink_Dragon__Revealing_the_Rel_20251216_185841 2026-01-17 15:29:29 +01:00
SirBroccoli
8aa05e13a4 Merge branch 'master' into update_PEASS-winpeas-SOAPwn__Pwning__NET_Framework_Applic_20251211_184735 2026-01-17 15:27:28 +01:00
Carlos Polop
4559fd09ea Fix SOAP service enumeration yield in try/catch 2026-01-17 15:25:23 +01:00
SirBroccoli
4f8a3b3f25 Merge pull request #531 from peass-ng/update_PEASS-winpeas-pipetap___A_Windows_Named_Pipe_Multi_20251209_013140
[WINPEAS] Add privilege escalation check: pipetap – A Windows Named Pipe Multi-too...
2026-01-17 15:24:20 +01:00
Carlos Polop
0ed7a39a7d Fix unassigned out vars in OEM pipe check 2026-01-17 15:21:50 +01:00
SirBroccoli
974cfe028f Merge pull request #533 from peass-ng/update_PEASS-winpeas-Cracking_ValleyRAT__From_Builder_Sec_20251210_185002
[WINPEAS] Add privilege escalation check: Cracking ValleyRAT From Builder Secrets ...
2026-01-17 15:20:38 +01:00
SirBroccoli
f627e80a1b Merge pull request #528 from peass-ng/update_PEASS-winpeas-LDAP_BOF_Collection___In_Memory_LDAP_20251207_013625
[WINPEAS] Add privilege escalation check: LDAP BOF Collection – In‑Memory LDAP Too...
2026-01-17 15:15:36 +01:00
Carlos Polop
a83d33d409 Merge branch 'master' into update_PEASS-winpeas-LDAP_BOF_Collection___In_Memory_LDAP_20251207_013625 2026-01-17 13:36:53 +01:00
Carlos Polop
1cdd473d79 Merge branch 'master' into update_PEASS-winpeas-Pwning_ASUS_DriverHub__MSI_Center__A_20251207_130236 2026-01-17 13:36:49 +01:00
Carlos Polop
0e29450869 Merge branch 'master' into update_PEASS-winpeas-pipetap___A_Windows_Named_Pipe_Multi_20251209_013140 2026-01-17 13:36:45 +01:00
Carlos Polop
efe9c1625f Merge branch 'master' into update_PEASS-winpeas-Cracking_ValleyRAT__From_Builder_Sec_20251210_185002 2026-01-17 13:36:41 +01:00
Carlos Polop
4255330728 Merge branch 'master' into update_PEASS-winpeas-SOAPwn__Pwning__NET_Framework_Applic_20251211_184735 2026-01-17 13:36:38 +01:00
Carlos Polop
8f928f8c5d Merge branch 'master' into update_PEASS-linpeas-HTB_WhiteRabbit__n8n_HMAC_Forgery__S_20251213_183617 2026-01-17 13:36:34 +01:00
Carlos Polop
0e8959a6db Merge branch 'master' into update_PEASS-winpeas-Inside_Ink_Dragon__Revealing_the_Rel_20251216_185841 2026-01-17 13:36:30 +01:00
Carlos Polop
ea787df91c Merge branch 'master' into update_PEASS-winpeas-Windows_Exploitation_Technique__Ampl_20251217_012647 2026-01-17 13:36:26 +01:00
Carlos Polop
a86dedb553 Merge branch 'master' into update_PEASS-linpeas-From_Chrome_Renderer_Code_Execution__20251217_020557 2026-01-17 13:36:18 +01:00
Carlos Polop
7e4743d9be Merge branch 'master' into update_PEASS-linpeas-CVE-2025-38352___In-the-wild_Android_20251222_130932 2026-01-17 13:36:10 +01:00
Carlos Polop
14aa117a0e Merge branch 'master' into update_PEASS-winpeas-Kerberoasting__Low-Tech__High-Impact_20251229_013424 2026-01-17 13:36:06 +01:00
Carlos Polop
2046d18e5d chore: trigger CI 2026-01-16 18:17:44 +01:00
Carlos Polop
7a5aa8dcae chore: trigger CI 2026-01-16 18:17:40 +01:00
Carlos Polop
34d0f18aad chore: trigger CI 2026-01-16 18:17:37 +01:00
Carlos Polop
b66eebcc3d chore: trigger CI 2026-01-16 18:17:33 +01:00
Carlos Polop
49644a9813 chore: trigger CI 2026-01-16 18:17:29 +01:00
Carlos Polop
35eb4f5be7 chore: trigger CI 2026-01-16 18:17:25 +01:00
Carlos Polop
13656ba172 chore: trigger CI 2026-01-16 18:17:21 +01:00
Carlos Polop
8c043b6164 chore: trigger CI 2026-01-16 18:17:18 +01:00
Carlos Polop
dc7ce70104 chore: trigger CI 2026-01-16 18:17:10 +01:00
Carlos Polop
a5114ef5dd chore: trigger CI 2026-01-16 18:17:02 +01:00
Carlos Polop
ff41d5b0e7 chore: trigger CI 2026-01-16 18:16:58 +01:00
Carlos Polop
2f9115f97d chore: trigger CI 2026-01-16 18:15:24 +01:00
Carlos Polop
3aa04a53fc chore: trigger CI 2026-01-16 18:15:20 +01:00
Carlos Polop
373ed3cce2 chore: trigger CI 2026-01-16 18:15:17 +01:00
Carlos Polop
8eec7cf7f9 chore: trigger CI 2026-01-16 18:15:13 +01:00
Carlos Polop
79d53de4cf chore: trigger CI 2026-01-16 18:15:09 +01:00
Carlos Polop
3a89398e39 chore: trigger CI 2026-01-16 18:15:05 +01:00
HackTricks News Bot
be72fecfa8 Add winpeas privilege escalation checks from: Kerberoasting: Low-Tech, High-Impact Attacks from Legacy Kerberos Crypto 2025-12-29 01:42:21 +00:00
HackTricks News Bot
0e52c2feea Add linpeas privilege escalation checks from: CVE-2025-38352 – In-the-wild Android Kernel Vulnerability Analysis and PoC 2025-12-22 13:20:16 +00:00
HackTricks News Bot
1039cc2eff Add linpeas privilege escalation checks from: From Chrome Renderer Code Execution to Linux Kernel RCE via AF_UNIX MSG_OOB (CVE 2025-12-17 02:19:32 +00:00
HackTricks News Bot
488d388830 Add winpeas privilege escalation checks from: Windows Exploitation Technique: Amplifying Race Windows via Slow Object Manager 2025-12-17 01:34:41 +00:00
HackTricks News Bot
85aa98a841 Add winpeas privilege escalation checks from: Inside Ink Dragon: Revealing the Relay Network and Inner Workings of a Stealthy 2025-12-16 19:11:20 +00:00
HackTricks News Bot
74521345f6 Add linpeas privilege escalation checks from: HTB WhiteRabbit: n8n HMAC Forgery, SQL Injection, restic Abuse, and Time-Seeded 2025-12-13 18:41:50 +00:00
HackTricks News Bot
6100bfaceb Add winpeas privilege escalation checks from: SOAPwn: Pwning .NET Framework Applications Through HTTP Client Proxies and WSDL 2025-12-11 19:05:05 +00:00
HackTricks News Bot
9123910f9d Add winpeas privilege escalation checks from: Cracking ValleyRAT: From Builder Secrets to Kernel Rootkits 2025-12-10 19:18:07 +00:00
HackTricks News Bot
b7b7aebf1c Add winpeas privilege escalation checks from: pipetap – A Windows Named Pipe Multi-tool and Proxy for Intercepting and Replayi 2025-12-09 02:07:57 +00:00
HackTricks News Bot
6c75f10fae Add winpeas privilege escalation checks from: Pwning ASUS DriverHub, MSI Center, Acer Control Centre and Razer Synapse 4 2025-12-07 13:22:49 +00:00
HackTricks News Bot
4dad7599e6 Add winpeas privilege escalation checks from: LDAP BOF Collection – In‑Memory LDAP Toolkit for Active Directory Exploitation 2025-12-07 01:59:18 +00:00
25 changed files with 3625 additions and 68 deletions

18
.github/codex/pr-merge-schema.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"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"]
}

View File

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

113
.github/workflows/codex-pr-triage.yml vendored Normal file
View File

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

@@ -0,0 +1,136 @@
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 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 the 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,4 +37,3 @@ 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.*"
bad_regex: "secret.*|password.*|token.*|SecretKey.*|credentialId.*"
remove_empty_lines: True
type: f
search_in:
@@ -3360,7 +3360,7 @@ search:
- name: "config.xml"
value:
bad_regex: "secret.*|password.*"
bad_regex: "secret.*|password.*|token.*|SecretKey.*|credentialId.*"
only_bad_lines: True
type: f
search_in:

View File

@@ -6,6 +6,8 @@
Check the **Local Linux Privilege Escalation checklist** from **[book.hacktricks.wiki](https://book.hacktricks.wiki/en/linux-hardening/linux-privilege-escalation-checklist.html)**.
> **Dec 2025 update:** linpeas now inspects Linux kernels for CVE-2025-38352 (POSIX CPU timers race) by combining CONFIG_POSIX_CPU_TIMERS_TASK_WORK state with kernel build information, so you immediately know if publicly available PoCs might succeed.
[![asciicast](https://asciinema.org/a/250532.png)](https://asciinema.org/a/309566)
## MacPEAS
@@ -98,6 +100,10 @@ The goal of this script is to search for possible **Privilege Escalation Paths**
This script doesn't have any dependency.
### Recent updates
- **Dec 2025**: Added detection for sudo configurations that expose restic's `--password-command` helper, a common privilege escalation vector observed in real environments.
It uses **/bin/sh** syntax, so can run in anything supporting `sh` (and the binaries and parameters used).
By default, **linpeas won't write anything to disk and won't try to login as any other user using `su`**.

View File

@@ -0,0 +1,126 @@
# Title: System Information - CVE_2025_38236
# ID: SY_CVE_2025_38236
# Author: HT Bot
# Last Update: 17-12-2025
# Description: Detect Linux kernels exposed to CVE-2025-38236 (AF_UNIX MSG_OOB UAF) that allow local privilege escalation:
# - Vulnerable scope:
# * Linux kernels 6.9+ before commit 32ca245464e1479bfea8592b9db227fdc1641705
# * AF_UNIX stream sockets with MSG_OOB enabled (CONFIG_AF_UNIX_OOB or implicit support)
# - Exploitation summary:
# * send/recv MSG_OOB pattern leaves zero-length SKBs in the receive queue
# * manage_oob() skips cleanup, freeing the OOB SKB while u->oob_skb still points to it
# * Subsequent recv(MSG_OOB) dereferences the dangling pointer → kernel UAF → LPE
# - Mitigations:
# * Update to a kernel that includes commit 32ca245464e1479bfea8592b9db227fdc1641705 (or newer)
# * Disable CONFIG_AF_UNIX_OOB or block MSG_OOB in sandboxed processes
# * Backport vendor fixes or follow Chrome's MSG_OOB filtering approach
# License: GNU GPL
# Version: 1.0
# Functions Used: print_2title, print_info
# Global Variables: $MACPEAS, $SED_RED_YELLOW, $SED_GREEN, $E
# Initial Functions:
# Generated Global Variables: $cve38236_kernel_release, $cve38236_kernel_version, $cve38236_oob_line, $cve38236_unix_line, $cve38236_oob_status, $CVE38236_CONFIG_SOURCE, $cve38236_conf_file, $cve38236_config_key, $cve38236_release, $cve38236_cfg, $cve38236_config_line
# Fat linpeas: 0
# Small linpeas: 1
_cve38236_version_to_number() {
if [ -z "$1" ]; then
printf '0\n'
return
fi
echo "$1" | awk -F. '{
major=$1+0
if (NF>=2) minor=$2+0; else minor=0
if (NF>=3) patch=$3+0; else patch=0
printf "%d\n", (major*1000000)+(minor*1000)+patch
}'
}
_cve38236_version_ge() {
local v1 v2
v1=$(_cve38236_version_to_number "$1")
v2=$(_cve38236_version_to_number "$2")
[ "$v1" -ge "$v2" ]
}
_cve38236_cat_config_file() {
local cve38236_conf_file="$1"
if [ -z "$cve38236_conf_file" ] || ! [ -r "$cve38236_conf_file" ]; then
return 1
fi
if printf '%s' "$cve38236_conf_file" | grep -q '\\.gz$'; then
if command -v zcat >/dev/null 2>&1; then
zcat "$cve38236_conf_file" 2>/dev/null
elif command -v gzip >/dev/null 2>&1; then
gzip -dc "$cve38236_conf_file" 2>/dev/null
else
cat "$cve38236_conf_file" 2>/dev/null
fi
else
cat "$cve38236_conf_file" 2>/dev/null
fi
}
_cve38236_read_config_line() {
local cve38236_config_key="$1"
local cve38236_release cve38236_config_line cve38236_cfg
cve38236_release="$(uname -r 2>/dev/null)"
for cve38236_cfg in /proc/config.gz \
"/boot/config-${cve38236_release}" \
"/usr/lib/modules/${cve38236_release}/build/.config" \
"/lib/modules/${cve38236_release}/build/.config"; do
if [ -r "$cve38236_cfg" ]; then
cve38236_config_line=$(_cve38236_cat_config_file "$cve38236_cfg" | grep -E "^(${cve38236_config_key}=|# ${cve38236_config_key} is not set)" | head -n1)
if [ -n "$cve38236_config_line" ]; then
CVE38236_CONFIG_SOURCE="$cve38236_cfg"
printf '%s\n' "$cve38236_config_line"
return 0
fi
fi
done
return 1
}
if [ ! "$MACPEAS" ]; then
cve38236_kernel_release="$(uname -r 2>/dev/null)"
cve38236_kernel_version="$(printf '%s' "$cve38236_kernel_release" | sed 's/[^0-9.].*//')"
if [ -n "$cve38236_kernel_version" ] && _cve38236_version_ge "$cve38236_kernel_version" "6.9.0"; then
print_2title "CVE-2025-38236 - AF_UNIX MSG_OOB UAF"
cve38236_oob_line=$(_cve38236_read_config_line "CONFIG_AF_UNIX_OOB")
cve38236_oob_status="unknown"
if printf '%s' "$cve38236_oob_line" | grep -q '=y\|=m'; then
cve38236_oob_status="enabled"
elif printf '%s' "$cve38236_oob_line" | grep -q 'not set'; then
cve38236_oob_status="disabled"
fi
if [ "$cve38236_oob_status" = "unknown" ]; then
cve38236_unix_line=$(_cve38236_read_config_line "CONFIG_UNIX")
if printf '%s' "$cve38236_unix_line" | grep -q 'not set'; then
cve38236_oob_status="disabled"
elif printf '%s' "$cve38236_unix_line" | grep -q '=y\|=m'; then
cve38236_oob_status="enabled"
fi
fi
if [ "$cve38236_oob_status" = "disabled" ]; then
printf 'Kernel %s >= 6.9 but MSG_OOB support is disabled (%s).\n' "$cve38236_kernel_release" "${cve38236_oob_line:-CONFIG_AF_UNIX disabled}" | sed -${E} "s,.*,${SED_GREEN},"
print_info "CVE-2025-38236 requires AF_UNIX MSG_OOB; disabling CONFIG_AF_UNIX_OOB/CONFIG_UNIX mitigates it."
else
printf 'Kernel %s (parsed %s) may be vulnerable to CVE-2025-38236 - AF_UNIX MSG_OOB UAF.\n' "$cve38236_kernel_release" "$cve38236_kernel_version" | sed -${E} "s,.*,${SED_RED_YELLOW},"
[ -n "$cve38236_oob_line" ] && print_info "Config hint: $cve38236_oob_line"
if [ "$cve38236_oob_status" = "unknown" ]; then
print_info "Could not read CONFIG_AF_UNIX_OOB directly; AF_UNIX appears enabled, so assume MSG_OOB reachable."
fi
print_info "Exploit chain: crafted MSG_OOB send/recv frees the OOB SKB while u->oob_skb still points to it, enabling kernel UAF → arbitrary read/write primitives (Project Zero 2025/08)."
print_info "Mitigations: update to a kernel containing commit 32ca245464e1479bfea8592b9db227fdc1641705, disable CONFIG_AF_UNIX_OOB, or filter MSG_OOB in sandbox policies."
print_info "Heuristic detection: based solely on uname -r and kernel config; vendor kernels with backported fixes should be verified manually."
fi
echo ""
fi
fi

View File

@@ -0,0 +1,183 @@
# Title: System Information - CVE_2025_38352
# ID: SY_CVE_2025_38352
# Author: HT Bot
# Last Update: 22-12-2025
# Description: Detect Linux kernels that may still be vulnerable to CVE-2025-38352 (race-condition UAF in POSIX CPU timers)
# - Highlights kernels built without CONFIG_POSIX_CPU_TIMERS_TASK_WORK
# - Flags 6.12.x builds older than the fix commit f90fff1e152dedf52b932240ebbd670d83330eca (first shipped in 6.12.34)
# - Provides quick risk scoring so operators can decide whether to attempt the publicly available PoC
# - Core requirements for exploitation:
# * CONFIG_POSIX_CPU_TIMERS_TASK_WORK disabled (common on 32-bit Android / custom kernels)
# * Lack of the upstream exit_state guard in run_posix_cpu_timers()
# License: GNU GPL
# Version: 1.0
# Functions Used: echo_not_found, print_2title, print_list
# Global Variables: $E, $SED_GREEN, $SED_RED, $SED_RED_YELLOW, $SED_YELLOW
# Initial Functions:
# Generated Global Variables: $cve38352_kernel_release, $cve38352_kernel_version_cmp, $cve38352_symbol, $cve38352_task_work_state, $cve38352_config_status, $cve38352_config_source, $cve38352_config_candidates, $cve38352_cfg, $cve38352_line, $cve38352_patch_state, $cve38352_patch_label, $cve38352_fix_tag, $cve38352_last_vuln_tag, $cve38352_risk_msg, $cve38352_risk_color, $cve38352_task_line, $cve38352_patch_line, $cve38352_risk_line
# Fat linpeas: 0
# Small linpeas: 1
cve38352_version_lt(){
awk -v v1="$1" -v v2="$2" '
function cleannum(val) {
gsub(/[^0-9].*/, "", val)
if (val == "") {
val = 0
}
return val + 0
}
BEGIN {
n = split(v1, a, ".")
m = split(v2, b, ".")
max = (n > m ? n : m)
for (i = 1; i <= max; i++) {
av = (i <= n ? cleannum(a[i]) : 0)
bv = (i <= m ? cleannum(b[i]) : 0)
if (av < bv) {
exit 0
}
if (av > bv) {
exit 1
}
}
exit 1
}'
}
cve38352_sanitize_version(){
printf "%s" "$1" | tr '-' '.' | sed 's/[^0-9.].*$//' | sed 's/\.\./\./g' | sed 's/^\.//' | sed 's/\.$//'
}
print_2title "CVE-2025-38352 - POSIX CPU timers race"
cve38352_kernel_release=$(uname -r 2>/dev/null)
if [ -z "$cve38352_kernel_release" ]; then
echo_not_found "uname -r"
echo ""
else
cve38352_kernel_version_cmp=$(cve38352_sanitize_version "$cve38352_kernel_release")
if [ -z "$cve38352_kernel_version_cmp" ]; then
cve38352_kernel_version_cmp="unknown"
fi
cve38352_symbol="CONFIG_POSIX_CPU_TIMERS_TASK_WORK"
cve38352_task_work_state="unknown"
cve38352_config_status="Unknown ($cve38352_symbol not found)"
cve38352_config_source=""
cve38352_config_candidates="/boot/config-$cve38352_kernel_release /proc/config.gz /lib/modules/$cve38352_kernel_release/build/.config /usr/lib/modules/$cve38352_kernel_release/build/.config /usr/src/linux/.config"
for cve38352_cfg in $cve38352_config_candidates; do
[ -r "$cve38352_cfg" ] || continue
if printf "%s" "$cve38352_cfg" | grep -q '\\.gz$'; then
cve38352_line=$(gzip -dc "$cve38352_cfg" 2>/dev/null | grep -E "^(# )?$cve38352_symbol" | head -n1)
else
cve38352_line=$(grep -E "^(# )?$cve38352_symbol" "$cve38352_cfg" 2>/dev/null | head -n1)
fi
[ -z "$cve38352_line" ] && continue
cve38352_config_source="$cve38352_cfg"
case "$cve38352_line" in
"$cve38352_symbol=y")
cve38352_task_work_state="enabled"
cve38352_config_status="Enabled (y)"
;;
"$cve38352_symbol=m")
cve38352_task_work_state="enabled"
cve38352_config_status="Built as module (m)"
;;
"$cve38352_symbol=n")
cve38352_task_work_state="disabled"
cve38352_config_status="Disabled (n)"
;;
"# $cve38352_symbol is not set")
cve38352_task_work_state="disabled"
cve38352_config_status="Not set"
;;
*)
cve38352_config_status="Found: $cve38352_line"
;;
esac
break
done
cve38352_patch_state="unknown_branch"
cve38352_patch_label="Unable to determine kernel train"
cve38352_fix_tag="6.12.34"
cve38352_last_vuln_tag="6.12.33"
case "$cve38352_kernel_version_cmp" in
6.12|6.12.*)
if cve38352_version_lt "$cve38352_kernel_version_cmp" "$cve38352_fix_tag"; then
cve38352_patch_state="pre_fix"
cve38352_patch_label="6.12.x build < $cve38352_fix_tag (last known vulnerable LTS: $cve38352_last_vuln_tag)"
else
cve38352_patch_state="post_fix"
cve38352_patch_label="6.12.x build >= $cve38352_fix_tag (should include fix f90fff1e152d)"
fi
;;
unknown)
cve38352_patch_label="Kernel version string could not be parsed"
;;
*)
cve38352_patch_label="Kernel train $cve38352_kernel_version_cmp (verify commit f90fff1e152dedf52b932240ebbd670d83330eca manually)"
;;
esac
cve38352_risk_msg="Unknown - missing configuration data"
cve38352_risk_color=""
if [ "$cve38352_task_work_state" = "enabled" ]; then
cve38352_risk_msg="Low - CONFIG_POSIX_CPU_TIMERS_TASK_WORK is enabled"
cve38352_risk_color="green"
elif [ "$cve38352_task_work_state" = "disabled" ]; then
if [ "$cve38352_patch_state" = "pre_fix" ]; then
cve38352_risk_msg="High - task_work disabled & kernel predates fix f90fff1e152d"
cve38352_risk_color="red"
else
cve38352_risk_msg="Review - task_work disabled, ensure fix f90fff1e152d is backported"
cve38352_risk_color="yellow"
fi
fi
print_list "Kernel release ............... $cve38352_kernel_release\n"
print_list "Comparable version ........... $cve38352_kernel_version_cmp\n"
cve38352_task_line="Task_work config ............. $cve38352_config_status"
if [ -n "$cve38352_config_source" ]; then
cve38352_task_line="$cve38352_task_line (from $cve38352_config_source)"
fi
cve38352_task_line="$cve38352_task_line\n"
if [ "$cve38352_task_work_state" = "disabled" ]; then
print_list "$cve38352_task_line" | sed -${E} "s,.*,${SED_RED},"
elif [ "$cve38352_task_work_state" = "enabled" ]; then
print_list "$cve38352_task_line" | sed -${E} "s,.*,${SED_GREEN},"
else
print_list "$cve38352_task_line"
fi
cve38352_patch_line="Patch status ................. $cve38352_patch_label\n"
if [ "$cve38352_patch_state" = "pre_fix" ]; then
print_list "$cve38352_patch_line" | sed -${E} "s,.*,${SED_RED_YELLOW},"
elif [ "$cve38352_patch_state" = "post_fix" ]; then
print_list "$cve38352_patch_line" | sed -${E} "s,.*,${SED_GREEN},"
else
print_list "$cve38352_patch_line" | sed -${E} "s,.*,${SED_YELLOW},"
fi
cve38352_risk_line="CVE-2025-38352 risk .......... $cve38352_risk_msg\n"
case "$cve38352_risk_color" in
red)
print_list "$cve38352_risk_line" | sed -${E} "s,.*,${SED_RED_YELLOW},"
;;
green)
print_list "$cve38352_risk_line" | sed -${E} "s,.*,${SED_GREEN},"
;;
yellow)
print_list "$cve38352_risk_line" | sed -${E} "s,.*,${SED_YELLOW},"
;;
*)
print_list "$cve38352_risk_line"
;;
esac
echo ""
fi

View File

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

View File

@@ -77,19 +77,12 @@ 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?!?!?!
@@ -153,6 +146,7 @@ Once you have installed and activated it you need to:
- [x] Applocker Configuration & bypass suggestions
- [x] Printers
- [x] Named Pipes
- [x] Named Pipe ACL abuse candidates
- [x] AMSI Providers
- [x] SysMon
- [x] .NET Versions

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,7 +1,9 @@
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
@@ -34,6 +36,9 @@ namespace winPEAS.Checks
PrintModifiableServices,
PrintWritableRegServices,
PrintPathDllHijacking,
PrintOemPrivilegedUtilities,
PrintLegacySignedKernelDrivers,
PrintKernelQuickIndicators,
}.ForEach(action => CheckRunner.Run(action, isDebug));
}
@@ -206,5 +211,190 @@ 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

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using winPEAS.Helpers;
using winPEAS.Info.ApplicationInfo;
namespace winPEAS.Checks
{
internal class SoapClientInfo : ISystemCheck
{
public void PrintInfo(bool isDebug)
{
Beaprint.GreatPrint(".NET SOAP Client Proxies (SOAPwn)");
CheckRunner.Run(PrintSoapClientFindings, isDebug);
}
private static void PrintSoapClientFindings()
{
try
{
Beaprint.MainPrint("Potential SOAPwn / HttpWebClientProtocol abuse surfaces");
Beaprint.LinkPrint(
"https://labs.watchtowr.com/soapwn-pwning-net-framework-applications-through-http-client-proxies-and-wsdl/",
"Look for .NET services that let attackers control SoapHttpClientProtocol URLs or WSDL imports to coerce NTLM or drop files.");
List<SoapClientProxyFinding> findings = SoapClientProxyAnalyzer.CollectFindings();
if (findings.Count == 0)
{
Beaprint.NotFoundPrint();
return;
}
foreach (SoapClientProxyFinding finding in findings)
{
string severity = finding.BinaryIndicators.Contains("ServiceDescriptionImporter")
? "Dynamic WSDL import"
: "SOAP proxy usage";
Beaprint.BadPrint($" [{severity}] {finding.BinaryPath}");
foreach (SoapClientProxyInstance instance in finding.Instances)
{
string instanceInfo = $" -> {instance.SourceType}: {instance.Name}";
if (!string.IsNullOrEmpty(instance.Account))
{
instanceInfo += $" ({instance.Account})";
}
if (!string.IsNullOrEmpty(instance.Extra))
{
instanceInfo += $" | {instance.Extra}";
}
Beaprint.GrayPrint(instanceInfo);
}
if (finding.BinaryIndicators.Count > 0)
{
Beaprint.BadPrint(" Binary indicators: " + string.Join(", ", finding.BinaryIndicators));
}
if (finding.ConfigIndicators.Count > 0)
{
string configLabel = string.IsNullOrEmpty(finding.ConfigPath)
? "Config indicators"
: $"Config indicators ({finding.ConfigPath})";
Beaprint.BadPrint(" " + configLabel + ": " + string.Join(", ", finding.ConfigIndicators));
}
if (finding.BinaryScanFailed)
{
Beaprint.GrayPrint(" (Binary scan skipped due to access/size limits)");
}
if (finding.ConfigScanFailed)
{
Beaprint.GrayPrint(" (Unable to read config file)");
}
Beaprint.PrintLineSeparator();
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
}
}
}

View File

@@ -82,6 +82,7 @@ namespace winPEAS.Checks
PrintKrbRelayUp,
PrintInsideContainer,
PrintAlwaysInstallElevated,
PrintObjectManagerRaceAmplification,
PrintLSAInfo,
PrintNtlmSettings,
PrintLocalGroupPolicy,
@@ -89,6 +90,7 @@ namespace winPEAS.Checks
AppLockerHelper.PrintAppLockerPolicy,
PrintPrintersWMIInfo,
PrintNamedPipes,
PrintNamedPipeAbuseCandidates,
PrintAMSIProviders,
PrintSysmon,
PrintDotNetVersions
@@ -733,6 +735,31 @@ namespace winPEAS.Checks
}
}
static void PrintObjectManagerRaceAmplification()
{
try
{
Beaprint.MainPrint("Object Manager race-window amplification primitives");
Beaprint.LinkPrint("https://projectzero.google/2025/12/windows-exploitation-techniques.html", "Project Zero write-up:");
if (ObjectManagerHelper.TryCreateSessionEvent(out var objectName, out var error))
{
Beaprint.BadPrint($" Created a test named event ({objectName}) under \\BaseNamedObjects.");
Beaprint.InfoPrint(" -> Low-privileged users can slow NtOpen*/NtCreate* lookups using ~32k-character names or ~16k-level directory chains.");
Beaprint.InfoPrint(" -> Point attacker-controlled symbolic links to the slow path to stretch kernel race windows.");
Beaprint.InfoPrint(" -> Use this whenever a bug follows check -> NtOpenX -> privileged action patterns.");
}
else
{
Beaprint.InfoPrint($" Could not create a test event under \\BaseNamedObjects ({error}). The namespace might be locked down.");
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
}
private static void PrintNtlmSettings()
{
Beaprint.MainPrint($"Enumerating NTLM Settings");
@@ -857,6 +884,48 @@ namespace winPEAS.Checks
}
}
private static void PrintNamedPipeAbuseCandidates()
{
Beaprint.MainPrint("Named Pipes with Low-Priv Write Access to Privileged Servers");
try
{
var candidates = NamedPipeSecurityAnalyzer.GetNamedPipeAbuseCandidates().ToList();
if (!candidates.Any())
{
Beaprint.NoColorPrint(" No risky named pipe ACLs were found.\n");
return;
}
foreach (var candidate in candidates)
{
var aclSummary = candidate.LowPrivilegeAces.Any()
? string.Join("; ", candidate.LowPrivilegeAces.Select(ace =>
$"{ace.Principal} [{ace.RightsDescription}]").Where(s => !string.IsNullOrEmpty(s)))
: "Unknown";
var serverSummary = candidate.Processes.Any()
? string.Join("; ", candidate.Processes.Select(proc =>
$"{proc.ProcessName} (PID {proc.Pid}, {proc.UserName ?? proc.UserSid})"))
: "No privileged handles observed (service idle or access denied)";
var color = candidate.HasPrivilegedServer ? Beaprint.ansi_color_bad : Beaprint.ansi_color_yellow;
Beaprint.ColorPrint($" \\\\.\\pipe\\{candidate.Name}", color);
Beaprint.NoColorPrint($" Low-priv ACLs : {aclSummary}");
Beaprint.NoColorPrint($" Observed owners: {serverSummary}");
Beaprint.NoColorPrint($" SDDL : {candidate.Sddl}");
Beaprint.PrintLineSeparator();
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
}
private void PrintAMSIProviders()
{
Beaprint.MainPrint("Enumerating AMSI registered providers");

View File

@@ -156,15 +156,63 @@ namespace winPEAS.Checks
try
{
Beaprint.MainPrint("RDP Sessions");
Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/credentials-mgmt/rdp-sessions", "Disconnected high-privilege RDP sessions keep reusable tokens inside LSASS.");
List<Dictionary<string, string>> rdp_sessions = UserInfoHelper.GetRDPSessions();
if (rdp_sessions.Count > 0)
{
string format = " {0,-10}{1,-15}{2,-15}{3,-25}{4,-10}{5}";
string header = string.Format(format, "SessID", "pSessionName", "pUserName", "pDomainName", "State", "SourceIP");
string format = " {0,-8}{1,-15}{2,-20}{3,-22}{4,-15}{5,-18}{6,-10}";
string header = string.Format(format, "SessID", "Session", "User", "Domain", "State", "SourceIP", "HighPriv");
Beaprint.GrayPrint(header);
var colors = ColorsU();
List<Dictionary<string, string>> flaggedSessions = new List<Dictionary<string, string>>();
foreach (Dictionary<string, string> rdpSes in rdp_sessions)
{
Beaprint.AnsiPrint(string.Format(format, rdpSes["SessionID"], rdpSes["pSessionName"], rdpSes["pUserName"], rdpSes["pDomainName"], rdpSes["State"], rdpSes["SourceIP"]), ColorsU());
rdpSes.TryGetValue("SessionID", out string sessionId);
rdpSes.TryGetValue("pSessionName", out string sessionName);
rdpSes.TryGetValue("pUserName", out string userName);
rdpSes.TryGetValue("pDomainName", out string domainName);
rdpSes.TryGetValue("State", out string state);
rdpSes.TryGetValue("SourceIP", out string sourceIp);
sessionId = sessionId ?? string.Empty;
sessionName = sessionName ?? string.Empty;
userName = userName ?? string.Empty;
domainName = domainName ?? string.Empty;
state = state ?? string.Empty;
sourceIp = sourceIp ?? string.Empty;
bool isHighPriv = UserInfoHelper.IsHighPrivilegeAccount(userName, domainName);
string highPrivLabel = isHighPriv ? "Yes" : "No";
rdpSes["HighPriv"] = highPrivLabel;
if (isHighPriv && string.Equals(state, "Disconnected", StringComparison.OrdinalIgnoreCase))
{
flaggedSessions.Add(rdpSes);
}
Beaprint.AnsiPrint(string.Format(format, sessionId, sessionName, userName, domainName, state, sourceIp, highPrivLabel), colors);
}
if (flaggedSessions.Count > 0)
{
Beaprint.BadPrint(" [!] Disconnected high-privilege RDP sessions detected. Their credentials/tokens stay in LSASS until the user signs out.");
foreach (Dictionary<string, string> session in flaggedSessions)
{
session.TryGetValue("pDomainName", out string flaggedDomain);
session.TryGetValue("pUserName", out string flaggedUser);
session.TryGetValue("SessionID", out string flaggedSessionId);
session.TryGetValue("SourceIP", out string flaggedIp);
flaggedDomain = flaggedDomain ?? string.Empty;
flaggedUser = flaggedUser ?? string.Empty;
flaggedSessionId = flaggedSessionId ?? string.Empty;
flaggedIp = flaggedIp ?? string.Empty;
string userDisplay = string.Format("{0}\\{1}", flaggedDomain, flaggedUser).Trim('\\');
string source = string.IsNullOrEmpty(flaggedIp) ? "local" : flaggedIp;
Beaprint.BadPrint(string.Format(" -> Session {0} ({1}) from {2}", flaggedSessionId, userDisplay, source));
}
Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/credentials-mgmt/rdp-sessions", "Dump LSASS / steal tokens (e.g., comsvcs.dll, LsaLogonSessions, custom SSPs) to reuse those privileges.");
}
}
else

View File

@@ -24,36 +24,51 @@ namespace winPEAS.Helpers
////////////////////////////////////
/////// MISC - Files & Paths ///////
////////////////////////////////////
public static bool CheckIfDotNet(string path)
public static bool CheckIfDotNet(string path, bool ignoreCompanyName = false)
{
bool isDotNet = false;
FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(path);
string companyName = myFileVersionInfo.CompanyName;
if ((string.IsNullOrEmpty(companyName)) ||
(!Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase)))
string companyName = string.Empty;
try
{
try
FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(path);
companyName = myFileVersionInfo.CompanyName;
}
catch
{
// Unable to read version information, continue with assembly inspection
}
bool shouldInspectAssembly = ignoreCompanyName ||
(string.IsNullOrEmpty(companyName)) ||
(!Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase));
if (!shouldInspectAssembly)
{
return false;
}
try
{
AssemblyName.GetAssemblyName(path);
isDotNet = true;
}
catch (System.IO.FileNotFoundException)
{
// System.Console.WriteLine("The file cannot be found.");
}
catch (System.BadImageFormatException exception)
{
if (Regex.IsMatch(exception.Message,
".*This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.*",
RegexOptions.IgnoreCase))
{
AssemblyName myAssemblyName = AssemblyName.GetAssemblyName(path);
isDotNet = true;
}
catch (System.IO.FileNotFoundException)
{
// System.Console.WriteLine("The file cannot be found.");
}
catch (System.BadImageFormatException exception)
{
if (Regex.IsMatch(exception.Message,
".*This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.*",
RegexOptions.IgnoreCase))
{
isDotNet = true;
}
}
catch
{
// System.Console.WriteLine("The assembly has already been loaded.");
}
}
catch
{
// System.Console.WriteLine("The assembly has already been loaded.");
}
return isDotNet;

View File

@@ -0,0 +1,34 @@
using System;
using System.Diagnostics;
using System.Threading;
namespace winPEAS.Helpers
{
internal static class ObjectManagerHelper
{
public static bool TryCreateSessionEvent(out string objectName, out string error)
{
objectName = $"PEAS_OMNS_{Process.GetCurrentProcess().Id}_{Guid.NewGuid():N}";
error = string.Empty;
try
{
using (var handle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, objectName, out var createdNew))
{
if (!createdNew)
{
error = "A test event with the generated name already existed.";
return false;
}
}
return true;
}
catch (Exception ex)
{
error = ex.Message;
return false;
}
}
}
}

View File

@@ -0,0 +1,369 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using System.Text;
using winPEAS.Helpers;
using winPEAS.Info.ProcessInfo;
namespace winPEAS.Info.ApplicationInfo
{
internal class SoapClientProxyInstance
{
public string SourceType { get; set; }
public string Name { get; set; }
public string Account { get; set; }
public string Extra { get; set; }
}
internal class SoapClientProxyFinding
{
public string BinaryPath { get; set; }
public List<SoapClientProxyInstance> Instances { get; } = new List<SoapClientProxyInstance>();
public HashSet<string> BinaryIndicators { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public HashSet<string> ConfigIndicators { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public string ConfigPath { get; set; }
public bool BinaryScanFailed { get; set; }
public bool ConfigScanFailed { get; set; }
}
internal static class SoapClientProxyAnalyzer
{
private class SoapClientProxyCandidate
{
public string BinaryPath { get; set; }
public string SourceType { get; set; }
public string Name { get; set; }
public string Account { get; set; }
public string Extra { get; set; }
}
private static readonly string[] BinaryIndicatorStrings = new[]
{
"SoapHttpClientProtocol",
"HttpWebClientProtocol",
"DiscoveryClientProtocol",
"HttpSimpleClientProtocol",
"HttpGetClientProtocol",
"HttpPostClientProtocol",
"ServiceDescriptionImporter",
"System.Web.Services.Description.ServiceDescriptionImporter",
};
private static readonly Dictionary<string, string> ConfigIndicatorMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "soap:address", "soap:address element present" },
{ "soap12:address", "soap12:address element present" },
{ "?wsdl", "?wsdl reference" },
{ "<wsdl:", "WSDL schema embedded in config" },
{ "servicedescriptionimporter", "ServiceDescriptionImporter referenced in config" },
{ "system.web.services.description", "System.Web.Services.Description namespace referenced" },
{ "new-webserviceproxy", "PowerShell New-WebServiceProxy referenced" },
{ "file://", "file:// scheme referenced" },
};
private const long MaxBinaryScanSize = 200 * 1024 * 1024; // 200MB
private static readonly object DotNetCacheLock = new object();
private static readonly Dictionary<string, bool> DotNetCache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
public static List<SoapClientProxyFinding> CollectFindings()
{
var findings = new Dictionary<string, SoapClientProxyFinding>(StringComparer.OrdinalIgnoreCase);
foreach (var candidate in EnumerateServiceCandidates().Concat(EnumerateProcessCandidates()))
{
if (string.IsNullOrEmpty(candidate.BinaryPath) || !File.Exists(candidate.BinaryPath))
{
continue;
}
if (!findings.TryGetValue(candidate.BinaryPath, out var finding))
{
finding = new SoapClientProxyFinding
{
BinaryPath = candidate.BinaryPath,
};
findings.Add(candidate.BinaryPath, finding);
}
finding.Instances.Add(new SoapClientProxyInstance
{
SourceType = candidate.SourceType,
Name = candidate.Name,
Account = string.IsNullOrEmpty(candidate.Account) ? "Unknown" : candidate.Account,
Extra = candidate.Extra ?? string.Empty,
});
}
foreach (var finding in findings.Values)
{
ScanBinaryIndicators(finding);
ScanConfigIndicators(finding);
}
return findings.Values
.Where(f => f.BinaryIndicators.Count > 0 || f.ConfigIndicators.Count > 0)
.OrderByDescending(f => f.BinaryIndicators.Contains("ServiceDescriptionImporter"))
.ThenBy(f => f.BinaryPath, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static IEnumerable<SoapClientProxyCandidate> EnumerateServiceCandidates()
{
var results = new List<SoapClientProxyCandidate>();
try
{
using (var searcher = new ManagementObjectSearcher(@"root\\cimv2", "SELECT Name, DisplayName, PathName, StartName FROM Win32_Service"))
using (var services = searcher.Get())
{
foreach (ManagementObject service in services)
{
string pathName = service["PathName"]?.ToString();
string binaryPath = MyUtils.GetExecutableFromPath(pathName ?? string.Empty);
if (string.IsNullOrEmpty(binaryPath) || !File.Exists(binaryPath))
continue;
if (!IsDotNetBinary(binaryPath))
continue;
results.Add(new SoapClientProxyCandidate
{
BinaryPath = binaryPath,
SourceType = "Service",
Name = service["Name"]?.ToString() ?? string.Empty,
Account = service["StartName"]?.ToString() ?? string.Empty,
Extra = service["DisplayName"]?.ToString() ?? string.Empty,
});
}
}
}
catch (Exception ex)
{
Beaprint.GrayPrint("Error while enumerating services for SOAP client analysis: " + ex.Message);
}
return results;
}
private static IEnumerable<SoapClientProxyCandidate> EnumerateProcessCandidates()
{
var results = new List<SoapClientProxyCandidate>();
try
{
List<Dictionary<string, string>> processes = ProcessesInfo.GetProcInfo();
foreach (var proc in processes)
{
string path = proc.ContainsKey("ExecutablePath") ? proc["ExecutablePath"] : string.Empty;
if (string.IsNullOrEmpty(path) || !File.Exists(path))
continue;
if (!IsDotNetBinary(path))
continue;
string owner = proc.ContainsKey("Owner") ? proc["Owner"] : string.Empty;
if (!IsInterestingProcessOwner(owner))
continue;
results.Add(new SoapClientProxyCandidate
{
BinaryPath = path,
SourceType = "Process",
Name = proc.ContainsKey("Name") ? proc["Name"] : string.Empty,
Account = owner,
Extra = proc.ContainsKey("ProcessID") ? $"PID {proc["ProcessID"]}" : string.Empty,
});
}
}
catch (Exception ex)
{
Beaprint.GrayPrint("Error while enumerating processes for SOAP client analysis: " + ex.Message);
}
return results;
}
private static bool IsInterestingProcessOwner(string owner)
{
if (string.IsNullOrEmpty(owner))
return true;
string normalizedOwner = owner;
if (owner.Contains("\\"))
{
normalizedOwner = owner.Split('\\').Last();
}
return !normalizedOwner.Equals(Environment.UserName, StringComparison.OrdinalIgnoreCase);
}
private static bool IsDotNetBinary(string path)
{
lock (DotNetCacheLock)
{
if (DotNetCache.TryGetValue(path, out bool cached))
{
return cached;
}
bool result = false;
try
{
result = MyUtils.CheckIfDotNet(path, true);
}
catch
{
}
DotNetCache[path] = result;
return result;
}
}
private static void ScanBinaryIndicators(SoapClientProxyFinding finding)
{
try
{
FileInfo fi = new FileInfo(finding.BinaryPath);
if (!fi.Exists || fi.Length == 0)
return;
if (fi.Length > MaxBinaryScanSize)
{
finding.BinaryScanFailed = true;
return;
}
foreach (var indicator in BinaryIndicatorStrings)
{
if (FileContainsString(finding.BinaryPath, indicator))
{
finding.BinaryIndicators.Add(indicator);
}
}
}
catch
{
finding.BinaryScanFailed = true;
}
}
private static void ScanConfigIndicators(SoapClientProxyFinding finding)
{
string configPath = GetConfigPath(finding.BinaryPath);
if (!string.IsNullOrEmpty(configPath) && File.Exists(configPath))
{
finding.ConfigPath = configPath;
try
{
string content = File.ReadAllText(configPath);
foreach (var kvp in ConfigIndicatorMap)
{
if (content.IndexOf(kvp.Key, StringComparison.OrdinalIgnoreCase) >= 0)
{
finding.ConfigIndicators.Add(kvp.Value);
}
}
}
catch
{
finding.ConfigScanFailed = true;
}
}
string directory = Path.GetDirectoryName(finding.BinaryPath);
if (!string.IsNullOrEmpty(directory))
{
try
{
var wsdlFiles = Directory.GetFiles(directory, "*.wsdl", SearchOption.TopDirectoryOnly);
if (wsdlFiles.Length > 0)
{
finding.ConfigIndicators.Add($"Found {wsdlFiles.Length} WSDL file(s) next to binary");
}
}
catch
{
// ignore
}
}
}
private static string GetConfigPath(string binaryPath)
{
if (string.IsNullOrEmpty(binaryPath))
return string.Empty;
string candidate = binaryPath + ".config";
return File.Exists(candidate) ? candidate : string.Empty;
}
private static bool FileContainsString(string path, string value)
{
const int bufferSize = 64 * 1024;
byte[] pattern = Encoding.UTF8.GetBytes(value);
if (pattern.Length == 0)
return false;
try
{
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
{
byte[] buffer = new byte[bufferSize + pattern.Length];
int bufferLen = 0;
int bytesRead;
while ((bytesRead = fs.Read(buffer, bufferLen, bufferSize)) > 0)
{
int total = bufferLen + bytesRead;
if (IndexOf(buffer, total, pattern) >= 0)
{
return true;
}
if (pattern.Length > 1)
{
bufferLen = Math.Min(pattern.Length - 1, total);
Buffer.BlockCopy(buffer, total - bufferLen, buffer, 0, bufferLen);
}
else
{
bufferLen = 0;
}
}
}
}
catch
{
return false;
}
return false;
}
private static int IndexOf(byte[] buffer, int bufferLength, byte[] pattern)
{
int limit = bufferLength - pattern.Length;
if (limit < 0)
return -1;
for (int i = 0; i <= limit; i++)
{
bool match = true;
for (int j = 0; j < pattern.Length; j++)
{
if (buffer[i + j] != pattern[j])
{
match = false;
break;
}
}
if (match)
return i;
}
return -1;
}
}
}

View File

@@ -0,0 +1,458 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
using System.ServiceProcess;
using winPEAS.Helpers;
namespace winPEAS.Info.ServicesInfo
{
internal static class OemSoftwareHelper
{
internal static List<OemSoftwareFinding> GetPotentiallyVulnerableComponents(Dictionary<string, string> currentUserSids)
{
var findings = new List<OemSoftwareFinding>();
var services = GetServiceSnapshot();
var processes = GetProcessSnapshot();
foreach (var definition in GetDefinitions())
{
var finding = new OemSoftwareFinding
{
Name = definition.Name,
Description = definition.Description,
Cves = definition.Cves,
};
AppendServiceEvidence(definition, services, finding);
AppendProcessEvidence(definition, processes, finding);
AppendPathEvidence(definition, currentUserSids, finding);
AppendPipeEvidence(definition, finding);
if (finding.Evidence.Count > 0)
{
findings.Add(finding);
}
}
return findings;
}
private static void AppendServiceEvidence(OemComponentDefinition definition, List<ServiceSnapshot> services, OemSoftwareFinding finding)
{
if (definition.ServiceHints == null || definition.ServiceHints.Length == 0)
{
return;
}
foreach (var serviceHint in definition.ServiceHints)
{
foreach (var service in services)
{
if (ContainsIgnoreCase(service.Name, serviceHint) ||
ContainsIgnoreCase(service.DisplayName, serviceHint))
{
finding.Evidence.Add(new OemEvidence
{
EvidenceType = "service",
Highlight = true,
Message = $"Service '{service.Name}' (Display: {service.DisplayName}) matches indicator '{serviceHint}'"
});
}
}
}
}
private static void AppendProcessEvidence(OemComponentDefinition definition, List<ProcessSnapshot> processes, OemSoftwareFinding finding)
{
if (definition.ProcessHints == null || definition.ProcessHints.Length == 0)
{
return;
}
foreach (var processHint in definition.ProcessHints)
{
foreach (var process in processes)
{
bool matchesName = ContainsIgnoreCase(process.Name, processHint);
bool matchesPath = ContainsIgnoreCase(process.FullPath, processHint);
if (matchesName || matchesPath)
{
var location = string.IsNullOrWhiteSpace(process.FullPath) ? "Unknown" : process.FullPath;
finding.Evidence.Add(new OemEvidence
{
EvidenceType = "process",
Highlight = true,
Message = $"Process '{process.Name}' (Path: {location}) matches indicator '{processHint}'"
});
}
}
}
}
private static void AppendPathEvidence(OemComponentDefinition definition, Dictionary<string, string> currentUserSids, OemSoftwareFinding finding)
{
if ((definition.DirectoryHints == null || definition.DirectoryHints.Length == 0) &&
(definition.FileHints == null || definition.FileHints.Length == 0))
{
return;
}
if (definition.DirectoryHints != null)
{
foreach (var dirHint in definition.DirectoryHints)
{
var expandedPath = ExpandPath(dirHint.Path);
if (!Directory.Exists(expandedPath))
{
continue;
}
var permissions = PermissionsHelper.GetPermissionsFolder(expandedPath, currentUserSids, PermissionType.WRITEABLE_OR_EQUIVALENT);
bool isWritable = permissions.Count > 0;
finding.Evidence.Add(new OemEvidence
{
EvidenceType = "path",
Highlight = isWritable,
Message = BuildPathMessage(expandedPath, dirHint.Description, isWritable, permissions)
});
}
}
if (definition.FileHints != null)
{
foreach (var fileHint in definition.FileHints)
{
var expandedPath = ExpandPath(fileHint);
if (!File.Exists(expandedPath))
{
continue;
}
var permissions = PermissionsHelper.GetPermissionsFile(expandedPath, currentUserSids, PermissionType.WRITEABLE_OR_EQUIVALENT);
bool isWritable = permissions.Count > 0;
finding.Evidence.Add(new OemEvidence
{
EvidenceType = "file",
Highlight = isWritable,
Message = BuildPathMessage(expandedPath, "file", isWritable, permissions)
});
}
}
}
private static void AppendPipeEvidence(OemComponentDefinition definition, OemSoftwareFinding finding)
{
if (definition.PipeHints == null)
{
return;
}
foreach (var pipeHint in definition.PipeHints)
{
try
{
var path = $"\\\\.\\pipe\\{pipeHint.Name}";
var security = File.GetAccessControl(path);
string sddl = security.GetSecurityDescriptorSddlForm(AccessControlSections.All);
string identity = string.Empty;
string rights = string.Empty;
bool worldWritable = false;
if (pipeHint.CheckWorldWritable)
{
worldWritable = HasWorldWritableAce(security, out identity, out rights);
}
string details = worldWritable
? $"Named pipe '{pipeHint.Name}' ({pipeHint.Description}) is writable by {identity} ({rights})."
: $"Named pipe '{pipeHint.Name}' ({pipeHint.Description}) present. SDDL: {sddl}";
finding.Evidence.Add(new OemEvidence
{
EvidenceType = "pipe",
Highlight = worldWritable,
Message = details
});
}
catch (FileNotFoundException)
{
// Pipe not present.
}
catch (DirectoryNotFoundException)
{
// Pipe namespace not accessible.
}
catch (Exception)
{
// Best effort: pipes might disappear during enumeration or deny access.
}
}
}
private static List<ServiceSnapshot> GetServiceSnapshot()
{
var services = new List<ServiceSnapshot>();
try
{
foreach (var service in ServiceController.GetServices())
{
services.Add(new ServiceSnapshot
{
Name = service.ServiceName ?? string.Empty,
DisplayName = service.DisplayName ?? string.Empty
});
}
}
catch (Exception)
{
// Ignore - this is best effort.
}
return services;
}
private static List<ProcessSnapshot> GetProcessSnapshot()
{
var processes = new List<ProcessSnapshot>();
try
{
foreach (var process in Process.GetProcesses())
{
string fullPath = string.Empty;
try
{
fullPath = process.MainModule?.FileName ?? string.Empty;
}
catch
{
// Access denied or 64-bit vs 32-bit mismatch.
}
processes.Add(new ProcessSnapshot
{
Name = process.ProcessName ?? string.Empty,
FullPath = fullPath ?? string.Empty
});
}
}
catch (Exception)
{
// Ignore - enumeration is best effort.
}
return processes;
}
private static string ExpandPath(string rawPath)
{
if (string.IsNullOrWhiteSpace(rawPath))
{
return string.Empty;
}
var expanded = Environment.ExpandEnvironmentVariables(rawPath);
return expanded.Trim().Trim('"');
}
private static string BuildPathMessage(string path, string description, bool isWritable, List<string> permissions)
{
string descriptor = string.IsNullOrWhiteSpace(description) ? "" : $" ({description})";
if (isWritable)
{
return $"Path '{path}'{descriptor} is writable by current user: {string.Join(", ", permissions)}";
}
return $"Path '{path}'{descriptor} detected.";
}
private static bool ContainsIgnoreCase(string value, string toFind)
{
if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(toFind))
{
return false;
}
return value.IndexOf(toFind, StringComparison.OrdinalIgnoreCase) >= 0;
}
private static bool HasWorldWritableAce(FileSecurity security, out string identity, out string rights)
{
identity = string.Empty;
rights = string.Empty;
try
{
var rules = security.GetAccessRules(true, true, typeof(SecurityIdentifier));
foreach (FileSystemAccessRule rule in rules)
{
if (rule.AccessControlType != AccessControlType.Allow)
{
continue;
}
if (rule.IdentityReference is SecurityIdentifier sid)
{
bool isWorld = sid.IsWellKnown(WellKnownSidType.WorldSid);
bool isAuthenticated = sid.IsWellKnown(WellKnownSidType.AuthenticatedUserSid);
if (!isWorld && !isAuthenticated)
{
continue;
}
const FileSystemRights interestingRights =
FileSystemRights.FullControl |
FileSystemRights.Modify |
FileSystemRights.Write |
FileSystemRights.WriteData |
FileSystemRights.CreateFiles |
FileSystemRights.ChangePermissions;
if ((rule.FileSystemRights & interestingRights) != 0)
{
identity = isWorld ? "Everyone" : "Authenticated Users";
rights = rule.FileSystemRights.ToString();
return true;
}
}
}
}
catch
{
// Ignore parsing issues.
}
return false;
}
private static IEnumerable<OemComponentDefinition> GetDefinitions()
{
return new List<OemComponentDefinition>
{
new OemComponentDefinition
{
Name = "ASUS DriverHub",
Description = "Local web API exposed by ADU.exe allowed bypassing origin/url validation and signature checks.",
Cves = new[] { "CVE-2025-3462", "CVE-2025-3463" },
ServiceHints = new[] { "asusdriverhub", "asus driverhub" },
ProcessHints = new[] { "adu", "asusdriverhub" },
DirectoryHints = new[]
{
new PathHint { Path = "%ProgramFiles%\\ASUS\\AsusDriverHub", Description = "Program Files" },
new PathHint { Path = "%ProgramFiles(x86)%\\ASUS\\AsusDriverHub", Description = "Program Files (x86)" },
new PathHint { Path = "%ProgramData%\\ASUS\\AsusDriverHub\\SupportTemp", Description = "SupportTemp updater staging" }
},
FileHints = new[]
{
"%ProgramData%\\ASUS\\AsusDriverHub\\SupportTemp\\Installer.json"
}
},
new OemComponentDefinition
{
Name = "MSI Center",
Description = "MSI.CentralServer.exe exposed TCP commands with TOCTOU and signature bypass issues.",
Cves = new[] { "CVE-2025-27812", "CVE-2025-27813" },
ServiceHints = new[] { "msi.center", "msi centralserver" },
ProcessHints = new[] { "msi.centralserver", "msi center" },
DirectoryHints = new[]
{
new PathHint { Path = "%ProgramFiles%\\MSI\\MSI Center", Description = "Main installation" },
new PathHint { Path = "%ProgramFiles(x86)%\\MSI\\MSI Center", Description = "Main installation (x86)" },
new PathHint { Path = "%ProgramData%\\MSI\\MSI Center", Description = "Shared data" },
new PathHint { Path = "%ProgramData%\\MSI Center SDK", Description = "SDK temp copy location" }
}
},
new OemComponentDefinition
{
Name = "Acer Control Centre",
Description = "ACCSvc.exe exposes treadstone_service_LightMode named pipe with weak impersonation controls.",
Cves = new[] { "CVE-2025-5491" },
ServiceHints = new[] { "accsvc", "acer control" },
ProcessHints = new[] { "accsvc", "accstd" },
DirectoryHints = new[]
{
new PathHint { Path = "%ProgramFiles%\\Acer\\Care Center", Description = "Install directory" },
new PathHint { Path = "%ProgramFiles(x86)%\\Acer\\Care Center", Description = "Install directory (x86)" }
},
PipeHints = new[]
{
new PipeHint { Name = "treadstone_service_LightMode", Description = "Command dispatcher", CheckWorldWritable = true }
}
},
new OemComponentDefinition
{
Name = "Razer Synapse 4 Elevation Service",
Description = "razer_elevation_service.exe exposes COM elevation helpers that allowed arbitrary process launch.",
Cves = new[] { "CVE-2025-27811" },
ServiceHints = new[] { "razer_elevation_service" },
ProcessHints = new[] { "razer_elevation_service" },
DirectoryHints = new[]
{
new PathHint { Path = "%ProgramFiles%\\Razer\\RazerAppEngine", Description = "Razer App Engine" },
new PathHint { Path = "%ProgramFiles(x86)%\\Razer\\RazerAppEngine", Description = "Razer App Engine (x86)" }
}
}
};
}
private class ServiceSnapshot
{
public string Name { get; set; }
public string DisplayName { get; set; }
}
private class ProcessSnapshot
{
public string Name { get; set; }
public string FullPath { get; set; }
}
private class OemComponentDefinition
{
public string Name { get; set; }
public string Description { get; set; }
public string[] Cves { get; set; } = Array.Empty<string>();
public string[] ServiceHints { get; set; } = Array.Empty<string>();
public string[] ProcessHints { get; set; } = Array.Empty<string>();
public PathHint[] DirectoryHints { get; set; } = Array.Empty<PathHint>();
public string[] FileHints { get; set; } = Array.Empty<string>();
public PipeHint[] PipeHints { get; set; } = Array.Empty<PipeHint>();
}
private class PathHint
{
public string Path { get; set; }
public string Description { get; set; }
}
private class PipeHint
{
public string Name { get; set; }
public string Description { get; set; }
public bool CheckWorldWritable { get; set; }
}
}
internal class OemSoftwareFinding
{
public string Name { get; set; }
public string Description { get; set; }
public string[] Cves { get; set; } = Array.Empty<string>();
public List<OemEvidence> Evidence { get; } = new List<OemEvidence>();
}
internal class OemEvidence
{
public string EvidenceType { get; set; }
public string Message { get; set; }
public bool Highlight { get; set; }
}
}

View File

@@ -2,11 +2,14 @@
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;
@@ -276,6 +279,109 @@ 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

@@ -0,0 +1,508 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using winPEAS.Helpers;
using winPEAS.Native;
namespace winPEAS.Info.SystemInfo.NamedPipes
{
internal static class NamedPipeSecurityAnalyzer
{
private const string DeviceNamedPipePrefix = @"\Device\NamedPipe\";
private static readonly char[] CandidateSeparators = { '\\', '/', '-', ':', '(' };
private static readonly HashSet<string> LowPrivSidSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"S-1-1-0", // Everyone
"S-1-5-11", // Authenticated Users
"S-1-5-32-545", // Users
"S-1-5-32-546", // Guests
"S-1-5-32-547", // Power Users
"S-1-5-32-554", // Pre-Windows 2000 Compatible Access
"S-1-5-32-555", // Remote Desktop Users
"S-1-5-32-558", // Performance Log Users
"S-1-5-32-559", // Performance Monitor Users
"S-1-5-32-562", // Distributed COM Users
"S-1-5-32-569", // Remote Management Users
"S-1-5-4", // Interactive
"S-1-5-2", // Network
"S-1-5-1", // Dialup
"S-1-5-7" // Anonymous Logon
};
private static readonly HashSet<string> LowPrivPrincipalKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"everyone",
"authenticated users",
"users",
"guests",
"power users",
"remote desktop users",
"remote management users",
"distributed com users",
"anonymous logon",
"interactive",
"network",
"local",
"batch",
"iis_iusrs"
};
private static readonly HashSet<string> PrivilegedSidSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"S-1-5-18", // SYSTEM
"S-1-5-19", // LOCAL SERVICE
"S-1-5-20", // NETWORK SERVICE
"S-1-5-32-544" // Administrators
};
private static readonly (string Label, FileSystemRights Right)[] DangerousRightsMap = new[]
{
("FullControl", FileSystemRights.FullControl),
("Modify", FileSystemRights.Modify),
("Write", FileSystemRights.Write),
("WriteData", FileSystemRights.WriteData),
("AppendData", FileSystemRights.AppendData),
("CreateFiles", FileSystemRights.CreateFiles),
("CreateDirectories", FileSystemRights.CreateDirectories),
("WriteAttributes", FileSystemRights.WriteAttributes),
("WriteExtendedAttributes", FileSystemRights.WriteExtendedAttributes),
("Delete", FileSystemRights.Delete),
("ChangePermissions", FileSystemRights.ChangePermissions),
("TakeOwnership", FileSystemRights.TakeOwnership)
};
public static IEnumerable<NamedPipeSecurityIssue> GetNamedPipeAbuseCandidates()
{
var insecurePipes = DiscoverInsecurePipes();
if (!insecurePipes.Any())
{
return Enumerable.Empty<NamedPipeSecurityIssue>();
}
AttachProcesses(insecurePipes);
return insecurePipes.Values
.Where(issue => issue.LowPrivilegeAces.Any())
.OrderByDescending(issue => issue.HasPrivilegedServer)
.ThenBy(issue => issue.Name)
.ToList();
}
private static Dictionary<string, NamedPipeSecurityIssue> DiscoverInsecurePipes()
{
var result = new Dictionary<string, NamedPipeSecurityIssue>(StringComparer.OrdinalIgnoreCase);
foreach (var pipe in NamedPipes.GetNamedPipeInfos())
{
if (string.IsNullOrWhiteSpace(pipe.Sddl) || pipe.Sddl.Equals("ERROR", StringComparison.OrdinalIgnoreCase))
continue;
try
{
var descriptor = new RawSecurityDescriptor(pipe.Sddl);
if (descriptor.DiscretionaryAcl == null)
continue;
foreach (GenericAce ace in descriptor.DiscretionaryAcl)
{
if (!(ace is CommonAce commonAce))
continue;
var sid = commonAce.SecurityIdentifier;
if (sid == null || !IsLowPrivilegePrincipal(sid))
continue;
if (!HasDangerousWriteRights(commonAce.AccessMask))
continue;
var rights = DescribeRights(commonAce.AccessMask).ToList();
if (!rights.Any())
continue;
if (!result.TryGetValue(pipe.Name, out var issue))
{
issue = new NamedPipeSecurityIssue(pipe.Name, pipe.Sddl, NormalizePipeName(pipe.Name));
result[pipe.Name] = issue;
}
var account = ResolveSidToName(sid);
issue.AddLowPrivPrincipal(account, sid.Value, rights);
}
}
catch
{
// Ignore malformed SDDL strings
}
}
return result;
}
private static void AttachProcesses(Dictionary<string, NamedPipeSecurityIssue> insecurePipes)
{
if (!insecurePipes.Any())
return;
var lookup = BuildLookup(insecurePipes.Values);
if (!lookup.Any())
return;
List<HandlesHelper.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> handles;
try
{
handles = HandlesHelper.GetAllHandlers();
}
catch
{
return;
}
var currentProcess = Kernel32.GetCurrentProcess();
var processCache = new Dictionary<int, NamedPipeProcessInfo>();
foreach (var handle in handles)
{
IntPtr processHandle = IntPtr.Zero;
IntPtr duplicatedHandle = IntPtr.Zero;
try
{
int pid = GetPid(handle);
if (pid <= 0)
continue;
processHandle = Kernel32.OpenProcess(
HandlesHelper.ProcessAccessFlags.DupHandle | HandlesHelper.ProcessAccessFlags.QueryLimitedInformation,
false,
pid);
if (processHandle == IntPtr.Zero)
continue;
if (!Kernel32.DuplicateHandle(processHandle, handle.HandleValue, currentProcess, out duplicatedHandle, 0, false, HandlesHelper.DUPLICATE_SAME_ACCESS))
continue;
var typeName = HandlesHelper.GetObjectType(duplicatedHandle);
if (!string.Equals(typeName, "File", StringComparison.OrdinalIgnoreCase))
continue;
var objectName = HandlesHelper.GetObjectName(duplicatedHandle);
if (string.IsNullOrEmpty(objectName) || !objectName.StartsWith(DeviceNamedPipePrefix, StringComparison.OrdinalIgnoreCase))
continue;
var normalizedHandleName = NormalizePipeName(objectName.Substring(DeviceNamedPipePrefix.Length));
var candidates = GetCandidateKeys(normalizedHandleName);
bool matched = false;
foreach (var candidate in candidates)
{
if (!lookup.TryGetValue(candidate, out var matchedIssues))
continue;
if (!processCache.TryGetValue(pid, out var processInfo))
{
var raw = HandlesHelper.getProcInfoById(pid);
processInfo = new NamedPipeProcessInfo(raw.pid, raw.name, raw.userName, raw.userSid, IsHighPrivilegeAccount(raw.userSid, raw.userName));
processCache[pid] = processInfo;
}
foreach (var issue in matchedIssues)
{
issue.AddProcess(processInfo);
}
matched = true;
break;
}
if (!matched)
continue;
}
catch
{
// Ignore per-handle failures
}
finally
{
if (duplicatedHandle != IntPtr.Zero)
{
Kernel32.CloseHandle(duplicatedHandle);
}
if (processHandle != IntPtr.Zero)
{
Kernel32.CloseHandle(processHandle);
}
}
}
}
private static Dictionary<string, List<NamedPipeSecurityIssue>> BuildLookup(IEnumerable<NamedPipeSecurityIssue> issues)
{
var lookup = new Dictionary<string, List<NamedPipeSecurityIssue>>(StringComparer.OrdinalIgnoreCase);
foreach (var issue in issues)
{
foreach (var key in GetCandidateKeys(issue.NormalizedName))
{
if (!lookup.TryGetValue(key, out var list))
{
list = new List<NamedPipeSecurityIssue>();
lookup[key] = list;
}
if (!list.Contains(issue))
{
list.Add(issue);
}
}
}
return lookup;
}
private static IEnumerable<string> GetCandidateKeys(string normalizedName)
{
if (string.IsNullOrEmpty(normalizedName))
return Array.Empty<string>();
var candidates = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
normalizedName
};
foreach (var separator in CandidateSeparators)
{
var idx = normalizedName.IndexOf(separator);
if (idx > 0)
{
candidates.Add(normalizedName.Substring(0, idx));
}
}
return candidates;
}
private static string NormalizePipeName(string rawName)
{
if (string.IsNullOrWhiteSpace(rawName))
return string.Empty;
var normalized = rawName.Replace('/', '\\').Trim();
while (normalized.StartsWith("\\", StringComparison.Ordinal))
{
normalized = normalized.Substring(1);
}
return normalized.ToLowerInvariant();
}
private static bool HasDangerousWriteRights(int accessMask)
{
var rights = (FileSystemRights)accessMask;
foreach (var entry in DangerousRightsMap)
{
if ((rights & entry.Right) == entry.Right)
return true;
}
return false;
}
private static IEnumerable<string> DescribeRights(int accessMask)
{
var rights = (FileSystemRights)accessMask;
var descriptions = new List<string>();
foreach (var entry in DangerousRightsMap)
{
if ((rights & entry.Right) == entry.Right)
{
descriptions.Add(entry.Label);
if (entry.Right == FileSystemRights.FullControl)
break;
}
}
if (!descriptions.Any())
{
descriptions.Add($"0x{accessMask:x}");
}
return descriptions;
}
private static bool IsLowPrivilegePrincipal(SecurityIdentifier sid)
{
if (sid == null)
return false;
if (LowPrivSidSet.Contains(sid.Value))
return true;
var accountName = ResolveSidToName(sid);
if (string.IsNullOrEmpty(accountName))
return false;
return LowPrivPrincipalKeywords.Any(keyword => accountName.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0);
}
private static string ResolveSidToName(SecurityIdentifier sid)
{
if (sid == null)
return string.Empty;
try
{
return sid.Translate(typeof(NTAccount)).Value;
}
catch
{
return sid.Value;
}
}
private static bool IsHighPrivilegeAccount(string sid, string userName)
{
if (!string.IsNullOrEmpty(sid))
{
if (PrivilegedSidSet.Contains(sid))
return true;
if (sid.StartsWith("S-1-5-80-", StringComparison.OrdinalIgnoreCase)) // Service SID
return true;
if (sid.StartsWith("S-1-5-82-", StringComparison.OrdinalIgnoreCase)) // AppPool / service-like SIDs
return true;
}
if (!string.IsNullOrEmpty(userName))
{
if (string.Equals(userName, HandlesHelper.elevatedProcess, StringComparison.OrdinalIgnoreCase))
return true;
var normalized = userName.ToUpperInvariant();
if (normalized.Contains("SYSTEM") || normalized.Contains("LOCAL SERVICE") || normalized.Contains("NETWORK SERVICE"))
return true;
if (normalized.StartsWith("NT SERVICE\\", StringComparison.Ordinal))
return true;
if (normalized.EndsWith("$", StringComparison.Ordinal) && normalized.Contains("\\"))
return true;
}
return false;
}
private static int GetPid(HandlesHelper.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handle)
{
unchecked
{
if (IntPtr.Size == 4)
{
return (int)handle.UniqueProcessId.ToUInt32();
}
return (int)handle.UniqueProcessId.ToUInt64();
}
}
}
internal class NamedPipeSecurityIssue
{
private readonly Dictionary<string, NamedPipePrincipalAccess> _principalAccess = new Dictionary<string, NamedPipePrincipalAccess>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<int, NamedPipeProcessInfo> _processes = new Dictionary<int, NamedPipeProcessInfo>();
public NamedPipeSecurityIssue(string name, string sddl, string normalizedName)
{
Name = name;
Sddl = sddl;
NormalizedName = normalizedName;
}
public string Name { get; }
public string Sddl { get; }
public string NormalizedName { get; }
public IReadOnlyCollection<NamedPipePrincipalAccess> LowPrivilegeAces => _principalAccess.Values;
public IReadOnlyCollection<NamedPipeProcessInfo> Processes => _processes.Values;
public bool HasPrivilegedServer => _processes.Values.Any(process => process.IsHighPrivilege);
public void AddLowPrivPrincipal(string principal, string sid, IEnumerable<string> rights)
{
if (string.IsNullOrEmpty(sid))
return;
if (!_principalAccess.TryGetValue(sid, out var access))
{
access = new NamedPipePrincipalAccess(principal, sid);
_principalAccess[sid] = access;
}
access.AddRights(rights);
}
public void AddProcess(NamedPipeProcessInfo process)
{
if (process == null)
return;
if (!_processes.ContainsKey(process.Pid))
{
_processes[process.Pid] = process;
}
}
}
internal class NamedPipePrincipalAccess
{
private readonly HashSet<string> _rights = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public NamedPipePrincipalAccess(string principal, string sid)
{
Principal = principal;
Sid = sid;
}
public string Principal { get; }
public string Sid { get; }
public string RightsDescription => _rights.Count == 0 ? string.Empty : string.Join("|", _rights.OrderBy(r => r));
public IEnumerable<string> Rights => _rights;
public void AddRights(IEnumerable<string> rights)
{
if (rights == null)
return;
foreach (var right in rights)
{
if (!string.IsNullOrWhiteSpace(right))
{
_rights.Add(right.Trim());
}
}
}
}
internal class NamedPipeProcessInfo
{
public NamedPipeProcessInfo(int pid, string processName, string userName, string userSid, bool isHighPrivilege)
{
Pid = pid;
ProcessName = processName;
UserName = userName;
UserSid = userSid;
IsHighPrivilege = isHighPrivilege;
}
public int Pid { get; }
public string ProcessName { get; }
public string UserName { get; }
public string UserSid { get; }
public bool IsHighPrivilege { get; }
}
}

View File

@@ -16,6 +16,20 @@ namespace winPEAS.Info.UserInfo
{
class UserInfoHelper
{
private static readonly Dictionary<string, bool> _highPrivAccountCache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
private static readonly string[] _highPrivGroupIndicators = new string[]
{
"administrators",
"domain admins",
"enterprise admins",
"schema admins",
"server operators",
"account operators",
"backup operators",
"dnsadmins",
"hyper-v administrators"
};
// https://stackoverflow.com/questions/5247798/get-list-of-local-computer-usernames-in-windows
@@ -91,6 +105,65 @@ namespace winPEAS.Info.UserInfo
return oPrincipalContext;
}
public static bool IsHighPrivilegeAccount(string userName, string domain)
{
if (string.IsNullOrWhiteSpace(userName))
{
return false;
}
string cacheKey = ($"{domain}\\{userName}").Trim('\\');
if (_highPrivAccountCache.TryGetValue(cacheKey, out bool cached))
{
return cached;
}
bool isHighPriv = false;
try
{
string resolvedDomain = string.IsNullOrWhiteSpace(domain) ? Checks.Checks.CurrentUserDomainName : domain;
List<string> groups = User.GetUserGroups(userName, resolvedDomain);
foreach (string group in groups)
{
if (IsHighPrivilegeGroup(group))
{
isHighPriv = true;
break;
}
}
}
catch (Exception ex)
{
Beaprint.GrayPrint(string.Format(" [-] Unable to resolve groups for {0}\\{1}: {2}", domain, userName, ex.Message));
}
if (!isHighPriv)
{
isHighPriv = string.Equals(userName, "administrator", StringComparison.OrdinalIgnoreCase) || userName.StartsWith("admin", StringComparison.OrdinalIgnoreCase);
}
_highPrivAccountCache[cacheKey] = isHighPriv;
return isHighPriv;
}
private static bool IsHighPrivilegeGroup(string groupName)
{
if (string.IsNullOrWhiteSpace(groupName))
{
return false;
}
foreach (string indicator in _highPrivGroupIndicators)
{
if (groupName.IndexOf(indicator, StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
}
return false;
}
//From Seatbelt
public enum WTS_CONNECTSTATE_CLASS
{

View File

@@ -1197,6 +1197,7 @@
<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" />
@@ -1223,6 +1224,7 @@
<Compile Include="Info\ApplicationInfo\ApplicationInfoHelper.cs" />
<Compile Include="Info\ApplicationInfo\AutoRuns.cs" />
<Compile Include="Info\ApplicationInfo\DeviceDrivers.cs" />
<Compile Include="Info\ApplicationInfo\SoapClientProxyAnalyzer.cs" />
<Compile Include="Info\ApplicationInfo\InstalledApps.cs" />
<Compile Include="Helpers\Beaprint.cs" />
<Compile Include="Info\CloudInfo\AWSInfo.cs" />
@@ -1291,6 +1293,7 @@
<Compile Include="Info\SystemInfo\GroupPolicy\GroupPolicy.cs" />
<Compile Include="Info\SystemInfo\GroupPolicy\LocalGroupPolicyInfo.cs" />
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipeInfo.cs" />
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipeSecurityAnalyzer.cs" />
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipes.cs" />
<Compile Include="Info\SystemInfo\Ntlm\Ntlm.cs" />
<Compile Include="Info\SystemInfo\Ntlm\NtlmSettingsInfo.cs" />
@@ -1359,6 +1362,7 @@
<Compile Include="KnownFileCreds\Vault\Structs\VAULT_ITEM_WIN8.cs" />
<Compile Include="KnownFileCreds\Vault\VaultCli.cs" />
<Compile Include="Helpers\MyUtils.cs" />
<Compile Include="Helpers\ObjectManagerHelper.cs" />
<Compile Include="Info\UserInfo\SAM\Enums.cs" />
<Compile Include="Info\UserInfo\SAM\SamServer.cs" />
<Compile Include="Info\UserInfo\SAM\Structs.cs" />
@@ -1458,6 +1462,7 @@
<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" />