PAM Module Integrity Verification: Detecting Backdoors Like PamDOORa

PAM Module Integrity Verification: Detecting Backdoors Like PamDOORa

The Problem

Every login, every sudo command, every SSH session on a Linux host passes through PAM — the Pluggable Authentication Modules framework. PAM’s design is its strength and its vulnerability: a shared library dropped into /lib/security/ and a one-line addition to a file in /etc/pam.d/ is all it takes to intercept the entire authentication stack. The module runs with the privileges of the calling process. For SSH and sudo, that means root.

In May 2026, a post-exploitation toolkit called PamDOORa appeared on the Rehub cybercriminal forum at a listed price of $1,600. The toolkit’s persistence mechanism is a compiled PAM shared library. An attacker who gains temporary root access — through a kernel exploit, a stolen privileged credential, or a container escape — installs the module in /lib/security/ and inserts a reference to it at the top of the PAM configuration for sshd, sudo, and su. From that point forward, the module executes on every authentication event:

  • It captures plaintext credentials as they pass through the auth stack, before any cryptographic handling.
  • It executes attacker-defined shell commands triggered by authentication events, including specific magic-password triggers that open a root shell without appearing in lastlog or wtmp.
  • It exfiltrates captured credentials via DNS or HTTPS to a configured C2 address.
  • It survives password changes — the backdoor is in the PAM stack, not in a user account.
  • It survives reboots — PAM modules load from disk on each process invocation; no daemon registration is required.
  • It is invisible in ps and netstat during normal operation — it activates only during authentication events.

The attack chain does not require a persistent process. There is no daemon to kill, no cron job to remove, no suspicious network connection to trace between authentication events. The entire persistence mechanism is a single .so file and a one-line configuration change — both of which look like any other PAM module installation at a cursory glance.

This article covers the detection and prevention controls that catch PamDOORa and attacks that follow the same pattern: IMA/EVM module signing, file integrity monitoring for the PAM stack, auditd and Falco rules for PAM write events, runtime enumeration of PAM modules against package-manager records, and AppArmor/SELinux restrictions on who can write to /lib/security/.

Threat Model

PamDOORa is one instance of a recurring attack pattern. The threat model covers three distinct entry vectors, all of which share the same persistence mechanism.

Vector 1: Temporary root via exploit or credential theft. An attacker exploits a kernel vulnerability or obtains a root credential through phishing or credential stuffing. They have root access for a bounded window — perhaps minutes — before the credential is rotated or the exploit is patched. They use that window to install a PAM backdoor. From that point, the temporary access window is irrelevant: every future authentication event on that host is compromised without any ongoing attacker presence.

Vector 2: Supply chain compromise of a PAM module package. A distribution repository serves a tampered version of a legitimate PAM module package — libpam-modules, pam_krb5, pam_ldap, or any of the optional authentication modules installed by default on enterprise Linux hosts. A normal apt upgrade or dnf update installs the tampered module. The module’s package integrity hash matches the repository’s record, because the compromise happened at the repository level. No privilege escalation is required beyond what the package manager already possesses.

Vector 3: Container escape to host. A container runtime vulnerability allows escape to the host. The escape grants host-level write access for a brief window. A PAM backdoor dropped during this window persists on the host even after the container is stopped and its runtime environment is removed. The container escape is a one-time event; the PAM backdoor is permanent until explicitly removed.

Detection requirements: For all three vectors, the critical detection window is insertion of an unexpected PAM module — a file write to /lib/security/ or a modification to /etc/pam.d/ that references a module not present in any installed package. Detection must occur within seconds of the file write, not at the next scheduled integrity scan. A daily AIDE scan that runs at 03:00 gives an attacker a 24-hour window of silent operation.

Out of scope: Memory-resident PAM hooks injected via ptrace or LD_PRELOAD into a running sshd process. These require separate controls (process integrity monitoring, seccomp profiles). The controls in this article address the far more common case: a PAM module installed on disk and loaded by PAM’s standard dlopen() mechanism.

