PAM Configuration and Modules: Linux Auth Backdoors
Linux PAM stacks in /etc/pam.d and the pam_*.so modules they load: where attackers plant password loggers and master passwords, and how to verify them.
- Location
- /etc/pam.d/, /usr/lib64/security/ (RHEL, SUSE), /usr/lib/x86_64-linux-gnu/security/ (Debian/Ubuntu), /usr/lib/security/ (Arch)
- Proves
- How the host authenticates logins, sudo and su, and whether that chain was altered to accept a backdoor password or capture credentials
- Timestamps
- File system times of stack files and modules; PAM messages in auth logs and the journal
- Access
- root to modify; configuration world-readable, modules readable by all
- Retention
- Persistent until changed; package updates may overwrite a patched module
- Collection
- UAC, Velociraptor, cp -a, tar
Tools
Compare all tools- rpmCLI · built into Linux
- dpkgCLI · built into Linux
- debsumsCLI · open source
- authselectCLI · built into Linux
- findCLI · built into Linux
- grep / zgrepCLI · built into Linux
- stringsCLI · built into Linux
What it is
Pluggable Authentication Modules (Linux-PAM) sit between programs that need to authenticate a user (sshd, login, sudo, su, gdm, cron, passwd) and the actual checks. Each program has a stack file in /etc/pam.d/ listing modules by type (auth, account, password, session) and control flag (required, requisite, sufficient, optional, or the bracket syntax). The modules are shared libraries, typically pam_unix.so for /etc/shadow passwords.
Because every password passes through PAM in clear text, it is a prime target. MITRE ATT&CK T1556.003 covers three patterns seen in real intrusions: a patched pam_unix.so that accepts a hard-coded master password or writes credentials to a file, an extra malicious module inserted into a stack, and a legitimate module misused, such as pam_exec.so with expose_authtok feeding every password to a script.
Where it lives
| Item | Debian / Ubuntu | RHEL / Fedora | SUSE | Arch |
|---|---|---|---|---|
| Stack files | /etc/pam.d/ | same | same (also /usr/lib/pam.d/ vendor defaults on recent releases) | same |
| Shared includes | common-auth, common-account, common-password, common-session | system-auth, password-auth (symlinks into /etc/authselect/) | common-* symlinks to common-*-pc | system-auth, system-login |
| Include manager | pam-auth-update from /usr/share/pam-configs/ | authselect (RHEL 8+, Fedora 28+) | pam-config | none, edited by hand |
| Modules | /usr/lib/x86_64-linux-gnu/security/ (also reachable as /lib/... on merged-usr systems) | /usr/lib64/security/ | /usr/lib64/security/ or /lib64/security/ on older releases | /usr/lib/security/ |
| Module settings | /etc/security/ (access.conf, limits.conf, pam_env.conf, faillock.conf) | same | same | same |
A legacy single file, /etc/pam.conf, is still honoured when /etc/pam.d/ is missing. It should not exist on a modern system.
What it proves
- Which modules run for each service and in which order, so you can tell whether a password check can be skipped (
auth sufficient pam_permit.soat the top ofsshdorcommon-authaccepts any password). - Whether a module file differs from the vendor package (patched
pam_unix.so) or is not owned by any package at all. - Whether passwords were exposed to an external program (
pam_exec.so expose_authtok,pam_script, or an unknown.so). - When the configuration changed, from file times and from package manager or authselect state.
- It does not directly show which passwords were captured; look for the output file the backdoor writes, often in
/tmp,/dev/shm,/var/tmpor a hidden path found withstrings.
Key fields
A normal Debian common-auth next to a tampered one:
# normal
auth [success=1 default=ignore] pam_unix.so nullok
auth requisite pam_deny.so
auth required pam_permit.so
# tampered
auth optional pam_exec.so quiet expose_authtok /usr/lib/.cache/pw.sh
auth sufficient pam_sshd_helper.so
auth [success=1 default=ignore] pam_unix.so nullok
| Element | What to check |
|---|---|
Type (auth, account, password, session) | auth and password lines see the clear-text password |
| Control | sufficient before pam_unix.so means success there ends the check |
| Module path | Bare names resolve to the module directory; absolute paths to other directories are unusual |
| Arguments | expose_authtok, seteuid, script paths, nullok (allows empty passwords) |
@include / include / substack | Follow them; a malicious line can hide in a shared include |
Timestamps
Stack files and modules only carry file system times. Package-installed modules keep their build-time mtime, so a module whose ctime is much newer than the last pam or libpam-modules update in the package logs was touched afterwards. Attackers often copy the original mtime back with touch -r, which does not reset ctime or ext4 birth time.
PAM itself logs through syslog (pam_unix(sshd:auth): authentication failure, session opened for user), so authentication events land in auth.log or secure and the journal. With auditd running, PAM also emits USER_AUTH and USER_ACCT records whose grantors= field lists the modules that granted access (an unknown module name there is a strong lead), see audit.log.
Retention
Configuration persists until changed. A package update of pam or libpam-modules replaces a patched pam_unix.so with the vendor file, silently removing the backdoor, while leaving stack edits in place (dpkg treats /etc/pam.d/ files as conffiles and keeps local changes).
Collection
# Dead box
tar -C /mnt/evidence -cpf /cases/2026-017/pam.tar etc/pam.d etc/pam.conf etc/security etc/authselect \
usr/lib/x86_64-linux-gnu/security usr/lib64/security lib64/security usr/lib/security 2>/dev/null
UAC collects all of /etc and hashes binaries in its default profiles; add the module directory to a file collector if you need the libraries themselves. Velociraptor's Linux.Search.FileFinder with a hash option covers the module paths fleet-wide.
Parsing
R=/mnt/evidence
# Package verification of modules and configs
rpm --root "$R" -V pam # RHEL/SUSE: 5 = digest changed, M = mode, T = mtime
dpkg --root="$R" -V libpam-modules libpam-modules-bin libpam-runtime # Debian/Ubuntu
debsums -r "$R" -s libpam-modules # alternative on Debian
authselect check # live RHEL: reports edits made outside authselect
# Modules not owned by any package (Debian)
for m in "$R"/usr/lib/x86_64-linux-gnu/security/*.so; do dpkg --root="$R" -S "${m#$R}" >/dev/null 2>&1 || echo "unowned: $m"; done
# Suspicious arguments and newest stack files
grep -rnE 'expose_authtok|pam_exec|pam_script|pam_permit|/tmp/|/dev/shm/' "$R"/etc/pam.d/
find "$R"/etc/pam.d "$R"/usr/lib64/security "$R"/usr/lib/x86_64-linux-gnu/security -type f -printf '%C+ %p\n' 2>/dev/null | sort -r | head -20
# Strings in a suspect module: hard-coded passwords, log paths
strings -a "$R"/usr/lib64/security/pam_unix.so | grep -E '^/|%s|pass' | head -50
Investigator tips
- Compare
pam_unix.soagainst a clean copy from the same package version, or the vendor's hash; size alone can match on a carefully patched binary. - Check every service stack, not just
sshd:sudo,su,login,gdm-password,sshdandcommon-auth/system-authare all used for backdoors. - Credential log files written by a PAM backdoor often sit next to legitimate files with plausible names. Search for files modified at the same second as recent successful logins.
- A module that exists only in memory will not be on disk. On a live host, check
/proc/<sshd pid>/mapsfor.sopaths outside the module directory and compare with LD preload hijacks. - After containment, reinstall the PAM packages and review stack files by hand; package reinstall does not revert local edits to conffiles.
See also
Related artifacts
- SSH Artifacts: authorized_keys, known_hosts and sshd Logs
- sudo Logs: Linux Privilege Use Records
- auth.log, secure and syslog: Linux Text Logs
- /etc/passwd, shadow and group: Linux Local Accounts
- /etc/ld.so.preload and LD_PRELOAD: Linux Linker Hijacking
- dpkg, APT, RPM and DNF Logs: Linux Package History
- auditd audit.log: Linux Kernel Audit Trail