Compare commits

...

15 Commits

Author SHA1 Message Date
codex-action
2a6464cc31 Fix CI failures for PR #551 2026-01-18 23:15:03 +00:00
Carlos Polop
1f6661816b Intentional failure for Codex fix test 2026-01-19 00:00:24 +01:00
SirBroccoli
9216b31b10 Auto-merge PR #550 (Codex)
* f

* new actions

* Doc tweak for Codex merge test
2026-01-18 22:59:38 +00:00
SirBroccoli
9d8a14d2ec Merge pull request #545 from peass-ng/update_PEASS-linpeas-ECS_on_EC2__Covering_Gaps_in_IMDS_Ha_20251229_015718
[LINPEAS] Add privilege escalation check: ECS on EC2 Covering Gaps in IMDS Hardeni...
2026-01-17 16:25:39 +01:00
SirBroccoli
9c49dfd2bb Merge pull request #529 from peass-ng/update_PEASS-winpeas-Pwning_ASUS_DriverHub__MSI_Center__A_20251207_130236
[WINPEAS] Add privilege escalation check: Pwning ASUS DriverHub, MSI Center, Acer ...
2026-01-17 16:06:06 +01:00
SirBroccoli
9acc2ef61d Merge pull request #540 from peass-ng/update_PEASS-winpeas-The_Windows_Registry_Adventure__Part_20251217_014635
[WINPEAS] Add privilege escalation check: The Windows Registry Adventure, Part 8 E...
2026-01-17 16:05:46 +01:00
Carlos Polop
ce5bd84575 Merge ECS IMDS checks into ECS module 2026-01-17 15:48:55 +01:00
SirBroccoli
c447ca993d Merge branch 'master' into update_PEASS-winpeas-The_Windows_Registry_Adventure__Part_20251217_014635 2026-01-17 15:45:17 +01:00
Carlos Polop
2f44379713 Fix registry hive resolution in ACL scanner 2026-01-17 15:33:09 +01:00
Carlos Polop
c14f9aeb30 Merge branch 'master' into update_PEASS-winpeas-The_Windows_Registry_Adventure__Part_20251217_014635 2026-01-17 13:36:22 +01:00
Carlos Polop
7016e5a0b4 Merge branch 'master' into update_PEASS-linpeas-ECS_on_EC2__Covering_Gaps_in_IMDS_Ha_20251229_015718 2026-01-17 13:36:02 +01:00
Carlos Polop
aca58f36b9 chore: trigger CI 2026-01-16 18:17:14 +01:00
Carlos Polop
fa58c6688b chore: trigger CI 2026-01-16 18:16:54 +01:00
HackTricks News Bot
e77867b2d3 Add linpeas privilege escalation checks from: ECS on EC2: Covering Gaps in IMDS Hardening 2025-12-29 02:02:46 +00:00
HackTricks News Bot
3268701ed6 Add winpeas privilege escalation checks from: The Windows Registry Adventure, Part 8: Exploitation of Hive-based Memory Corrup 2025-12-17 02:00:18 +00:00
14 changed files with 802 additions and 33 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

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

15
codex_prompt.txt Normal file
View File

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

