PAM authorization bypass

Check account status and access policies after successful PAM authentication.

Description

PAM (Pluggable Authentication Modules) uses pam_authenticate() to verify credentials, but that does not replace account management policy checks. Ignoring the result of pam_acct_mgmt() after authentication can miss account expiry or access restrictions.

Potential impact

  • Users prohibited by account management policies may be allowed to log in.
  • Account or password expiry conditions may not be handled correctly.
  • The actual impact depends on the PAM modules and account policies configured for the service.

Remediation

  1. After authentication succeeds, call pam_acct_mgmt() to check account status and access conditions. Do not grant access if this check fails.
  2. PAM_NEW_AUTHTOK_REQD means that the password must be changed. Deny access until an authorized password-change procedure is completed.
  3. Configure the required authentication and account management modules and least-privilege policies. Log login successes and failures without recording passwords.

Examples

These excerpts illustrate the PAM binding flow. Bindings for pam_start and handle_conv, password delivery and pam_end cleanup are omitted. A real implementation must check initialization and clean up successfully started transactions on every exit path.

Before

python
from pam import PamHandle, PamConv
from ctypes import CDLL, c_int, byref
from ctypes.util import find_library

libpam = CDLL(find_library("pam"))

pam_authenticate = libpam.pam_authenticate
pam_authenticate.argtypes = [PamHandle, c_int]
pam_authenticate.restype = c_int

def authenticate(username, password, service='login'):
    handle = PamHandle()
    conv = PamConv(handle_conv, 0)
    retval = pam_start(service, username, byref(conv), byref(handle))
    if retval != 0:
        return False
    # Authenticate without checking account policy
    return pam_authenticate(handle, 0) == 0

After

python
from pam import PamHandle, PamConv
from ctypes import CDLL, c_int, byref
from ctypes.util import find_library

libpam = CDLL(find_library("pam"))

pam_authenticate = libpam.pam_authenticate
pam_authenticate.argtypes = [PamHandle, c_int]
pam_authenticate.restype = c_int

pam_acct_mgmt = libpam.pam_acct_mgmt
pam_acct_mgmt.argtypes = [PamHandle, c_int]
pam_acct_mgmt.restype = c_int

def authenticate(username, password, service='login'):
    handle = PamHandle()
    conv = PamConv(handle_conv, 0)
    retval = pam_start(service, username, byref(conv), byref(handle))
    if retval != 0:
        return False

    # Also check account status
    return pam_authenticate(handle, 0) == 0 and pam_acct_mgmt(handle, 0) == 0

Explanation:

  • Before: Checks only authentication, without checking the account management policy result.
  • After: Returns True only if authentication and the account status check both succeed.

References