Hardening Configuration

1. Understanding the PAM module surface

PAM modules are shared libraries loaded from two directories:

/lib/security/          # 32-bit and legacy location; also used on Debian/Ubuntu
/lib64/security/        # 64-bit modules on RPM-based distributions
/usr/lib/security/      # Used on some distributions; check with:
find /usr/lib -path "*/security/pam_*.so" 2>/dev/null | head -5

PAM configuration files in /etc/pam.d/ reference modules by name without path. When sshd calls pam_authenticate(), the PAM library searches these directories in order and loads the first match via dlopen(). An attacker-placed module that shadows a legitimate one in search order wins.

Enumerate every currently referenced module:

grep -rhE '^(auth|account|session|password)\s' /etc/pam.d/ \
  | awk '{print $3}' \
  | grep '\.so' \
  | sort -u

This lists every .so file referenced by the active PAM configuration. Cross-check each against what is physically present in the module directories:

find /lib/security /lib64/security /usr/lib/security \
  -name "pam_*.so" 2>/dev/null | sort -u

The difference between referenced modules (from pam.d parsing) and installed modules (from find) reveals orphaned references and unreferenced installed files — both are anomalies worth investigating.

2. Generating and verifying a PAM module baseline

Before deploying any detection controls, establish a clean baseline immediately after a known-good system build or after rpm -Va / dpkg --verify confirms package integrity.

# Generate baseline checksums for all PAM modules
find /lib/security /lib64/security /usr/lib/security \
  -name "pam_*.so" 2>/dev/null \
  -exec sha256sum {} \; | sort > /root/pam-baseline.sha256

# Also checksum PAM configuration files
find /etc/pam.d -type f \
  -exec sha256sum {} \; | sort >> /root/pam-baseline.sha256

# Store the baseline in a location the attacker cannot easily overwrite
# Options: read-only NFS mount, immutable file (chattr +i), or external storage
chattr +i /root/pam-baseline.sha256

To verify against the baseline at any time:

sha256sum --check /root/pam-baseline.sha256 2>&1 | grep -v ': OK$'

Any line not ending in : OK is a file whose content has changed since baseline. An empty output confirms no changes.

Verify PAM modules against their packages directly using the package manager:

# Debian/Ubuntu
dpkg --verify libpam-modules libpam-runtime 2>&1

# RPM-based (RHEL, CentOS, Fedora)
rpm -Va --nomtime | grep '/lib.*security'

dpkg --verify exits non-zero and prints the changed file if any installed file differs from the package’s recorded hash. rpm -Va prints a string of attribute indicators — 5 in the fifth position indicates a MD5/SHA256 hash mismatch on the file content.

A PamDOORa-style installation drops a file that is not owned by any package. To find unpackaged files in the PAM module directories:

# Debian/Ubuntu: list all files in /lib/security not owned by any package
comm -23 \
  <(find /lib/security /usr/lib/security -name "*.so" 2>/dev/null | sort) \
  <(dpkg -S '/lib/security/*.so' 2>/dev/null | cut -d: -f2 | tr -d ' ' | sort)

# RPM-based: same approach
comm -23 \
  <(find /lib/security /lib64/security -name "*.so" 2>/dev/null | sort) \
  <(rpm -qal | grep '/lib.*security.*\.so' | sort)

Any file present in the module directory but not owned by a package is an immediate indicator of compromise.

3. IMA (Integrity Measurement Architecture) for PAM modules

IMA is a Linux kernel subsystem that measures file content at load time and records the measurement in a kernel-maintained log. With an appropriate policy, IMA measures every PAM module when it is first opened by the PAM library, before any code in the module executes.

Check whether IMA is active on your kernel:

cat /sys/kernel/security/ima/policy 2>/dev/null | head -5
# If this file is empty or absent, IMA is not configured