@@ -1,14 +1,14 @@
# Title: Cloud - AWS ECS
# ID: CL_AWS_ECS
# Author: Carlos Polop
# Last Update: 22-08-2023
# Last Update: 17-01-2026
# Description: AWS ECS Enumeration
# License: GNU GPL
# Version: 1.0
# Functions Used: check_aws_ecs, exec_with_jq, print_2title, print_3title
# Global Variables: $aws_ecs_metadata_uri, $aws_ecs_service_account_uri, $is_aws_ecs
# Initial Functions: check_aws_ecs
# Generated Global Variables: $aws_ecs_req
# Generated Global Variables: $aws_ecs_req, $aws_exec_env, $ecs_task_metadata, $launch_type, $network_modes, $imds_tool, $imds_token, $imds_roles, $imds_http_code, $ecs_block_line, $ecs_host_line, $iptables_cmd, $docker_rules, $first_role
# Fat linpeas: 0
# Small linpeas: 1
@@ -44,5 +44,146 @@ if [ "$is_aws_ecs" = "Yes" ]; then
else
echo "I couldn't find AWS_CONTAINER_CREDENTIALS_RELATIVE_URI env var to get IAM role info (the task is running without a task role probably)"
fi
print_3title "ECS task metadata hints"
aws_exec_env=$(printenv AWS_EXECUTION_ENV 2>/dev/null)
if [ "$aws_exec_env" ]; then
printf "AWS_EXECUTION_ENV=%s\n" "$aws_exec_env"
fi
ecs_task_metadata=""
if [ "$aws_ecs_metadata_uri" ]; then
ecs_task_metadata=$(eval $aws_ecs_req "$aws_ecs_metadata_uri/task" 2>/dev/null)
fi
if [ "$ecs_task_metadata" ]; then
launch_type=$(printf "%s" "$ecs_task_metadata" | grep -oE '"LaunchType":"[^"]+"' | head -n 1 | cut -d '"' -f4)
if [ "$launch_type" ]; then
printf "ECS LaunchType reported: %s\n" "$launch_type"
fi
network_modes=$(printf "%s" "$ecs_task_metadata" | grep -oE '"NetworkMode":"[^"]+"' | cut -d '"' -f4 | sort -u | tr '\n' ' ')
if [ "$network_modes" ]; then
printf "Reported NetworkMode(s): %s\n" "$network_modes"
fi
else
echo "Unable to fetch task metadata (check ECS_CONTAINER_METADATA_URI)."
fi
echo ""
fi
print_3title "IMDS reachability from this task"
imds_token=""
imds_roles=""
imds_http_code=""
imds_tool=""
if command -v curl >/dev/null 2>&1; then
imds_tool="curl"
elif command -v wget >/dev/null 2>&1; then
imds_tool="wget"
fi
if [ "$imds_tool" = "curl" ]; then
imds_token=$(curl -s --connect-timeout 2 --max-time 2 -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null)
if [ "$imds_token" ]; then
printf "[!] IMDSv2 token request succeeded (metadata reachable from this task).\n"
imds_roles=$(curl -s --connect-timeout 2 --max-time 2 -H "X-aws-ec2-metadata-token: $imds_token" "http://169.254.169.254/latest/meta-data/iam/security-credentials/" 2>/dev/null | tr '\n' ' ')
if [ "$imds_roles" ]; then
printf " Instance profile role(s) exposed via IMDS: %s\n" "$imds_roles"
first_role=$(printf "%s" "$imds_roles" | awk '{print $1}')
if [ "$first_role" ]; then
printf " Example: curl -H 'X-aws-ec2-metadata-token: <TOKEN>' http://169.254.169.254/latest/meta-data/iam/security-credentials/%s\n" "$first_role"
fi
else
printf " No IAM role names returned (instance profile might be missing).\n"
fi
else
imds_http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 --max-time 2 "http://169.254.169.254/latest/meta-data/" 2>/dev/null)
case "$imds_http_code" in
000|"")
printf "[i] IMDS endpoint did not respond (likely blocked via hop-limit or host firewalling).\n"
;;
401)
printf "[i] IMDS requires v2 tokens but token requests are being blocked (bridge-mode tasks rely on this when hop limit = 1).\n"
;;
*)
printf "[i] IMDS GET returned HTTP %s (investigate host configuration).\n" "$imds_http_code"
;;
esac
fi
elif [ "$imds_tool" = "wget" ]; then
imds_token=$(wget -q -O - --timeout=2 --tries=1 --method=PUT --header="X-aws-ec2-metadata-token-ttl-seconds: 21600" "http://169.254.169.254/latest/api/token" 2>/dev/null)
if [ "$imds_token" ]; then
printf "[!] IMDSv2 token request succeeded (metadata reachable from this task).\n"
imds_roles=$(wget -q -O - --timeout=2 --tries=1 --header="X-aws-ec2-metadata-token: $imds_token" "http://169.254.169.254/latest/meta-data/iam/security-credentials/" 2>/dev/null | tr '\n' ' ')
if [ "$imds_roles" ]; then
printf " Instance profile role(s) exposed via IMDS: %s\n" "$imds_roles"
else
printf " No IAM role names returned (instance profile might be missing).\n"
fi
else
wget --server-response -O /dev/null --timeout=2 --tries=1 "http://169.254.169.254/latest/meta-data/" 2>&1 | awk 'BEGIN{code=""} /^ HTTP/{code=$2} END{ if(code!="") { printf("[i] IMDS GET returned HTTP %s (token could not be retrieved).\n", code); } else { print "[i] IMDS endpoint did not respond (likely blocked)."; } }'
fi
else
echo "Neither curl nor wget were found, I can't test IMDS reachability."
fi
echo ""
print_3title "ECS agent IMDS settings"
if [ -r "/etc/ecs/ecs.config" ]; then
ecs_block_line=$(grep -E "^ECS_AWSVPC_BLOCK_IMDS=" /etc/ecs/ecs.config 2>/dev/null | tail -n 1)
ecs_host_line=$(grep -E "^ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST=" /etc/ecs/ecs.config 2>/dev/null | tail -n 1)
if [ "$ecs_block_line" ]; then
printf "%s\n" "$ecs_block_line"
if echo "$ecs_block_line" | grep -qi "=true"; then
echo " -> awsvpc-mode tasks should be blocked from IMDS by the ECS agent."
else
echo " -> awsvpc-mode tasks can still reach IMDS (set this to true to block)."
fi
else
echo "ECS_AWSVPC_BLOCK_IMDS not set (awsvpc tasks inherit host IMDS reachability)."
fi
if [ "$ecs_host_line" ]; then
printf "%s\n" "$ecs_host_line"
if echo "$ecs_host_line" | grep -qi "=false"; then
echo " -> Host-network tasks lose IAM task roles but IMDS is blocked."
else
echo " -> Host-network tasks keep IAM task roles and retain IMDS access."
fi
else
echo "ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST not set (defaults keep IMDS reachable for host-mode tasks)."
fi
else
echo "Cannot read /etc/ecs/ecs.config (file missing or permissions denied)."
fi
echo ""
print_3title "DOCKER-USER IMDS filtering"
iptables_cmd=""
if command -v iptables >/dev/null 2>&1; then
iptables_cmd=$(command -v iptables)
elif command -v iptables-nft >/dev/null 2>&1; then
iptables_cmd=$(command -v iptables-nft)
fi
if [ "$iptables_cmd" ]; then
docker_rules=$($iptables_cmd -S DOCKER-USER 2>/dev/null)
if [ $? -eq 0 ]; then
if [ "$docker_rules" ]; then
echo "$docker_rules"
else
echo "(DOCKER-USER chain exists but no rules were found)"
fi
if echo "$docker_rules" | grep -q "169\\.254\\.169\\.254"; then
echo " -> IMDS traffic is explicitly filtered before Docker NAT."
else
echo " -> No DOCKER-USER rule drops 169.254.169.254 traffic (bridge tasks rely on hop limit or host firewalling)."
fi
else
echo "Unable to read DOCKER-USER chain (missing chain or insufficient permissions)."
fi
else
echo "iptables binary not found; cannot inspect DOCKER-USER chain."
fi
echo ""
fi

