mirror of
https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite.git
synced 2026-02-04 11:46:36 +00:00
Compare commits
124 Commits
update_PEA
...
fix/system
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ca4115a6b | ||
|
|
c5a77725db | ||
|
|
04c0b8aab3 | ||
|
|
a6c0491438 | ||
|
|
fce28d2b81 | ||
|
|
fcc78b919a | ||
|
|
29d350fa79 | ||
|
|
1473fedcbf | ||
|
|
f8f4250b81 | ||
|
|
1fb419fa0c | ||
|
|
651dc9cd7d | ||
|
|
0808fb7f1b | ||
|
|
c332fab519 | ||
|
|
577dcc9964 | ||
|
|
b591f3d524 | ||
|
|
b3ac8c6d22 | ||
|
|
83580fcd8a | ||
|
|
ab3a5899de | ||
|
|
0fac664048 | ||
|
|
db30e3bd7d | ||
|
|
7ad87a85e6 | ||
|
|
b24694f00b | ||
|
|
e777c81eba | ||
|
|
21a86bc365 | ||
|
|
ac7cb9c73c | ||
|
|
d054715fbd | ||
|
|
b4c1043a93 | ||
|
|
1b8706aac6 | ||
|
|
3371be7bd6 | ||
|
|
2344f5b106 | ||
|
|
485f91d46c | ||
|
|
018e8866e6 | ||
|
|
d707317278 | ||
|
|
f4ef371afc | ||
|
|
61f6282b5f | ||
|
|
a363541d77 | ||
|
|
6fc41c9a23 | ||
|
|
170a4b2c70 | ||
|
|
710709834a | ||
|
|
21b2bac892 | ||
|
|
5fdb99b38e | ||
|
|
787bc8fa8a | ||
|
|
c5401bd33d | ||
|
|
bd18d96837 | ||
|
|
ede5960b7c | ||
|
|
c54f483648 | ||
|
|
e533bf3ba5 | ||
|
|
66c3d4e342 | ||
|
|
917f88b76c | ||
|
|
21a967acb5 | ||
|
|
4155093e56 | ||
|
|
be1b0cdbd0 | ||
|
|
89a55bde9b | ||
|
|
4308caddf1 | ||
|
|
54fc62d29b | ||
|
|
9216b31b10 | ||
|
|
9d8a14d2ec | ||
|
|
9c49dfd2bb | ||
|
|
9acc2ef61d | ||
|
|
e7663381f2 | ||
|
|
ce5bd84575 | ||
|
|
c447ca993d | ||
|
|
a1aa60faf8 | ||
|
|
e81c436d80 | ||
|
|
f4883f814e | ||
|
|
4a7fb83165 | ||
|
|
ff13e8bebb | ||
|
|
e80425aa3d | ||
|
|
dc5ae45cc3 | ||
|
|
ff21b3dcb9 | ||
|
|
2f44379713 | ||
|
|
2c6cbfa43d | ||
|
|
8ae2667800 | ||
|
|
43a7684621 | ||
|
|
182c8974fd | ||
|
|
7b4a83d51d | ||
|
|
8aa05e13a4 | ||
|
|
4559fd09ea | ||
|
|
4f8a3b3f25 | ||
|
|
0ed7a39a7d | ||
|
|
974cfe028f | ||
|
|
f627e80a1b | ||
|
|
a83d33d409 | ||
|
|
1cdd473d79 | ||
|
|
0e29450869 | ||
|
|
efe9c1625f | ||
|
|
4255330728 | ||
|
|
8f928f8c5d | ||
|
|
0e8959a6db | ||
|
|
ea787df91c | ||
|
|
c14f9aeb30 | ||
|
|
7e4743d9be | ||
|
|
14aa117a0e | ||
|
|
7016e5a0b4 | ||
|
|
2046d18e5d | ||
|
|
7a5aa8dcae | ||
|
|
34d0f18aad | ||
|
|
b66eebcc3d | ||
|
|
49644a9813 | ||
|
|
35eb4f5be7 | ||
|
|
13656ba172 | ||
|
|
8c043b6164 | ||
|
|
aca58f36b9 | ||
|
|
a5114ef5dd | ||
|
|
ff41d5b0e7 | ||
|
|
fa58c6688b | ||
|
|
2f9115f97d | ||
|
|
3aa04a53fc | ||
|
|
373ed3cce2 | ||
|
|
8eec7cf7f9 | ||
|
|
79d53de4cf | ||
|
|
3a89398e39 | ||
|
|
e77867b2d3 | ||
|
|
be72fecfa8 | ||
|
|
0e52c2feea | ||
|
|
3268701ed6 | ||
|
|
488d388830 | ||
|
|
85aa98a841 | ||
|
|
74521345f6 | ||
|
|
6100bfaceb | ||
|
|
9123910f9d | ||
|
|
b7b7aebf1c | ||
|
|
6c75f10fae | ||
|
|
4dad7599e6 |
18
.github/codex/pr-merge-schema.json
vendored
Normal file
18
.github/codex/pr-merge-schema.json
vendored
Normal 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"]
|
||||
}
|
||||
26
.github/workflows/CI-PR_from_dev.yml
vendored
26
.github/workflows/CI-PR_from_dev.yml
vendored
@@ -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 }}
|
||||
|
||||
28
.github/workflows/CI-master_tests.yml
vendored
28
.github/workflows/CI-master_tests.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
steps:
|
||||
# checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
@@ -36,11 +36,11 @@ jobs:
|
||||
|
||||
# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
|
||||
- name: Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
# Setup NuGet
|
||||
- name: Setup NuGet.exe
|
||||
uses: nuget/setup-nuget@v1
|
||||
uses: nuget/setup-nuget@v2
|
||||
|
||||
# Restore the packages for testing
|
||||
- name: Restore the application
|
||||
@@ -48,23 +48,23 @@ jobs:
|
||||
|
||||
# build
|
||||
- name: run MSBuild
|
||||
run: msbuild $env:Solution_Path
|
||||
run: msbuild $env:Solution_Path /p:Configuration=$env:Configuration /p:UseSharedCompilation=false
|
||||
|
||||
# Execute all unit tests in the solution
|
||||
#- name: Execute unit tests
|
||||
# run: dotnet test $env:Solution_Path
|
||||
- name: Execute unit tests
|
||||
run: dotnet test $env:Solution_Path --configuration $env:Configuration
|
||||
|
||||
# Build & update all versions
|
||||
- name: Build all versions
|
||||
run: |
|
||||
echo "build x64"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="x64"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="x64" /p:UseSharedCompilation=false
|
||||
|
||||
echo "build x86"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="x86"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="x86" /p:UseSharedCompilation=false
|
||||
|
||||
echo "build Any CPU"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="Any CPU"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="Any CPU" /p:UseSharedCompilation=false
|
||||
|
||||
- name: Execute winPEAS -h
|
||||
shell: pwsh
|
||||
@@ -220,6 +220,7 @@ jobs:
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.23'
|
||||
cache: false
|
||||
- run: go version
|
||||
|
||||
# Build linpeas
|
||||
@@ -230,6 +231,9 @@ jobs:
|
||||
python3 -m builder.linpeas_builder --all --output linpeas_fat.sh
|
||||
python3 -m builder.linpeas_builder --all-no-fat --output linpeas.sh
|
||||
python3 -m builder.linpeas_builder --small --output linpeas_small.sh
|
||||
|
||||
- name: Run linPEAS builder tests
|
||||
run: python3 -m unittest discover -s linPEAS/tests -p "test_*.py"
|
||||
|
||||
# Build linpeas binaries
|
||||
- name: Build linpeas binaries
|
||||
@@ -362,7 +366,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Download repo
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
# Build linpeas
|
||||
- name: Build macpeas
|
||||
@@ -469,11 +473,11 @@ jobs:
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y%m%d')"
|
||||
run: echo "date=$(date +'%Y%m%d')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate random
|
||||
id: random_n
|
||||
run: echo "::set-output name=some_rand::$(openssl rand -hex 4)"
|
||||
run: echo "some_rand=$(openssl rand -hex 4)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Create the release
|
||||
- name: Create Release
|
||||
|
||||
28
.github/workflows/PR-tests.yml
vendored
28
.github/workflows/PR-tests.yml
vendored
@@ -8,6 +8,8 @@ on:
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
Build_and_test_winpeas_pr:
|
||||
runs-on: windows-latest
|
||||
@@ -20,7 +22,7 @@ jobs:
|
||||
steps:
|
||||
# checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
@@ -30,11 +32,11 @@ jobs:
|
||||
|
||||
# Add MSBuild to the PATH
|
||||
- name: Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
# Setup NuGet
|
||||
- name: Setup NuGet.exe
|
||||
uses: nuget/setup-nuget@v1
|
||||
uses: nuget/setup-nuget@v2
|
||||
|
||||
# Restore the packages for testing
|
||||
- name: Restore the application
|
||||
@@ -42,19 +44,23 @@ jobs:
|
||||
|
||||
# build
|
||||
- name: run MSBuild
|
||||
run: msbuild $env:Solution_Path
|
||||
run: msbuild $env:Solution_Path /p:Configuration=$env:Configuration /p:UseSharedCompilation=false
|
||||
|
||||
# Execute unit tests in the solution
|
||||
- name: Execute unit tests
|
||||
run: dotnet test $env:Solution_Path --configuration $env:Configuration
|
||||
|
||||
# Build all versions
|
||||
- name: Build all versions
|
||||
run: |
|
||||
echo "build x64"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="x64"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="x64" /p:UseSharedCompilation=false
|
||||
|
||||
echo "build x86"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="x86"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="x86" /p:UseSharedCompilation=false
|
||||
|
||||
echo "build Any CPU"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="Any CPU"
|
||||
msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="Any CPU" /p:UseSharedCompilation=false
|
||||
|
||||
- name: Execute winPEAS -h
|
||||
shell: pwsh
|
||||
@@ -105,7 +111,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Download repo
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
@@ -113,6 +119,7 @@ jobs:
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.23'
|
||||
cache: false
|
||||
- run: go version
|
||||
|
||||
# Build linpeas
|
||||
@@ -123,6 +130,9 @@ jobs:
|
||||
python3 -m builder.linpeas_builder --all --output linpeas_fat.sh
|
||||
python3 -m builder.linpeas_builder --all-no-fat --output linpeas.sh
|
||||
python3 -m builder.linpeas_builder --small --output linpeas_small.sh
|
||||
|
||||
- name: Run linPEAS builder tests
|
||||
run: python3 -m unittest discover -s linPEAS/tests -p "test_*.py"
|
||||
|
||||
# Run linpeas help as quick test
|
||||
- name: Run linpeas help
|
||||
@@ -161,7 +171,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Download repo
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
|
||||
158
.github/workflows/ci-master-failure-codex-pr.yml
vendored
Normal file
158
.github/workflows/ci-master-failure-codex-pr.yml
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
name: CI-master Failure Codex PR
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["CI-master_test"]
|
||||
types: [completed]
|
||||
|
||||
jobs:
|
||||
codex_fix_master_failure:
|
||||
if: >
|
||||
${{ github.event.workflow_run.conclusion == 'failure' &&
|
||||
github.event.workflow_run.head_branch == 'master' &&
|
||||
!startsWith(github.event.workflow_run.head_commit.message, 'Fix CI-master failures for run #') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
actions: read
|
||||
env:
|
||||
TARGET_BRANCH: master
|
||||
FIX_BRANCH: codex/ci-master-fix-${{ github.event.workflow_run.id }}
|
||||
steps:
|
||||
- name: Checkout failing commit
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: true
|
||||
token: ${{ secrets.CODEX_FIXER_TOKEN }}
|
||||
|
||||
- name: Configure git author
|
||||
run: |
|
||||
git config user.name "codex-action"
|
||||
git config user.email "codex-action@users.noreply.github.com"
|
||||
|
||||
- name: Create fix branch
|
||||
run: git checkout -b "$FIX_BRANCH"
|
||||
|
||||
- name: Fetch failure summary and failed-step logs
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
run: |
|
||||
failed_logs_file="$(pwd)/codex_failed_steps_logs.txt"
|
||||
if gh run view "$RUN_ID" --repo "${{ github.repository }}" --log-failed > "$failed_logs_file"; then
|
||||
if [ ! -s "$failed_logs_file" ]; then
|
||||
echo "No failed step logs were returned by gh run view --log-failed." > "$failed_logs_file"
|
||||
fi
|
||||
else
|
||||
echo "Failed to download failed step logs with gh run view --log-failed." > "$failed_logs_file"
|
||||
fi
|
||||
echo "FAILED_LOGS_PATH=$failed_logs_file" >> "$GITHUB_ENV"
|
||||
|
||||
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:
|
||||
RUN_URL: ${{ github.event.workflow_run.html_url }}
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
run: |
|
||||
{
|
||||
echo "You are fixing a failing CI-master_test run in ${{ github.repository }}."
|
||||
echo "The failing workflow run is: ${RUN_URL}"
|
||||
echo "The failing commit SHA is: ${HEAD_SHA}"
|
||||
echo "The target branch for the final PR is: ${TARGET_BRANCH}"
|
||||
echo ""
|
||||
echo "Failure summary:"
|
||||
cat codex_failure_summary.txt
|
||||
echo ""
|
||||
echo "Failed-step logs file absolute path (local runner): ${FAILED_LOGS_PATH}"
|
||||
echo "Read that file to inspect the exact failing logs."
|
||||
echo ""
|
||||
echo "Please identify the cause, apply an 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; changes will be committed and pushed automatically."
|
||||
} > 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 fix branch if changed
|
||||
id: push_fix
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "No changes to commit."
|
||||
echo "pushed=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
rm -f codex_failure_summary.txt codex_prompt.txt codex_failed_steps_logs.txt
|
||||
git add -A
|
||||
git reset -- codex_failure_summary.txt codex_prompt.txt codex_failed_steps_logs.txt
|
||||
git commit -m "Fix CI-master failures for run #${{ github.event.workflow_run.id }}"
|
||||
git push origin HEAD:"$FIX_BRANCH"
|
||||
echo "pushed=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create PR to master
|
||||
if: ${{ steps.push_fix.outputs.pushed == 'true' }}
|
||||
id: create_pr
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CODEX_FIXER_TOKEN }}
|
||||
RUN_URL: ${{ github.event.workflow_run.html_url }}
|
||||
run: |
|
||||
pr_url=$(gh pr create \
|
||||
--title "Fix CI-master_test failure (run #${{ github.event.workflow_run.id }})" \
|
||||
--body "Automated Codex fix for failing CI-master_test run: ${RUN_URL}" \
|
||||
--base "$TARGET_BRANCH" \
|
||||
--head "$FIX_BRANCH")
|
||||
echo "url=$pr_url" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Comment on created PR with Codex result
|
||||
if: ${{ steps.push_fix.outputs.pushed == 'true' && steps.run_codex.outputs.final-message != '' }}
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
PR_URL: ${{ steps.create_pr.outputs.url }}
|
||||
CODEX_MESSAGE: ${{ steps.run_codex.outputs.final-message }}
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
const prUrl = process.env.PR_URL;
|
||||
const match = prUrl.match(/\/pull\/(\d+)$/);
|
||||
if (!match) {
|
||||
core.info(`Could not parse PR number from URL: ${prUrl}`);
|
||||
return;
|
||||
}
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: Number(match[1]),
|
||||
body: process.env.CODEX_MESSAGE,
|
||||
});
|
||||
168
.github/workflows/codex-pr-triage.yml
vendored
Normal file
168
.github/workflows/codex-pr-triage.yml
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
name: Codex PR Triage
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["PR-tests"]
|
||||
types: [completed]
|
||||
|
||||
jobs:
|
||||
codex_triage:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
outputs:
|
||||
should_run: ${{ steps.gate.outputs.should_run }}
|
||||
pr_number: ${{ steps.gate.outputs.pr_number }}
|
||||
pr_title: ${{ steps.gate.outputs.pr_title }}
|
||||
pr_body: ${{ steps.gate.outputs.pr_body }}
|
||||
base_ref: ${{ steps.gate.outputs.base_ref }}
|
||||
head_ref: ${{ steps.gate.outputs.head_ref }}
|
||||
base_sha: ${{ steps.gate.outputs.base_sha }}
|
||||
head_sha: ${{ steps.gate.outputs.head_sha }}
|
||||
decision: ${{ steps.parse.outputs.decision }}
|
||||
message: ${{ steps.parse.outputs.message }}
|
||||
|
||||
steps:
|
||||
- name: Resolve PR context
|
||||
id: gate
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
pr_number="${{ github.event.workflow_run.pull_requests[0].number }}"
|
||||
if [ -z "$pr_number" ]; then
|
||||
echo "No pull request found for this workflow_run; skipping."
|
||||
echo "should_run=false" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_number=" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
author="$(gh pr view "$pr_number" --json author --jq .author.login)"
|
||||
if [ "$author" != "carlospolop" ]; then
|
||||
echo "PR author is $author; skipping."
|
||||
echo "should_run=false" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pr_title="$(gh pr view "$pr_number" --json title --jq .title)"
|
||||
pr_body="$(gh pr view "$pr_number" --json body --jq .body)"
|
||||
base_ref="$(gh pr view "$pr_number" --json baseRefName --jq .baseRefName)"
|
||||
head_ref="$(gh pr view "$pr_number" --json headRefName --jq .headRefName)"
|
||||
base_sha="$(gh pr view "$pr_number" --json baseRefOid --jq .baseRefOid)"
|
||||
head_sha="$(gh pr view "$pr_number" --json headRefOid --jq .headRefOid)"
|
||||
|
||||
echo "should_run=true" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_title<<EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "$pr_title" >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_body<<EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "$pr_body" >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "base_ref=$base_ref" >> "$GITHUB_OUTPUT"
|
||||
echo "head_ref=$head_ref" >> "$GITHUB_OUTPUT"
|
||||
echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT"
|
||||
echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout PR merge ref
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: refs/pull/${{ steps.gate.outputs.pr_number }}/merge
|
||||
if: ${{ steps.gate.outputs.should_run == 'true' }}
|
||||
|
||||
- name: Pre-fetch base and head refs
|
||||
if: ${{ steps.gate.outputs.should_run == 'true' }}
|
||||
run: |
|
||||
git fetch --no-tags origin \
|
||||
${{ steps.gate.outputs.base_ref }} \
|
||||
+refs/pull/${{ steps.gate.outputs.pr_number }}/head
|
||||
|
||||
- name: Run Codex
|
||||
id: run_codex
|
||||
if: ${{ steps.gate.outputs.should_run == 'true' }}
|
||||
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 #${{ steps.gate.outputs.pr_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:
|
||||
----
|
||||
${{ steps.gate.outputs.pr_title }}
|
||||
${{ steps.gate.outputs.pr_body }}
|
||||
|
||||
Review ONLY the changes introduced by the PR:
|
||||
git log --oneline ${{ steps.gate.outputs.base_sha }}...${{ steps.gate.outputs.head_sha }}
|
||||
|
||||
Output JSON only, following the provided schema.
|
||||
|
||||
- name: Parse Codex decision
|
||||
id: parse
|
||||
if: ${{ steps.gate.outputs.should_run == 'true' }}
|
||||
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: ${{ github.event.workflow_run.conclusion == 'success' && needs.codex_triage.outputs.should_run == 'true' && 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: ${{ needs.codex_triage.outputs.pr_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: ${{ needs.codex_triage.outputs.pr_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,
|
||||
});
|
||||
193
.github/workflows/pr-failure-codex-dispatch.yml
vendored
Normal file
193
.github/workflows/pr-failure-codex-dispatch.yml
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
name: PR Failure Codex Dispatch
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["PR-tests"]
|
||||
types: [completed]
|
||||
|
||||
jobs:
|
||||
resolve_pr_context:
|
||||
if: >
|
||||
${{ github.event.workflow_run.conclusion == 'failure' &&
|
||||
github.event.workflow_run.pull_requests &&
|
||||
github.event.workflow_run.pull_requests[0] &&
|
||||
!startsWith(github.event.workflow_run.head_commit.message, 'Fix CI failures for PR #') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: read
|
||||
issues: read
|
||||
outputs:
|
||||
number: ${{ steps.pr_context.outputs.number }}
|
||||
author: ${{ steps.pr_context.outputs.author }}
|
||||
head_repo: ${{ steps.pr_context.outputs.head_repo }}
|
||||
head_branch: ${{ steps.pr_context.outputs.head_branch }}
|
||||
should_run: ${{ steps.pr_context.outputs.should_run }}
|
||||
steps:
|
||||
- name: Resolve PR context
|
||||
id: pr_context
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
pr_author=$(gh api -H "Accept: application/vnd.github+json" \
|
||||
/repos/${{ github.repository }}/pulls/${PR_NUMBER} \
|
||||
--jq '.user.login')
|
||||
pr_head_repo=$(gh api -H "Accept: application/vnd.github+json" \
|
||||
/repos/${{ github.repository }}/pulls/${PR_NUMBER} \
|
||||
--jq '.head.repo.full_name')
|
||||
pr_head_branch=$(gh api -H "Accept: application/vnd.github+json" \
|
||||
/repos/${{ github.repository }}/pulls/${PR_NUMBER} \
|
||||
--jq '.head.ref')
|
||||
pr_labels=$(gh api -H "Accept: application/vnd.github+json" \
|
||||
/repos/${{ github.repository }}/issues/${PR_NUMBER} \
|
||||
--jq '.labels[].name')
|
||||
if echo "$pr_labels" | grep -q "^codex-fix-attempted$"; then
|
||||
echo "codex fix already attempted for PR #${PR_NUMBER}; skipping."
|
||||
should_run=false
|
||||
else
|
||||
should_run=true
|
||||
fi
|
||||
{
|
||||
echo "number=${PR_NUMBER}"
|
||||
echo "author=${pr_author}"
|
||||
echo "head_repo=${pr_head_repo}"
|
||||
echo "head_branch=${pr_head_branch}"
|
||||
echo "should_run=${should_run}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
codex_on_failure:
|
||||
needs: resolve_pr_context
|
||||
if: ${{ needs.resolve_pr_context.outputs.author == 'carlospolop' && needs.resolve_pr_context.outputs.should_run == 'true' }}
|
||||
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: ${{ needs.resolve_pr_context.outputs.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: Mark fix attempt
|
||||
env:
|
||||
PR_NUMBER: ${{ needs.resolve_pr_context.outputs.number }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh api -X POST -H "Accept: application/vnd.github+json" \
|
||||
/repos/${{ github.repository }}/issues/${PR_NUMBER}/labels \
|
||||
-f labels[]=codex-fix-attempted
|
||||
|
||||
- name: Checkout PR head
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: ${{ needs.resolve_pr_context.outputs.head_repo }}
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: true
|
||||
token: ${{ secrets.CODEX_FIXER_TOKEN }}
|
||||
|
||||
- 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: ${{ needs.resolve_pr_context.outputs.number }}
|
||||
RUN_URL: ${{ github.event.workflow_run.html_url }}
|
||||
HEAD_BRANCH: ${{ needs.resolve_pr_context.outputs.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: ${{ needs.resolve_pr_context.outputs.head_branch }}
|
||||
PR_NUMBER: ${{ needs.resolve_pr_context.outputs.number }}
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "No changes to commit."
|
||||
exit 0
|
||||
fi
|
||||
rm -f codex_failure_summary.txt codex_prompt.txt
|
||||
git add -A
|
||||
git reset -- codex_failure_summary.txt codex_prompt.txt
|
||||
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: ${{ needs.resolve_pr_context.outputs.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,
|
||||
});
|
||||
3
README.md
Executable file → Normal file
3
README.md
Executable file → Normal 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.
|
||||
|
||||
|
||||
@@ -1705,7 +1705,7 @@ search:
|
||||
auto_check: True
|
||||
exec:
|
||||
- '( redis-server --version || echo_not_found "redis-server") 2>/dev/null'
|
||||
- if [ "`redis-cli INFO 2>/dev/null`" ] && ! [ "`redis-cli INFO 2>/dev/null | grep -i NOAUTH`" ]; then echo "Redis isn't password protected" | sed -${E} "s,.*,${SED_RED},"; fi
|
||||
- redis_info="$(if [ "$TIMEOUT" ]; then $TIMEOUT 2 redis-cli INFO 2>/dev/null; else redis-cli INFO 2>/dev/null; fi)"; if [ "$redis_info" ] && ! echo "$redis_info" | grep -i NOAUTH; then echo "Redis isn't password protected" | sed -${E} "s,.*,${SED_RED},"; fi
|
||||
|
||||
files:
|
||||
- name: "redis.conf"
|
||||
@@ -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:
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
Check the **Local Linux Privilege Escalation checklist** from **[book.hacktricks.wiki](https://book.hacktricks.wiki/en/linux-hardening/linux-privilege-escalation-checklist.html)**.
|
||||
|
||||
> **Dec 2025 update:** linpeas now inspects Linux kernels for CVE-2025-38352 (POSIX CPU timers race) by combining CONFIG_POSIX_CPU_TIMERS_TASK_WORK state with kernel build information, so you immediately know if publicly available PoCs might succeed.
|
||||
|
||||
[](https://asciinema.org/a/309566)
|
||||
|
||||
## MacPEAS
|
||||
@@ -98,6 +100,10 @@ The goal of this script is to search for possible **Privilege Escalation Paths**
|
||||
|
||||
This script doesn't have any dependency.
|
||||
|
||||
### Recent updates
|
||||
|
||||
- **Dec 2025**: Added detection for sudo configurations that expose restic's `--password-command` helper, a common privilege escalation vector observed in real environments.
|
||||
|
||||
It uses **/bin/sh** syntax, so can run in anything supporting `sh` (and the binaries and parameters used).
|
||||
|
||||
By default, **linpeas won't write anything to disk and won't try to login as any other user using `su`**.
|
||||
|
||||
@@ -30,10 +30,9 @@
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 0
|
||||
|
||||
if apt list --installed 2>/dev/null | grep -q 'polkit.*0\.105-26' || \
|
||||
yum list installed 2>/dev/null | grep -q 'polkit.*\(0\.117-2\|0\.115-6\)' || \
|
||||
rpm -qa 2>/dev/null | grep -q 'polkit.*\(0\.117-2\|0\.115-6\)'; then
|
||||
if apt list --installed 2>/dev/null | grep -E 'polkit.*0\.105-26' | grep -qEv 'ubuntu1\.[1-9]' || \
|
||||
yum list installed 2>/dev/null | grep -qE 'polkit.*\(0\.117-2\|0\.115-6\|0\.11[3-9]\)' || \
|
||||
rpm -qa 2>/dev/null | grep -qE 'polkit.*\(0\.117-2\|0\.115-6\|0\.11[3-9]\)'; then
|
||||
echo "Vulnerable to CVE-2021-3560" | sed -${E} "s,.*,${SED_RED_YELLOW},"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
|
||||
@@ -30,11 +30,33 @@
|
||||
# Functions Used: echo_not_found, print_2title, print_list, warn_exec
|
||||
# Global Variables:
|
||||
# Initial Functions:
|
||||
# Generated Global Variables: $ASLR, $hypervisorflag, $detectedvirt, $unpriv_userns_clone, $perf_event_paranoid, $mmap_min_addr, $ptrace_scope, $dmesg_restrict, $kptr_restrict, $unpriv_bpf_disabled
|
||||
# Generated Global Variables: $ASLR, $hypervisorflag, $detectedvirt, $unpriv_userns_clone, $perf_event_paranoid, $mmap_min_addr, $ptrace_scope, $dmesg_restrict, $kptr_restrict, $unpriv_bpf_disabled, $protected_symlinks, $protected_hardlinks, $label, $sysctl_path, $sysctl_var, $zero_color, $nonzero_color, $sysctl_value
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 0
|
||||
|
||||
|
||||
print_sysctl_eq_zero() {
|
||||
local label="$1"
|
||||
local sysctl_path="$2"
|
||||
local sysctl_var="$3"
|
||||
local zero_color="$4"
|
||||
local nonzero_color="$5"
|
||||
local sysctl_value
|
||||
|
||||
print_list "$label" "$NC"
|
||||
sysctl_value=$(cat "$sysctl_path" 2>/dev/null)
|
||||
eval "$sysctl_var=\$sysctl_value"
|
||||
if [ -z "$sysctl_value" ]; then
|
||||
echo_not_found "$sysctl_path"
|
||||
else
|
||||
if [ "$sysctl_value" -eq 0 ]; then
|
||||
echo "0" | sed -${E} "s,0,${zero_color},"
|
||||
else
|
||||
echo "$sysctl_value" | sed -${E} "s,.*,${nonzero_color},g"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
#-- SY) AppArmor
|
||||
print_2title "Protections"
|
||||
print_list "AppArmor enabled? .............. "$NC
|
||||
@@ -81,51 +103,25 @@ print_list "User namespace? ................ "$NC
|
||||
if [ "$(cat /proc/self/uid_map 2>/dev/null)" ]; then echo "enabled" | sed "s,enabled,${SED_GREEN},"; else echo "disabled" | sed "s,disabled,${SED_RED},"; fi
|
||||
|
||||
#-- SY) Unprivileged user namespaces
|
||||
print_list "unpriv_userns_clone? ........... "$NC
|
||||
unpriv_userns_clone=$(cat /proc/sys/kernel/unprivileged_userns_clone 2>/dev/null)
|
||||
if [ -z "$unpriv_userns_clone" ]; then
|
||||
echo_not_found "/proc/sys/kernel/unprivileged_userns_clone"
|
||||
else
|
||||
if [ "$unpriv_userns_clone" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_GREEN},"; else echo "$unpriv_userns_clone" | sed -${E} "s,.*,${SED_RED},g"; fi
|
||||
fi
|
||||
print_sysctl_eq_zero "unpriv_userns_clone? ........... " "/proc/sys/kernel/unprivileged_userns_clone" "unpriv_userns_clone" "$SED_GREEN" "$SED_RED"
|
||||
|
||||
#-- SY) Unprivileged eBPF
|
||||
print_list "unpriv_bpf_disabled? ........... "$NC
|
||||
unpriv_bpf_disabled=$(cat /proc/sys/kernel/unprivileged_bpf_disabled 2>/dev/null)
|
||||
if [ -z "$unpriv_bpf_disabled" ]; then
|
||||
echo_not_found "/proc/sys/kernel/unprivileged_bpf_disabled"
|
||||
else
|
||||
if [ "$unpriv_bpf_disabled" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$unpriv_bpf_disabled" | sed -${E} "s,.*,${SED_GREEN},g"; fi
|
||||
fi
|
||||
print_sysctl_eq_zero "unpriv_bpf_disabled? ........... " "/proc/sys/kernel/unprivileged_bpf_disabled" "unpriv_bpf_disabled" "$SED_RED" "$SED_GREEN"
|
||||
|
||||
#-- SY) cgroup2
|
||||
print_list "Cgroup2 enabled? ............... "$NC
|
||||
([ "$(grep cgroup2 /proc/filesystems 2>/dev/null)" ] && echo "enabled" || echo "disabled") | sed "s,disabled,${SED_RED}," | sed "s,enabled,${SED_GREEN},"
|
||||
|
||||
#-- SY) Kernel hardening sysctls
|
||||
print_list "kptr_restrict? ................. "$NC
|
||||
kptr_restrict=$(cat /proc/sys/kernel/kptr_restrict 2>/dev/null)
|
||||
if [ -z "$kptr_restrict" ]; then
|
||||
echo_not_found "/proc/sys/kernel/kptr_restrict"
|
||||
else
|
||||
if [ "$kptr_restrict" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$kptr_restrict" | sed -${E} "s,.*,${SED_GREEN},g"; fi
|
||||
fi
|
||||
print_sysctl_eq_zero "kptr_restrict? ................. " "/proc/sys/kernel/kptr_restrict" "kptr_restrict" "$SED_RED" "$SED_GREEN"
|
||||
|
||||
print_list "dmesg_restrict? ................ "$NC
|
||||
dmesg_restrict=$(cat /proc/sys/kernel/dmesg_restrict 2>/dev/null)
|
||||
if [ -z "$dmesg_restrict" ]; then
|
||||
echo_not_found "/proc/sys/kernel/dmesg_restrict"
|
||||
else
|
||||
if [ "$dmesg_restrict" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$dmesg_restrict" | sed -${E} "s,.*,${SED_GREEN},g"; fi
|
||||
fi
|
||||
print_sysctl_eq_zero "dmesg_restrict? ................ " "/proc/sys/kernel/dmesg_restrict" "dmesg_restrict" "$SED_RED" "$SED_GREEN"
|
||||
|
||||
print_list "ptrace_scope? .................. "$NC
|
||||
ptrace_scope=$(cat /proc/sys/kernel/yama/ptrace_scope 2>/dev/null)
|
||||
if [ -z "$ptrace_scope" ]; then
|
||||
echo_not_found "/proc/sys/kernel/yama/ptrace_scope"
|
||||
else
|
||||
if [ "$ptrace_scope" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$ptrace_scope" | sed -${E} "s,.*,${SED_GREEN},g"; fi
|
||||
fi
|
||||
print_sysctl_eq_zero "ptrace_scope? .................. " "/proc/sys/kernel/yama/ptrace_scope" "ptrace_scope" "$SED_RED" "$SED_GREEN"
|
||||
|
||||
print_sysctl_eq_zero "protected_symlinks? ............ " "/proc/sys/fs/protected_symlinks" "protected_symlinks" "$SED_RED" "$SED_GREEN"
|
||||
|
||||
print_sysctl_eq_zero "protected_hardlinks? ........... " "/proc/sys/fs/protected_hardlinks" "protected_hardlinks" "$SED_RED" "$SED_GREEN"
|
||||
|
||||
print_list "perf_event_paranoid? ........... "$NC
|
||||
perf_event_paranoid=$(cat /proc/sys/kernel/perf_event_paranoid 2>/dev/null)
|
||||
@@ -135,13 +131,7 @@ else
|
||||
if [ "$perf_event_paranoid" -le 1 ]; then echo "$perf_event_paranoid" | sed -${E} "s,.*,${SED_RED},g"; else echo "$perf_event_paranoid" | sed -${E} "s,.*,${SED_GREEN},g"; fi
|
||||
fi
|
||||
|
||||
print_list "mmap_min_addr? ................. "$NC
|
||||
mmap_min_addr=$(cat /proc/sys/vm/mmap_min_addr 2>/dev/null)
|
||||
if [ -z "$mmap_min_addr" ]; then
|
||||
echo_not_found "/proc/sys/vm/mmap_min_addr"
|
||||
else
|
||||
if [ "$mmap_min_addr" -eq 0 ]; then echo "0" | sed -${E} "s,0,${SED_RED},"; else echo "$mmap_min_addr" | sed -${E} "s,.*,${SED_GREEN},g"; fi
|
||||
fi
|
||||
print_sysctl_eq_zero "mmap_min_addr? ................. " "/proc/sys/vm/mmap_min_addr" "mmap_min_addr" "$SED_RED" "$SED_GREEN"
|
||||
|
||||
print_list "lockdown mode? ................. "$NC
|
||||
if [ -f "/sys/kernel/security/lockdown" ]; then
|
||||
|
||||
@@ -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
|
||||
@@ -4,6 +4,7 @@
|
||||
# Last Update: 07-03-2024
|
||||
# Description: Check for additional disk information and system resources relevant to privilege escalation:
|
||||
# - Disk utilization
|
||||
# - Inode usage
|
||||
# - System resources
|
||||
# - Storage statistics
|
||||
# - Common vulnerable scenarios:
|
||||
@@ -44,4 +45,8 @@ if [ "$EXTRA_CHECKS" ] || [ "$DEBUG" ]; then
|
||||
(df -h || lsblk) 2>/dev/null || echo_not_found "df and lsblk"
|
||||
warn_exec free 2>/dev/null
|
||||
echo ""
|
||||
fi
|
||||
|
||||
print_2title "Inode usage"
|
||||
warn_exec df -i 2>/dev/null
|
||||
echo ""
|
||||
fi
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# Title: Cloud - AWS ECS
|
||||
# ID: CL_AWS_ECS
|
||||
# Author: Carlos Polop
|
||||
# Last Update: 22-08-2023
|
||||
# Last Update: 17-01-2026
|
||||
# Description: AWS ECS Enumeration
|
||||
# License: GNU GPL
|
||||
# Version: 1.0
|
||||
# Functions Used: check_aws_ecs, exec_with_jq, print_2title, print_3title
|
||||
# Global Variables: $aws_ecs_metadata_uri, $aws_ecs_service_account_uri, $is_aws_ecs
|
||||
# Initial Functions: check_aws_ecs
|
||||
# Generated Global Variables: $aws_ecs_req
|
||||
# Generated Global Variables: $aws_ecs_req, $aws_exec_env, $ecs_task_metadata, $launch_type, $network_modes, $imds_tool, $imds_token, $imds_roles, $imds_http_code, $ecs_block_line, $ecs_host_line, $iptables_cmd, $docker_rules, $first_role
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
@@ -44,5 +44,146 @@ if [ "$is_aws_ecs" = "Yes" ]; then
|
||||
else
|
||||
echo "I couldn't find AWS_CONTAINER_CREDENTIALS_RELATIVE_URI env var to get IAM role info (the task is running without a task role probably)"
|
||||
fi
|
||||
|
||||
print_3title "ECS task metadata hints"
|
||||
aws_exec_env=$(printenv AWS_EXECUTION_ENV 2>/dev/null)
|
||||
if [ "$aws_exec_env" ]; then
|
||||
printf "AWS_EXECUTION_ENV=%s\n" "$aws_exec_env"
|
||||
fi
|
||||
|
||||
ecs_task_metadata=""
|
||||
if [ "$aws_ecs_metadata_uri" ]; then
|
||||
ecs_task_metadata=$(eval $aws_ecs_req "$aws_ecs_metadata_uri/task" 2>/dev/null)
|
||||
fi
|
||||
|
||||
if [ "$ecs_task_metadata" ]; then
|
||||
launch_type=$(printf "%s" "$ecs_task_metadata" | grep -oE '"LaunchType":"[^"]+"' | head -n 1 | cut -d '"' -f4)
|
||||
if [ "$launch_type" ]; then
|
||||
printf "ECS LaunchType reported: %s\n" "$launch_type"
|
||||
fi
|
||||
network_modes=$(printf "%s" "$ecs_task_metadata" | grep -oE '"NetworkMode":"[^"]+"' | cut -d '"' -f4 | sort -u | tr '\n' ' ')
|
||||
if [ "$network_modes" ]; then
|
||||
printf "Reported NetworkMode(s): %s\n" "$network_modes"
|
||||
fi
|
||||
else
|
||||
echo "Unable to fetch task metadata (check ECS_CONTAINER_METADATA_URI)."
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
print_3title "IMDS reachability from this task"
|
||||
imds_token=""
|
||||
imds_roles=""
|
||||
imds_http_code=""
|
||||
imds_tool=""
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
imds_tool="curl"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
imds_tool="wget"
|
||||
fi
|
||||
|
||||
if [ "$imds_tool" = "curl" ]; then
|
||||
imds_token=$(curl -s --connect-timeout 2 --max-time 2 -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null)
|
||||
if [ "$imds_token" ]; then
|
||||
printf "[!] IMDSv2 token request succeeded (metadata reachable from this task).\n"
|
||||
imds_roles=$(curl -s --connect-timeout 2 --max-time 2 -H "X-aws-ec2-metadata-token: $imds_token" "http://169.254.169.254/latest/meta-data/iam/security-credentials/" 2>/dev/null | tr '\n' ' ')
|
||||
if [ "$imds_roles" ]; then
|
||||
printf " Instance profile role(s) exposed via IMDS: %s\n" "$imds_roles"
|
||||
first_role=$(printf "%s" "$imds_roles" | awk '{print $1}')
|
||||
if [ "$first_role" ]; then
|
||||
printf " Example: curl -H 'X-aws-ec2-metadata-token: <TOKEN>' http://169.254.169.254/latest/meta-data/iam/security-credentials/%s\n" "$first_role"
|
||||
fi
|
||||
else
|
||||
printf " No IAM role names returned (instance profile might be missing).\n"
|
||||
fi
|
||||
else
|
||||
imds_http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 --max-time 2 "http://169.254.169.254/latest/meta-data/" 2>/dev/null)
|
||||
case "$imds_http_code" in
|
||||
000|"")
|
||||
printf "[i] IMDS endpoint did not respond (likely blocked via hop-limit or host firewalling).\n"
|
||||
;;
|
||||
401)
|
||||
printf "[i] IMDS requires v2 tokens but token requests are being blocked (bridge-mode tasks rely on this when hop limit = 1).\n"
|
||||
;;
|
||||
*)
|
||||
printf "[i] IMDS GET returned HTTP %s (investigate host configuration).\n" "$imds_http_code"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
elif [ "$imds_tool" = "wget" ]; then
|
||||
imds_token=$(wget -q -O - --timeout=2 --tries=1 --method=PUT --header="X-aws-ec2-metadata-token-ttl-seconds: 21600" "http://169.254.169.254/latest/api/token" 2>/dev/null)
|
||||
if [ "$imds_token" ]; then
|
||||
printf "[!] IMDSv2 token request succeeded (metadata reachable from this task).\n"
|
||||
imds_roles=$(wget -q -O - --timeout=2 --tries=1 --header="X-aws-ec2-metadata-token: $imds_token" "http://169.254.169.254/latest/meta-data/iam/security-credentials/" 2>/dev/null | tr '\n' ' ')
|
||||
if [ "$imds_roles" ]; then
|
||||
printf " Instance profile role(s) exposed via IMDS: %s\n" "$imds_roles"
|
||||
else
|
||||
printf " No IAM role names returned (instance profile might be missing).\n"
|
||||
fi
|
||||
else
|
||||
wget --server-response -O /dev/null --timeout=2 --tries=1 "http://169.254.169.254/latest/meta-data/" 2>&1 | awk 'BEGIN{code=""} /^ HTTP/{code=$2} END{ if(code!="") { printf("[i] IMDS GET returned HTTP %s (token could not be retrieved).\n", code); } else { print "[i] IMDS endpoint did not respond (likely blocked)."; } }'
|
||||
fi
|
||||
else
|
||||
echo "Neither curl nor wget were found, I can't test IMDS reachability."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
print_3title "ECS agent IMDS settings"
|
||||
if [ -r "/etc/ecs/ecs.config" ]; then
|
||||
ecs_block_line=$(grep -E "^ECS_AWSVPC_BLOCK_IMDS=" /etc/ecs/ecs.config 2>/dev/null | tail -n 1)
|
||||
ecs_host_line=$(grep -E "^ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST=" /etc/ecs/ecs.config 2>/dev/null | tail -n 1)
|
||||
if [ "$ecs_block_line" ]; then
|
||||
printf "%s\n" "$ecs_block_line"
|
||||
if echo "$ecs_block_line" | grep -qi "=true"; then
|
||||
echo " -> awsvpc-mode tasks should be blocked from IMDS by the ECS agent."
|
||||
else
|
||||
echo " -> awsvpc-mode tasks can still reach IMDS (set this to true to block)."
|
||||
fi
|
||||
else
|
||||
echo "ECS_AWSVPC_BLOCK_IMDS not set (awsvpc tasks inherit host IMDS reachability)."
|
||||
fi
|
||||
|
||||
if [ "$ecs_host_line" ]; then
|
||||
printf "%s\n" "$ecs_host_line"
|
||||
if echo "$ecs_host_line" | grep -qi "=false"; then
|
||||
echo " -> Host-network tasks lose IAM task roles but IMDS is blocked."
|
||||
else
|
||||
echo " -> Host-network tasks keep IAM task roles and retain IMDS access."
|
||||
fi
|
||||
else
|
||||
echo "ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST not set (defaults keep IMDS reachable for host-mode tasks)."
|
||||
fi
|
||||
else
|
||||
echo "Cannot read /etc/ecs/ecs.config (file missing or permissions denied)."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
print_3title "DOCKER-USER IMDS filtering"
|
||||
iptables_cmd=""
|
||||
if command -v iptables >/dev/null 2>&1; then
|
||||
iptables_cmd=$(command -v iptables)
|
||||
elif command -v iptables-nft >/dev/null 2>&1; then
|
||||
iptables_cmd=$(command -v iptables-nft)
|
||||
fi
|
||||
|
||||
if [ "$iptables_cmd" ]; then
|
||||
docker_rules=$($iptables_cmd -S DOCKER-USER 2>/dev/null)
|
||||
if [ $? -eq 0 ]; then
|
||||
if [ "$docker_rules" ]; then
|
||||
echo "$docker_rules"
|
||||
else
|
||||
echo "(DOCKER-USER chain exists but no rules were found)"
|
||||
fi
|
||||
if echo "$docker_rules" | grep -q "169\\.254\\.169\\.254"; then
|
||||
echo " -> IMDS traffic is explicitly filtered before Docker NAT."
|
||||
else
|
||||
echo " -> No DOCKER-USER rule drops 169.254.169.254 traffic (bridge tasks rely on hop limit or host firewalling)."
|
||||
fi
|
||||
else
|
||||
echo "Unable to read DOCKER-USER chain (missing chain or insufficient permissions)."
|
||||
fi
|
||||
else
|
||||
echo "iptables binary not found; cannot inspect DOCKER-USER chain."
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# License: GNU GPL
|
||||
# Version: 1.2
|
||||
# Functions Used: echo_not_found, print_2title, print_info, print_3title
|
||||
# Global Variables: $EXTRA_CHECKS, $SEARCH_IN_FOLDER, $IAMROOT, $WRITABLESYSTEMDPATH
|
||||
# Global Variables: $EXTRA_CHECKS, $IAMROOT, $SEARCH_IN_FOLDER, $TIMEOUT, $WRITABLESYSTEMDPATH
|
||||
# Initial Functions:
|
||||
# Generated Global Variables: $service_unit, $service_path, $service_content, $finding, $findings, $service_file, $exec_path, $exec_paths, $service, $line, $target_file, $target_exec, $relpath1, $relpath2
|
||||
# Fat linpeas: 0
|
||||
@@ -178,7 +178,11 @@ if ! [ "$SEARCH_IN_FOLDER" ]; then
|
||||
if [ "$EXTRA_CHECKS" ]; then
|
||||
echo ""
|
||||
print_3title "Service versions and status:"
|
||||
(service --status-all || service -e || chkconfig --list || rc-status || launchctl list) 2>/dev/null || echo_not_found "service|chkconfig|rc-status|launchctl"
|
||||
if [ "$TIMEOUT" ]; then
|
||||
$TIMEOUT 30 sh -c "(service --status-all || service -e || chkconfig --list || rc-status || launchctl list) 2>/dev/null" || echo_not_found "service|chkconfig|rc-status|launchctl"
|
||||
else
|
||||
(service --status-all || service -e || chkconfig --list || rc-status || launchctl list) 2>/dev/null || echo_not_found "service|chkconfig|rc-status|launchctl"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check systemd path writability
|
||||
@@ -190,4 +194,4 @@ if ! [ "$SEARCH_IN_FOLDER" ]; then
|
||||
fi
|
||||
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# Functions Used: print_2title, print_list, echo_not_found
|
||||
# Global Variables: $SEARCH_IN_FOLDER, $Wfolders, $SED_RED, $SED_RED_YELLOW, $NC
|
||||
# Initial Functions:
|
||||
# Generated Global Variables: $WRITABLESYSTEMDPATH, $line, $service, $file, $version, $user, $caps, $path, $path_line, $service_file, $exec_line, $cmd
|
||||
# Generated Global Variables: $WRITABLESYSTEMDPATH, $line, $service, $file, $version, $user, $caps, $path, $path_line, $service_file, $exec_line, $exec_value, $cmd, $cmd_path
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
@@ -116,18 +116,20 @@ if ! [ "$SEARCH_IN_FOLDER" ]; then
|
||||
# Check ExecStart paths
|
||||
grep -E "ExecStart|ExecStartPre|ExecStartPost" "$service_file" 2>/dev/null |
|
||||
while read -r exec_line; do
|
||||
# Extract the first word after ExecStart* as the command
|
||||
cmd=$(echo "$exec_line" | awk '{print $2}' | tr -d '"')
|
||||
# Extract the rest as arguments
|
||||
args=$(echo "$exec_line" | awk '{$1=$2=""; print $0}' | tr -d '"')
|
||||
# Extract command from the right side of Exec*=, not from argv
|
||||
exec_value="${exec_line#*=}"
|
||||
exec_value=$(echo "$exec_value" | sed 's/^[[:space:]]*//')
|
||||
cmd=$(echo "$exec_value" | awk '{print $1}' | tr -d '"')
|
||||
# Strip systemd command prefixes (-, @, :, +, !) before path checks
|
||||
cmd_path=$(echo "$cmd" | sed -E 's/^[-@:+!]+//')
|
||||
|
||||
# Only check the command path, not arguments
|
||||
if [ -n "$cmd" ] && [ -w "$cmd" ]; then
|
||||
echo "$service: $cmd (from $exec_line)" | sed -${E} "s,.*,${SED_RED},g"
|
||||
if [ -n "$cmd_path" ] && [ -w "$cmd_path" ]; then
|
||||
echo "$service: $cmd_path (from $exec_line)" | sed -${E} "s,.*,${SED_RED},g"
|
||||
fi
|
||||
# Check for relative paths only in the command, not arguments
|
||||
if [ -n "$cmd" ] && [ "${cmd#/}" = "$cmd" ] && ! echo "$cmd" | grep -qE '^-|^--'; then
|
||||
echo "$service: Uses relative path '$cmd' (from $exec_line)" | sed -${E} "s,.*,${SED_RED},g"
|
||||
if [ -n "$cmd_path" ] && [ "${cmd_path#/}" = "$cmd_path" ] && [ "${cmd_path#\$}" = "$cmd_path" ]; then
|
||||
echo "$service: Uses relative path '$cmd_path' (from $exec_line)" | sed -${E} "s,.*,${SED_RED},g"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
@@ -153,4 +155,4 @@ if ! [ "$SEARCH_IN_FOLDER" ]; then
|
||||
fi
|
||||
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Title: Processes & Cron & Services & Timers - Deleted open files
|
||||
# ID: PR_Deleted_open_files
|
||||
# Author: Carlos Polop
|
||||
# Last Update: 2025-01-07
|
||||
# Description: Identify deleted files still held open by running processes
|
||||
# License: GNU GPL
|
||||
# Version: 1.0
|
||||
# Functions Used: print_2title, print_info
|
||||
# Global Variables: $DEBUG, $EXTRA_CHECKS, $E, $SED_RED
|
||||
# Initial Functions:
|
||||
# Generated Global Variables:
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
if [ "$(command -v lsof 2>/dev/null || echo -n '')" ] || [ "$DEBUG" ]; then
|
||||
print_2title "Deleted files still open"
|
||||
print_info "Open deleted files can hide tools and still consume disk space"
|
||||
lsof +L1 2>/dev/null | sed -${E} "s,\\(deleted\\),${SED_RED},g"
|
||||
echo ""
|
||||
elif [ "$EXTRA_CHECKS" ] || [ "$DEBUG" ]; then
|
||||
print_2title "Deleted files still open"
|
||||
print_info "lsof not found, scanning /proc for deleted file descriptors"
|
||||
ls -l /proc/[0-9]*/fd 2>/dev/null | grep "(deleted)" | sed -${E} "s,\\(deleted\\),${SED_RED},g" | head -n 200
|
||||
echo ""
|
||||
fi
|
||||
@@ -23,6 +23,7 @@ if ! [ "$SEARCH_IN_FOLDER" ]; then
|
||||
incrontab -l 2>/dev/null
|
||||
ls -alR /etc/cron* /var/spool/cron/crontabs /var/spool/anacron 2>/dev/null | sed -${E} "s,$cronjobsG,${SED_GREEN},g" | sed "s,$cronjobsB,${SED_RED},g"
|
||||
cat /etc/cron* /etc/at* /etc/anacrontab /var/spool/cron/crontabs/* /etc/incron.d/* /var/spool/incron/* 2>/dev/null | tr -d "\r" | grep -v "^#" | sed -${E} "s,$Wfolders,${SED_RED_YELLOW},g" | sed -${E} "s,$sh_usrs,${SED_LIGHT_CYAN}," | sed "s,$USER,${SED_LIGHT_MAGENTA}," | sed -${E} "s,$nosh_usrs,${SED_BLUE}," | sed "s,root,${SED_RED},"
|
||||
grep -Hn '^PATH=' /etc/crontab /etc/cron.d/* 2>/dev/null | sed -${E} "s,$Wfolders,${SED_RED_YELLOW},g"
|
||||
crontab -l -u "$USER" 2>/dev/null | tr -d "\r"
|
||||
ls -lR /usr/lib/cron/tabs/ /private/var/at/jobs /var/at/tabs/ /etc/periodic/ 2>/dev/null | sed -${E} "s,$cronjobsG,${SED_GREEN},g" | sed "s,$cronjobsB,${SED_RED},g" #MacOS paths
|
||||
atq 2>/dev/null
|
||||
@@ -247,4 +248,4 @@ else
|
||||
print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#scheduledcron-jobs"
|
||||
find "$SEARCH_IN_FOLDER" '(' -type d -or -type f ')' '(' -name "cron*" -or -name "anacron" -or -name "anacrontab" -or -name "incron.d" -or -name "incron" -or -name "at" -or -name "periodic" ')' -exec echo {} \; -exec ls -lR {} \;
|
||||
fi
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# Functions Used: print_2title, print_info
|
||||
# Global Variables: $Groups, $groupsB, $groupsVB, $nosh_usrs, $sh_usrs, $USER
|
||||
# Initial Functions:
|
||||
# Generated Global Variables: $pkexec_bin, $policy_dir, $policy_file
|
||||
# Generated Global Variables: $pkexec_bin, $pkexec_version, $policy_dir, $policy_file
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
@@ -30,6 +30,10 @@ if [ -n "$pkexec_bin" ]; then
|
||||
# Check polkit version for known vulnerabilities
|
||||
if command -v pkexec >/dev/null 2>&1; then
|
||||
pkexec --version 2>/dev/null
|
||||
pkexec_version="$(pkexec --version 2>/dev/null | grep -oE '[0-9]+(\\.[0-9]+)+')"
|
||||
if [ "$pkexec_version" ] && [ "$(printf '%s\n' "$pkexec_version" "0.120" | sort -V | head -n1)" = "$pkexec_version" ] && [ "$pkexec_version" != "0.120" ]; then
|
||||
echo "Potentially vulnerable to CVE-2021-4034 (PwnKit) - check distro patches" | sed -${E} "s,.*,${SED_RED_YELLOW},"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# License: GNU GPL
|
||||
# Version: 1.0
|
||||
# Functions Used: print_2title
|
||||
# Global Variables: $MACPEAS, $sh_usrs, $USER
|
||||
# Global Variables: $MACPEAS, $sh_usrs, $TIMEOUT, $USER
|
||||
# Initial Functions:
|
||||
# Generated Global Variables: $ushell, $no_shells, $unexpected_shells
|
||||
# Fat linpeas: 0
|
||||
@@ -26,8 +26,16 @@ else
|
||||
no_shells=$(grep -Ev "sh$" /etc/passwd 2>/dev/null | cut -d ':' -f 7 | sort | uniq)
|
||||
unexpected_shells=""
|
||||
printf "%s\n" "$no_shells" | while read f; do
|
||||
if $f -c 'whoami' 2>/dev/null | grep -q "$USER"; then
|
||||
unexpected_shells="$f\n$unexpected_shells"
|
||||
if [ -x "$f" ]; then
|
||||
if [ "$TIMEOUT" ]; then
|
||||
if $TIMEOUT 1 "$f" -c 'whoami' 2>/dev/null | grep -q "$USER"; then
|
||||
unexpected_shells="$f\n$unexpected_shells"
|
||||
fi
|
||||
else
|
||||
if "$f" -c 'whoami' 2>/dev/null | grep -q "$USER"; then
|
||||
unexpected_shells="$f\n$unexpected_shells"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
grep "sh$" /etc/passwd 2>/dev/null | sort | sed -${E} "s,$sh_usrs,${SED_LIGHT_CYAN}," | sed "s,$USER,${SED_LIGHT_MAGENTA}," | sed "s,root,${SED_RED},"
|
||||
@@ -41,4 +49,4 @@ else
|
||||
done
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# Functions Used: echo_not_found, print_2title, print_info
|
||||
# Global Variables:$IAMROOT, $PASSWORD, $sudoB, $sudoG, $sudoVB1, $sudoVB2
|
||||
# Initial Functions:
|
||||
# Generated Global Variables:
|
||||
# Generated Global Variables: $secure_path_line
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
@@ -19,6 +19,16 @@ print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation
|
||||
if [ "$PASSWORD" ]; then
|
||||
(echo "$PASSWORD" | timeout 1 sudo -S -l | sed "s,_proxy,${SED_RED},g" | sed "s,$sudoG,${SED_GREEN},g" | sed -${E} "s,$sudoVB1,${SED_RED_YELLOW}," | sed -${E} "s,$sudoVB2,${SED_RED_YELLOW}," | sed -${E} "s,$sudoB,${SED_RED},g") 2>/dev/null || echo_not_found "sudo"
|
||||
fi
|
||||
(sudo -n -l 2>/dev/null | sed "s,_proxy,${SED_RED},g" | sed "s,$sudoG,${SED_GREEN},g" | sed -${E} "s,$sudoVB1,${SED_RED_YELLOW}," | sed -${E} "s,$sudoVB2,${SED_RED_YELLOW}," | sed -${E} "s,$sudoB,${SED_RED},g" | sed "s,\!root,${SED_RED},") 2>/dev/null || echo "No cached sudo token (sudo -n -l)"
|
||||
|
||||
secure_path_line=$(sudo -l 2>/dev/null | grep -o "secure_path=[^,]*" | head -n 1 | cut -d= -f2)
|
||||
if [ "$secure_path_line" ]; then
|
||||
for p in $(echo "$secure_path_line" | tr ':' ' '); do
|
||||
if [ -w "$p" ]; then
|
||||
echo "Writable secure_path entry: $p" | sed -${E} "s,.*,${SED_RED},g"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
( grep -Iv "^$" cat /etc/sudoers | grep -v "#" | sed "s,_proxy,${SED_RED},g" | sed "s,$sudoG,${SED_GREEN},g" | sed -${E} "s,$sudoVB1,${SED_RED_YELLOW}," | sed -${E} "s,$sudoVB2,${SED_RED_YELLOW}," | sed -${E} "s,$sudoB,${SED_RED},g" | sed "s,pwfeedback,${SED_RED},g" ) 2>/dev/null || echo_not_found "/etc/sudoers"
|
||||
if ! [ "$IAMROOT" ] && [ -w '/etc/sudoers.d/' ]; then
|
||||
echo "You can create a file in /etc/sudoers.d/ and escalate privileges" | sed -${E} "s,.*,${SED_RED_YELLOW},"
|
||||
@@ -29,4 +39,4 @@ for f in /etc/sudoers.d/*; do
|
||||
grep -Iv "^$" "$f" | grep -v "#" | sed "s,_proxy,${SED_RED},g" | sed "s,$sudoG,${SED_GREEN},g" | sed -${E} "s,$sudoVB1,${SED_RED_YELLOW}," | sed -${E} "s,$sudoVB2,${SED_RED_YELLOW}," | sed -${E} "s,$sudoB,${SED_RED},g" | sed "s,pwfeedback,${SED_RED},g"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
@@ -40,4 +40,18 @@ else
|
||||
echo "ptrace protection is enabled ($ptrace_scope)" | sed "s,is enabled,${SED_GREEN},g";
|
||||
|
||||
fi
|
||||
|
||||
if [ -d "/var/run/sudo/ts" ]; then
|
||||
echo "Sudo token directory perms:" | sed -${E} "s,.*,${SED_LIGHT_CYAN},g"
|
||||
ls -ld /var/run/sudo/ts 2>/dev/null
|
||||
if [ -w "/var/run/sudo/ts" ]; then
|
||||
echo "/var/run/sudo/ts is writable" | sed -${E} "s,.*,${SED_RED},g"
|
||||
fi
|
||||
if [ -f "/var/run/sudo/ts/$USER" ]; then
|
||||
ls -l "/var/run/sudo/ts/$USER" 2>/dev/null
|
||||
if [ -w "/var/run/sudo/ts/$USER" ]; then
|
||||
echo "User sudo token file is writable" | sed -${E} "s,.*,${SED_RED},g"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Title: Software Information - Browser Profiles
|
||||
# ID: SW_Browser_profiles
|
||||
# Author: Carlos Polop
|
||||
# Last Update: 10-03-2025
|
||||
# Description: List browser profiles that may store credentials/cookies
|
||||
# License: GNU GPL
|
||||
# Version: 1.0
|
||||
# Functions Used: print_2title, print_3title, print_info
|
||||
# Global Variables: $HOMESEARCH, $SED_RED
|
||||
# Initial Functions:
|
||||
# Generated Global Variables: $h, $firefox_ini, $chrome_base, $profiles
|
||||
# Fat linpeas: 0
|
||||
# Small linpeas: 1
|
||||
|
||||
print_2title "Browser Profiles"
|
||||
print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#browser-data"
|
||||
|
||||
echo ""
|
||||
|
||||
for h in $HOMESEARCH; do
|
||||
[ -d "$h" ] || continue
|
||||
|
||||
firefox_ini="$h/.mozilla/firefox/profiles.ini"
|
||||
if [ -f "$firefox_ini" ]; then
|
||||
print_3title "Firefox profiles ($h)"
|
||||
awk -F= '
|
||||
/^\[Profile/ { in_profile=1 }
|
||||
/^Path=/ { path=$2 }
|
||||
/^IsRelative=/ { isrel=$2 }
|
||||
/^$/ {
|
||||
if (path != "") {
|
||||
if (isrel == "1") {
|
||||
print base "/.mozilla/firefox/" path
|
||||
} else {
|
||||
print path
|
||||
}
|
||||
}
|
||||
path=""; isrel=""
|
||||
}
|
||||
END {
|
||||
if (path != "") {
|
||||
if (isrel == "1") {
|
||||
print base "/.mozilla/firefox/" path
|
||||
} else {
|
||||
print path
|
||||
}
|
||||
}
|
||||
}
|
||||
' base="$h" "$firefox_ini" 2>/dev/null | sed -${E} "s,.*,${SED_RED},"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
for chrome_base in "$h/.config/google-chrome" "$h/.config/chromium" "$h/.config/BraveSoftware/Brave-Browser" "$h/.config/microsoft-edge" "$h/.config/microsoft-edge-beta" "$h/.config/microsoft-edge-dev"; do
|
||||
if [ -d "$chrome_base" ]; then
|
||||
profiles=$(find "$chrome_base" -maxdepth 1 -type d \( -name "Default" -o -name "Profile *" \) 2>/dev/null)
|
||||
if [ "$profiles" ]; then
|
||||
print_3title "Chromium profiles ($chrome_base)"
|
||||
printf "%s\n" "$profiles" | sed -${E} "s,.*,${SED_RED},"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
done
|
||||
@@ -37,14 +37,14 @@ printf "%s\n" "$suids_files" | while read s; do
|
||||
else
|
||||
c="a"
|
||||
for b in $sidB; do
|
||||
if echo $s | grep -q $(echo $b | cut -d % -f 1); then
|
||||
if echo "$sname" | grep -q $(echo $b | cut -d % -f 1); then
|
||||
echo "$s" | sed -${E} "s,$(echo $b | cut -d % -f 1),${C}[1;31m& ---> $(echo $b | cut -d % -f 2)${C}[0m,"
|
||||
c=""
|
||||
break;
|
||||
fi
|
||||
done;
|
||||
if [ "$c" ]; then
|
||||
if echo "$s" | grep -qE "$sidG1" || echo "$s" | grep -qE "$sidG2" || echo "$s" | grep -qE "$sidG3" || echo "$s" | grep -qE "$sidG4" || echo "$s" | grep -qE "$sidVB" || echo "$s" | grep -qE "$sidVB2"; then
|
||||
if echo "$sname" | grep -qE "$sidG1" || echo "$sname" | grep -qE "$sidG2" || echo "$sname" | grep -qE "$sidG3" || echo "$sname" | grep -qE "$sidG4" || echo "$sname" | grep -qE "$sidVB" || echo "$sname" | grep -qE "$sidVB2"; then
|
||||
echo "$s" | sed -${E} "s,$sidG1,${SED_GREEN}," | sed -${E} "s,$sidG2,${SED_GREEN}," | sed -${E} "s,$sidG3,${SED_GREEN}," | sed -${E} "s,$sidG4,${SED_GREEN}," | sed -${E} "s,$sidVB,${SED_RED_YELLOW}," | sed -${E} "s,$sidVB2,${SED_RED_YELLOW},"
|
||||
else
|
||||
echo "$s (Unknown SUID binary!)" | sed -${E} "s,/.*,${SED_RED},"
|
||||
@@ -96,4 +96,4 @@ printf "%s\n" "$suids_files" | while read s; do
|
||||
fi
|
||||
fi
|
||||
done;
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
@@ -17,10 +17,10 @@ check_external_hostname(){
|
||||
INTERNET_SEARCH_TIMEOUT=15
|
||||
# wget or curl?
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl "https://2e6ppt7izvuv66qmx2r3et2ufi0mxwqs.lambda-url.us-east-1.on.aws/" -H "User-Agent: linpeas" -d "{\"hostname\":\"$(hostname)\"}" -H "Content-Type: application/json" --max-time "$INTERNET_SEARCH_TIMEOUT"
|
||||
curl "https://tools.hacktricks.wiki/api/host-checker" -H "User-Agent: linpeas" -d "{\"hostname\":\"$(hostname)\"}" -H "Content-Type: application/json" --max-time "$INTERNET_SEARCH_TIMEOUT"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -q -O - "https://2e6ppt7izvuv66qmx2r3et2ufi0mxwqs.lambda-url.us-east-1.on.aws/" --header "User-Agent: linpeas" --post-data "{\"hostname\":\"$(hostname)\"}" -H "Content-Type: application/json" --timeout "$INTERNET_SEARCH_TIMEOUT"
|
||||
wget -q -O - "https://tools.hacktricks.wiki/api/host-checker" --header "User-Agent: linpeas" --post-data "{\"hostname\":\"$(hostname)\"}" -H "Content-Type: application/json" --timeout "$INTERNET_SEARCH_TIMEOUT"
|
||||
else
|
||||
echo "wget or curl not found"
|
||||
fi
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
|
||||
check_tcp_443_bin () {
|
||||
local TIMEOUT_INTERNET_SECONDS_443_BIN=$1
|
||||
local url_lambda="https://2e6ppt7izvuv66qmx2r3et2ufi0mxwqs.lambda-url.us-east-1.on.aws/"
|
||||
local url_lambda="https://tools.hacktricks.wiki/api/host-checker"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
if curl -s --connect-timeout $TIMEOUT_INTERNET_SECONDS_443_BIN "$url_lambda" \
|
||||
-H "User-Agent: linpeas" -H "Content-Type: application/json" >/dev/null 2>&1
|
||||
-H "User-Agent: linpeas" -H "Content-Type: application/json" \
|
||||
-d "{\"hostname\":\"$(hostname)\"}" >/dev/null 2>&1
|
||||
then
|
||||
echo "Port 443 is accessible with curl"
|
||||
return 0 # ✅ success
|
||||
@@ -30,7 +31,8 @@ check_tcp_443_bin () {
|
||||
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
if wget -q --timeout=$TIMEOUT_INTERNET_SECONDS_443_BIN -O - "$url_lambda" \
|
||||
--header "User-Agent: linpeas" -H "Content-Type: application/json" >/dev/null 2>&1
|
||||
--header "User-Agent: linpeas" -H "Content-Type: application/json" \
|
||||
--post-data "{\"hostname\":\"$(hostname)\"}" >/dev/null 2>&1
|
||||
then
|
||||
echo "Port 443 is accessible with wget"
|
||||
return 0
|
||||
|
||||
@@ -15,6 +15,5 @@
|
||||
|
||||
sidG1="/abuild-sudo$|/accton$|/allocate$|/ARDAgent$|/arping$|/atq$|/atrm$|/authpf$|/authpf-noip$|/authopen$|/batch$|/bbsuid$|/bsd-write$|/btsockstat$|/bwrap$|/cacaocsc$|/camel-lock-helper-1.2$|/ccreds_validate$|/cdrw$|/chage$|/check-foreground-console$|/chrome-sandbox$|/chsh$|/cons.saver$|/crontab$|/ct$|/cu$|/dbus-daemon-launch-helper$|/deallocate$|/desktop-create-kmenu$|/dma$|/dma-mbox-create$|/dmcrypt-get-device$|/doas$|/dotlockfile$|/dotlock.mailutils$|/dtaction$|/dtfile$|/eject$|/execabrt-action-install-debuginfo-to-abrt-cache$|/execdbus-daemon-launch-helper$|/execdma-mbox-create$|/execlockspool$|/execlogin_chpass$|/execlogin_lchpass$|/execlogin_passwd$|/execssh-keysign$|/execulog-helper$|/exim4|/expiry$|/fdformat$|/fstat$|/fusermount$|/fusermount3$"
|
||||
sidG2="/gnome-pty-helper$|/glines$|/gnibbles$|/gnobots2$|/gnome-suspend$|/gnometris$|/gnomine$|/gnotski$|/gnotravex$|/gpasswd$|/gpg$|/gpio$|/gtali|/.hal-mtab-lock$|/helper$|/imapd$|/inndstart$|/kismet_cap_nrf_51822$|/kismet_cap_nxp_kw41z$|/kismet_cap_ti_cc_2531$|/kismet_cap_ti_cc_2540$|/kismet_cap_ubertooth_one$|/kismet_capture$|/kismet_cap_linux_bluetooth$|/kismet_cap_linux_wifi$|/kismet_cap_nrf_mousejack$|/ksu$|/list_devices$|/load_osxfuse$|/locate$|/lock$|/lockdev$|/lockfile$|/login_activ$|/login_crypto$|/login_radius$|/login_skey$|/login_snk$|/login_token$|/login_yubikey$|/lpc$|/lpd$|/lpd-port$|/lppasswd$|/lpq$|/lpr$|/lprm$|/lpset$|/lxc-user-nic$|/mahjongg$|/mail-lock$|/mailq$|/mail-touchlock$|/mail-unlock$|/mksnap_ffs$|/mlocate$|/mlock$|/mount$|/mount.cifs$|/mount.ecryptfs_private$|/mount.nfs$|/mount.nfs4$|/mount_osxfuse$|/mtr$|/mutt_dotlock$"
|
||||
sidG3="/ncsa_auth$|/netpr$|/netkit-rcp$|/netkit-rlogin$|/netkit-rsh$|/netreport$|/netstat$|/newgidmap$|/newtask$|/newuidmap$|/nvmmctl$|/opieinfo$|/opiepasswd$|/pam_auth$|/pam_extrausers_chkpwd$|/pam_timestamp_check$|/pamverifier$|/pfexec$|/ping$|/ping6$|/pmconfig$|/pmap$|/polkit-agent-helper-1$|/polkit-explicit-grant-helper$|/polkit-grant-helper$|/polkit-grant-helper-pam$|/polkit-read-auth-helper$|/polkit-resolve-exe-helper$|/polkit-revoke-helper$|/polkit-set-default-helper$|/postdrop$|/postqueue$|/poweroff$|/ppp$|/procmail$|/pstat$|/pt_chmod$|/pwdb_chkpwd$|/quota$|/rcmd|/remote.unknown$|/rlogin$|/rmformat$|/rnews$|/run-mailcap$|/sacadm$|/same-gnome$|screen.real$|/security_authtrampoline$|/sendmail.sendmail$|/shutdown$|/skeyaudit$|/skeyinfo$|/skeyinit$|/sliplogin|/slocate$|/smbmnt$|/smbumount$|/smpatch$|/smtpctl$|/sperl5.8.8$|/ssh-agent$|/ssh-keysign$|/staprun$|/startinnfeed$|/stclient$|/su$|/suexec$|/sys-suspend$|/sysstat$|/systat$"
|
||||
sidG3="/ncsa_auth$|/netpr$|/netkit-rcp$|/netkit-rlogin$|/netkit-rsh$|/netreport$|/netstat$|/newgidmap$|/newtask$|/newuidmap$|/nvmmctl$|/opieinfo$|/opiepasswd$|/pam_auth$|/pam_extrausers_chkpwd$|/pam_timestamp_check$|/pamverifier$|/pfexec$|/hping3$|/ping$|/ping6$|/pmconfig$|/pmap$|/polkit-agent-helper-1$|/polkit-explicit-grant-helper$|/polkit-grant-helper$|/polkit-grant-helper-pam$|/polkit-read-auth-helper$|/polkit-resolve-exe-helper$|/polkit-revoke-helper$|/polkit-set-default-helper$|/postdrop$|/postqueue$|/poweroff$|/ppp$|/procmail$|/pstat$|/pt_chmod$|/pwdb_chkpwd$|/quota$|/rcmd|/remote.unknown$|/rlogin$|/rmformat$|/rnews$|/run-mailcap$|/sacadm$|/same-gnome$|screen.real$|/security_authtrampoline$|/sendmail.sendmail$|/shutdown$|/skeyaudit$|/skeyinfo$|/skeyinit$|/sliplogin|/slocate$|/smbmnt$|/smbumount$|/smpatch$|/smtpctl$|/sperl5.8.8$|/ssh-agent$|/ssh-keysign$|/staprun$|/startinnfeed$|/stclient$|/su$|/suexec$|/sys-suspend$|/sysstat$|/systat$"
|
||||
sidG4="/telnetlogin$|/timedc$|/tip$|/top$|/traceroute6$|/traceroute6.iputils$|/trpt$|/tsoldtlabel$|/tsoljdslabel$|/tsolxagent$|/ufsdump$|/ufsrestore$|/ulog-helper$|/umount.cifs$|/umount.nfs$|/umount.nfs4$|/unix_chkpwd$|/uptime$|/userhelper$|/userisdnctl$|/usernetctl$|/utempter$|/utmp_update$|/uucico$|/uuglist$|/uuidd$|/uuname$|/uusched$|/uustat$|/uux$|/uuxqt$|/VBoxHeadless$|/VBoxNetAdpCtl$|/VBoxNetDHCP$|/VBoxNetNAT$|/VBoxSDL$|/VBoxVolInfo$|/VirtualBoxVM$|/vmstat$|/vmware-authd$|/vmware-user-suid-wrapper$|/vmware-vmx$|/vmware-vmx-debug$|/vmware-vmx-stats$|/vncserver-x11$|/volrmmount$|/w$|/wall$|/whodo$|/write$|/X$|/Xorg.wrap$|/Xsun$|/Xvnc$|/yppasswd$"
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -13,5 +13,5 @@
|
||||
# Small linpeas: 1
|
||||
|
||||
|
||||
sudoVB1=" \*|env_keep\W*\+=.*LD_PRELOAD|env_keep\W*\+=.*LD_LIBRARY_PATH|env_keep\W*\+=.*BASH_ENV|env_keep\W*\+=.* ENV|peass{SUDOVB1_HERE}"
|
||||
sudoVB1=" \*|env_keep\W*\+=.*LD_PRELOAD|env_keep\W*\+=.*LD_LIBRARY_PATH|env_keep\W*\+=.*BASH_ENV|env_keep\W*\+=.* ENV|env_keep\W*\+=.*PATH|!env_reset|!requiretty|peass{SUDOVB1_HERE}"
|
||||
sudoVB2="peass{SUDOVB2_HERE}"
|
||||
|
||||
@@ -405,7 +405,7 @@ class LinpeasBuilder:
|
||||
name = entry["name"]
|
||||
caseinsensitive = entry.get("caseinsensitive", False)
|
||||
regex = entry["regex"]
|
||||
regex = regex.replace('"', '\\"').strip()
|
||||
regex = regex.replace("\\", "\\\\").replace('"', '\\"').strip()
|
||||
falsePositives = entry.get("falsePositives", False)
|
||||
|
||||
if falsePositives:
|
||||
|
||||
@@ -8,6 +8,7 @@ from .yamlGlobals import (
|
||||
class LinpeasModule:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
real_path = os.path.realpath(path)
|
||||
with open(path, 'r') as file:
|
||||
self.module_text = file.read()
|
||||
|
||||
@@ -29,7 +30,7 @@ class LinpeasModule:
|
||||
self.section_info = {}
|
||||
if not (self.is_base or self.is_function or self.is_variable):
|
||||
for module in LINPEAS_PARTS["modules"]:
|
||||
if module["folder_path"] in path:
|
||||
if os.path.realpath(module["folder_path"]) in real_path:
|
||||
self.section_info = module
|
||||
self.is_check = True
|
||||
break
|
||||
|
||||
40
linPEAS/tests/test_builder.py
Normal file
40
linPEAS/tests/test_builder.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class LinpeasBuilderTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.repo_root = Path(__file__).resolve().parents[2]
|
||||
self.linpeas_dir = self.repo_root / "linPEAS"
|
||||
|
||||
def _run_builder(self, args, output_path):
|
||||
cmd = ["python3", "-m", "builder.linpeas_builder"] + args + ["--output", str(output_path)]
|
||||
result = subprocess.run(cmd, cwd=str(self.linpeas_dir), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise AssertionError(
|
||||
f"linpeas_builder failed:\nstdout:\n{result.stdout}\nstderr:\n{result.stderr}"
|
||||
)
|
||||
|
||||
def test_small_build_creates_executable(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
output_path = Path(tmpdir) / "linpeas_small.sh"
|
||||
self._run_builder(["--small"], output_path)
|
||||
self.assertTrue(output_path.exists(), "linpeas_small.sh was not created.")
|
||||
mode = output_path.stat().st_mode
|
||||
self.assertTrue(mode & stat.S_IXUSR, "linpeas_small.sh is not executable.")
|
||||
|
||||
def test_include_exclude_modules(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
output_path = Path(tmpdir) / "linpeas_include.sh"
|
||||
self._run_builder(["--include", "system_information,container", "--exclude", "container"], output_path)
|
||||
content = output_path.read_text(encoding="utf-8", errors="ignore")
|
||||
self.assertIn("Operative system", content)
|
||||
self.assertNotIn("Am I Containered?", content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
60
linPEAS/tests/test_modules_metadata.py
Normal file
60
linPEAS/tests/test_modules_metadata.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class LinpeasModulesMetadataTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.repo_root = Path(__file__).resolve().parents[2]
|
||||
cls.linpeas_dir = cls.repo_root / "linPEAS"
|
||||
cls.parts_dir = cls.linpeas_dir / "builder" / "linpeas_parts"
|
||||
|
||||
# Ensure `import builder.*` works when tests are run from repo root.
|
||||
sys.path.insert(0, str(cls.linpeas_dir))
|
||||
|
||||
from builder.src.linpeasModule import LinpeasModule # pylint: disable=import-error
|
||||
|
||||
cls.LinpeasModule = LinpeasModule
|
||||
|
||||
def _iter_module_files(self):
|
||||
return sorted(self.parts_dir.rglob("*.sh"))
|
||||
|
||||
def test_all_modules_parse(self):
|
||||
module_files = self._iter_module_files()
|
||||
self.assertGreater(len(module_files), 0, "No linPEAS module files were found.")
|
||||
|
||||
# Parsing a module validates its metadata and dependencies.
|
||||
for path in module_files:
|
||||
_ = self.LinpeasModule(str(path))
|
||||
|
||||
def test_check_module_id_matches_filename(self):
|
||||
for path in self._iter_module_files():
|
||||
module = self.LinpeasModule(str(path))
|
||||
if not getattr(module, "is_check", False):
|
||||
continue
|
||||
|
||||
# For checks, the filename (without numeric prefix) must match the module ID
|
||||
# (either full ID or stripping section prefix like `SI_`).
|
||||
file_base = re.sub(r"^[0-9]+_", "", path.stem)
|
||||
module_id = getattr(module, "id", "")
|
||||
module_id_tail = module_id[3:] if len(module_id) >= 3 else ""
|
||||
self.assertIn(
|
||||
file_base,
|
||||
{module_id, module_id_tail},
|
||||
f"Module ID mismatch in {path}: id={module_id} expected suffix={file_base}",
|
||||
)
|
||||
|
||||
def test_module_ids_are_unique(self):
|
||||
ids = []
|
||||
for path in self._iter_module_files():
|
||||
module = self.LinpeasModule(str(path))
|
||||
ids.append(getattr(module, "id", ""))
|
||||
|
||||
duplicates = {x for x in ids if x and ids.count(x) > 1}
|
||||
self.assertEqual(set(), duplicates, f"Duplicate module IDs found: {sorted(duplicates)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -127,7 +127,9 @@ def parse_line(line: str):
|
||||
|
||||
elif is_section(line, INFO_PATTERN):
|
||||
title = parse_title(line)
|
||||
C_SECTION["infos"].append(title)
|
||||
if C_SECTION == {}:
|
||||
return
|
||||
C_SECTION.setdefault("infos", []).append(title)
|
||||
|
||||
#If here, then it's text
|
||||
else:
|
||||
|
||||
@@ -71,7 +71,7 @@ CALL :T_Progress 2
|
||||
:ListHotFixes
|
||||
where wmic >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
wmic qfe get Caption,Description,HotFixID,InstalledOn | more
|
||||
wmic qfe get Caption,Description,HotFixID,InstalledOn
|
||||
) else (
|
||||
powershell -command "Get-HotFix | Format-Table -AutoSize"
|
||||
)
|
||||
@@ -204,7 +204,7 @@ CALL :T_Progress 1
|
||||
CALL :ColorLine " %E%33m[+]%E%97m Registered Anti-Virus(AV)"
|
||||
where wmic >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
WMIC /Node:localhost /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct Get displayName /Format:List | more
|
||||
WMIC /Node:localhost /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct Get displayName /Format:List
|
||||
) else (
|
||||
powershell -command "Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntiVirusProduct | Select-Object -ExpandProperty displayName"
|
||||
)
|
||||
@@ -238,7 +238,7 @@ CALL :ColorLine " %E%33m[+]%E%97m MOUNTED DISKS"
|
||||
ECHO. [i] Maybe you find something interesting
|
||||
where wmic >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
wmic logicaldisk get caption | more
|
||||
wmic logicaldisk get caption
|
||||
) else (
|
||||
fsutil fsinfo drives
|
||||
)
|
||||
@@ -670,7 +670,7 @@ if "%long%" == "true" (
|
||||
ECHO.
|
||||
where wmic >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
for /f %%x in ('wmic logicaldisk get name ^| more') do (
|
||||
for /f %%x in ('wmic logicaldisk get name') do (
|
||||
set tdrive=%%x
|
||||
if "!tdrive:~1,2!" == ":" (
|
||||
%%x
|
||||
|
||||
26
winPEAS/winPEASexe/CMakeLists.txt
Normal file
26
winPEAS/winPEASexe/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(winPEAS_dotnet NONE)
|
||||
|
||||
set(PROJECT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/winPEAS.csproj")
|
||||
|
||||
find_program(DOTNET_EXECUTABLE dotnet)
|
||||
find_program(MSBUILD_EXECUTABLE msbuild)
|
||||
find_program(XBUILD_EXECUTABLE xbuild)
|
||||
|
||||
if(DOTNET_EXECUTABLE)
|
||||
set(BUILD_TOOL "${DOTNET_EXECUTABLE}")
|
||||
set(BUILD_ARGS build "${PROJECT_FILE}" -c Release)
|
||||
elseif(MSBUILD_EXECUTABLE)
|
||||
set(BUILD_TOOL "${MSBUILD_EXECUTABLE}")
|
||||
set(BUILD_ARGS "${PROJECT_FILE}" /p:Configuration=Release)
|
||||
elseif(XBUILD_EXECUTABLE)
|
||||
set(BUILD_TOOL "${XBUILD_EXECUTABLE}")
|
||||
set(BUILD_ARGS "${PROJECT_FILE}" /p:Configuration=Release)
|
||||
else()
|
||||
message(FATAL_ERROR "dotnet, msbuild, or xbuild is required to build winPEAS")
|
||||
endif()
|
||||
|
||||
add_custom_target(winpeas ALL
|
||||
COMMAND ${BUILD_TOOL} ${BUILD_ARGS}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
@@ -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?!?!?!
|
||||
|
||||
@@ -135,7 +128,7 @@ Once you have installed and activated it you need to:
|
||||
|
||||
- **System Information**
|
||||
- [x] Basic System info information
|
||||
- [x] Use Watson to search for vulnerabilities
|
||||
- [x] Use WES-NG to search for vulnerabilities
|
||||
- [x] Enumerate Microsoft updates
|
||||
- [x] PS, Audit, WEF and LAPS Settings
|
||||
- [x] LSA protection
|
||||
@@ -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
|
||||
@@ -268,7 +262,7 @@ Once you have installed and activated it you need to:
|
||||
|
||||
## TODO
|
||||
- Add more checks
|
||||
- Mantain updated Watson (last JAN 2021)
|
||||
- Maintain updated WES-NG
|
||||
|
||||
If you want to help with any of this, you can do it using **[github issues](https://github.com/peass-ng/PEASS-ng/issues)** or you can submit a pull request.
|
||||
|
||||
|
||||
36
winPEAS/winPEASexe/Tests/ArgumentParsingTests.cs
Normal file
36
winPEAS/winPEASexe/Tests/ArgumentParsingTests.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace winPEAS.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class ArgumentParsingTests
|
||||
{
|
||||
private static bool InvokeIsNetworkTypeValid(string arg)
|
||||
{
|
||||
var method = typeof(winPEAS.Checks.Checks).GetMethod("IsNetworkTypeValid", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
Assert.IsNotNull(method, "IsNetworkTypeValid method not found.");
|
||||
return (bool)method.Invoke(null, new object[] { arg });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAcceptValidNetworkTypes()
|
||||
{
|
||||
Assert.IsTrue(InvokeIsNetworkTypeValid("-network=auto"));
|
||||
Assert.IsTrue(InvokeIsNetworkTypeValid("-network=10.10.10.10"));
|
||||
Assert.IsTrue(InvokeIsNetworkTypeValid("-network=10.10.10.10/24"));
|
||||
Assert.IsTrue(InvokeIsNetworkTypeValid("-network=10.10.10.10,10.10.10.20"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldRejectInvalidNetworkTypes()
|
||||
{
|
||||
Assert.IsFalse(InvokeIsNetworkTypeValid("-network="));
|
||||
Assert.IsFalse(InvokeIsNetworkTypeValid("-network=10.10.10.999"));
|
||||
Assert.IsFalse(InvokeIsNetworkTypeValid("-network=10.10.10.10/64"));
|
||||
Assert.IsFalse(InvokeIsNetworkTypeValid("-network=999.999.999.999/24"));
|
||||
Assert.IsFalse(InvokeIsNetworkTypeValid("-network=not-an-ip"));
|
||||
}
|
||||
}
|
||||
}
|
||||
37
winPEAS/winPEASexe/Tests/ChecksArgumentEdgeCasesTests.cs
Normal file
37
winPEAS/winPEASexe/Tests/ChecksArgumentEdgeCasesTests.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace winPEAS.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class ChecksArgumentEdgeCasesTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldNotThrowOnEmptyLogFileArg()
|
||||
{
|
||||
// Should return early with a user-friendly error, not crash.
|
||||
Program.Main(new[] { "log=" });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotThrowOnPortsWithoutNetwork()
|
||||
{
|
||||
// Should warn and return early because -network was not provided.
|
||||
Program.Main(new[] { "-ports=80,443" });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotThrowOnInvalidNetworkArgument()
|
||||
{
|
||||
// Should warn and return early because the IP is invalid.
|
||||
Program.Main(new[] { "-network=10.10.10.999" });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotThrowOnEmptyNetworkArgument()
|
||||
{
|
||||
// Should warn and return early because the value is empty.
|
||||
Program.Main(new[] { "-network=" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,9 +61,11 @@
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MSTest.TestFramework.2.2.5\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MSTest.TestFramework.2.2.5\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
@@ -95,6 +97,7 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ArgumentParsingTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SmokeTests.cs" />
|
||||
</ItemGroup>
|
||||
@@ -108,6 +111,40 @@
|
||||
<Name>winPEAS</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CopyVSTestFrameworkToMSTestAdapter" AfterTargets="Build">
|
||||
<PropertyGroup>
|
||||
<_PackagesDir>$(MSBuildThisFileDirectory)..\packages\</_PackagesDir>
|
||||
<_MSTestFrameworkDir>$(_PackagesDir)MSTest.TestFramework.2.2.5\lib\net45\</_MSTestFrameworkDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="Exists('$(_MSTestFrameworkDir)')">
|
||||
<_VSTestFrameworkDlls Include="$(_MSTestFrameworkDir)Microsoft.VisualStudio.TestPlatform.TestFramework*.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_VSTestCopyDirs Include="$(TargetDir)" Condition="'$(TargetDir)' != '' AND Exists('$(TargetDir)')" />
|
||||
<_MSTestAdapterDirs Include="$(_PackagesDir)MSTest.TestAdapter.2.2.5\build\net45\" Condition="Exists('$(_PackagesDir)MSTest.TestAdapter.2.2.5\build\net45\')" />
|
||||
<_MSTestAdapterDirs Include="$(_PackagesDir)MSTest.TestAdapter.2.2.5\build\_common\" Condition="Exists('$(_PackagesDir)MSTest.TestAdapter.2.2.5\build\_common\')" />
|
||||
</ItemGroup>
|
||||
|
||||
<Message
|
||||
Condition="@(_VSTestFrameworkDlls) != ''"
|
||||
Importance="high"
|
||||
Text="CopyVSTestFrameworkToMSTestAdapter: copying @( _VSTestFrameworkDlls )" />
|
||||
|
||||
<Copy
|
||||
Condition="@(_VSTestFrameworkDlls) != '' AND @(_VSTestCopyDirs) != ''"
|
||||
SourceFiles="@(_VSTestFrameworkDlls)"
|
||||
DestinationFolder="%(_VSTestCopyDirs.Identity)"
|
||||
SkipUnchangedFiles="true" />
|
||||
|
||||
<Copy
|
||||
Condition="@(_VSTestFrameworkDlls) != '' AND @(_MSTestAdapterDirs) != ''"
|
||||
SourceFiles="@(_VSTestFrameworkDlls)"
|
||||
DestinationFolder="%(_MSTestAdapterDirs.Identity)"
|
||||
SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
@@ -133,4 +170,4 @@
|
||||
<Import Project="..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net451\Stub.System.Data.SQLite.Core.NetFramework.targets" Condition="Exists('..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net451\Stub.System.Data.SQLite.Core.NetFramework.targets')" />
|
||||
<Import Project="..\packages\Fody.6.5.5\build\Fody.targets" Condition="Exists('..\packages\Fody.6.5.5\build\Fody.targets')" />
|
||||
<Import Project="..\packages\Costura.Fody.5.7.0\build\Costura.Fody.targets" Condition="Exists('..\packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -88,11 +88,13 @@ namespace winPEAS.Checks
|
||||
new SystemCheck("userinfo", new UserInfo()),
|
||||
new SystemCheck("processinfo", new ProcessInfo()),
|
||||
new SystemCheck("servicesinfo", new ServicesInfo()),
|
||||
new SystemCheck("soapclientinfo", new SoapClientInfo()),
|
||||
new SystemCheck("applicationsinfo", new ApplicationsInfo()),
|
||||
new SystemCheck("networkinfo", new NetworkInfo()),
|
||||
new SystemCheck("activedirectoryinfo", new ActiveDirectoryInfo()),
|
||||
new SystemCheck("cloudinfo", new CloudInfo()),
|
||||
new SystemCheck("windowscreds", new WindowsCreds()),
|
||||
new SystemCheck("registryinfo", new RegistryInfo()),
|
||||
new SystemCheck("browserinfo", new BrowserInfo()),
|
||||
new SystemCheck("filesinfo", new FilesInfo()),
|
||||
new SystemCheck("fileanalysis", new FileAnalysis()),
|
||||
@@ -354,7 +356,7 @@ namespace winPEAS.Checks
|
||||
{
|
||||
var rangeParts = networkType.Split('/');
|
||||
|
||||
if (rangeParts.Length == 2 && int.TryParse(rangeParts[1], out int res) && res <= 32 && res >= 0)
|
||||
if (rangeParts.Length == 2 && IPAddress.TryParse(rangeParts[0], out _) && int.TryParse(rangeParts[1], out int res) && res <= 32 && res >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -392,7 +392,7 @@ namespace winPEAS.Checks
|
||||
|
||||
foreach (string regHkcu in passRegHkcu)
|
||||
{
|
||||
Beaprint.DictPrint(RegistryHelper.GetRegValues("HKLM", regHkcu), false);
|
||||
Beaprint.DictPrint(RegistryHelper.GetRegValues("HKCU", regHkcu), false);
|
||||
}
|
||||
|
||||
foreach (string regHklm in passRegHklm)
|
||||
@@ -524,7 +524,7 @@ namespace winPEAS.Checks
|
||||
{
|
||||
Beaprint.MainPrint("Looking for documents --limit 100--");
|
||||
List<string> docFiles = InterestingFiles.InterestingFiles.ListUsersDocs();
|
||||
Beaprint.ListPrint(docFiles.GetRange(0, docFiles.Count <= 100 ? docFiles.Count : 100));
|
||||
Beaprint.ListPrint(MyUtils.GetLimitedRange(docFiles, 100));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -546,7 +546,7 @@ namespace winPEAS.Checks
|
||||
|
||||
if (recFiles.Count != 0)
|
||||
{
|
||||
foreach (Dictionary<string, string> recF in recFiles.GetRange(0, recFiles.Count <= 70 ? recFiles.Count : 70))
|
||||
foreach (Dictionary<string, string> recF in MyUtils.GetLimitedRange(recFiles, 70))
|
||||
{
|
||||
Beaprint.AnsiPrint(" " + recF["Target"] + "(" + recF["Accessed"] + ")", colorF);
|
||||
}
|
||||
|
||||
@@ -348,8 +348,7 @@ namespace winPEAS.Checks
|
||||
Beaprint.MainPrint("DNS cached --limit 70--");
|
||||
Beaprint.GrayPrint(string.Format(" {0,-38}{1,-38}{2}", "Entry", "Name", "Data"));
|
||||
List<Dictionary<string, string>> DNScache = NetworkInfoHelper.GetDNSCache();
|
||||
foreach (Dictionary<string, string> entry in DNScache.GetRange(0,
|
||||
DNScache.Count <= 70 ? DNScache.Count : 70))
|
||||
foreach (Dictionary<string, string> entry in MyUtils.GetLimitedRange(DNScache, 70))
|
||||
{
|
||||
Console.WriteLine($" {entry["Entry"],-38}{entry["Name"],-38}{entry["Data"]}");
|
||||
}
|
||||
|
||||
141
winPEAS/winPEASexe/winPEAS/Checks/RegistryInfo.cs
Normal file
141
winPEAS/winPEASexe/winPEAS/Checks/RegistryInfo.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using winPEAS.Helpers;
|
||||
using winPEAS.Helpers.Registry;
|
||||
|
||||
namespace winPEAS.Checks
|
||||
{
|
||||
internal class RegistryInfo : ISystemCheck
|
||||
{
|
||||
private const string TypingInsightsRelativePath = @"Software\Microsoft\Input\TypingInsights";
|
||||
|
||||
private static readonly string[] KnownWritableSystemKeyCandidates = new[]
|
||||
{
|
||||
@"SOFTWARE\Microsoft\CoreShell",
|
||||
@"SOFTWARE\Microsoft\DRM",
|
||||
@"SOFTWARE\Microsoft\Input\Locales",
|
||||
@"SOFTWARE\Microsoft\Input\Settings",
|
||||
@"SOFTWARE\Microsoft\Shell\Oobe",
|
||||
@"SOFTWARE\Microsoft\Shell\Session",
|
||||
@"SOFTWARE\Microsoft\Tracing",
|
||||
@"SOFTWARE\Microsoft\Windows\UpdateApi",
|
||||
@"SOFTWARE\Microsoft\WindowsUpdate\UX",
|
||||
@"SOFTWARE\WOW6432Node\Microsoft\DRM",
|
||||
@"SOFTWARE\WOW6432Node\Microsoft\Tracing",
|
||||
@"SYSTEM\Software\Microsoft\TIP",
|
||||
@"SYSTEM\ControlSet001\Control\Cryptography\WebSignIn\Navigation",
|
||||
@"SYSTEM\ControlSet001\Control\MUI\StringCacheSettings",
|
||||
@"SYSTEM\ControlSet001\Control\USB\AutomaticSurpriseRemoval",
|
||||
@"SYSTEM\ControlSet001\Services\BTAGService\Parameters\Settings",
|
||||
};
|
||||
|
||||
private static readonly string[] ScanBasePaths = new[]
|
||||
{
|
||||
@"SOFTWARE\Microsoft",
|
||||
@"SOFTWARE\WOW6432Node\Microsoft",
|
||||
@"SYSTEM\CurrentControlSet\Services",
|
||||
@"SYSTEM\CurrentControlSet\Control",
|
||||
@"SYSTEM\ControlSet001\Control",
|
||||
};
|
||||
|
||||
public void PrintInfo(bool isDebug)
|
||||
{
|
||||
Beaprint.GreatPrint("Registry permissions for hive exploitation");
|
||||
|
||||
new List<Action>
|
||||
{
|
||||
PrintTypingInsightsPermissions,
|
||||
PrintKnownSystemWritableKeys,
|
||||
PrintHeuristicWritableKeys,
|
||||
}.ForEach(action => CheckRunner.Run(action, isDebug));
|
||||
}
|
||||
|
||||
private void PrintTypingInsightsPermissions()
|
||||
{
|
||||
Beaprint.MainPrint("Cross-user TypingInsights key (HKCU/HKU)");
|
||||
|
||||
var matches = new List<RegistryWritableKeyInfo>();
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (RegistryAclScanner.TryGetWritableKey("HKCU", TypingInsightsRelativePath, out var currentUserKey))
|
||||
{
|
||||
if (seen.Add(currentUserKey.FullPath))
|
||||
{
|
||||
matches.Add(currentUserKey);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sid in RegistryHelper.GetUserSIDs())
|
||||
{
|
||||
if (string.IsNullOrEmpty(sid) || sid.Equals(".DEFAULT", StringComparison.OrdinalIgnoreCase) || sid.EndsWith("_Classes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string relativePath = $"{sid}\\{TypingInsightsRelativePath}";
|
||||
if (RegistryAclScanner.TryGetWritableKey("HKU", relativePath, out var info) && seen.Add(info.FullPath))
|
||||
{
|
||||
matches.Add(info);
|
||||
}
|
||||
}
|
||||
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] TypingInsights key does not grant write access to low-privileged groups.");
|
||||
return;
|
||||
}
|
||||
|
||||
PrintEntries(matches);
|
||||
Beaprint.LinkPrint("https://projectzero.google/2025/05/the-windows-registry-adventure-8-exploitation.html", "Writable TypingInsights enables cross-user hive tampering and DoS.");
|
||||
}
|
||||
|
||||
private void PrintKnownSystemWritableKeys()
|
||||
{
|
||||
Beaprint.MainPrint("Known HKLM descendants writable by standard users");
|
||||
|
||||
var matches = new List<RegistryWritableKeyInfo>();
|
||||
foreach (var path in KnownWritableSystemKeyCandidates)
|
||||
{
|
||||
if (RegistryAclScanner.TryGetWritableKey("HKLM", path, out var info))
|
||||
{
|
||||
matches.Add(info);
|
||||
}
|
||||
}
|
||||
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] None of the tracked HKLM keys are writable by low-privileged groups.");
|
||||
return;
|
||||
}
|
||||
|
||||
PrintEntries(matches);
|
||||
}
|
||||
|
||||
private void PrintHeuristicWritableKeys()
|
||||
{
|
||||
Beaprint.MainPrint("Sample of additional writable HKLM keys (depth-limited scan)");
|
||||
|
||||
var matches = RegistryAclScanner.ScanWritableKeys("HKLM", ScanBasePaths, maxDepth: 3, maxResults: 25);
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
Beaprint.GrayPrint(" [-] No additional writable HKLM keys were found within the sampled paths.");
|
||||
return;
|
||||
}
|
||||
|
||||
PrintEntries(matches);
|
||||
Beaprint.GrayPrint(" [*] Showing up to 25 entries from the sampled paths to avoid noisy output.");
|
||||
}
|
||||
|
||||
private static void PrintEntries(IEnumerable<RegistryWritableKeyInfo> entries)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var principals = string.Join(", ", entry.Principals);
|
||||
var rights = entry.Rights.Count > 0 ? string.Join(", ", entry.Rights.Distinct(StringComparer.OrdinalIgnoreCase)) : "Write access";
|
||||
var displayPath = string.IsNullOrEmpty(entry.FullPath) ? $"{entry.Hive}\\{entry.RelativePath}" : entry.FullPath;
|
||||
Beaprint.BadPrint($" [!] {displayPath} -> {principals} ({rights})");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
88
winPEAS/winPEASexe/winPEAS/Checks/SoapClientInfo.cs
Normal file
88
winPEAS/winPEASexe/winPEAS/Checks/SoapClientInfo.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using winPEAS.Helpers;
|
||||
using winPEAS.Info.ApplicationInfo;
|
||||
|
||||
namespace winPEAS.Checks
|
||||
{
|
||||
internal class SoapClientInfo : ISystemCheck
|
||||
{
|
||||
public void PrintInfo(bool isDebug)
|
||||
{
|
||||
Beaprint.GreatPrint(".NET SOAP Client Proxies (SOAPwn)");
|
||||
|
||||
CheckRunner.Run(PrintSoapClientFindings, isDebug);
|
||||
}
|
||||
|
||||
private static void PrintSoapClientFindings()
|
||||
{
|
||||
try
|
||||
{
|
||||
Beaprint.MainPrint("Potential SOAPwn / HttpWebClientProtocol abuse surfaces");
|
||||
Beaprint.LinkPrint(
|
||||
"https://labs.watchtowr.com/soapwn-pwning-net-framework-applications-through-http-client-proxies-and-wsdl/",
|
||||
"Look for .NET services that let attackers control SoapHttpClientProtocol URLs or WSDL imports to coerce NTLM or drop files.");
|
||||
|
||||
List<SoapClientProxyFinding> findings = SoapClientProxyAnalyzer.CollectFindings();
|
||||
if (findings.Count == 0)
|
||||
{
|
||||
Beaprint.NotFoundPrint();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (SoapClientProxyFinding finding in findings)
|
||||
{
|
||||
string severity = finding.BinaryIndicators.Contains("ServiceDescriptionImporter")
|
||||
? "Dynamic WSDL import"
|
||||
: "SOAP proxy usage";
|
||||
|
||||
Beaprint.BadPrint($" [{severity}] {finding.BinaryPath}");
|
||||
|
||||
foreach (SoapClientProxyInstance instance in finding.Instances)
|
||||
{
|
||||
string instanceInfo = $" -> {instance.SourceType}: {instance.Name}";
|
||||
if (!string.IsNullOrEmpty(instance.Account))
|
||||
{
|
||||
instanceInfo += $" ({instance.Account})";
|
||||
}
|
||||
if (!string.IsNullOrEmpty(instance.Extra))
|
||||
{
|
||||
instanceInfo += $" | {instance.Extra}";
|
||||
}
|
||||
|
||||
Beaprint.GrayPrint(instanceInfo);
|
||||
}
|
||||
|
||||
if (finding.BinaryIndicators.Count > 0)
|
||||
{
|
||||
Beaprint.BadPrint(" Binary indicators: " + string.Join(", ", finding.BinaryIndicators));
|
||||
}
|
||||
|
||||
if (finding.ConfigIndicators.Count > 0)
|
||||
{
|
||||
string configLabel = string.IsNullOrEmpty(finding.ConfigPath)
|
||||
? "Config indicators"
|
||||
: $"Config indicators ({finding.ConfigPath})";
|
||||
Beaprint.BadPrint(" " + configLabel + ": " + string.Join(", ", finding.ConfigIndicators));
|
||||
}
|
||||
|
||||
if (finding.BinaryScanFailed)
|
||||
{
|
||||
Beaprint.GrayPrint(" (Binary scan skipped due to access/size limits)");
|
||||
}
|
||||
|
||||
if (finding.ConfigScanFailed)
|
||||
{
|
||||
Beaprint.GrayPrint(" (Unable to read config file)");
|
||||
}
|
||||
|
||||
Beaprint.PrintLineSeparator();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.PrintException(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,13 +82,16 @@ namespace winPEAS.Checks
|
||||
PrintKrbRelayUp,
|
||||
PrintInsideContainer,
|
||||
PrintAlwaysInstallElevated,
|
||||
PrintObjectManagerRaceAmplification,
|
||||
PrintLSAInfo,
|
||||
PrintNtlmSettings,
|
||||
PrintLocalGroupPolicy,
|
||||
PrintPotentialGPOAbuse,
|
||||
AppLockerHelper.PrintAppLockerPolicy,
|
||||
PrintPrintNightmarePointAndPrint,
|
||||
PrintPrintersWMIInfo,
|
||||
PrintNamedPipes,
|
||||
PrintNamedPipeAbuseCandidates,
|
||||
PrintAMSIProviders,
|
||||
PrintSysmon,
|
||||
PrintDotNetVersions
|
||||
@@ -733,6 +736,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");
|
||||
@@ -809,6 +837,39 @@ namespace winPEAS.Checks
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintPrintNightmarePointAndPrint()
|
||||
{
|
||||
Beaprint.MainPrint("PrintNightmare PointAndPrint Policies");
|
||||
Beaprint.LinkPrint("https://itm4n.github.io/printnightmare-exploitation/", "Check PointAndPrint policy hardening");
|
||||
|
||||
try
|
||||
{
|
||||
string key = @"Software\\Policies\\Microsoft\\Windows NT\\Printers\\PointAndPrint";
|
||||
var restrict = RegistryHelper.GetDwordValue("HKLM", key, "RestrictDriverInstallationToAdministrators");
|
||||
var noWarn = RegistryHelper.GetDwordValue("HKLM", key, "NoWarningNoElevationOnInstall");
|
||||
var updatePrompt = RegistryHelper.GetDwordValue("HKLM", key, "UpdatePromptSettings");
|
||||
|
||||
if (restrict == null && noWarn == null && updatePrompt == null)
|
||||
{
|
||||
Beaprint.NotFoundPrint();
|
||||
return;
|
||||
}
|
||||
|
||||
Beaprint.NoColorPrint($" RestrictDriverInstallationToAdministrators: {restrict}\n" +
|
||||
$" NoWarningNoElevationOnInstall: {noWarn}\n" +
|
||||
$" UpdatePromptSettings: {updatePrompt}");
|
||||
|
||||
if (restrict == 0 && noWarn == 1 && updatePrompt == 2)
|
||||
{
|
||||
Beaprint.BadPrint(" [!] Potentially vulnerable to PrintNightmare misconfiguration");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.PrintException(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintPrintersWMIInfo()
|
||||
{
|
||||
Beaprint.MainPrint("Enumerating Printers (WMI)");
|
||||
@@ -857,6 +918,48 @@ namespace winPEAS.Checks
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void PrintNamedPipeAbuseCandidates()
|
||||
{
|
||||
Beaprint.MainPrint("Named Pipes with Low-Priv Write Access to Privileged Servers");
|
||||
|
||||
try
|
||||
{
|
||||
var candidates = NamedPipeSecurityAnalyzer.GetNamedPipeAbuseCandidates().ToList();
|
||||
|
||||
if (!candidates.Any())
|
||||
{
|
||||
Beaprint.NoColorPrint(" No risky named pipe ACLs were found.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
var aclSummary = candidate.LowPrivilegeAces.Any()
|
||||
? string.Join("; ", candidate.LowPrivilegeAces.Select(ace =>
|
||||
$"{ace.Principal} [{ace.RightsDescription}]").Where(s => !string.IsNullOrEmpty(s)))
|
||||
: "Unknown";
|
||||
|
||||
var serverSummary = candidate.Processes.Any()
|
||||
? string.Join("; ", candidate.Processes.Select(proc =>
|
||||
$"{proc.ProcessName} (PID {proc.Pid}, {proc.UserName ?? proc.UserSid})"))
|
||||
: "No privileged handles observed (service idle or access denied)";
|
||||
|
||||
var color = candidate.HasPrivilegedServer ? Beaprint.ansi_color_bad : Beaprint.ansi_color_yellow;
|
||||
|
||||
Beaprint.ColorPrint($" \\\\.\\pipe\\{candidate.Name}", color);
|
||||
Beaprint.NoColorPrint($" Low-priv ACLs : {aclSummary}");
|
||||
Beaprint.NoColorPrint($" Observed owners: {serverSummary}");
|
||||
Beaprint.NoColorPrint($" SDDL : {candidate.Sddl}");
|
||||
Beaprint.PrintLineSeparator();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.PrintException(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintAMSIProviders()
|
||||
{
|
||||
Beaprint.MainPrint("Enumerating AMSI registered providers");
|
||||
|
||||
@@ -156,15 +156,63 @@ namespace winPEAS.Checks
|
||||
try
|
||||
{
|
||||
Beaprint.MainPrint("RDP Sessions");
|
||||
Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/credentials-mgmt/rdp-sessions", "Disconnected high-privilege RDP sessions keep reusable tokens inside LSASS.");
|
||||
List<Dictionary<string, string>> rdp_sessions = UserInfoHelper.GetRDPSessions();
|
||||
if (rdp_sessions.Count > 0)
|
||||
{
|
||||
string format = " {0,-10}{1,-15}{2,-15}{3,-25}{4,-10}{5}";
|
||||
string header = string.Format(format, "SessID", "pSessionName", "pUserName", "pDomainName", "State", "SourceIP");
|
||||
string format = " {0,-8}{1,-15}{2,-20}{3,-22}{4,-15}{5,-18}{6,-10}";
|
||||
string header = string.Format(format, "SessID", "Session", "User", "Domain", "State", "SourceIP", "HighPriv");
|
||||
Beaprint.GrayPrint(header);
|
||||
var colors = ColorsU();
|
||||
List<Dictionary<string, string>> flaggedSessions = new List<Dictionary<string, string>>();
|
||||
foreach (Dictionary<string, string> rdpSes in rdp_sessions)
|
||||
{
|
||||
Beaprint.AnsiPrint(string.Format(format, rdpSes["SessionID"], rdpSes["pSessionName"], rdpSes["pUserName"], rdpSes["pDomainName"], rdpSes["State"], rdpSes["SourceIP"]), ColorsU());
|
||||
rdpSes.TryGetValue("SessionID", out string sessionId);
|
||||
rdpSes.TryGetValue("pSessionName", out string sessionName);
|
||||
rdpSes.TryGetValue("pUserName", out string userName);
|
||||
rdpSes.TryGetValue("pDomainName", out string domainName);
|
||||
rdpSes.TryGetValue("State", out string state);
|
||||
rdpSes.TryGetValue("SourceIP", out string sourceIp);
|
||||
|
||||
sessionId = sessionId ?? string.Empty;
|
||||
sessionName = sessionName ?? string.Empty;
|
||||
userName = userName ?? string.Empty;
|
||||
domainName = domainName ?? string.Empty;
|
||||
state = state ?? string.Empty;
|
||||
sourceIp = sourceIp ?? string.Empty;
|
||||
|
||||
bool isHighPriv = UserInfoHelper.IsHighPrivilegeAccount(userName, domainName);
|
||||
string highPrivLabel = isHighPriv ? "Yes" : "No";
|
||||
rdpSes["HighPriv"] = highPrivLabel;
|
||||
|
||||
if (isHighPriv && string.Equals(state, "Disconnected", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
flaggedSessions.Add(rdpSes);
|
||||
}
|
||||
|
||||
Beaprint.AnsiPrint(string.Format(format, sessionId, sessionName, userName, domainName, state, sourceIp, highPrivLabel), colors);
|
||||
}
|
||||
|
||||
if (flaggedSessions.Count > 0)
|
||||
{
|
||||
Beaprint.BadPrint(" [!] Disconnected high-privilege RDP sessions detected. Their credentials/tokens stay in LSASS until the user signs out.");
|
||||
foreach (Dictionary<string, string> session in flaggedSessions)
|
||||
{
|
||||
session.TryGetValue("pDomainName", out string flaggedDomain);
|
||||
session.TryGetValue("pUserName", out string flaggedUser);
|
||||
session.TryGetValue("SessionID", out string flaggedSessionId);
|
||||
session.TryGetValue("SourceIP", out string flaggedIp);
|
||||
|
||||
flaggedDomain = flaggedDomain ?? string.Empty;
|
||||
flaggedUser = flaggedUser ?? string.Empty;
|
||||
flaggedSessionId = flaggedSessionId ?? string.Empty;
|
||||
flaggedIp = flaggedIp ?? string.Empty;
|
||||
|
||||
string userDisplay = string.Format("{0}\\{1}", flaggedDomain, flaggedUser).Trim('\\');
|
||||
string source = string.IsNullOrEmpty(flaggedIp) ? "local" : flaggedIp;
|
||||
Beaprint.BadPrint(string.Format(" -> Session {0} ({1}) from {2}", flaggedSessionId, userDisplay, source));
|
||||
}
|
||||
Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/credentials-mgmt/rdp-sessions", "Dump LSASS / steal tokens (e.g., comsvcs.dll, LsaLogonSessions, custom SSPs) to reuse those privileges.");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -132,6 +132,7 @@ namespace winPEAS.Helpers
|
||||
Console.WriteLine(LCYAN + " activedirectoryinfo" + GRAY + " Quick AD checks (gMSA readable passwords, AD CS template rights)" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " cloudinfo" + GRAY + " Enumerate cloud information" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " windowscreds" + GRAY + " Search windows credentials" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " registryinfo" + GRAY + " Flag writable HKLM/HKU keys that enable hive tampering" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " browserinfo" + GRAY + " Search browser information" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " filesinfo" + GRAY + " Search generic files that can contains credentials" + NOCOLOR);
|
||||
Console.WriteLine(LCYAN + " fileanalysis" + GRAY + " [NOT RUN BY DEFAULT] Search specific files that can contains credentials and for regexes inside files. Might take several minutes." + NOCOLOR);
|
||||
|
||||
@@ -21,39 +21,59 @@ namespace winPEAS.Helpers
|
||||
""); //To get the default object you need to use an empty string
|
||||
}
|
||||
|
||||
public static List<T> GetLimitedRange<T>(List<T> items, int limit)
|
||||
{
|
||||
return items.GetRange(0, Math.Min(items.Count, limit));
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
/////// MISC - Files & Paths ///////
|
||||
////////////////////////////////////
|
||||
public static bool CheckIfDotNet(string path)
|
||||
public static bool CheckIfDotNet(string path, bool ignoreCompanyName = false)
|
||||
{
|
||||
bool isDotNet = false;
|
||||
FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(path);
|
||||
string companyName = myFileVersionInfo.CompanyName;
|
||||
if ((string.IsNullOrEmpty(companyName)) ||
|
||||
(!Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase)))
|
||||
string companyName = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(path);
|
||||
companyName = myFileVersionInfo.CompanyName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Unable to read version information, continue with assembly inspection
|
||||
}
|
||||
|
||||
bool shouldInspectAssembly = ignoreCompanyName ||
|
||||
(string.IsNullOrEmpty(companyName)) ||
|
||||
(!Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase));
|
||||
|
||||
if (!shouldInspectAssembly)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AssemblyName.GetAssemblyName(path);
|
||||
isDotNet = true;
|
||||
}
|
||||
catch (System.IO.FileNotFoundException)
|
||||
{
|
||||
// System.Console.WriteLine("The file cannot be found.");
|
||||
}
|
||||
catch (System.BadImageFormatException exception)
|
||||
{
|
||||
if (Regex.IsMatch(exception.Message,
|
||||
".*This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.*",
|
||||
RegexOptions.IgnoreCase))
|
||||
{
|
||||
AssemblyName myAssemblyName = AssemblyName.GetAssemblyName(path);
|
||||
isDotNet = true;
|
||||
}
|
||||
catch (System.IO.FileNotFoundException)
|
||||
{
|
||||
// System.Console.WriteLine("The file cannot be found.");
|
||||
}
|
||||
catch (System.BadImageFormatException exception)
|
||||
{
|
||||
if (Regex.IsMatch(exception.Message,
|
||||
".*This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.*",
|
||||
RegexOptions.IgnoreCase))
|
||||
{
|
||||
isDotNet = true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// System.Console.WriteLine("The assembly has already been loaded.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// System.Console.WriteLine("The assembly has already been loaded.");
|
||||
}
|
||||
|
||||
return isDotNet;
|
||||
|
||||
34
winPEAS/winPEASexe/winPEAS/Helpers/ObjectManagerHelper.cs
Normal file
34
winPEAS/winPEASexe/winPEAS/Helpers/ObjectManagerHelper.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace winPEAS.Helpers
|
||||
{
|
||||
internal static class ObjectManagerHelper
|
||||
{
|
||||
public static bool TryCreateSessionEvent(out string objectName, out string error)
|
||||
{
|
||||
objectName = $"PEAS_OMNS_{Process.GetCurrentProcess().Id}_{Guid.NewGuid():N}";
|
||||
error = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
using (var handle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, objectName, out var createdNew))
|
||||
{
|
||||
if (!createdNew)
|
||||
{
|
||||
error = "A test event with the generated name already existed.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using winPEAS.Helpers;
|
||||
|
||||
namespace winPEAS.Helpers.Registry
|
||||
{
|
||||
internal class RegistryWritableKeyInfo
|
||||
{
|
||||
public string Hive { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public string FullPath { get; set; }
|
||||
public List<string> Principals { get; set; } = new List<string>();
|
||||
public List<string> Rights { get; set; } = new List<string>();
|
||||
}
|
||||
|
||||
internal static class RegistryAclScanner
|
||||
{
|
||||
private static readonly Dictionary<string, string> LowPrivSidMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null).Value, "BUILTIN\\Users" },
|
||||
{ new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null).Value, "Authenticated Users" },
|
||||
{ new SecurityIdentifier(WellKnownSidType.WorldSid, null).Value, "Everyone" },
|
||||
{ new SecurityIdentifier(WellKnownSidType.InteractiveSid, null).Value, "Interactive" },
|
||||
{ new SecurityIdentifier(WellKnownSidType.BuiltinGuestsSid, null).Value, "BUILTIN\\Guests" },
|
||||
};
|
||||
|
||||
public static bool TryGetWritableKey(string hive, string relativePath, out RegistryWritableKeyInfo info)
|
||||
{
|
||||
info = null;
|
||||
using (var key = OpenKey(hive, relativePath))
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryCollectWritableInfo(hive, relativePath, key, out info);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<RegistryWritableKeyInfo> ScanWritableKeys(string hive, IEnumerable<string> basePaths, int maxDepth, int maxResults)
|
||||
{
|
||||
var results = new List<RegistryWritableKeyInfo>();
|
||||
var seenPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var basePath in basePaths ?? Enumerable.Empty<string>())
|
||||
{
|
||||
if (results.Count >= maxResults)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
using (var key = OpenKey(hive, basePath))
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Traverse(hive, key, basePath, 0, maxDepth, maxResults, seenPaths, results);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static void Traverse(string hive, RegistryKey currentKey, string currentPath, int depth, int maxDepth, int maxResults, HashSet<string> seenPaths, List<RegistryWritableKeyInfo> results)
|
||||
{
|
||||
if (currentKey == null || results.Count >= maxResults)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryCollectWritableInfo(hive, currentPath, currentKey, out var info))
|
||||
{
|
||||
if (seenPaths.Add(info.FullPath))
|
||||
{
|
||||
results.Add(info);
|
||||
}
|
||||
|
||||
if (results.Count >= maxResults)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth >= maxDepth)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] subKeys;
|
||||
try
|
||||
{
|
||||
subKeys = currentKey.GetSubKeyNames();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var subKeyName in subKeys)
|
||||
{
|
||||
if (results.Count >= maxResults)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var childKey = currentKey.OpenSubKey(subKeyName))
|
||||
{
|
||||
if (childKey == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string childPath = string.IsNullOrEmpty(currentPath) ? subKeyName : $"{currentPath}\\{subKeyName}";
|
||||
Traverse(hive, childKey, childPath, depth + 1, maxDepth, maxResults, seenPaths, results);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore keys we cannot open
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryCollectWritableInfo(string hive, string relativePath, RegistryKey key, out RegistryWritableKeyInfo info)
|
||||
{
|
||||
info = null;
|
||||
|
||||
try
|
||||
{
|
||||
var acl = key.GetAccessControl(AccessControlSections.Access);
|
||||
|
||||
var principals = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var rights = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (RegistryAccessRule rule in acl.GetAccessRules(true, true, typeof(SecurityIdentifier)))
|
||||
{
|
||||
if (rule.AccessControlType != AccessControlType.Allow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var sid = rule.IdentityReference as SecurityIdentifier ?? rule.IdentityReference.Translate(typeof(SecurityIdentifier)) as SecurityIdentifier;
|
||||
if (sid == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!LowPrivSidMap.TryGetValue(sid.Value, out var label))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string interestingRight = PermissionsHelper.PermInt2Str((int)rule.RegistryRights, PermissionType.WRITEABLE_OR_EQUIVALENT_REG);
|
||||
if (string.IsNullOrEmpty(interestingRight))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
principals.Add($"{label} ({sid.Value})");
|
||||
rights.Add(interestingRight);
|
||||
}
|
||||
|
||||
if (principals.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string normalizedRelativePath = relativePath ?? string.Empty;
|
||||
string fullPath = string.IsNullOrEmpty(normalizedRelativePath) ? key.Name : $"{hive}\\{normalizedRelativePath}";
|
||||
|
||||
info = new RegistryWritableKeyInfo
|
||||
{
|
||||
Hive = hive,
|
||||
RelativePath = normalizedRelativePath,
|
||||
FullPath = fullPath,
|
||||
Principals = principals.ToList(),
|
||||
Rights = rights.ToList(),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static RegistryKey OpenKey(string hive, string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RegistryKey baseKey = hive switch
|
||||
{
|
||||
"HKLM" => Microsoft.Win32.Registry.LocalMachine,
|
||||
"HKCU" => Microsoft.Win32.Registry.CurrentUser,
|
||||
"HKU" => Microsoft.Win32.Registry.Users,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return baseKey?.OpenSubKey(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Text;
|
||||
using winPEAS.Helpers;
|
||||
using winPEAS.Info.ProcessInfo;
|
||||
|
||||
namespace winPEAS.Info.ApplicationInfo
|
||||
{
|
||||
internal class SoapClientProxyInstance
|
||||
{
|
||||
public string SourceType { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Account { get; set; }
|
||||
public string Extra { get; set; }
|
||||
}
|
||||
|
||||
internal class SoapClientProxyFinding
|
||||
{
|
||||
public string BinaryPath { get; set; }
|
||||
public List<SoapClientProxyInstance> Instances { get; } = new List<SoapClientProxyInstance>();
|
||||
public HashSet<string> BinaryIndicators { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
public HashSet<string> ConfigIndicators { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
public string ConfigPath { get; set; }
|
||||
public bool BinaryScanFailed { get; set; }
|
||||
public bool ConfigScanFailed { get; set; }
|
||||
}
|
||||
|
||||
internal static class SoapClientProxyAnalyzer
|
||||
{
|
||||
private class SoapClientProxyCandidate
|
||||
{
|
||||
public string BinaryPath { get; set; }
|
||||
public string SourceType { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Account { get; set; }
|
||||
public string Extra { get; set; }
|
||||
}
|
||||
|
||||
private static readonly string[] BinaryIndicatorStrings = new[]
|
||||
{
|
||||
"SoapHttpClientProtocol",
|
||||
"HttpWebClientProtocol",
|
||||
"DiscoveryClientProtocol",
|
||||
"HttpSimpleClientProtocol",
|
||||
"HttpGetClientProtocol",
|
||||
"HttpPostClientProtocol",
|
||||
"ServiceDescriptionImporter",
|
||||
"System.Web.Services.Description.ServiceDescriptionImporter",
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> ConfigIndicatorMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "soap:address", "soap:address element present" },
|
||||
{ "soap12:address", "soap12:address element present" },
|
||||
{ "?wsdl", "?wsdl reference" },
|
||||
{ "<wsdl:", "WSDL schema embedded in config" },
|
||||
{ "servicedescriptionimporter", "ServiceDescriptionImporter referenced in config" },
|
||||
{ "system.web.services.description", "System.Web.Services.Description namespace referenced" },
|
||||
{ "new-webserviceproxy", "PowerShell New-WebServiceProxy referenced" },
|
||||
{ "file://", "file:// scheme referenced" },
|
||||
};
|
||||
|
||||
private const long MaxBinaryScanSize = 200 * 1024 * 1024; // 200MB
|
||||
private static readonly object DotNetCacheLock = new object();
|
||||
private static readonly Dictionary<string, bool> DotNetCache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public static List<SoapClientProxyFinding> CollectFindings()
|
||||
{
|
||||
var findings = new Dictionary<string, SoapClientProxyFinding>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var candidate in EnumerateServiceCandidates().Concat(EnumerateProcessCandidates()))
|
||||
{
|
||||
if (string.IsNullOrEmpty(candidate.BinaryPath) || !File.Exists(candidate.BinaryPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!findings.TryGetValue(candidate.BinaryPath, out var finding))
|
||||
{
|
||||
finding = new SoapClientProxyFinding
|
||||
{
|
||||
BinaryPath = candidate.BinaryPath,
|
||||
};
|
||||
|
||||
findings.Add(candidate.BinaryPath, finding);
|
||||
}
|
||||
|
||||
finding.Instances.Add(new SoapClientProxyInstance
|
||||
{
|
||||
SourceType = candidate.SourceType,
|
||||
Name = candidate.Name,
|
||||
Account = string.IsNullOrEmpty(candidate.Account) ? "Unknown" : candidate.Account,
|
||||
Extra = candidate.Extra ?? string.Empty,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var finding in findings.Values)
|
||||
{
|
||||
ScanBinaryIndicators(finding);
|
||||
ScanConfigIndicators(finding);
|
||||
}
|
||||
|
||||
return findings.Values
|
||||
.Where(f => f.BinaryIndicators.Count > 0 || f.ConfigIndicators.Count > 0)
|
||||
.OrderByDescending(f => f.BinaryIndicators.Contains("ServiceDescriptionImporter"))
|
||||
.ThenBy(f => f.BinaryPath, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<SoapClientProxyCandidate> EnumerateServiceCandidates()
|
||||
{
|
||||
var results = new List<SoapClientProxyCandidate>();
|
||||
try
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher(@"root\\cimv2", "SELECT Name, DisplayName, PathName, StartName FROM Win32_Service"))
|
||||
using (var services = searcher.Get())
|
||||
{
|
||||
foreach (ManagementObject service in services)
|
||||
{
|
||||
string pathName = service["PathName"]?.ToString();
|
||||
string binaryPath = MyUtils.GetExecutableFromPath(pathName ?? string.Empty);
|
||||
if (string.IsNullOrEmpty(binaryPath) || !File.Exists(binaryPath))
|
||||
continue;
|
||||
|
||||
if (!IsDotNetBinary(binaryPath))
|
||||
continue;
|
||||
|
||||
results.Add(new SoapClientProxyCandidate
|
||||
{
|
||||
BinaryPath = binaryPath,
|
||||
SourceType = "Service",
|
||||
Name = service["Name"]?.ToString() ?? string.Empty,
|
||||
Account = service["StartName"]?.ToString() ?? string.Empty,
|
||||
Extra = service["DisplayName"]?.ToString() ?? string.Empty,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.GrayPrint("Error while enumerating services for SOAP client analysis: " + ex.Message);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static IEnumerable<SoapClientProxyCandidate> EnumerateProcessCandidates()
|
||||
{
|
||||
var results = new List<SoapClientProxyCandidate>();
|
||||
try
|
||||
{
|
||||
List<Dictionary<string, string>> processes = ProcessesInfo.GetProcInfo();
|
||||
foreach (var proc in processes)
|
||||
{
|
||||
string path = proc.ContainsKey("ExecutablePath") ? proc["ExecutablePath"] : string.Empty;
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
||||
continue;
|
||||
|
||||
if (!IsDotNetBinary(path))
|
||||
continue;
|
||||
|
||||
string owner = proc.ContainsKey("Owner") ? proc["Owner"] : string.Empty;
|
||||
if (!IsInterestingProcessOwner(owner))
|
||||
continue;
|
||||
|
||||
results.Add(new SoapClientProxyCandidate
|
||||
{
|
||||
BinaryPath = path,
|
||||
SourceType = "Process",
|
||||
Name = proc.ContainsKey("Name") ? proc["Name"] : string.Empty,
|
||||
Account = owner,
|
||||
Extra = proc.ContainsKey("ProcessID") ? $"PID {proc["ProcessID"]}" : string.Empty,
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Beaprint.GrayPrint("Error while enumerating processes for SOAP client analysis: " + ex.Message);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static bool IsInterestingProcessOwner(string owner)
|
||||
{
|
||||
if (string.IsNullOrEmpty(owner))
|
||||
return true;
|
||||
|
||||
string normalizedOwner = owner;
|
||||
if (owner.Contains("\\"))
|
||||
{
|
||||
normalizedOwner = owner.Split('\\').Last();
|
||||
}
|
||||
|
||||
return !normalizedOwner.Equals(Environment.UserName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsDotNetBinary(string path)
|
||||
{
|
||||
lock (DotNetCacheLock)
|
||||
{
|
||||
if (DotNetCache.TryGetValue(path, out bool cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
try
|
||||
{
|
||||
result = MyUtils.CheckIfDotNet(path, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
DotNetCache[path] = result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ScanBinaryIndicators(SoapClientProxyFinding finding)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileInfo fi = new FileInfo(finding.BinaryPath);
|
||||
if (!fi.Exists || fi.Length == 0)
|
||||
return;
|
||||
|
||||
if (fi.Length > MaxBinaryScanSize)
|
||||
{
|
||||
finding.BinaryScanFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var indicator in BinaryIndicatorStrings)
|
||||
{
|
||||
if (FileContainsString(finding.BinaryPath, indicator))
|
||||
{
|
||||
finding.BinaryIndicators.Add(indicator);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finding.BinaryScanFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ScanConfigIndicators(SoapClientProxyFinding finding)
|
||||
{
|
||||
string configPath = GetConfigPath(finding.BinaryPath);
|
||||
if (!string.IsNullOrEmpty(configPath) && File.Exists(configPath))
|
||||
{
|
||||
finding.ConfigPath = configPath;
|
||||
try
|
||||
{
|
||||
string content = File.ReadAllText(configPath);
|
||||
foreach (var kvp in ConfigIndicatorMap)
|
||||
{
|
||||
if (content.IndexOf(kvp.Key, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
finding.ConfigIndicators.Add(kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finding.ConfigScanFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
string directory = Path.GetDirectoryName(finding.BinaryPath);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
try
|
||||
{
|
||||
var wsdlFiles = Directory.GetFiles(directory, "*.wsdl", SearchOption.TopDirectoryOnly);
|
||||
if (wsdlFiles.Length > 0)
|
||||
{
|
||||
finding.ConfigIndicators.Add($"Found {wsdlFiles.Length} WSDL file(s) next to binary");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetConfigPath(string binaryPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(binaryPath))
|
||||
return string.Empty;
|
||||
|
||||
string candidate = binaryPath + ".config";
|
||||
return File.Exists(candidate) ? candidate : string.Empty;
|
||||
}
|
||||
|
||||
private static bool FileContainsString(string path, string value)
|
||||
{
|
||||
const int bufferSize = 64 * 1024;
|
||||
byte[] pattern = Encoding.UTF8.GetBytes(value);
|
||||
if (pattern.Length == 0)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
|
||||
{
|
||||
byte[] buffer = new byte[bufferSize + pattern.Length];
|
||||
int bufferLen = 0;
|
||||
int bytesRead;
|
||||
while ((bytesRead = fs.Read(buffer, bufferLen, bufferSize)) > 0)
|
||||
{
|
||||
int total = bufferLen + bytesRead;
|
||||
if (IndexOf(buffer, total, pattern) >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pattern.Length > 1)
|
||||
{
|
||||
bufferLen = Math.Min(pattern.Length - 1, total);
|
||||
Buffer.BlockCopy(buffer, total - bufferLen, buffer, 0, bufferLen);
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferLen = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int IndexOf(byte[] buffer, int bufferLength, byte[] pattern)
|
||||
{
|
||||
int limit = bufferLength - pattern.Length;
|
||||
if (limit < 0)
|
||||
return -1;
|
||||
|
||||
for (int i = 0; i <= limit; i++)
|
||||
{
|
||||
bool match = true;
|
||||
for (int j = 0; j < pattern.Length; j++)
|
||||
{
|
||||
if (buffer[i + j] != pattern[j])
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ namespace winPEAS.Info.NetworkInfo
|
||||
|
||||
// 4. Call external checker
|
||||
var resp = httpClient
|
||||
.PostAsync("https://2e6ppt7izvuv66qmx2r3et2ufi0mxwqs.lambda-url.us-east-1.on.aws/", payload)
|
||||
.PostAsync("https://tools.hacktricks.wiki/api/host-checker", payload)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
if (resp.IsSuccessStatusCode)
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
|
||||
namespace winPEAS.Info.NetworkInfo
|
||||
@@ -48,7 +50,7 @@ namespace winPEAS.Info.NetworkInfo
|
||||
{ "1.1.1.1", "8.8.8.8" };
|
||||
|
||||
private const string LAMBDA_URL =
|
||||
"https://2e6ppt7izvuv66qmx2r3et2ufi0mxwqs.lambda-url.us-east-1.on.aws/";
|
||||
"https://tools.hacktricks.wiki/api/host-checker";
|
||||
|
||||
// Shared HttpClient (kept for HTTP & Lambda checks)
|
||||
private static readonly HttpClient http = new HttpClient
|
||||
@@ -118,7 +120,12 @@ namespace winPEAS.Info.NetworkInfo
|
||||
using var cts =
|
||||
new CancellationTokenSource(TimeSpan.FromMilliseconds(HTTP_TIMEOUT_MS));
|
||||
|
||||
var req = new HttpRequestMessage(HttpMethod.Get, LAMBDA_URL);
|
||||
var payload = new StringContent(
|
||||
JsonSerializer.Serialize(new { hostname = Environment.MachineName }),
|
||||
Encoding.UTF8,
|
||||
"application/json");
|
||||
var req = new HttpRequestMessage(HttpMethod.Post, LAMBDA_URL);
|
||||
req.Content = payload;
|
||||
req.Headers.UserAgent.ParseAdd("winpeas");
|
||||
req.Headers.Accept.Add(
|
||||
new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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 /////////
|
||||
//////////////////////////////////////
|
||||
|
||||
@@ -0,0 +1,508 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using winPEAS.Helpers;
|
||||
using winPEAS.Native;
|
||||
|
||||
namespace winPEAS.Info.SystemInfo.NamedPipes
|
||||
{
|
||||
internal static class NamedPipeSecurityAnalyzer
|
||||
{
|
||||
private const string DeviceNamedPipePrefix = @"\Device\NamedPipe\";
|
||||
private static readonly char[] CandidateSeparators = { '\\', '/', '-', ':', '(' };
|
||||
|
||||
private static readonly HashSet<string> LowPrivSidSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"S-1-1-0", // Everyone
|
||||
"S-1-5-11", // Authenticated Users
|
||||
"S-1-5-32-545", // Users
|
||||
"S-1-5-32-546", // Guests
|
||||
"S-1-5-32-547", // Power Users
|
||||
"S-1-5-32-554", // Pre-Windows 2000 Compatible Access
|
||||
"S-1-5-32-555", // Remote Desktop Users
|
||||
"S-1-5-32-558", // Performance Log Users
|
||||
"S-1-5-32-559", // Performance Monitor Users
|
||||
"S-1-5-32-562", // Distributed COM Users
|
||||
"S-1-5-32-569", // Remote Management Users
|
||||
"S-1-5-4", // Interactive
|
||||
"S-1-5-2", // Network
|
||||
"S-1-5-1", // Dialup
|
||||
"S-1-5-7" // Anonymous Logon
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> LowPrivPrincipalKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"everyone",
|
||||
"authenticated users",
|
||||
"users",
|
||||
"guests",
|
||||
"power users",
|
||||
"remote desktop users",
|
||||
"remote management users",
|
||||
"distributed com users",
|
||||
"anonymous logon",
|
||||
"interactive",
|
||||
"network",
|
||||
"local",
|
||||
"batch",
|
||||
"iis_iusrs"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> PrivilegedSidSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"S-1-5-18", // SYSTEM
|
||||
"S-1-5-19", // LOCAL SERVICE
|
||||
"S-1-5-20", // NETWORK SERVICE
|
||||
"S-1-5-32-544" // Administrators
|
||||
};
|
||||
|
||||
private static readonly (string Label, FileSystemRights Right)[] DangerousRightsMap = new[]
|
||||
{
|
||||
("FullControl", FileSystemRights.FullControl),
|
||||
("Modify", FileSystemRights.Modify),
|
||||
("Write", FileSystemRights.Write),
|
||||
("WriteData", FileSystemRights.WriteData),
|
||||
("AppendData", FileSystemRights.AppendData),
|
||||
("CreateFiles", FileSystemRights.CreateFiles),
|
||||
("CreateDirectories", FileSystemRights.CreateDirectories),
|
||||
("WriteAttributes", FileSystemRights.WriteAttributes),
|
||||
("WriteExtendedAttributes", FileSystemRights.WriteExtendedAttributes),
|
||||
("Delete", FileSystemRights.Delete),
|
||||
("ChangePermissions", FileSystemRights.ChangePermissions),
|
||||
("TakeOwnership", FileSystemRights.TakeOwnership)
|
||||
};
|
||||
|
||||
public static IEnumerable<NamedPipeSecurityIssue> GetNamedPipeAbuseCandidates()
|
||||
{
|
||||
var insecurePipes = DiscoverInsecurePipes();
|
||||
if (!insecurePipes.Any())
|
||||
{
|
||||
return Enumerable.Empty<NamedPipeSecurityIssue>();
|
||||
}
|
||||
|
||||
AttachProcesses(insecurePipes);
|
||||
|
||||
return insecurePipes.Values
|
||||
.Where(issue => issue.LowPrivilegeAces.Any())
|
||||
.OrderByDescending(issue => issue.HasPrivilegedServer)
|
||||
.ThenBy(issue => issue.Name)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static Dictionary<string, NamedPipeSecurityIssue> DiscoverInsecurePipes()
|
||||
{
|
||||
var result = new Dictionary<string, NamedPipeSecurityIssue>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var pipe in NamedPipes.GetNamedPipeInfos())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pipe.Sddl) || pipe.Sddl.Equals("ERROR", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var descriptor = new RawSecurityDescriptor(pipe.Sddl);
|
||||
if (descriptor.DiscretionaryAcl == null)
|
||||
continue;
|
||||
|
||||
foreach (GenericAce ace in descriptor.DiscretionaryAcl)
|
||||
{
|
||||
if (!(ace is CommonAce commonAce))
|
||||
continue;
|
||||
|
||||
var sid = commonAce.SecurityIdentifier;
|
||||
if (sid == null || !IsLowPrivilegePrincipal(sid))
|
||||
continue;
|
||||
|
||||
if (!HasDangerousWriteRights(commonAce.AccessMask))
|
||||
continue;
|
||||
|
||||
var rights = DescribeRights(commonAce.AccessMask).ToList();
|
||||
if (!rights.Any())
|
||||
continue;
|
||||
|
||||
if (!result.TryGetValue(pipe.Name, out var issue))
|
||||
{
|
||||
issue = new NamedPipeSecurityIssue(pipe.Name, pipe.Sddl, NormalizePipeName(pipe.Name));
|
||||
result[pipe.Name] = issue;
|
||||
}
|
||||
|
||||
var account = ResolveSidToName(sid);
|
||||
issue.AddLowPrivPrincipal(account, sid.Value, rights);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore malformed SDDL strings
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void AttachProcesses(Dictionary<string, NamedPipeSecurityIssue> insecurePipes)
|
||||
{
|
||||
if (!insecurePipes.Any())
|
||||
return;
|
||||
|
||||
var lookup = BuildLookup(insecurePipes.Values);
|
||||
if (!lookup.Any())
|
||||
return;
|
||||
|
||||
List<HandlesHelper.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> handles;
|
||||
try
|
||||
{
|
||||
handles = HandlesHelper.GetAllHandlers();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentProcess = Kernel32.GetCurrentProcess();
|
||||
var processCache = new Dictionary<int, NamedPipeProcessInfo>();
|
||||
|
||||
foreach (var handle in handles)
|
||||
{
|
||||
IntPtr processHandle = IntPtr.Zero;
|
||||
IntPtr duplicatedHandle = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
int pid = GetPid(handle);
|
||||
if (pid <= 0)
|
||||
continue;
|
||||
|
||||
processHandle = Kernel32.OpenProcess(
|
||||
HandlesHelper.ProcessAccessFlags.DupHandle | HandlesHelper.ProcessAccessFlags.QueryLimitedInformation,
|
||||
false,
|
||||
pid);
|
||||
|
||||
if (processHandle == IntPtr.Zero)
|
||||
continue;
|
||||
|
||||
if (!Kernel32.DuplicateHandle(processHandle, handle.HandleValue, currentProcess, out duplicatedHandle, 0, false, HandlesHelper.DUPLICATE_SAME_ACCESS))
|
||||
continue;
|
||||
|
||||
var typeName = HandlesHelper.GetObjectType(duplicatedHandle);
|
||||
if (!string.Equals(typeName, "File", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var objectName = HandlesHelper.GetObjectName(duplicatedHandle);
|
||||
if (string.IsNullOrEmpty(objectName) || !objectName.StartsWith(DeviceNamedPipePrefix, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var normalizedHandleName = NormalizePipeName(objectName.Substring(DeviceNamedPipePrefix.Length));
|
||||
var candidates = GetCandidateKeys(normalizedHandleName);
|
||||
|
||||
bool matched = false;
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (!lookup.TryGetValue(candidate, out var matchedIssues))
|
||||
continue;
|
||||
|
||||
if (!processCache.TryGetValue(pid, out var processInfo))
|
||||
{
|
||||
var raw = HandlesHelper.getProcInfoById(pid);
|
||||
processInfo = new NamedPipeProcessInfo(raw.pid, raw.name, raw.userName, raw.userSid, IsHighPrivilegeAccount(raw.userSid, raw.userName));
|
||||
processCache[pid] = processInfo;
|
||||
}
|
||||
|
||||
foreach (var issue in matchedIssues)
|
||||
{
|
||||
issue.AddProcess(processInfo);
|
||||
}
|
||||
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!matched)
|
||||
continue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore per-handle failures
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (duplicatedHandle != IntPtr.Zero)
|
||||
{
|
||||
Kernel32.CloseHandle(duplicatedHandle);
|
||||
}
|
||||
if (processHandle != IntPtr.Zero)
|
||||
{
|
||||
Kernel32.CloseHandle(processHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, List<NamedPipeSecurityIssue>> BuildLookup(IEnumerable<NamedPipeSecurityIssue> issues)
|
||||
{
|
||||
var lookup = new Dictionary<string, List<NamedPipeSecurityIssue>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var issue in issues)
|
||||
{
|
||||
foreach (var key in GetCandidateKeys(issue.NormalizedName))
|
||||
{
|
||||
if (!lookup.TryGetValue(key, out var list))
|
||||
{
|
||||
list = new List<NamedPipeSecurityIssue>();
|
||||
lookup[key] = list;
|
||||
}
|
||||
|
||||
if (!list.Contains(issue))
|
||||
{
|
||||
list.Add(issue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetCandidateKeys(string normalizedName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(normalizedName))
|
||||
return Array.Empty<string>();
|
||||
|
||||
var candidates = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
normalizedName
|
||||
};
|
||||
|
||||
foreach (var separator in CandidateSeparators)
|
||||
{
|
||||
var idx = normalizedName.IndexOf(separator);
|
||||
if (idx > 0)
|
||||
{
|
||||
candidates.Add(normalizedName.Substring(0, idx));
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private static string NormalizePipeName(string rawName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawName))
|
||||
return string.Empty;
|
||||
|
||||
var normalized = rawName.Replace('/', '\\').Trim();
|
||||
while (normalized.StartsWith("\\", StringComparison.Ordinal))
|
||||
{
|
||||
normalized = normalized.Substring(1);
|
||||
}
|
||||
|
||||
return normalized.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static bool HasDangerousWriteRights(int accessMask)
|
||||
{
|
||||
var rights = (FileSystemRights)accessMask;
|
||||
foreach (var entry in DangerousRightsMap)
|
||||
{
|
||||
if ((rights & entry.Right) == entry.Right)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> DescribeRights(int accessMask)
|
||||
{
|
||||
var rights = (FileSystemRights)accessMask;
|
||||
var descriptions = new List<string>();
|
||||
|
||||
foreach (var entry in DangerousRightsMap)
|
||||
{
|
||||
if ((rights & entry.Right) == entry.Right)
|
||||
{
|
||||
descriptions.Add(entry.Label);
|
||||
if (entry.Right == FileSystemRights.FullControl)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!descriptions.Any())
|
||||
{
|
||||
descriptions.Add($"0x{accessMask:x}");
|
||||
}
|
||||
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
private static bool IsLowPrivilegePrincipal(SecurityIdentifier sid)
|
||||
{
|
||||
if (sid == null)
|
||||
return false;
|
||||
|
||||
if (LowPrivSidSet.Contains(sid.Value))
|
||||
return true;
|
||||
|
||||
var accountName = ResolveSidToName(sid);
|
||||
if (string.IsNullOrEmpty(accountName))
|
||||
return false;
|
||||
|
||||
return LowPrivPrincipalKeywords.Any(keyword => accountName.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
}
|
||||
|
||||
private static string ResolveSidToName(SecurityIdentifier sid)
|
||||
{
|
||||
if (sid == null)
|
||||
return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
return sid.Translate(typeof(NTAccount)).Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return sid.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsHighPrivilegeAccount(string sid, string userName)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(sid))
|
||||
{
|
||||
if (PrivilegedSidSet.Contains(sid))
|
||||
return true;
|
||||
|
||||
if (sid.StartsWith("S-1-5-80-", StringComparison.OrdinalIgnoreCase)) // Service SID
|
||||
return true;
|
||||
|
||||
if (sid.StartsWith("S-1-5-82-", StringComparison.OrdinalIgnoreCase)) // AppPool / service-like SIDs
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(userName))
|
||||
{
|
||||
if (string.Equals(userName, HandlesHelper.elevatedProcess, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
var normalized = userName.ToUpperInvariant();
|
||||
if (normalized.Contains("SYSTEM") || normalized.Contains("LOCAL SERVICE") || normalized.Contains("NETWORK SERVICE"))
|
||||
return true;
|
||||
|
||||
if (normalized.StartsWith("NT SERVICE\\", StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
if (normalized.EndsWith("$", StringComparison.Ordinal) && normalized.Contains("\\"))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int GetPid(HandlesHelper.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handle)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
if (IntPtr.Size == 4)
|
||||
{
|
||||
return (int)handle.UniqueProcessId.ToUInt32();
|
||||
}
|
||||
|
||||
return (int)handle.UniqueProcessId.ToUInt64();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class NamedPipeSecurityIssue
|
||||
{
|
||||
private readonly Dictionary<string, NamedPipePrincipalAccess> _principalAccess = new Dictionary<string, NamedPipePrincipalAccess>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<int, NamedPipeProcessInfo> _processes = new Dictionary<int, NamedPipeProcessInfo>();
|
||||
|
||||
public NamedPipeSecurityIssue(string name, string sddl, string normalizedName)
|
||||
{
|
||||
Name = name;
|
||||
Sddl = sddl;
|
||||
NormalizedName = normalizedName;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string Sddl { get; }
|
||||
public string NormalizedName { get; }
|
||||
|
||||
public IReadOnlyCollection<NamedPipePrincipalAccess> LowPrivilegeAces => _principalAccess.Values;
|
||||
public IReadOnlyCollection<NamedPipeProcessInfo> Processes => _processes.Values;
|
||||
public bool HasPrivilegedServer => _processes.Values.Any(process => process.IsHighPrivilege);
|
||||
|
||||
public void AddLowPrivPrincipal(string principal, string sid, IEnumerable<string> rights)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sid))
|
||||
return;
|
||||
|
||||
if (!_principalAccess.TryGetValue(sid, out var access))
|
||||
{
|
||||
access = new NamedPipePrincipalAccess(principal, sid);
|
||||
_principalAccess[sid] = access;
|
||||
}
|
||||
|
||||
access.AddRights(rights);
|
||||
}
|
||||
|
||||
public void AddProcess(NamedPipeProcessInfo process)
|
||||
{
|
||||
if (process == null)
|
||||
return;
|
||||
|
||||
if (!_processes.ContainsKey(process.Pid))
|
||||
{
|
||||
_processes[process.Pid] = process;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class NamedPipePrincipalAccess
|
||||
{
|
||||
private readonly HashSet<string> _rights = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public NamedPipePrincipalAccess(string principal, string sid)
|
||||
{
|
||||
Principal = principal;
|
||||
Sid = sid;
|
||||
}
|
||||
|
||||
public string Principal { get; }
|
||||
public string Sid { get; }
|
||||
public string RightsDescription => _rights.Count == 0 ? string.Empty : string.Join("|", _rights.OrderBy(r => r));
|
||||
public IEnumerable<string> Rights => _rights;
|
||||
|
||||
public void AddRights(IEnumerable<string> rights)
|
||||
{
|
||||
if (rights == null)
|
||||
return;
|
||||
|
||||
foreach (var right in rights)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(right))
|
||||
{
|
||||
_rights.Add(right.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class NamedPipeProcessInfo
|
||||
{
|
||||
public NamedPipeProcessInfo(int pid, string processName, string userName, string userSid, bool isHighPrivilege)
|
||||
{
|
||||
Pid = pid;
|
||||
ProcessName = processName;
|
||||
UserName = userName;
|
||||
UserSid = userSid;
|
||||
IsHighPrivilege = isHighPrivilege;
|
||||
}
|
||||
|
||||
public int Pid { get; }
|
||||
public string ProcessName { get; }
|
||||
public string UserName { get; }
|
||||
public string UserSid { get; }
|
||||
public bool IsHighPrivilege { get; }
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@ namespace winPEAS.Info.UserInfo.SAM
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_maxPasswordAge == long.MinValue)
|
||||
{
|
||||
return TimeSpan.MinValue;
|
||||
}
|
||||
return -new TimeSpan(_maxPasswordAge);
|
||||
}
|
||||
set
|
||||
@@ -28,6 +32,10 @@ namespace winPEAS.Info.UserInfo.SAM
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_minPasswordAge == long.MinValue)
|
||||
{
|
||||
return TimeSpan.MinValue;
|
||||
}
|
||||
return -new TimeSpan(_minPasswordAge);
|
||||
}
|
||||
set
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -88,6 +88,10 @@ namespace winPEAS.KnownFileCreds
|
||||
if (SID.StartsWith("S-1-5") && !SID.EndsWith("_Classes"))
|
||||
{
|
||||
string[] subKeys = RegistryHelper.GetRegSubkeys("HKU", string.Format("{0}\\Software\\SimonTatham\\PuTTY\\Sessions\\", SID));
|
||||
if (subKeys.Length == 0)
|
||||
{
|
||||
subKeys = RegistryHelper.GetRegSubkeys("HKU", string.Format("{0}\\Software\\SimonTatham\\PuTTY\\Sessions", SID));
|
||||
}
|
||||
|
||||
foreach (string sessionName in subKeys)
|
||||
{
|
||||
@@ -129,6 +133,10 @@ namespace winPEAS.KnownFileCreds
|
||||
else
|
||||
{
|
||||
string[] subKeys = RegistryHelper.GetRegSubkeys("HKCU", "Software\\SimonTatham\\PuTTY\\Sessions\\");
|
||||
if (subKeys.Length == 0)
|
||||
{
|
||||
subKeys = RegistryHelper.GetRegSubkeys("HKCU", "Software\\SimonTatham\\PuTTY\\Sessions");
|
||||
}
|
||||
RegistryKey selfKey = Registry.CurrentUser.OpenSubKey(@"Software\\SimonTatham\\PuTTY\\Sessions"); // extract own Sessions registry keys
|
||||
|
||||
if (selfKey != null)
|
||||
@@ -198,6 +206,10 @@ namespace winPEAS.KnownFileCreds
|
||||
if (SID.StartsWith("S-1-5") && !SID.EndsWith("_Classes"))
|
||||
{
|
||||
Dictionary<string, object> hostKeys = RegistryHelper.GetRegValues("HKU", string.Format("{0}\\Software\\SimonTatham\\PuTTY\\SshHostKeys\\", SID));
|
||||
if ((hostKeys == null) || (hostKeys.Count == 0))
|
||||
{
|
||||
hostKeys = RegistryHelper.GetRegValues("HKU", string.Format("{0}\\Software\\SimonTatham\\PuTTY\\SshHostKeys", SID));
|
||||
}
|
||||
if ((hostKeys != null) && (hostKeys.Count != 0))
|
||||
{
|
||||
Dictionary<string, string> putty_ssh = new Dictionary<string, string>
|
||||
@@ -216,6 +228,10 @@ namespace winPEAS.KnownFileCreds
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> hostKeys = RegistryHelper.GetRegValues("HKCU", "Software\\SimonTatham\\PuTTY\\SshHostKeys\\");
|
||||
if ((hostKeys == null) || (hostKeys.Count == 0))
|
||||
{
|
||||
hostKeys = RegistryHelper.GetRegValues("HKCU", "Software\\SimonTatham\\PuTTY\\SshHostKeys");
|
||||
}
|
||||
if ((hostKeys != null) && (hostKeys.Count != 0))
|
||||
{
|
||||
Dictionary<string, string> putty_ssh = new Dictionary<string, string>();
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace winPEAS
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// TODO: keep Main minimal; this line was an intentional break in test PR.
|
||||
Checks.Checks.Run(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<RunCodeAnalysis>false</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<CodeAnalysisRuleSet Condition="Exists('MinimumRecommendedRules.ruleset')">MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
@@ -71,7 +71,7 @@
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<CodeAnalysisRuleSet Condition="Exists('MinimumRecommendedRules.ruleset')">MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<NoWarn>0168 ; 0169; 0414; 0618; 0649</NoWarn>
|
||||
@@ -84,7 +84,7 @@
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<CodeAnalysisRuleSet Condition="Exists('MinimumRecommendedRules.ruleset')">MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
@@ -96,7 +96,7 @@
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<CodeAnalysisRuleSet Condition="Exists('MinimumRecommendedRules.ruleset')">MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
@@ -108,7 +108,7 @@
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<CodeAnalysisRuleSet Condition="Exists('MinimumRecommendedRules.ruleset')">MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
@@ -1197,9 +1197,11 @@
|
||||
<Compile Include="Checks\NetworkInfo.cs" />
|
||||
<Compile Include="Checks\ProcessInfo.cs" />
|
||||
<Compile Include="Checks\ServicesInfo.cs" />
|
||||
<Compile Include="Checks\SoapClientInfo.cs" />
|
||||
<Compile Include="Checks\SystemInfo.cs" />
|
||||
<Compile Include="Checks\UserInfo.cs" />
|
||||
<Compile Include="Checks\WindowsCreds.cs" />
|
||||
<Compile Include="Checks\RegistryInfo.cs" />
|
||||
<Compile Include="Helpers\AppLocker\AppLockerHelper.cs" />
|
||||
<Compile Include="Helpers\AppLocker\AppLockerRules.cs" />
|
||||
<Compile Include="Helpers\AppLocker\IAppIdPolicyHandler.cs" />
|
||||
@@ -1223,6 +1225,7 @@
|
||||
<Compile Include="Info\ApplicationInfo\ApplicationInfoHelper.cs" />
|
||||
<Compile Include="Info\ApplicationInfo\AutoRuns.cs" />
|
||||
<Compile Include="Info\ApplicationInfo\DeviceDrivers.cs" />
|
||||
<Compile Include="Info\ApplicationInfo\SoapClientProxyAnalyzer.cs" />
|
||||
<Compile Include="Info\ApplicationInfo\InstalledApps.cs" />
|
||||
<Compile Include="Helpers\Beaprint.cs" />
|
||||
<Compile Include="Info\CloudInfo\AWSInfo.cs" />
|
||||
@@ -1291,6 +1294,7 @@
|
||||
<Compile Include="Info\SystemInfo\GroupPolicy\GroupPolicy.cs" />
|
||||
<Compile Include="Info\SystemInfo\GroupPolicy\LocalGroupPolicyInfo.cs" />
|
||||
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipeInfo.cs" />
|
||||
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipeSecurityAnalyzer.cs" />
|
||||
<Compile Include="Info\SystemInfo\NamedPipes\NamedPipes.cs" />
|
||||
<Compile Include="Info\SystemInfo\Ntlm\Ntlm.cs" />
|
||||
<Compile Include="Info\SystemInfo\Ntlm\NtlmSettingsInfo.cs" />
|
||||
@@ -1359,6 +1363,7 @@
|
||||
<Compile Include="KnownFileCreds\Vault\Structs\VAULT_ITEM_WIN8.cs" />
|
||||
<Compile Include="KnownFileCreds\Vault\VaultCli.cs" />
|
||||
<Compile Include="Helpers\MyUtils.cs" />
|
||||
<Compile Include="Helpers\ObjectManagerHelper.cs" />
|
||||
<Compile Include="Info\UserInfo\SAM\Enums.cs" />
|
||||
<Compile Include="Info\UserInfo\SAM\SamServer.cs" />
|
||||
<Compile Include="Info\UserInfo\SAM\Structs.cs" />
|
||||
@@ -1458,12 +1463,14 @@
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Info\ServicesInfo\ServicesInfoHelper.cs" />
|
||||
<Compile Include="Info\ServicesInfo\OemSoftwareHelper.cs" />
|
||||
<Compile Include="Info\SystemInfo\SystemInfo.cs" />
|
||||
<Compile Include="Info\UserInfo\UserInfoHelper.cs" />
|
||||
<Compile Include="Helpers\DomainHelper.cs" />
|
||||
<Compile Include="Helpers\CheckRunner.cs" />
|
||||
<Compile Include="Helpers\ReflectionHelper.cs" />
|
||||
<Compile Include="Helpers\Registry\RegistryHelper.cs" />
|
||||
<Compile Include="Helpers\Registry\RegistryAclScanner.cs" />
|
||||
<Compile Include="Helpers\Search\SearchHelper.cs" />
|
||||
<Compile Include="Wifi\Wifi.cs" />
|
||||
<Compile Include="Wifi\NativeWifiApi\Interop.cs" />
|
||||
|
||||
@@ -815,12 +815,40 @@ systeminfo.exe
|
||||
Write-Host ""
|
||||
if ($TimeStamp) { TimeElapsed }
|
||||
Write-Host -ForegroundColor Blue "=========|| WINDOWS HOTFIXES"
|
||||
Write-Host "=| Check if windows is vulnerable with Watson https://github.com/rasta-mouse/Watson" -ForegroundColor Yellow
|
||||
Write-Host "=| Check missing patches with WES-NG https://github.com/bitsadmin/wesng" -ForegroundColor Yellow
|
||||
Write-Host "Possible exploits (https://github.com/codingo/OSCP-2/blob/master/Windows/WinPrivCheck.bat)" -ForegroundColor Yellow
|
||||
$Hotfix = Get-HotFix | Sort-Object -Descending -Property InstalledOn -ErrorAction SilentlyContinue | Select-Object HotfixID, Description, InstalledBy, InstalledOn
|
||||
$Hotfix | Format-Table -AutoSize
|
||||
|
||||
|
||||
# PrintNightmare PointAndPrint policy checks
|
||||
Write-Host ""
|
||||
if ($TimeStamp) { TimeElapsed }
|
||||
Write-Host -ForegroundColor Blue "=========|| PRINTNIGHTMARE POINTANDPRINT POLICY"
|
||||
$pnKey = "HKLM:\Software\Policies\Microsoft\Windows NT\Printers\PointAndPrint"
|
||||
if (Test-Path $pnKey) {
|
||||
$pn = Get-ItemProperty -Path $pnKey -ErrorAction SilentlyContinue
|
||||
$restrict = $pn.RestrictDriverInstallationToAdministrators
|
||||
$noWarn = $pn.NoWarningNoElevationOnInstall
|
||||
$updatePrompt = $pn.UpdatePromptSettings
|
||||
|
||||
Write-Host "RestrictDriverInstallationToAdministrators: $restrict"
|
||||
Write-Host "NoWarningNoElevationOnInstall: $noWarn"
|
||||
Write-Host "UpdatePromptSettings: $updatePrompt"
|
||||
|
||||
$hasAllValues = ($null -ne $restrict) -and ($null -ne $noWarn) -and ($null -ne $updatePrompt)
|
||||
if (-not $hasAllValues) {
|
||||
Write-Host "PointAndPrint policy values are missing or not configured" -ForegroundColor Gray
|
||||
} elseif (($restrict -eq 0) -and ($noWarn -eq 1) -and ($updatePrompt -eq 2)) {
|
||||
Write-Host "Potentially vulnerable to PrintNightmare misconfiguration" -ForegroundColor Red
|
||||
} else {
|
||||
Write-Host "PointAndPrint policy is not in the known risky configuration" -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "PointAndPrint policy key not found" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
|
||||
#Show all unique updates installed
|
||||
Write-Host ""
|
||||
if ($TimeStamp) { TimeElapsed }
|
||||
@@ -1649,7 +1677,7 @@ if ($TimeStamp) { TimeElapsed }
|
||||
Write-Host -ForegroundColor Blue "=========|| WHOAMI INFO"
|
||||
Write-Host ""
|
||||
if ($TimeStamp) { TimeElapsed }
|
||||
Write-Host -ForegroundColor Blue "=========|| Check Token access here: https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens.html#abusing-tokens" -ForegroundColor yellow
|
||||
Write-Host -ForegroundColor Blue "=========|| Check Token access here: https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens.html#abusing-tokens"
|
||||
Write-Host -ForegroundColor Blue "=========|| Check if you are inside the Administrators group or if you have enabled any token that can be use to escalate privileges like SeImpersonatePrivilege, SeAssignPrimaryPrivilege, SeTcbPrivilege, SeBackupPrivilege, SeRestorePrivilege, SeCreateTokenPrivilege, SeLoadDriverPrivilege, SeTakeOwnershipPrivilege, SeDebugPrivilege"
|
||||
Write-Host "https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#users--groups" -ForegroundColor Yellow
|
||||
Start-Process whoami.exe -ArgumentList "/all" -Wait -NoNewWindow
|
||||
|
||||
Reference in New Issue
Block a user