Write an IMA policy that measures all files in the PAM module directories:

# /etc/ima/ima-policy (append to or create this file)
# Measure all PAM shared libraries at open time
measure func=FILE_MMAP mask=MAY_EXEC fowner=0 \
  obj_type=lib_t path=/lib/security/
measure func=FILE_MMAP mask=MAY_EXEC fowner=0 \
  obj_type=lib_t path=/lib64/security/
# Also measure PAM configuration files at read
measure func=FILE_CHECK mask=MAY_READ path=/etc/pam.d/

Load the policy at boot by passing ima_policy=tcb to the kernel command line, or load a custom policy file:

# Load a custom IMA policy (requires root, early in boot)
echo /etc/ima/ima-policy > /sys/kernel/security/ima/policy

Read the IMA measurement log to see which PAM modules have been loaded since boot:

cat /sys/kernel/security/ima/ascii_runtime_measurements \
  | grep -E '(lib/security|pam\.d)'

Each line contains: PCR index, SHA-256 hash of the file, template name, file path. A module that appears in this log but whose hash does not match your baseline indicates either a tampered module that was loaded, or a legitimate update that occurred since baseline.

For enforcement — rejecting execution of unmeasured or mismatched modules — IMA requires EVM.

4. EVM (Extended Verification Module) for PAM module signing

EVM binds IMA measurements to HMAC or RSA signatures stored in extended attributes (security.ima). When EVM enforcement is active, the kernel refuses to load a shared library whose security.ima extended attribute does not carry a valid signature from a trusted key.

Generate an EVM signing key (do this on a secure offline system and store the private key in HSM or offline media):

# Generate RSA key pair for EVM signing
openssl genrsa -out /etc/keys/pam-evm-private.pem 4096
openssl rsa -in /etc/keys/pam-evm-private.pem \
  -pubout -out /etc/keys/pam-evm-public.pem

# Import the public key into the IMA keyring
evmctl import /etc/keys/pam-evm-public.pem \
  $(keyctl show | awk '/_ima/ {print $1}')

Sign all PAM modules with evmctl:

find /lib/security /lib64/security /usr/lib/security \
  -name "pam_*.so" 2>/dev/null | while read module; do
    evmctl sign --imasig \
      --key /etc/keys/pam-evm-private.pem \
      "$module"
    echo "Signed: $module"
done

Verify that signatures are present and valid:

find /lib/security /lib64/security -name "pam_*.so" 2>/dev/null \
  | while read module; do
    if evmctl verify --imasig "$module" 2>/dev/null; then
      echo "OK: $module"
    else
      echo "FAIL: $module"
    fi
done

When EVM enforcement mode is active (echo 1 > /sys/kernel/security/evm), any PAM module without a valid security.ima extended attribute will be rejected by the kernel when the PAM library attempts to dlopen() it. A PamDOORa module installed without the private signing key has no valid signature and cannot be loaded.

Operational requirement: Every legitimate PAM module update via the package manager must re-sign the new module files. Integrate signing into your package update workflow or post-install hook:

# /etc/apt/apt.conf.d/99-pam-resign (Debian/Ubuntu)
DPkg::Post-Invoke {
  "find /lib/security /usr/lib/security -name 'pam_*.so' -newer /var/lib/dpkg/info/libpam-modules.list 2>/dev/null | xargs -I{} evmctl sign --imasig --key /etc/keys/pam-evm-private.pem {}";
};

5. AIDE file integrity monitoring for the PAM stack

AIDE (Advanced Intrusion Detection Environment) maintains a database of file attributes — hashes, permissions, ownership, extended attributes — and detects changes between scans. Configure AIDE to watch both the module binaries and the PAM configuration files:

# /etc/aide/aide.conf additions

# PAM module directories: check content hash, permissions, ownership, xattrs
/lib/security        CONTENT+p+u+g+sha256+xattrs
/lib64/security      CONTENT+p+u+g+sha256+xattrs
/usr/lib/security    CONTENT+p+u+g+sha256+xattrs