View File

@@ -94,6 +94,7 @@ namespace winPEAS.Checks
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

@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.Linq;
using winPEAS.Helpers;
using winPEAS.Helpers.Registry;
namespace winPEAS.Checks
{
internal class RegistryInfo : ISystemCheck
{
private const string TypingInsightsRelativePath = @"Software\Microsoft\Input\TypingInsights";
private static readonly string[] KnownWritableSystemKeyCandidates = new[]
{
@"SOFTWARE\Microsoft\CoreShell",
@"SOFTWARE\Microsoft\DRM",
@"SOFTWARE\Microsoft\Input\Locales",
@"SOFTWARE\Microsoft\Input\Settings",
@"SOFTWARE\Microsoft\Shell\Oobe",
@"SOFTWARE\Microsoft\Shell\Session",
@"SOFTWARE\Microsoft\Tracing",
@"SOFTWARE\Microsoft\Windows\UpdateApi",
@"SOFTWARE\Microsoft\WindowsUpdate\UX",
@"SOFTWARE\WOW6432Node\Microsoft\DRM",
@"SOFTWARE\WOW6432Node\Microsoft\Tracing",
@"SYSTEM\Software\Microsoft\TIP",
@"SYSTEM\ControlSet001\Control\Cryptography\WebSignIn\Navigation",
@"SYSTEM\ControlSet001\Control\MUI\StringCacheSettings",
@"SYSTEM\ControlSet001\Control\USB\AutomaticSurpriseRemoval",
@"SYSTEM\ControlSet001\Services\BTAGService\Parameters\Settings",
};
private static readonly string[] ScanBasePaths = new[]
{
@"SOFTWARE\Microsoft",
@"SOFTWARE\WOW6432Node\Microsoft",
@"SYSTEM\CurrentControlSet\Services",
@"SYSTEM\CurrentControlSet\Control",
@"SYSTEM\ControlSet001\Control",
};
public void PrintInfo(bool isDebug)
{
Beaprint.GreatPrint("Registry permissions for hive exploitation");
new List<Action>
{
PrintTypingInsightsPermissions,
PrintKnownSystemWritableKeys,
PrintHeuristicWritableKeys,
}.ForEach(action => CheckRunner.Run(action, isDebug));
}
private void PrintTypingInsightsPermissions()
{
Beaprint.MainPrint("Cross-user TypingInsights key (HKCU/HKU)");
var matches = new List<RegistryWritableKeyInfo>();
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (RegistryAclScanner.TryGetWritableKey("HKCU", TypingInsightsRelativePath, out var currentUserKey))
{
if (seen.Add(currentUserKey.FullPath))
{
matches.Add(currentUserKey);
}
}
foreach (var sid in RegistryHelper.GetUserSIDs())
{
if (string.IsNullOrEmpty(sid) || sid.Equals(".DEFAULT", StringComparison.OrdinalIgnoreCase) || sid.EndsWith("_Classes", StringComparison.OrdinalIgnoreCase))
{
continue;
}
string relativePath = $"{sid}\\{TypingInsightsRelativePath}";
if (RegistryAclScanner.TryGetWritableKey("HKU", relativePath, out var info) && seen.Add(info.FullPath))
{
matches.Add(info);
}
}
if (matches.Count == 0)
{
Beaprint.GrayPrint(" [-] TypingInsights key does not grant write access to low-privileged groups.");
return;
}
PrintEntries(matches);
Beaprint.LinkPrint("https://projectzero.google/2025/05/the-windows-registry-adventure-8-exploitation.html", "Writable TypingInsights enables cross-user hive tampering and DoS.");
}
private void PrintKnownSystemWritableKeys()
{
Beaprint.MainPrint("Known HKLM descendants writable by standard users");
var matches = new List<RegistryWritableKeyInfo>();
foreach (var path in KnownWritableSystemKeyCandidates)
{
if (RegistryAclScanner.TryGetWritableKey("HKLM", path, out var info))
{
matches.Add(info);
}
}
if (matches.Count == 0)
{
Beaprint.GrayPrint(" [-] None of the tracked HKLM keys are writable by low-privileged groups.");
return;
}
PrintEntries(matches);
}
private void PrintHeuristicWritableKeys()
{
Beaprint.MainPrint("Sample of additional writable HKLM keys (depth-limited scan)");
var matches = RegistryAclScanner.ScanWritableKeys("HKLM", ScanBasePaths, maxDepth: 3, maxResults: 25);
if (matches.Count == 0)
{
Beaprint.GrayPrint(" [-] No additional writable HKLM keys were found within the sampled paths.");
return;
}
PrintEntries(matches);
Beaprint.GrayPrint(" [*] Showing up to 25 entries from the sampled paths to avoid noisy output.");
}
private static void PrintEntries(IEnumerable<RegistryWritableKeyInfo> entries)
{
foreach (var entry in entries)
{
var principals = string.Join(", ", entry.Principals);
var rights = entry.Rights.Count > 0 ? string.Join(", ", entry.Rights.Distinct(StringComparer.OrdinalIgnoreCase)) : "Write access";
var displayPath = string.IsNullOrEmpty(entry.FullPath) ? $"{entry.Hive}\\{entry.RelativePath}" : entry.FullPath;
Beaprint.BadPrint($" [!] {displayPath} -> {principals} ({rights})");
}
}
}
}

View File

@@ -132,6 +132,7 @@ namespace winPEAS.Helpers
Console.WriteLine(LCYAN + " activedirectoryinfo" + GRAY + " Quick AD checks (gMSA readable passwords, AD CS template rights)" + NOCOLOR);
Console.WriteLine(LCYAN + " cloudinfo" + GRAY + " Enumerate cloud information" + NOCOLOR);
Console.WriteLine(LCYAN + " windowscreds" + GRAY + " Search windows credentials" + NOCOLOR);
Console.WriteLine(LCYAN + " registryinfo" + GRAY + " Flag writable HKLM/HKU keys that enable hive tampering" + NOCOLOR);
Console.WriteLine(LCYAN + " browserinfo" + GRAY + " Search browser information" + NOCOLOR);
Console.WriteLine(LCYAN + " filesinfo" + GRAY + " Search generic files that can contains credentials" + NOCOLOR);
Console.WriteLine(LCYAN + " fileanalysis" + GRAY + " [NOT RUN BY DEFAULT] Search specific files that can contains credentials and for regexes inside files. Might take several minutes." + NOCOLOR);

View File

@@ -0,0 +1,221 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using winPEAS.Helpers;
namespace winPEAS.Helpers.Registry
{
internal class RegistryWritableKeyInfo
{
public string Hive { get; set; }
public string RelativePath { get; set; }
public string FullPath { get; set; }
public List<string> Principals { get; set; } = new List<string>();
public List<string> Rights { get; set; } = new List<string>();
}
internal static class RegistryAclScanner
{
private static readonly Dictionary<string, string> LowPrivSidMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null).Value, "BUILTIN\\Users" },
{ new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null).Value, "Authenticated Users" },
{ new SecurityIdentifier(WellKnownSidType.WorldSid, null).Value, "Everyone" },
{ new SecurityIdentifier(WellKnownSidType.InteractiveSid, null).Value, "Interactive" },
{ new SecurityIdentifier(WellKnownSidType.BuiltinGuestsSid, null).Value, "BUILTIN\\Guests" },
};
public static bool TryGetWritableKey(string hive, string relativePath, out RegistryWritableKeyInfo info)
{
info = null;
using (var key = OpenKey(hive, relativePath))
{
if (key == null)
{
return false;
}
return TryCollectWritableInfo(hive, relativePath, key, out info);
}
}
public static List<RegistryWritableKeyInfo> ScanWritableKeys(string hive, IEnumerable<string> basePaths, int maxDepth, int maxResults)
{
var results = new List<RegistryWritableKeyInfo>();
var seenPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var basePath in basePaths ?? Enumerable.Empty<string>())
{
if (results.Count >= maxResults)
{
break;
}
using (var key = OpenKey(hive, basePath))
{
if (key == null)
{
continue;
}
Traverse(hive, key, basePath, 0, maxDepth, maxResults, seenPaths, results);
}
}
return results;
}
private static void Traverse(string hive, RegistryKey currentKey, string currentPath, int depth, int maxDepth, int maxResults, HashSet<string> seenPaths, List<RegistryWritableKeyInfo> results)
{
if (currentKey == null || results.Count >= maxResults)
{
return;
}
if (TryCollectWritableInfo(hive, currentPath, currentKey, out var info))
{
if (seenPaths.Add(info.FullPath))
{
results.Add(info);
}
if (results.Count >= maxResults)
{
return;
}
}
if (depth >= maxDepth)
{
return;
}
string[] subKeys;
try
{
subKeys = currentKey.GetSubKeyNames();
}
catch
{
return;
}
foreach (var subKeyName in subKeys)
{
if (results.Count >= maxResults)
{
break;
}
try
{
using (var childKey = currentKey.OpenSubKey(subKeyName))
{
if (childKey == null)
{
continue;
}
string childPath = string.IsNullOrEmpty(currentPath) ? subKeyName : $"{currentPath}\\{subKeyName}";
Traverse(hive, childKey, childPath, depth + 1, maxDepth, maxResults, seenPaths, results);
}
}
catch
{
// Ignore keys we cannot open
}
}
}
private static bool TryCollectWritableInfo(string hive, string relativePath, RegistryKey key, out RegistryWritableKeyInfo info)
{
info = null;
try
{
var acl = key.GetAccessControl(AccessControlSections.Access);
var principals = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var rights = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (RegistryAccessRule rule in acl.GetAccessRules(true, true, typeof(SecurityIdentifier)))
{
if (rule.AccessControlType != AccessControlType.Allow)
{
continue;
}
var sid = rule.IdentityReference as SecurityIdentifier ?? rule.IdentityReference.Translate(typeof(SecurityIdentifier)) as SecurityIdentifier;
if (sid == null)
{
continue;
}
if (!LowPrivSidMap.TryGetValue(sid.Value, out var label))
{
continue;
}
string interestingRight = PermissionsHelper.PermInt2Str((int)rule.RegistryRights, PermissionType.WRITEABLE_OR_EQUIVALENT_REG);
if (string.IsNullOrEmpty(interestingRight))
{
continue;
}
principals.Add($"{label} ({sid.Value})");
rights.Add(interestingRight);
}
if (principals.Count == 0)
{
return false;
}
string normalizedRelativePath = relativePath ?? string.Empty;
string fullPath = string.IsNullOrEmpty(normalizedRelativePath) ? key.Name : $"{hive}\\{normalizedRelativePath}";
info = new RegistryWritableKeyInfo
{
Hive = hive,
RelativePath = normalizedRelativePath,
FullPath = fullPath,
Principals = principals.ToList(),
Rights = rights.ToList(),
};
return true;
}
catch
{
return false;
}
}
private static RegistryKey OpenKey(string hive, string path)
{
if (string.IsNullOrEmpty(path))
{
return null;
}
try
{
RegistryKey baseKey = hive switch
{
"HKLM" => Microsoft.Win32.Registry.LocalMachine,
"HKCU" => Microsoft.Win32.Registry.CurrentUser,
"HKU" => Microsoft.Win32.Registry.Users,
_ => null,
};
return baseKey?.OpenSubKey(path);
}
catch
{
return null;
}
}
}
}

View File

@@ -1201,6 +1201,7 @@
<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" />
@@ -1469,6 +1470,7 @@
<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" />