# PAM configuration directory: check content and permissions
/etc/pam.d           CONTENT+p+u+g+sha256

Initialize the AIDE database on a known-good system:

aide --init
mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
# Protect the database from tampering
chattr +i /var/lib/aide/aide.db

Run checks and trigger alerts on changes. For near-real-time detection, run AIDE checks frequently — every 5 minutes is practical for the PAM paths — rather than relying on a nightly cron job:

# /etc/cron.d/aide-pam-check
*/5 * * * * root aide --check --config=/etc/aide/aide.conf 2>&1 \
  | grep -E '(lib/security|pam\.d)' \
  | logger -t aide-pam -p auth.crit

The logger call sends AIDE output to syslog with auth.crit priority, which your SIEM should alert on immediately.

6. Auditd rules for PAM modification events

Auditd provides kernel-level event logging for file system writes. Unlike AIDE’s periodic check, auditd reports modifications within milliseconds. Configure rules for both the module directories and the PAM configuration:

# /etc/audit/rules.d/pam-integrity.rules

# Watch PAM module directories for writes and attribute changes
-w /lib/security/ -p wa -k pam_modification
-w /lib64/security/ -p wa -k pam_modification
-w /usr/lib/security/ -p wa -k pam_modification

# Watch PAM configuration for any modification
-w /etc/pam.d/ -p wa -k pam_config_modification

# Track processes that read PAM configuration (detects enumeration)
-w /etc/pam.d/ -p r -k pam_config_read

# Reload rules
# augenrules --load

Load the rules:

augenrules --load
auditctl -l | grep pam

Query the audit log for PAM modification events:

ausearch -k pam_modification --start today

Each audit record includes the process name, PID, UID, and the exact file path written. A write to /lib/security/ by a process that is not dpkg, rpm, apt, or dnf is an immediate indicator requiring investigation.

Forward these events to your SIEM using audisp-remote or by shipping /var/log/audit/audit.log via your log agent. Configure a SIEM alert rule: any event with key pam_modification where exe is not in the approved package manager list triggers a P1 alert.

7. Falco rule for PAM write detection

Falco provides real-time system call monitoring. The following rule detects writes to PAM module directories from any process other than the package manager:

# /etc/falco/rules.d/pam_integrity.yaml

- macro: package_manager
  condition: >
    proc.name in (apt, apt-get, dpkg, rpm, dnf, yum, zypper, pacman)
    or proc.pname in (apt, apt-get, dpkg, rpm, dnf, yum, zypper, pacman)

- macro: pam_module_dir
  condition: >
    fd.name startswith /lib/security/
    or fd.name startswith /lib64/security/
    or fd.name startswith /usr/lib/security/

- macro: pam_config_dir
  condition: >
    fd.name startswith /etc/pam.d/

- rule: Unexpected Write to PAM Module Directory
  desc: >
    A process other than a package manager wrote to a PAM module directory.
    This is a strong indicator of a PAM backdoor installation (e.g. PamDOORa).
  condition: >
    (open_write or rename)
    and pam_module_dir
    and not package_manager
  output: >
    PAM module directory written by unexpected process
    (user=%user.name uid=%user.uid command=%proc.cmdline
     file=%fd.name parent=%proc.pname container=%container.id)
  priority: CRITICAL
  tags: [pam, backdoor, integrity, host]

- rule: Unexpected Write to PAM Configuration
  desc: >
    A process other than a package manager or configuration management tool
    modified the PAM configuration directory.
  condition: >
    (open_write or rename)
    and pam_config_dir
    and not package_manager
    and not proc.name in (ansible, puppet, chef-client, salt-minion)
  output: >
    PAM configuration modified by unexpected process
    (user=%user.name uid=%user.uid command=%proc.cmdline
     file=%fd.name parent=%proc.pname container=%container.id)
  priority: CRITICAL
  tags: [pam, backdoor, integrity, host]

Falco generates an alert within milliseconds of the write system call, before the PAM library has a chance to load the new module. When running Falco as a daemonset in Kubernetes environments, ensure the host /lib/security path is visible to the Falco eBPF probe via the appropriate volume mount.

8. Restricting PAM module installation with AppArmor

AppArmor profiles can restrict which processes are permitted to write to the PAM module directories. The following profile enforces that only package manager processes can create or modify files in /lib/security/:

# /etc/apparmor.d/abstractions/pam-module-protection

# Deny all writes to PAM module directories by default
deny /lib/security/** w,
deny /lib64/security/** w,
deny /usr/lib/security/** w,
deny /etc/pam.d/** w,

Include this abstraction in your default system profile and add explicit exceptions for package managers:

# /etc/apparmor.d/local/usr.bin.dpkg
# AppArmor local overrides for dpkg

/lib/security/** rw,
/lib64/security/** rw,
/usr/lib/security/** rw,
/etc/pam.d/** rw,

For SELinux systems (RHEL/CentOS), confine writes to the lib_t or pam_module_t type to only the package manager domain:

# Check current context of PAM module files
ls -Z /lib64/security/pam_unix.so

# Ensure PAM modules have correct SELinux type
restorecon -Rv /lib64/security/

# Create a policy module that denies writes from non-package-manager domains
# to files labeled pam_module_exec_t (consult your SELinux policy for exact type names)
semanage fcontext -a -t lib_t "/lib64/security(/.*)?"
restorecon -Rv /lib64/security/

Expected Behaviour

The following table maps authentication events and attack scenarios to the detection mechanism that fires and the expected alert:

Event Detection Mechanism Alert Generated
Legitimate PAM module update via apt upgrade Auditd pam_modification event fires; Falco rule suppressed because proc.name = dpkg; AIDE detects hash change at next scan Auditd event logged (no SIEM alert if allowlisted); AIDE reports expected change after update
PamDOORa installed by attacker with root Auditd fires immediately; Falco generates CRITICAL alert within milliseconds; AIDE detects unpackaged file at next scan; dpkg --verify reports unowned file P1 SIEM alert from Falco; auth.crit syslog from AIDE; auditd record with attacker UID and process name
PAM configuration modified by admin manually Auditd pam_config_modification fires; Falco alert unless admin process is allowlisted; AIDE detects config hash change SIEM alert (requires change ticket to suppress); AIDE change report
IMA measurement violation (module hash differs from IMA log) IMA appraisal rejects module load; dlopen() returns error; PAM authentication for that service fails Application authentication failure; kernel logs IMA appraisal failure to dmesg
Attacker drops unpackaged .so in /lib/security/ before adding PAM config reference Auditd and Falco fire on file write immediately Falco CRITICAL alert before the module is even referenced in config
Supply chain compromise: tampered libpam-modules installed via apt dpkg --verify shows hash mismatch; IMA detects new hash at load time; EVM signature invalid (package manager re-signed with wrong key) AIDE hash change alert; EVM appraisal failure at module load

Trade-offs

Control Benefit Cost / Limitation
IMA enforcement (appraisal mode) Blocks kernel-level loading of any PAM module whose content hash does not match a signed measurement; prevents PamDOORa from executing even if installed Requires all legitimate PAM modules to be signed before enforcement is enabled; a single unsigned legitimate module causes authentication failure for that service; rollout complexity on heterogeneous systems
EVM RSA signing Strong cryptographic assurance that modules came from your signing authority; survives filesystem copy and module replacement Private key management is critical — key compromise allows bypassing all EVM controls; requires secure key storage (HSM recommended); every package update requires re-signing
AIDE 5-minute check interval Detects changes within 5 minutes rather than 24 hours; catches slow-moving attackers Still a 5-minute window; does not catch the module before its first load if the attacker moves quickly; AIDE check itself is CPU and I/O intensive at high frequency
Auditd PAM watch rules Millisecond detection latency; kernel-level — cannot be suppressed from userspace; provides full process attribution High event volume if package manager runs frequently; auditd buffer overflow (audit backlog) can cause events to be dropped; tuning required to avoid log noise
Falco real-time alerts Near-instant notification; allowlist logic is expressive; integrates with SIEM and PagerDuty Falco itself must be protected from tampering; kernel module or eBPF probe must be loaded; a rootkit that terminates Falco or blocks its syscall events can evade detection
AppArmor/SELinux write restriction Prevents the write from completing in the first place; enforcement does not rely on detection latency Third-party or custom PAM modules (e.g., corporate SSO modules, RADIUS PAM, FIDO2 PAM) must have their install paths explicitly added to the profile; a missing exception breaks legitimate installation
dpkg --verify / rpm -Va Detects tampered package-owned files and identifies unpackaged files; uses package manager’s own cryptographic database Only detects modifications to package-owned files; an attacker who drops a completely new file not owned by any package is only detected by the “unpackaged file” check, not dpkg --verify directly

Failure Modes

Failure Mode Root Cause Consequence Mitigation
IMA policy not loaded at boot Kernel command line missing ima_policy= or policy file not loaded by initramfs No IMA measurement or enforcement; tampered modules load silently Add IMA policy loading to initramfs; verify at boot with cat /sys/kernel/security/ima/policy; use aide --check on IMA policy file itself
AIDE database tampered by attacker Attacker with root access regenerates the AIDE database to match the compromised state before detection All subsequent AIDE checks pass; no alert generated Store AIDE database on read-only NFS mount or write-once object storage; use chattr +i as a deterrent; compare database hash against out-of-band reference
Auditd buffer overflow drops events High event rate exceeds disp_qos buffer; kernel drops older events PAM write events are silently discarded; attacker’s installation is not logged Set backlog_limit = 8192 in /etc/audit/auditd.conf; monitor `ausearch --start today
AppArmor profile missing for a custom PAM module install path Third-party PAM module installs to /opt/pam-modules/ or similar non-standard path AppArmor denies the write; legitimate module installation fails; IT team disables the profile to resolve the break Audit all PAM module paths before enabling AppArmor restriction; add exceptions for non-standard paths before enforcement
Attacker modifies an existing legitimate module in place Instead of dropping a new file, attacker patches bytes in an existing pam_unix.so using dd or a binary patch tool File mtime may not update (if attacker resets timestamps with touch -t); AIDE change detection depends on mtime OR hash Configure AIDE with sha256 attribute, not mtime-only; CONTENT+sha256 detects byte-level changes regardless of timestamp
EVM key compromise Private signing key exfiltrated from build system Attacker can sign arbitrary PAM modules that pass EVM verification Store private key in HSM; rotate signing key annually; revoke compromised key and re-sign all modules; monitor for unexpected IMA signatures from your key
Falco probe crash or termination by rootkit Attacker’s rootkit detects and kills the Falco process or unloads the eBPF probe No real-time alerts for subsequent PAM modifications Run Falco as a privileged systemd service with Restart=always; monitor Falco health from a separate watchdog; combine with auditd (harder to kill)
  • PAM Hardening — baseline PAM configuration, restricting pam_debug, securing pam_env, and removing unused modules from the default stack
  • Linux IMA/EVM — end-to-end setup of Integrity Measurement Architecture and Extended Verification Module, including initramfs integration and key management
  • Developer Credential File Monitoring — monitoring for credential files accessed by unexpected processes, complementary to PAM-level credential capture detection
  • File Integrity Monitoring — AIDE, Tripwire, and auditd as a unified file integrity strategy across the full system, not just PAM paths
  • Auditd Deep Dive — audit rule design, buffer tuning, ausearch and aureport query patterns, and shipping audit events to a SIEM