当前位置:首页>Linux>别再只会 chmod:PAM 驱动的 Linux 用户权限治理全解

别再只会 chmod:PAM 驱动的 Linux 用户权限治理全解

  • 2026-02-26 19:23:23
别再只会 chmod:PAM 驱动的 Linux 用户权限治理全解

基于PAM的Linux用户权限精细化管控实践

一、概述

1.1 背景介绍

去年我们团队接手一个客户的系统,发现所有运维人员共用一个root账号登录服务器,sudoers文件里写的是 ALL=(ALL) NOPASSWD: ALL,出了问题根本查不到是谁操作的。更离谱的是有个离职半年的人还能用原来的密码登录,因为没人改过密码。

这种粗放的权限管理在中小团队很常见。多数运维只会改 /etc/sudoers,对底层的PAM机制了解不深。PAM(Pluggable Authentication Modules)是Linux统一认证框架,控制用户登录、su切换、sudo提权、密码策略等所有认证行为。掌握PAM配置,才能实现真正的权限精细化管控。

本篇从PAM架构原理讲起,覆盖密码复杂度策略、登录失败锁定、资源限制、访问控制、时间段限制、sudo集成等完整配置链路。所有配置在CentOS 7/8/9和Ubuntu 20.04/22.04上实际验证过。

1.2 技术特点

  • 模块化可插拔架构:PAM把认证逻辑拆成独立模块(.so文件),每个模块负责一个功能。需要密码复杂度检查就加pam_pwquality,需要登录锁定就加pam_faillock,像搭积木一样组合。新增功能不用改代码,加个模块配置就行。
  • 四种管理类型覆盖认证全流程:auth(身份验证)、account(账户检查)、password(密码修改)、session(会话管理)四个阶段各司其职。一次SSH登录会依次经过auth验证身份、account检查账户是否过期、session建立会话环境,每个阶段都可以插入不同的控制逻辑。
  • 控制标志实现灵活的策略组合:required(必须通过但继续检查后续模块)、requisite(失败立即返回)、sufficient(通过则跳过后续)、optional(结果不影响最终判断)四种控制标志,可以实现"必须满足A且满足B"、"满足A或满足B即可"等复杂逻辑。

1.3 适用场景

  • 场景一:密码安全策略强制执行。等保2.0要求密码长度不少于8位、包含大小写字母+数字+特殊字符、90天强制更换。靠口头通知用户设强密码不现实,用pam_pwquality从系统层面强制执行,不符合策略的密码直接拒绝。
  • 场景二:防暴力破解的登录锁定。SSH加固只是外层防护,如果攻击者通过其他途径(比如Web应用漏洞)拿到了shell,还能在本地尝试su提权。pam_faillock在PAM层面做锁定,不管从哪个入口尝试认证,失败5次就锁定账户15分钟。
  • 场景三:多角色权限分层管控。一个系统上有普通开发、运维、DBA、安全审计等不同角色,每个角色能做的事不一样。通过PAM的pam_access控制谁能从哪里登录,pam_limits控制资源配额,配合sudoers实现命令级权限控制。

1.4 环境要求

组件
版本要求
说明
操作系统
CentOS 7+/Ubuntu 20.04+
RHEL系和Debian系PAM配置路径有差异
PAM
1.3.1+
系统自带,CentOS 7是1.1.8,CentOS 8+是1.3.1
pam_pwquality
1.4.0+
密码复杂度检查模块,替代旧的pam_cracklib
pam_faillock
系统自带
登录失败锁定模块,替代已废弃的pam_tally2
libpwquality
1.4.0+
pam_pwquality的依赖库

二、详细步骤

2.1 准备工作

2.1.1 系统检查

# 检查系统版本
cat /etc/os-release

# 检查PAM版本
rpm -q pam 2>/dev/null || dpkg -l libpam-runtime 2>/dev/null

# 查看已安装的PAM模块
ls /usr/lib64/security/pam_*.so 2>/dev/null || ls /usr/lib/x86_64-linux-gnu/security/pam_*.so 2>/dev/null

# 检查当前PAM配置文件
ls -la /etc/pam.d/

# 查看当前密码策略
grep -v "^#" /etc/security/pwquality.conf 2>/dev/null

# 查看当前资源限制
cat /etc/security/limits.conf | grep -v "^#" | grep -v "^$"

PAM配置改错了会导致所有用户无法登录,包括root。 改之前必须保留一个root的rescue终端(物理控制台、VNC、串口),确认有不经过PAM认证的恢复通道。我们团队的规矩是:改PAM配置前先开一个root的tmux会话挂着,万一改错了还能恢复。

2.1.2 安装依赖

# CentOS/RHEL
sudo yum install -y pam pam-devel libpwquality

# CentOS 8+/RHEL 8+
sudo dnf install -y pam pam-devel libpwquality

# Ubuntu/Debian
sudo apt install -y libpam-modules libpam-pwquality libpam-modules-extra

# 验证关键模块是否存在
# CentOS/RHEL
ls -la /usr/lib64/security/pam_pwquality.so
ls -la /usr/lib64/security/pam_faillock.so
ls -la /usr/lib64/security/pam_access.so
ls -la /usr/lib64/security/pam_limits.so
ls -la /usr/lib64/security/pam_time.so
ls -la /usr/lib64/security/pam_exec.so

# Ubuntu/Debian
ls -la /usr/lib/x86_64-linux-gnu/security/pam_pwquality.so
ls -la /usr/lib/x86_64-linux-gnu/security/pam_faillock.so

2.1.3 备份原始配置

# 备份整个PAM配置目录
sudo cp -a /etc/pam.d /etc/pam.d.bak.$(date +%Y%m%d)

# 备份安全相关配置文件
sudo cp /etc/security/pwquality.conf /etc/security/pwquality.conf.bak.$(date +%Y%m%d)
sudo cp /etc/security/limits.conf /etc/security/limits.conf.bak.$(date +%Y%m%d)
sudo cp /etc/security/access.conf /etc/security/access.conf.bak.$(date +%Y%m%d)

# 验证备份
ls -la /etc/pam.d.bak.*
ls -la /etc/security/*.bak.*

2.2 核心配置

2.2.1 PAM配置文件结构

PAM的配置文件在 /etc/pam.d/ 目录下,每个服务一个文件。SSH登录看 /etc/pam.d/sshd,su切换看 /etc/pam.d/su,sudo提权看 /etc/pam.d/sudo

# 查看PAM配置目录
ls /etc/pam.d/
# 常见文件:
# system-auth      系统认证主配置(CentOS/RHEL)
# common-auth      系统认证主配置(Ubuntu/Debian)
# password-auth    密码认证配置(CentOS/RHEL)
# common-password  密码认证配置(Ubuntu/Debian)
# sshd             SSH登录认证
# su               su切换用户
# sudo             sudo提权
# login            本地终端登录
# postlogin        登录后处理

PAM配置文件格式

类型    控制标志    模块路径    模块参数
auth    required    pam_faillock.so    preauth deny=5 unlock_time=900

四列含义:

  • 类型:auth(验证身份)、account(检查账户状态)、password(修改密码)、session(会话管理)
  • 控制标志:required(必须通过)、requisite(失败立即返回)、sufficient(通过则跳过后续)、optional(可选)
  • 模块路径:.so模块文件名,系统会自动在 /usr/lib64/security/ 下查找
  • 模块参数:传给模块的参数,不同模块参数不同

控制标志详解

控制标志
失败时行为
成功时行为
典型用途
required
记录失败,继续检查后续模块,最终返回失败
继续检查后续模块
必须通过的检查,但不暴露具体哪个模块失败
requisite
立即返回失败,不再检查后续模块
继续检查后续模块
前置条件检查,失败了后面不用看了
sufficient
忽略,继续检查后续模块
如果前面没有required失败,立即返回成功
提供"快速通过"路径,比如本地密码通过就不查LDAP
optional
忽略
忽略
辅助功能,比如记录日志,不影响认证结果

理解这四个标志是掌握PAM的关键。举个例子:

auth    required      pam_env.so
auth    required      pam_faillock.so preauth
auth    sufficient    pam_unix.so try_first_pass
auth    required      pam_deny.so

执行流程:

  1. pam_env.so(required):设置环境变量,基本不会失败
  2. pam_faillock.so preauth(required):检查账户是否被锁定,被锁定则记录失败
  3. pam_unix.so(sufficient):验证密码,密码正确且前面没有required失败则立即返回成功
  4. pam_deny.so(required):如果走到这一步说明密码验证失败,pam_deny无条件返回失败

2.2.2 PAM模块类型和控制标志实践

# 查看某个服务的PAM配置(以sshd为例)
cat /etc/pam.d/sshd

# CentOS 7/8 的sshd PAM配置通常引用system-auth和password-auth
# 查看实际的认证链
cat /etc/pam.d/system-auth

# Ubuntu的sshd PAM配置引用common-auth等
cat /etc/pam.d/common-auth

CentOS/RHEL的PAM配置特点:使用 authselect 工具管理(CentOS 8+),不建议直接编辑 system-auth 和 password-auth,因为authselect会覆盖手动修改。

# CentOS 8+ 查看当前authselect配置
sudo authselect current

# 创建自定义profile(基于sssd模板)
sudo authselect create-profile custom-hardening -b sssd

# 自定义profile的文件在:
ls /etc/authselect/custom/custom-hardening/

# CentOS 7 没有authselect,直接编辑 /etc/pam.d/system-auth 和 password-auth

2.2.3 pam_pwquality配置密码复杂度策略

pam_pwquality是pam_cracklib的替代品,用于强制密码复杂度。等保2.0三级要求密码长度不少于8位,包含大小写字母、数字、特殊字符中的三种。我们团队的标准比等保更严:最少12位,四种字符全部包含。

# 编辑密码质量配置文件
sudo vim /etc/security/pwquality.conf
# /etc/security/pwquality.conf
# 密码最小长度12位
minlen = 12
# 至少包含1个数字(负数表示"至少N个")
dcredit = -1
# 至少包含1个大写字母
ucredit = -1
# 至少包含1个小写字母
lcredit = -1
# 至少包含1个特殊字符
ocredit = -1
# 新密码与旧密码至少有8个字符不同
difok = 8
# 不允许超过3个连续相同字符(如aaa)
maxrepeat = 3
# 不允许超过3个连续递增/递减字符(如abc、321)
maxsequence = 3
# 不允许包含用户名
usercheck = 1
# 密码复杂度检查的字符类别数(至少包含4类中的3类)
minclass = 3
# 记住最近12个密码,不允许重复使用
# 这个参数在pam_unix中配置,不在pwquality中
# 拒绝包含的词(字典文件路径)
dictpath = /usr/share/cracklib/pw_dict
# 强制root用户也遵守密码策略
enforce_for_root
# 重试次数
retry = 3

在PAM中启用pam_pwquality

# CentOS 7:编辑 /etc/pam.d/system-auth
# 找到password行,确保有pam_pwquality
# password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=

# CentOS 8+:通过authselect启用
sudo authselect select sssd with-faillock with-mkhomedir --force

# Ubuntu:编辑 /etc/pam.d/common-password
# 添加或修改:
# password    requisite     pam_pwquality.so retry=3 enforce_for_root

验证密码策略生效

# 用passwd命令测试(用普通用户)
passwd
# 尝试输入弱密码,比如 "123456" 或 "password"
# 预期输出:BAD PASSWORD: The password fails the dictionary check
# 尝试输入 "Abc12345"(只有8位)
# 预期输出:BAD PASSWORD: The password is shorter than 12 characters

# 查看pwquality模块的详细检查过程
sudo pam_tester --service login --user testuser --authenticate

配置密码历史记录(防止重复使用旧密码)

# 在 /etc/pam.d/system-auth(CentOS)或 /etc/pam.d/common-password(Ubuntu)中
# 找到 pam_unix.so 行,添加 remember=12 参数
# password    sufficient    pam_unix.so sha512 shadow try_first_pass use_authtok remember=12

# 密码历史存储在 /etc/security/opasswd 文件中
# 确保文件存在且权限正确
sudo touch /etc/security/opasswd
sudo chmod 600 /etc/security/opasswd
sudo chown root:root /etc/security/opasswd

2.2.4 pam_faillock配置登录失败锁定

pam_faillock替代了已废弃的pam_tally2(CentOS 8+移除了pam_tally2)。功能是统计认证失败次数,超过阈值后锁定账户一段时间。实测效果:配合SSH的MaxAuthTries,暴力破解在第5次尝试后账户被锁15分钟,攻击者每小时最多尝试20次,字典攻击完全失效。

# CentOS 8+/RHEL 8+ 使用authselect启用faillock
sudo authselect enable-feature with-faillock

# CentOS 7 手动编辑 /etc/pam.d/system-auth 和 /etc/pam.d/password-auth
# 在auth段添加(注意顺序):

CentOS 7 手动配置 /etc/pam.d/system-auth

# auth段 - 在pam_unix.so之前添加preauth,之后添加authfail和authsucc
auth        required      pam_env.so
auth        required      pam_faillock.so preauth silent deny=5 unlock_time=900 fail_interval=900
auth        sufficient    pam_unix.so try_first_pass
auth        [default=die] pam_faillock.so authfail deny=5 unlock_time=900 fail_interval=900
auth        requisite     pam_succeed_if.so uid >= 1000 quiet_success
auth        required      pam_deny.so

# account段 - 添加faillock检查
account     required      pam_faillock.so
account     required      pam_unix.so
account     sufficient    pam_localuser.so
account     sufficient    pam_succeed_if.so uid < 1000 quiet
account     required      pam_permit.so

CentOS 8+/RHEL 8+ 配置文件

# faillock的独立配置文件(CentOS 8+支持)
sudo vim /etc/security/faillock.conf
# /etc/security/faillock.conf
# 失败5次后锁定
deny = 5
# 锁定时间900秒(15分钟),设为0表示永久锁定直到管理员手动解锁
unlock_time = 900
# 统计窗口900秒(15分钟内失败5次才锁定)
fail_interval = 900
# 不锁定root账户(生产环境建议设为yes,但要确保有其他恢复手段)
even_deny_root = false
# root账户锁定时间(如果even_deny_root=true)
root_unlock_time = 60
# 静默模式,不提示剩余尝试次数(防止攻击者探测策略)
silent
# 审计日志
audit
# 锁定记录存储目录
dir = /var/run/faillock

faillock管理命令

# 查看某个用户的失败记录
sudo faillock --user opsadmin

# 输出示例:
# opsadmin:
# When                Type  Source                                           Valid
# 2025-01-15 10:23:45 RHOST 192.168.1.50                                    V
# 2025-01-15 10:23:48 RHOST 192.168.1.50                                    V
# 2025-01-15 10:23:51 RHOST 192.168.1.50                                    V

# 手动解锁用户(清除失败记录)
sudo faillock --user opsadmin --reset

# 查看所有用户的失败记录
sudo faillock

2.2.5 pam_limits配置资源限制

pam_limits通过 /etc/security/limits.conf 控制用户和组的资源配额。生产环境必须配,不然一个用户fork炸弹就能把整台机器搞挂。我们线上出过一次事故:一个Java应用没限制线程数,内存泄漏后疯狂创建线程,把系统的PID号耗尽,连root都无法新建进程。

# 编辑资源限制配置
sudo vim /etc/security/limits.conf
# /etc/security/limits.conf
# 格式:<domain> <type> <item> <value>
# domain: 用户名、@组名、* 通配符
# type: soft(软限制,用户可调整到hard上限)、hard(硬限制,只有root能改)

# ===== 全局默认限制 =====
# 最大打开文件数(防止文件描述符泄漏)
*               soft    nofile          65535
*               hard    nofile          131072

# 最大进程数(防止fork炸弹)
*               soft    nproc           4096
*               hard    nproc           8192

# 最大内存锁定(防止swap被锁死)
*               soft    memlock         65536
*               hard    memlock         65536

# ===== 运维用户组 - 较高限制 =====
@ops-team       soft    nofile          131072
@ops-team       hard    nofile          262144
@ops-team       soft    nproc           8192
@ops-team       hard    nproc           16384

# ===== 应用服务账户 - 按需配置 =====
# Nginx工作进程需要大量文件描述符
nginx           soft    nofile          131072
nginx           hard    nofile          262144

# MySQL需要大量文件描述符和内存锁定
mysql           soft    nofile          65535
mysql           hard    nofile          131072
mysql           soft    memlock         unlimited
mysql           hard    memlock         unlimited

# Elasticsearch需要大量文件描述符和内存锁定
elasticsearch   soft    nofile          65535
elasticsearch   hard    nofile          131072
elasticsearch   soft    memlock         unlimited
elasticsearch   hard    memlock         unlimited
elasticsearch   soft    nproc           4096
elasticsearch   hard    nproc           4096

# ===== 普通开发用户 - 严格限制 =====
@developers     soft    nofile          4096
@developers     hard    nofile          8192
@developers     soft    nproc           1024
@developers     hard    nproc           2048
# 限制core dump文件大小(防止磁盘被撑满)
@developers     soft    core            0
@developers     hard    core            0
# 限制最大文件大小(2GB)
@developers     hard    fsize           2097152

确保PAM加载了pam_limits模块

# 检查sshd的PAM配置中是否包含pam_limits
grep pam_limits /etc/pam.d/sshd
# 如果没有,添加到session段:
# session    required     pam_limits.so

# 检查system-auth中是否包含
grep pam_limits /etc/pam.d/system-auth
# 通常已经有了:session required pam_limits.so

验证限制生效

# 切换到目标用户查看当前限制
su - opsadmin
ulimit -a

# 查看特定限制
ulimit -n    # 最大打开文件数
ulimit -u    # 最大进程数
ulimit -l    # 最大内存锁定(KB)

# 查看某个运行中进程的实际限制
cat /proc/$(pgrep -f nginx | head -1)/limits

2.2.6 pam_access配置访问控制

pam_access通过 /etc/security/access.conf 控制谁能从哪里登录。比防火墙更细粒度——防火墙只能控制IP,pam_access可以控制"哪个用户从哪个IP登录"。

# 编辑访问控制配置
sudo vim /etc/security/access.conf
# /etc/security/access.conf
# 格式:permission : users/groups : origins
# permission: + 允许, - 拒绝
# origins: IP地址、网段、主机名、LOCAL(本地终端)、ALL

# 允许运维组从跳板机网段和本地终端登录
+ : @ops-team : 10.0.0.0/24 LOCAL

# 允许DBA组只从DBA跳板机登录
+ : @dba-team : 10.0.0.50

# 允许监控用户从监控服务器登录
+ : monitor : 10.0.0.60 10.0.0.61

# 允许root只从本地终端登录(物理控制台/VNC)
+ : root : LOCAL

# 拒绝其他所有访问(这行必须放最后)
- : ALL : ALL

这个配置的意思是:只有ops-team组能从10.0.0.0/24网段SSH登录,DBA只能从10.0.0.50登录,其他人全部拒绝。root只能在物理控制台操作,不能远程登录。

在PAM中启用pam_access

# 编辑 /etc/pam.d/sshd 或 /etc/pam.d/system-auth
# 在account段添加:
# account    required     pam_access.so

# 或者只对sshd生效(推荐,不影响本地登录)
sudo vim /etc/pam.d/sshd
# 在account段添加:
# account    required     pam_access.so accessfile=/etc/security/access.conf

验证访问控制

# 从允许的IP尝试登录
ssh -p 52222 opsadmin@目标服务器
# 预期:登录成功

# 从不允许的IP尝试登录
ssh -p 52222 opsadmin@目标服务器
# 预期:Permission denied

# 查看拒绝日志
sudo journalctl -u sshd | grep "access denied"

2.2.7 pam_time配置时间段访问控制

pam_time通过 /etc/security/time.conf 控制用户在什么时间段可以登录。比如限制开发人员只能在工作时间(周一到周五 9:00-18:00)登录生产服务器,非工作时间的操作必须走审批流程。

# 编辑时间控制配置
sudo vim /etc/security/time.conf
# /etc/security/time.conf
# 格式:services;ttys;users;times
# services: 服务名(sshd、login等),用|分隔多个
# ttys: 终端类型,*表示所有
# users: 用户名或@组名,!表示取反
# times: 时间段,格式为 日期时间范围
#   日期:Mo Tu We Th Fr Sa Su,Wk=工作日,Wd=周末,Al=所有
#   时间:HHMM-HHMM

# 开发人员只能在工作日9:00-18:00通过SSH登录
sshd;*;@developers;Wk0900-1800

# DBA只能在工作日8:00-22:00登录
sshd;*;@dba-team;Wk0800-2200

# 运维组不限制时间(7x24值班)
# 不配置就是不限制

# 禁止所有人在维护窗口(周日2:00-6:00)登录
# 用!取反实现
sshd;*;!root;Su0200-0600

在PAM中启用pam_time

# 编辑 /etc/pam.d/sshd
# 在account段添加:
# account    required     pam_time.so

# 注意:pam_time只检查登录时刻,不会踢掉已经登录的用户
# 如果需要在时间到期后强制断开,需要配合脚本实现

2.2.8 sudo与PAM集成配置

sudo本身通过 /etc/sudoers 控制命令权限,但sudo的认证过程走PAM。/etc/pam.d/sudo 控制sudo时的认证行为——比如sudo时是否需要输密码、失败几次锁定、是否记录日志。

# 查看sudo的PAM配置
cat /etc/pam.d/sudo

生产环境sudoers分层设计

# 编辑sudoers(必须用visudo,语法错误会导致sudo不可用)
sudo visudo

# 或者用独立文件(推荐,便于管理)
sudo visudo -f /etc/sudoers.d/ops-permissions
# /etc/sudoers.d/ops-permissions
# 运维组:可以执行所有命令,但需要输密码
%ops-team ALL=(ALL) ALL

# 高级运维:免密执行所有命令
%senior-ops ALL=(ALL) NOPASSWD: ALL

# DBA组:只能执行数据库相关命令
%dba-team ALL=(ALL) NOPASSWD: /usr/bin/mysql, /usr/bin/mysqldump, \
    /usr/bin/mysqlbinlog, /usr/sbin/service mysqld *, \
    /usr/bin/systemctl * mysqld, /usr/bin/systemctl * mariadb

# 开发人员:只能查看日志和重启应用
%developers ALL=(ALL) NOPASSWD: /usr/bin/tail -f /var/log/*, \
    /usr/bin/less /var/log/*, \
    /usr/bin/systemctl restart app-*, \
    /usr/bin/systemctl status app-*

# 监控用户:只能执行监控相关命令
monitor ALL=(ALL) NOPASSWD: /usr/bin/top, /usr/bin/iostat, \
    /usr/bin/vmstat, /usr/bin/free, /usr/bin/df, \
    /usr/sbin/ss, /usr/bin/netstat

# 禁止所有人通过sudo执行的危险命令
Cmnd_Alias DANGEROUS = /usr/bin/rm -rf /, /usr/sbin/fdisk, \
    /usr/sbin/mkfs*, /usr/bin/dd, /usr/sbin/shutdown, \
    /usr/sbin/reboot, /usr/sbin/init, /usr/bin/kill -9 1
%ops-team ALL=(ALL) !DANGEROUS

sudo日志审计

# 在 /etc/sudoers 中添加日志配置
# Defaults logfile="/var/log/sudo.log"
# Defaults log_input, log_output
# Defaults iolog_dir="/var/log/sudo-io/%{user}"

sudo visudo
# 添加以上三行(去掉注释符号)

# 查看sudo操作日志
sudo tail -f /var/log/sudo.log

# 回放sudo会话(查看用户具体执行了什么)
sudo sudoreplay -l
# 回放特定会话
sudo sudoreplay /var/log/sudo-io/opsadmin/00/00/01

2.2.9 pam_exec自定义脚本触发

pam_exec可以在认证的任意阶段执行自定义脚本。典型用途:用户登录时发送告警通知(钉钉/企业微信/邮件),用户登出时清理临时文件。

# 在 /etc/pam.d/sshd 的session段添加:
# session    optional     pam_exec.so /opt/scripts/login_notify.sh

登录通知脚本

#!/bin/bash
# 文件名:/opt/scripts/login_notify.sh
# 功能:SSH登录时发送钉钉/企业微信告警
# PAM会传递以下环境变量:
# PAM_USER - 登录用户名
# PAM_RHOST - 来源IP
# PAM_SERVICE - 服务名(sshd)
# PAM_TYPE - 阶段类型(open_session/close_session)

# 只在登录时通知,登出不通知
if [ "$PAM_TYPE" != "open_session" ]; then
exit 0
fi

# 忽略系统用户
if [ "$(id -u "$PAM_USER" 2>/dev/null)" -lt 1000 ] 2>/dev/null; then
exit 0
fi

HOSTNAME=$(hostname)
LOGIN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
WEBHOOK_URL="https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN_HERE"

# 钉钉机器人通知
MESSAGE="SSH登录告警\n主机: ${HOSTNAME}\n用户: ${PAM_USER}\n来源IP: ${PAM_RHOST}\n时间: ${LOGIN_TIME}"

curl -s -H "Content-Type: application/json" \
    -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${MESSAGE}\"}}" \
"$WEBHOOK_URL" &>/dev/null &

# 后台执行curl,不阻塞登录过程
exit 0
# 设置脚本权限
sudo chmod 755 /opt/scripts/login_notify.sh
sudo chown root:root /opt/scripts/login_notify.sh

# 测试脚本(模拟PAM环境变量)
PAM_USER=opsadmin PAM_RHOST=192.168.1.50 PAM_SERVICE=sshd PAM_TYPE=open_session /opt/scripts/login_notify.sh

2.3 启动和验证

2.3.1 配置生效确认

PAM配置修改后立即生效,不需要重启任何服务。但sshd的PAM配置修改后,只对新建立的SSH连接生效,已有连接不受影响。

# 1. 验证PAM配置文件语法(没有专门的语法检查工具,只能通过测试验证)
# 先用一个测试用户验证,不要直接用生产账号

# 2. 创建测试用户
sudo useradd -m -s /bin/bash pamtest
sudo passwd pamtest
sudo usermod -aG sshusers pamtest

# 3. 新开终端测试登录(保持当前root会话不断开)
ssh -p 52222 pamtest@localhost

2.3.2 功能验证

# 1. 验证密码复杂度策略
sudo su - pamtest
passwd
# 尝试弱密码 "abc123" -> 预期被拒绝
# 尝试短密码 "Abc@1234" -> 预期被拒绝(不足12位)
# 尝试合规密码 "Str0ng@Pass2025" -> 预期成功

# 2. 验证登录失败锁定
# 连续用错误密码尝试5次
ssh -p 52222 pamtest@localhost  # 输错密码5次
# 第6次尝试 -> 预期:账户被锁定
sudo faillock --user pamtest   # 查看锁定状态
sudo faillock --user pamtest --reset  # 解锁

# 3. 验证资源限制
su - pamtest
ulimit -n   # 查看文件描述符限制
ulimit -u   # 查看进程数限制

# 4. 验证访问控制(从不同IP测试)
# 从允许的IP登录 -> 成功
# 从不允许的IP登录 -> 被拒绝

# 5. 验证sudo权限分层
sudo su - pamtest
sudo systemctl status sshd  # 根据sudoers配置决定是否允许

2.3.3 回滚方案

# 如果PAM配置改错导致无法登录:
# 1. 通过保留的root会话恢复
sudo cp -a /etc/pam.d.bak.$(date +%Y%m%d)/* /etc/pam.d/

# 2. 如果没有活跃会话,通过单用户模式恢复
# 重启服务器,在GRUB菜单按e编辑启动项
# 在linux行末尾添加 init=/bin/bash 或 single
# 按Ctrl+X启动
# 挂载根分区为读写:mount -o remount,rw /
# 恢复备份:cp -a /etc/pam.d.bak.*/* /etc/pam.d/
# 重启:exec /sbin/init

# 3. 云服务器通过VNC控制台操作
# 4. 物理机通过IPMI/iLO/iDRAC远程管理卡操作

三、示例代码和配置

3.1 完整配置示例

3.1.1 生产级 /etc/pam.d/system-auth 完整配置(CentOS 7)

这份配置整合了密码复杂度、登录锁定、资源限制,在我们团队管理的CentOS 7集群上跑了一年多,覆盖80+台服务器。CentOS 8+用authselect管理,不要直接抄这个文件。

# 文件路径:/etc/pam.d/system-auth
# 适用系统:CentOS 7 / RHEL 7
# 修改前务必备份:cp /etc/pam.d/system-auth /etc/pam.d/system-auth.bak

# ============================================
# auth 段 - 身份验证
# ============================================
# 设置环境变量
auth        required      pam_env.so

# 登录失败锁定 - 预检查(检查账户是否已被锁定)
auth        required      pam_faillock.so preauth silent audit deny=5 unlock_time=900 fail_interval=900

# UNIX密码验证(sufficient:密码正确则跳过后续auth模块)
auth        sufficient    pam_unix.so try_first_pass

# 登录失败锁定 - 记录失败(密码验证失败后记录)
auth        [default=die] pam_faillock.so authfail audit deny=5 unlock_time=900 fail_interval=900

# UID >= 1000 的用户才继续(过滤系统用户)
auth        requisite     pam_succeed_if.so uid >= 1000 quiet_success

# 兜底拒绝(走到这里说明所有认证方式都失败了)
auth        required      pam_deny.so

# ============================================
# account 段 - 账户检查
# ============================================
# 检查faillock锁定状态
account     required      pam_faillock.so

# UNIX账户检查(密码过期、账户禁用等)
account     required      pam_unix.so

# 本地用户直接通过
account     sufficient    pam_localuser.so

# 系统用户(UID < 1000)直接通过
account     sufficient    pam_succeed_if.so uid < 1000 quiet

# 访问控制(基于access.conf)
account     required      pam_access.so

# 兜底允许
account     required      pam_permit.so

# ============================================
# password 段 - 密码修改
# ============================================
# 密码复杂度检查(参数在/etc/security/pwquality.conf中配置)
password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=

# UNIX密码更新(sha512加密,记住最近12个密码)
password    sufficient    pam_unix.so sha512 shadow try_first_pass use_authtok remember=12

# 兜底拒绝
password    required      pam_deny.so

# ============================================
# session 段 - 会话管理
# ============================================
# 设置登录后的umask
session     optional      pam_keyinit.so revoke
session     required      pam_limits.so

# UNIX会话(记录utmp/wtmp)
-session    optional      pam_systemd.so
session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session     required      pam_unix.so

# 登录通知脚本(可选)
session     optional      pam_exec.so /opt/scripts/login_notify.sh

3.1.2 PAM安全加固一键部署脚本

这个脚本把密码策略、登录锁定、资源限制、访问控制的配置打包成一键部署,支持CentOS 7和CentOS 8+两种模式。在新服务器交付时跑一次就行。

#!/bin/bash
# 文件名:pam_hardening.sh
# 功能:PAM安全加固一键部署
# 适用:CentOS 7/8/9, RHEL 7/8/9
# 用法:sudo bash pam_hardening.sh

set -euo pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

log_info()  { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn()  { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }

# 检查root权限
if [[ $EUID -ne 0 ]]; then
    log_error "必须以root权限运行"
exit 1
fi

# 检测系统版本
if [[ -f /etc/redhat-release ]]; then
    OS_VERSION=$(rpm -E %{rhel})
    log_info "检测到 RHEL/CentOS ${OS_VERSION}"
else
    log_error "仅支持RHEL/CentOS系统"
exit 1
fi

# 备份
BACKUP_DIR="/etc/pam.d.bak.$(date +%Y%m%d_%H%M%S)"
cp -a /etc/pam.d "$BACKUP_DIR"
cp /etc/security/pwquality.conf /etc/security/pwquality.conf.bak.$(date +%Y%m%d)
cp /etc/security/limits.conf /etc/security/limits.conf.bak.$(date +%Y%m%d)
log_info "配置已备份到 $BACKUP_DIR"

# ===== 1. 配置密码复杂度 =====
log_info "配置密码复杂度策略..."
cat > /etc/security/pwquality.conf << 'PWEOF'
minlen = 12
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
difok = 8
maxrepeat = 3
maxsequence = 3
usercheck = 1
minclass = 3
enforce_for_root
retry = 3
PWEOF

# ===== 2. 配置登录失败锁定 =====
log_info "配置登录失败锁定..."
if [[ $OS_VERSION -ge 8 ]]; then
# CentOS 8+ 使用faillock.conf
    cat > /etc/security/faillock.conf << 'FLEOF'
deny = 5
unlock_time = 900
fail_interval = 900
even_deny_root = false
silent
audit
dir = /var/run/faillock
FLEOF
# 通过authselect启用
    authselect select sssd with-faillock --force 2>/dev/null || \
    authselect select minimal with-faillock --force
else
# CentOS 7 手动配置(在2.2.4节已详细说明)
    log_warn "CentOS 7需要手动编辑system-auth,请参考文档"
fi

# ===== 3. 配置资源限制 =====
log_info "配置资源限制..."
cat >> /etc/security/limits.conf << 'LIMEOF'

# === PAM Hardening Script Added ===
*               soft    nofile          65535
*               hard    nofile          131072
*               soft    nproc           4096
*               hard    nproc           8192
*               soft    memlock         65536
*               hard    memlock         65536
LIMEOF

# ===== 4. 创建密码历史文件 =====
touch /etc/security/opasswd
chmod 600 /etc/security/opasswd
chown root:root /etc/security/opasswd

# ===== 5. 配置密码过期策略 =====
log_info "配置密码过期策略..."
sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS   90/' /etc/login.defs
sed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS   1/' /etc/login.defs
sed -i 's/^PASS_MIN_LEN.*/PASS_MIN_LEN    12/' /etc/login.defs
sed -i 's/^PASS_WARN_AGE.*/PASS_WARN_AGE   14/' /etc/login.defs

log_info "===== PAM加固完成 ====="
log_info "备份目录: $BACKUP_DIR"
log_warn "请立即用新终端测试登录,确认配置正确"
log_warn "如果无法登录,用当前会话恢复: cp -a ${BACKUP_DIR}/* /etc/pam.d/"

3.2 实际应用案例

案例一:密码策略+登录锁定+资源限制组合配置

场景描述:某金融客户等保三级要求,密码12位以上含四种字符、90天强制更换、连续失败5次锁定15分钟、记住最近12次密码不允许重复。同时要限制普通用户资源使用,防止影响核心业务进程。

完整配置清单

# 1. /etc/security/pwquality.conf - 密码复杂度
minlen = 12
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
difok = 8
maxrepeat = 3
maxsequence = 3
usercheck = 1
minclass = 3
enforce_for_root
retry = 3
# 2. /etc/security/faillock.conf - 登录锁定(CentOS 8+)
deny = 5
unlock_time = 900
fail_interval = 900
even_deny_root = false
silent
audit
# 3. /etc/login.defs - 密码过期策略
PASS_MAX_DAYS   90
PASS_MIN_DAYS   1
PASS_MIN_LEN    12
PASS_WARN_AGE   14
# 4. /etc/security/limits.conf - 资源限制
*               soft    nofile          65535
*               hard    nofile          131072
*               soft    nproc           4096
*               hard    nproc           8192
@developers     soft    nofile          4096
@developers     hard    nofile          8192
@developers     soft    nproc           1024
@developers     hard    nproc           2048
@developers     hard    core            0

验证结果

# 密码策略验证
$ passwd testuser
Changing password for user testuser.
New password: abc123
BAD PASSWORD: The password is shorter than 12 characters

New password: abcdefghijkl
BAD PASSWORD: The password fails the dictionary check - it is based on a dictionary word

New password: T3st@Secure2025
passwd: all authentication tokens updated successfully.

# 锁定验证(连续5次错误密码后)
$ ssh testuser@localhost
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
# 查看锁定状态
$ sudo faillock --user testuser
testuser:
When                Type  Source       Valid
2025-01-15 14:23:01 RHOST 127.0.0.1   V
2025-01-15 14:23:05 RHOST 127.0.0.1   V
2025-01-15 14:23:08 RHOST 127.0.0.1   V
2025-01-15 14:23:11 RHOST 127.0.0.1   V
2025-01-15 14:23:14 RHOST 127.0.0.1   V

案例二:限制特定用户只能在工作时间登录

场景描述:外包开发人员只允许在工作日9:00-18:00通过SSH登录生产服务器查看日志,非工作时间禁止登录。运维组不受时间限制。

实现步骤

  1. 创建用户组并分配用户:
# 创建外包开发组
sudo groupadd outsource-dev

# 将外包人员加入组
sudo usermod -aG outsource-dev dev-wang
sudo usermod -aG outsource-dev dev-li
  1. 配置时间控制:
# 编辑 /etc/security/time.conf
# 外包开发组只能在工作日9:00-18:00登录
sshd;*;@outsource-dev;Wk0900-1800
  1. 在PAM中启用时间控制:
# 编辑 /etc/pam.d/sshd,在account段添加
# account    required     pam_time.so
  1. 验证效果:
# 工作时间内测试(假设当前是周三14:00)
ssh dev-wang@server
# 预期:登录成功

# 非工作时间测试(假设当前是周六10:00)
ssh dev-wang@server
# 预期:Authentication failure
# 日志中会记录:pam_time(sshd:account): access denied for user 'dev-wang'

# 运维用户不受限制
ssh opsadmin@server
# 预期:任何时间都能登录

案例三:基于pam_exec的登录审计与告警联动

场景描述:安全合规要求所有SSH登录必须有审计记录,非工作时间登录和非常用IP登录需要实时告警。通过pam_exec在登录时触发审计脚本,记录详细信息并根据规则发送告警。

审计脚本

#!/bin/bash
# 文件名:/opt/scripts/login_audit.sh
# 功能:SSH登录审计+异常告警
# 在 /etc/pam.d/sshd 的session段调用:
# session optional pam_exec.so /opt/scripts/login_audit.sh

# 只处理登录事件
"$PAM_TYPE" != "open_session" ] && exit 0

# 审计日志文件
AUDIT_LOG="/var/log/ssh_audit.log"
# 告警webhook(钉钉/企业微信)
WEBHOOK_URL="https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"
# 常用IP白名单文件
WHITELIST_FILE="/etc/security/ssh_ip_whitelist.conf"

HOSTNAME=$(hostname)
LOGIN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
HOUR=$(date '+%H')
DAY_OF_WEEK=$(date '+%u')  # 1=周一, 7=周日

# 记录审计日志
echo"${LOGIN_TIME} | USER=${PAM_USER} | FROM=${PAM_RHOST} | SERVICE=${PAM_SERVICE} | HOST=${HOSTNAME}" >> "$AUDIT_LOG"

# 判断是否需要告警
NEED_ALERT=false
ALERT_REASON=""

# 规则1:非工作时间登录(22:00-08:00 或 周末)
if [ "$HOUR" -ge 22 ] || [ "$HOUR" -lt 8 ] || [ "$DAY_OF_WEEK" -ge 6 ]; then
    NEED_ALERT=true
    ALERT_REASON="非工作时间登录"
fi

# 规则2:非白名单IP登录
if [ -f "$WHITELIST_FILE" ]; then
if ! grep -q "^${PAM_RHOST}$""$WHITELIST_FILE" 2>/dev/null; then
        NEED_ALERT=true
        ALERT_REASON="${ALERT_REASON:+${ALERT_REASON}, }非白名单IP"
fi
fi

# 规则3:root用户登录(即使允许也要告警)
if [ "$PAM_USER" = "root" ]; then
    NEED_ALERT=true
    ALERT_REASON="${ALERT_REASON:+${ALERT_REASON}, }root用户登录"
fi

# 发送告警
if$NEED_ALERTthen
    MESSAGE="SSH登录告警\n原因: ${ALERT_REASON}\n主机: ${HOSTNAME}\n用户: ${PAM_USER}\n来源: ${PAM_RHOST}\n时间: ${LOGIN_TIME}"
    curl -s -H "Content-Type: application/json" \
        -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${MESSAGE}\"}}" \
"$WEBHOOK_URL" &>/dev/null &
fi

exit 0
# IP白名单文件
cat > /etc/security/ssh_ip_whitelist.conf << 'EOF'
10.0.0.1
10.0.0.2
10.0.0.50
172.16.1.100
EOF

# 设置权限
sudo chmod 755 /opt/scripts/login_audit.sh
sudo chown root:root /opt/scripts/login_audit.sh
sudo touch /var/log/ssh_audit.log
sudo chmod 640 /var/log/ssh_audit.log

审计日志输出示例

2025-01-15 14:23:45 | USER=opsadmin | FROM=10.0.0.50 | SERVICE=sshd | HOST=web-prod-01
2025-01-15 22:15:30 | USER=deployer | FROM=192.168.1.88 | SERVICE=sshd | HOST=web-prod-01
2025-01-16 03:42:11 | USER=root | FROM=10.0.0.1 | SERVICE=sshd | HOST=db-prod-01

四、最佳实践和注意事项

4.1 最佳实践

4.1.1 性能优化

  • PAM模块加载顺序影响认证速度:PAM按配置文件中的顺序依次加载模块。把最可能成功的认证方式放前面,用sufficient标志实现"快速通过"。比如本地密码认证放在LDAP认证前面,本地用户不需要等LDAP查询。实测在500人LDAP环境中,本地用户认证从平均1.2秒降到0.05秒。

    # 优化前:先查LDAP再查本地
    auth  sufficient  pam_ldap.so
    auth  sufficient  pam_unix.so

    # 优化后:先查本地再查LDAP
    auth  sufficient  pam_unix.so try_first_pass
    auth  sufficient  pam_ldap.so use_first_pass
  • pam_exec脚本必须异步执行耗时操作:pam_exec调用的脚本如果执行时间长(比如发HTTP请求),会阻塞登录过程。curl发钉钉通知要加 & 后台执行,脚本本身立即返回exit 0。实测不加后台执行时,钉钉接口偶尔超时会导致SSH登录卡住10秒以上。

    # 错误写法:同步执行curl,会阻塞登录
    curl -s -d "$DATA""$WEBHOOK_URL"

    # 正确写法:后台执行,不阻塞
    curl -s -d "$DATA""$WEBHOOK_URL" &>/dev/null &
    exit 0
  • faillock记录目录用tmpfs:faillock默认把锁定记录写在 /var/run/faillock/,这个目录在多数系统上是tmpfs(内存文件系统),读写速度快且重启自动清空。如果改到磁盘目录,高并发认证时磁盘IO会成为瓶颈。

    # 确认/var/run是tmpfs
    df -T /var/run
    # 输出应该是tmpfs类型

    # faillock.conf中保持默认
    dir = /var/run/faillock

4.1.2 安全加固

  • PAM配置修改前必须保留rescue终端:这条怎么强调都不过分。PAM配置错误会导致所有用户(包括root)无法登录。改之前开一个root的tmux/screen会话挂着,或者确认有VNC/IPMI等带外管理通道。我们团队有个不成文的规定:改PAM配置必须两人在场,一人操作一人保持会话。

    # 改配置前的标准操作流程
    # 1. 开一个tmux会话保持root登录
    tmux new -s rescue

    # 2. 备份当前配置
    sudo cp -a /etc/pam.d /etc/pam.d.bak.$(date +%Y%m%d_%H%M%S)

    # 3. 修改配置

    # 4. 新开终端测试登录(不要断开rescue会话)

    # 5. 确认正常后才关闭rescue会话
  • 使用pam_faillock替代已废弃的pam_tally2:CentOS 8开始移除了pam_tally2模块,继续用会导致认证链断裂。pam_faillock功能更完善,支持独立配置文件(faillock.conf),参数调整不用改PAM配置文件。如果你还在CentOS 7上用pam_tally2,建议趁早迁移到pam_faillock,CentOS 7也支持。

    # 检查是否还在用pam_tally2
    grep pam_tally2 /etc/pam.d/*
    # 如果有输出,说明还在用旧模块,需要替换

    # 替换方法:把pam_tally2行替换为pam_faillock
    # 旧:auth required pam_tally2.so deny=5 unlock_time=900
    # 新:auth required pam_faillock.so preauth deny=5 unlock_time=900
  • 分层权限设计:不要给所有运维人员相同的权限。我们团队的四层权限模型:

    # /etc/sudoers.d/permission-levels
    # L1 - 只读权限
    %l1-users ALL=(ALL) NOPASSWD: /usr/bin/tail -f /var/log/*, \
        /usr/bin/systemctl status *

    # L2 - 应用运维
    %l2-ops ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart app-*, \
        /usr/bin/systemctl stop app-*, \
        /usr/bin/systemctl start app-*, \
        /usr/bin/tail -f /var/log/*, \
        /usr/bin/less /var/log/*

    # L3 - 高级运维
    %l3-senior ALL=(ALL) ALL
    %l3-senior ALL=(ALL) !DANGEROUS

    # L4 - 超级管理员
    %l4-admin ALL=(ALL) ALL
    • L1 普通用户:只能查看日志、查看服务状态,不能重启服务、不能sudo
    • L2 运维人员:可以重启应用服务、查看配置文件,sudo受限命令列表
    • L3 高级运维/DBA:可以操作数据库、修改配置文件,sudo较宽但排除危险命令
    • L4 超级管理员:完整sudo权限,但所有操作有审计日志
  • 结合LDAP实现集中认证:超过20台服务器时,本地账户管理不现实。用LDAP(OpenLDAP或FreeIPA)集中管理用户,PAM通过pam_ldap或pam_sss对接LDAP。人员入职在LDAP加账号,所有服务器自动生效;离职在LDAP禁用,所有服务器立即无法登录。

4.1.3 高可用配置

  • LDAP认证的高可用:LDAP服务器挂了会导致所有依赖LDAP认证的服务器无法登录。必须配置LDAP主从复制,PAM配置中指定多个LDAP服务器地址。同时保留本地管理员账号作为应急通道。
  • PAM配置的版本管理:把 /etc/pam.d/ 和 /etc/security/ 下的配置文件纳入Ansible/Puppet管理,用Git做版本控制。配置变更走代码审查流程,避免手动修改导致的不一致。
  • 备份策略:每次修改PAM配置前自动备份,保留最近30天的备份。建议用crontab每天备份一次 /etc/pam.d/ 目录。

4.2 注意事项

4.2.1 配置注意事项

PAM配置改错的后果比SSH配置改错严重得多。SSH改错最多是远程连不上,PAM改错是所有认证方式全部失效,本地终端也登不了。

  • PAM配置文件中模块的顺序决定执行顺序,顺序错了逻辑就错了。pam_faillock的preauth必须在pam_unix之前,authfail必须在pam_unix之后
  • sufficient 标志的模块成功后会跳过后续同类型模块,放错位置会导致安全检查被跳过
  • /etc/pam.d/system-auth 被多个服务引用(sshd、su、sudo、login),改这个文件影响面最大,改之前想清楚
  • CentOS 8+用authselect管理PAM配置,手动编辑system-auth会被authselect覆盖。要么用authselect的自定义profile,要么在 /etc/pam.d/sshd 等服务专用文件中修改
  • pam_deny.so 作为兜底模块放在auth段最后,确保所有认证方式都失败时返回拒绝,不要漏掉

4.2.2 常见错误

错误现象
原因分析
解决方案
所有用户无法登录(包括root)
PAM配置语法错误或模块路径错误
通过单用户模式/VNC恢复备份的PAM配置
passwd修改密码报"Authentication token manipulation error"
/etc/security/opasswd文件不存在或权限不对
touch /etc/security/opasswd && chmod 600 /etc/security/opasswd
密码符合策略但仍被拒绝
cracklib字典匹配到了常见词根
换一个不包含字典词的密码,或调整dictpath参数
faillock锁定后手动解锁无效
解锁命令用错了(用了pam_tally2的命令)
用 faillock --user USERNAME --reset 而不是 pam_tally2 --reset
ulimit设置不生效
PAM配置中缺少pam_limits.so,或limits.conf语法错误
检查 /etc/pam.d/sshd 中是否有 session required pam_limits.so
sudo报"account validation failure"
PAM的account段配置有问题
检查 /etc/pam.d/sudo 的account段,确认pam_unix.so存在
pam_access拒绝了应该允许的用户
access.conf中规则顺序错误,- : ALL : ALL 放在了允许规则前面
允许规则必须在拒绝规则前面,PAM按顺序匹配第一条命中的规则

4.2.3 兼容性问题

  • 版本兼容:pam_faillock在CentOS 7上可用但没有独立配置文件(faillock.conf),参数只能写在PAM配置行内。CentOS 8+支持faillock.conf独立配置。pam_tally2在CentOS 8+已移除,升级系统后必须迁移到pam_faillock。
  • 平台兼容:CentOS/RHEL使用 /etc/pam.d/system-auth 和 /etc/pam.d/password-auth,Ubuntu/Debian使用 /etc/pam.d/common-auth 和 /etc/pam.d/common-password。模块路径也不同:CentOS在 /usr/lib64/security/,Ubuntu在 /usr/lib/x86_64-linux-gnu/security/
  • 组件依赖:pam_pwquality依赖libpwquality和cracklib-dicts(密码字典)。最小化安装的系统可能缺少cracklib-dicts,导致字典检查不生效。用 rpm -q cracklib-dicts 或 dpkg -l libcrack2 确认已安装。
  • authselect兼容:CentOS 8+引入authselect替代authconfig。直接编辑system-auth会被authselect覆盖。如果需要自定义PAM配置,用 authselect create-profile 创建自定义profile。

五、故障排查和监控

5.1 故障排查

5.1.1 日志查看

# 查看PAM相关的认证日志
# CentOS/RHEL
sudo tail -f /var/log/secure

# Ubuntu/Debian
sudo tail -f /var/log/auth.log

# 用journalctl过滤PAM相关日志
sudo journalctl | grep -i "pam_"

# 只看最近30分钟的PAM日志
sudo journalctl --since "30 minutes ago" | grep -i "pam_"

# 查看特定模块的日志
sudo journalctl | grep "pam_faillock"
sudo journalctl | grep "pam_pwquality"
sudo journalctl | grep "pam_access"

5.1.2 常见问题排查

问题一:PAM配置错误导致所有用户无法登录

这是PAM故障中最严重的情况。一旦发生,只能通过不经过PAM认证的方式恢复。

# 恢复方法1:通过已保留的root会话恢复
# 如果改配置前开了tmux/screen会话,直接在那个会话里操作
sudo cp -a /etc/pam.d.bak.20250115/* /etc/pam.d/

# 恢复方法2:单用户模式(物理机或VNC)
# 重启服务器,在GRUB菜单按e
# 找到linux行,在末尾添加 init=/bin/bash
# 按Ctrl+X启动
mount -o remount,rw /
cp -a /etc/pam.d.bak.*/* /etc/pam.d/
# 如果没有备份,恢复最小可用配置:
cat > /etc/pam.d/system-auth << 'EOF'
auth        required      pam_env.so
auth        sufficient    pam_unix.so try_first_pass
auth        required      pam_deny.so
account     required      pam_unix.so
password    sufficient    pam_unix.so sha512 shadow try_first_pass use_authtok
password    required      pam_deny.so
session     required      pam_limits.so
session     required      pam_unix.so
EOF
exec /sbin/init

# 恢复方法3:云服务器通过VNC控制台
# 登录云控制台 -> 实例管理 -> 远程连接 -> VNC
# 用root密码登录(VNC登录走/etc/pam.d/login,如果这个也改坏了就只能用方法2)

# 恢复方法4:挂载磁盘到另一台机器
# 云服务器可以卸载系统盘挂到另一台ECS上修复

问题二:密码策略过严导致无法修改密码

# 症状:passwd命令反复提示BAD PASSWORD,用户怎么设都不通过
# 排查:查看当前密码策略
cat /etc/security/pwquality.conf | grep -v "^#" | grep -v "^$"

# 常见原因1:minlen设太高(比如设了20)
# 解决:临时调低minlen,用户改完密码再调回来
sudo sed -i 's/^minlen.*/minlen = 12/' /etc/security/pwquality.conf

# 常见原因2:cracklib字典匹配过于严格
# 排查:用pwscore命令测试密码得分
echo"MyNewP@ss2025" | pwscore
# 输出分数,低于配置的阈值就会被拒绝

# 常见原因3:remember参数导致最近用过的密码不能重复
# 排查:
cat /etc/security/opasswd
# 如果需要清除密码历史(紧急情况)
sudo truncate -s 0 /etc/security/opasswd

# root用户绕过密码策略(应急用,不建议常规使用)
# root执行passwd时pwquality会警告但不会阻止
sudo passwd username

问题三:sudo认证失败排查

# 症状:sudo命令报 "sudo: PAM account management error: Authentication service cannot retrieve authentication info"
# 或者 "sudo: a]ccount validation failure, is your account locked?"

# 排查步骤:
# 1. 检查sudo的PAM配置
cat /etc/pam.d/sudo

# 2. 检查用户账户状态
sudo passwd -S username
# 输出含义:
# username PS 2025-01-15 1 90 14 -1 (Password set, SHA512 crypt.)
# PS=密码已设置, LK=已锁定, NP=无密码

# 3. 检查faillock是否锁定了用户
sudo faillock --user username

# 4. 检查账户是否过期
sudo chage -l username
# 关注 "Account expires" 和 "Password expires" 字段

# 5. 如果是LDAP用户,检查LDAP连接
sudo systemctl status sssd
sudo sssctl user-checks username

# 解决:
# 解锁faillock
sudo faillock --user username --reset
# 解锁passwd锁定
sudo passwd -u username
# 延长账户有效期
sudo chage -E -1 username

问题四:PAM模块加载失败

# 症状:日志中出现 "PAM unable to dlopen" 或 "Module is unknown"
# 原因:模块.so文件不存在或路径错误

# 排查:
# 1. 确认模块文件存在
ls -la /usr/lib64/security/pam_faillock.so
# 如果不存在,安装对应的包
sudo yum provides "*/pam_faillock.so"
# 输出会告诉你需要安装哪个包

# 2. 检查模块依赖
ldd /usr/lib64/security/pam_faillock.so
# 如果有 "not found" 的依赖库,需要安装

# 3. 检查SELinux是否阻止了模块加载
sudo ausearch -m avc -ts recent | grep pam
# 如果有denied记录,临时关闭SELinux测试
sudo setenforce 0
# 确认是SELinux问题后,添加策略而不是永久关闭
sudo audit2allow -a -M pam_custom
sudo semodule -i pam_custom.pp

5.1.3 调试模式

# PAM没有专门的调试命令,但可以通过以下方式排查

# 1. 用pamtester工具测试(需要安装)
sudo yum install -y pamtester  # CentOS
sudo apt install -y pamtester  # Ubuntu

# 测试sshd服务对某用户的认证
pamtester sshd opsadmin authenticate
# 会提示输入密码,然后显示认证结果

# 测试account检查
pamtester sshd opsadmin acct_mgmt

# 2. 临时开启PAM调试日志
# 在模块参数中添加 debug 关键字
# 例如:auth required pam_faillock.so preauth deny=5 debug
# 改完后查看 /var/log/secure 会有详细的模块执行日志
# 排查完务必去掉debug参数,否则日志量很大

# 3. 用strace跟踪PAM模块加载
sudo strace -f -e trace=open,openat -p $(pgrep -f sshd | head -1) 2>&1 | grep pam
# 可以看到sshd加载了哪些PAM模块文件

5.2 性能监控

5.2.1 关键指标监控

# 认证失败次数统计(最近1小时)
sudo journalctl --since "1 hour ago" | grep -c "authentication failure"

# 被faillock锁定的用户数
sudo faillock 2>/dev/null | grep -c "V$"

# 密码修改失败次数
sudo journalctl --since "1 hour ago" | grep -c "pam_pwquality"

# 当前登录用户数
who | wc -l

# sudo执行次数统计
sudo grep -c "COMMAND=" /var/log/sudo.log 2>/dev/null || echo 0

# 账户状态检查(过期账户数)
sudo awk -F: '{if($8!="" && $8<'$(date +%s)'/86400) print $1}' /etc/shadow | wc -l

5.2.2 监控指标说明

指标名称
正常范围
告警阈值
说明
认证失败次数/小时
0-10
>50
大量失败可能是暴力破解或配置错误
faillock锁定用户数
0
>5
多用户被锁定说明可能遭受攻击
密码修改失败次数/天
0-5
>20
频繁失败可能是策略过严需要调整
sudo执行次数/小时
0-50
>200
异常高频sudo可能是自动化脚本失控
过期账户数
0
>0
有过期账户说明账户生命周期管理有漏洞
密码即将过期用户数
0-5
>10
提前通知用户修改密码

5.2.3 Prometheus监控规则

# pam_security_rules.yml
# 文件路径:/etc/prometheus/rules/pam_rules.yml

groups:
-name:pam_security
interval:60s
rules:
# 认证失败率告警
-alert:PAMAuthFailureHigh
expr:rate(pam_auth_failures_total[5m])>5
for:5m
labels:
severity:warning
annotations:
summary:"PAM认证失败率过高 ({{ $labels.instance }})"
description:"5分钟内认证失败率超过5次/秒,可能遭受暴力破解或PAM配置异常"

# 用户被锁定告警
-alert:PAMUserLocked
expr:pam_locked_users>3
for:2m
labels:
severity:warning
annotations:
summary:"多个用户被faillock锁定 ({{ $labels.instance }})"
description:"当前有 {{ $value }} 个用户被锁定"

# 密码即将过期告警
-alert:PAMPasswordExpiringSoon
expr:pam_password_expiring_users>0
for:1h
labels:
severity:info
annotations:
summary:"有用户密码即将过期 ({{ $labels.instance }})"
description:"{{ $value }} 个用户的密码将在14天内过期"

# sudo异常高频使用
-alert:SudoUsageHigh
expr:rate(sudo_commands_total[5m])>10
for:5m
labels:
severity:warning
annotations:
summary:"sudo使用频率异常 ({{ $labels.instance }})"
description:"sudo命令执行频率超过10次/秒,请检查是否有异常操作"

5.3 备份与恢复

5.3.1 备份策略

#!/bin/bash
# 文件名:backup_pam_config.sh
# 功能:备份PAM和安全相关配置
# 建议每天执行一次,保留最近30天

BACKUP_BASE="/data/backup/pam"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="$BACKUP_BASE/$DATE"
KEEP_DAYS=30

mkdir -p "$BACKUP_DIR"

# 备份PAM配置目录
sudo cp -a /etc/pam.d "$BACKUP_DIR/"

# 备份安全配置文件
sudo cp -a /etc/security/pwquality.conf "$BACKUP_DIR/"
sudo cp -a /etc/security/limits.conf "$BACKUP_DIR/"
sudo cp -a /etc/security/access.conf "$BACKUP_DIR/"
sudo cp -a /etc/security/time.conf "$BACKUP_DIR/" 2>/dev/null
sudo cp -a /etc/security/faillock.conf "$BACKUP_DIR/" 2>/dev/null
sudo cp -a /etc/security/opasswd "$BACKUP_DIR/"

# 备份账户相关文件
sudo cp -a /etc/login.defs "$BACKUP_DIR/"
sudo cp -a /etc/sudoers "$BACKUP_DIR/"
sudo cp -a /etc/sudoers.d "$BACKUP_DIR/" 2>/dev/null

# 设置备份权限
sudo chmod -R 600 "$BACKUP_DIR"
sudo chmod 700 "$BACKUP_DIR"

# 清理过期备份
find "$BACKUP_BASE" -maxdepth 1 -type d -mtime +$KEEP_DAYS -exec rm -rf {} \;

echo"PAM配置备份完成: $BACKUP_DIR"

5.3.2 恢复流程

  1. 确认恢复通道

    # 确保有不经过PAM的访问方式(VNC/IPMI/物理控制台)
    # 或者有一个活跃的root会话
    who am i
  2. 恢复PAM配置

    # 找到最近的备份
    ls -lt /data/backup/pam/ | head -5

    # 恢复配置
    RESTORE_DIR="/data/backup/pam/20250115_020000"
    sudo cp -a "$RESTORE_DIR/pam.d/"* /etc/pam.d/
    sudo cp "$RESTORE_DIR/pwquality.conf" /etc/security/
    sudo cp "$RESTORE_DIR/limits.conf" /etc/security/
    sudo cp "$RESTORE_DIR/access.conf" /etc/security/
  3. 验证恢复结果

    # 新开终端测试登录
    ssh opsadmin@localhost
    # 测试sudo
    sudo whoami
  4. 恢复sudoers(如果需要):

    # sudoers文件必须用visudo验证语法
    sudo cp "$RESTORE_DIR/sudoers" /tmp/sudoers.restore
    sudo visudo -c -f /tmp/sudoers.restore
    # 语法正确后再覆盖
    sudo cp /tmp/sudoers.restore /etc/sudoers
    sudo chmod 440 /etc/sudoers

六、总结

6.1 技术要点回顾

  • PAM是Linux认证的底层框架:SSH登录、su切换、sudo提权、passwd改密码,所有认证行为都经过PAM。只改sudoers不碰PAM,等于只做了权限管控的表面功夫。
  • 四种管理类型各司其职:auth验证身份、account检查账户状态、password控制密码修改、session管理会话环境。一次完整的登录流程会依次经过这四个阶段,每个阶段都可以插入安全控制逻辑。
  • pam_pwquality + pam_faillock是密码安全的基础组合:pwquality从策略层面强制密码复杂度(12位+四种字符),faillock从行为层面防暴力破解(5次失败锁定15分钟)。两者配合覆盖了密码安全的两个维度。
  • pam_limits防止资源滥用:不限制nofile和nproc的服务器,一个fork炸弹或文件描述符泄漏就能把整台机器搞挂。生产环境必须配置合理的资源上限。
  • 分层权限设计是管理的核心:L1只读、L2应用运维、L3高级运维、L4超级管理员,每层权限边界清晰。配合pam_access控制登录来源、pam_time控制登录时间,实现立体化的权限管控。
  • 改PAM配置前必须有恢复手段:这条规矩比任何技术细节都重要。备份配置、保留rescue会话、确认VNC/IPMI可用,三选一必须满足。

6.2 进阶学习方向

  1. FreeIPA集中身份管理

    • FreeIPA整合了LDAP、Kerberos、DNS、CA,提供Web界面管理用户、组、主机、HBAC策略
    • PAM通过SSSD对接FreeIPA,实现集中认证和授权
    • 学习资源:FreeIPA官方文档 https://www.freeipa.org/page/Documentation
    • 实践建议:用两台虚拟机搭建FreeIPA主从,配置客户端加入域,体验集中管理的便利
  2. PAM与MFA多因素认证集成

    • Google Authenticator的PAM模块(pam_google_authenticator)可以给SSH登录加上TOTP二次验证
    • 配置方式:在auth段添加 auth required pam_google_authenticator.so,配合 AuthenticationMethods publickey,keyboard-interactive 实现密钥+OTP双因素
    • 学习资源:Google Authenticator PAM模块 GitHub仓库
    • 实践建议:先在测试环境配置,注意保留应急登录通道,OTP配置错误会锁死所有用户
  3. 自定义PAM模块开发

    • PAM模块本质是实现了特定接口的共享库(.so文件),用C语言开发
    • 可以开发自定义模块实现特殊需求:比如对接内部审批系统、实现地理位置限制、集成硬件令牌
    • 学习资源:Linux-PAM Module Writers' Guide
    • 实践建议:从最简单的pam_permit.c源码开始读,理解PAM模块的接口规范

6.3 参考资料

  • Linux-PAM官方文档 - PAM架构、模块接口、配置语法的权威说明
  • Red Hat PAM配置指南 - RHEL 8/9的PAM和authselect配置指南
  • CIS Benchmark for Linux - CIS安全基线中密码策略和账户锁定章节
  • pam_faillock手册页 - faillock模块的完整参数说明
  • libpwquality文档 - 密码质量检查库的源码和文档

附录

A. 命令速查表

# ===== PAM配置管理 =====
cat /etc/pam.d/sshd                              # 查看SSH的PAM配置
cat /etc/pam.d/system-auth                        # 查看系统认证主配置(CentOS)
cat /etc/pam.d/common-auth                        # 查看系统认证主配置(Ubuntu)
ls /usr/lib64/security/pam_*.so                   # 列出已安装的PAM模块(CentOS)
sudo authselect current                           # 查看当前authselect配置(CentOS 8+)
sudo authselect list-features sssd                # 列出可用的authselect特性

# ===== 密码策略管理 =====
cat /etc/security/pwquality.conf                  # 查看密码复杂度配置
echo"TestPass123" | pwscore                      # 测试密码强度评分
sudo chage -l username                            # 查看用户密码过期信息
sudo chage -M 90 -m 1 -W 14 username             # 设置密码90天过期、最少1天、提前14天警告
sudo passwd -S username                           # 查看用户密码状态

# ===== faillock管理 =====
sudo faillock --user username                     # 查看用户失败记录
sudo faillock --user username --reset             # 解锁用户(清除失败记录)
sudo faillock                                     # 查看所有用户的失败记录

# ===== 资源限制管理 =====
ulimit -a                                         # 查看当前用户所有限制
ulimit -n                                         # 查看最大打开文件数
ulimit -u                                         # 查看最大进程数
cat /proc/PID/limits                              # 查看指定进程的实际限制

# ===== 账户管理 =====
sudo passwd -l username                           # 锁定账户
sudo passwd -u username                           # 解锁账户
sudo usermod -aG groupname username               # 将用户加入组
id username                                       # 查看用户的UID和组信息

# ===== sudo管理 =====
sudo visudo                                       # 安全编辑sudoers
sudo visudo -c                                    # 检查sudoers语法
sudo -l -U username                               # 查看用户的sudo权限
sudo sudoreplay -l                                # 列出sudo会话记录

# ===== 调试排查 =====
pamtester sshd username authenticate              # 测试PAM认证
sudo journalctl | grep "pam_"# 查看PAM相关日志
ldd /usr/lib64/security/pam_faillock.so           # 检查模块依赖

B. 配置参数详解

pwquality.conf 关键参数

参数
默认值
推荐值
说明
minlen
8
12
密码最小长度
dcredit
0
-1
数字要求,负数表示至少N个
ucredit
0
-1
大写字母要求
lcredit
0
-1
小写字母要求
ocredit
0
-1
特殊字符要求
minclass
0
3
至少包含N种字符类别
maxrepeat
0
3
最大连续相同字符数,0不限制
maxsequence
0
3
最大连续递增/递减字符数
difok
1
8
新旧密码至少N个字符不同
usercheck
1
1
是否检查密码包含用户名
enforce_for_root
启用
是否对root也强制执行策略
retry
1
3
密码输入重试次数

faillock.conf 关键参数

参数
默认值
推荐值
说明
deny
3
5
失败N次后锁定账户
unlock_time
600
900
锁定时间(秒),0表示永久锁定
fail_interval
900
900
统计窗口(秒),窗口内累计失败次数
even_deny_root
false
false
是否锁定root,生产环境慎开
root_unlock_time
60
root锁定时间(秒),建议短一些
silent
启用
不提示剩余尝试次数
audit
启用
记录审计日志
dir
/var/run/faillock
默认
锁定记录存储目录

limits.conf 常用资源类型

资源项
说明
推荐值(普通用户)
推荐值(服务账户)
nofile
最大打开文件描述符数
65535
131072
nproc
最大进程/线程数
4096
8192
memlock
最大锁定内存(KB)
65536
unlimited(ES/DB)
core
core dump文件大小(KB)
0(禁用)
0(生产禁用)
fsize
最大文件大小(KB)
2097152(2GB)
不限制
stack
最大栈大小(KB)
8192
8192
as
最大虚拟内存(KB)
不限制
不限制

C. 术语表

术语
英文
解释
PAM
Pluggable Authentication Modules
可插拔认证模块,Linux统一认证框架,控制登录、su、sudo等所有认证行为
auth
Authentication
PAM四种管理类型之一,负责验证用户身份(密码、密钥、OTP等)
account
Account Management
PAM四种管理类型之一,负责检查账户状态(是否过期、是否锁定、是否允许访问)
password
Password Management
PAM四种管理类型之一,负责密码修改时的策略检查和更新操作
session
Session Management
PAM四种管理类型之一,负责登录后的会话环境设置(资源限制、日志记录等)
required
Required Control Flag
PAM控制标志,模块失败会记录但继续检查后续模块,最终返回失败
requisite
Requisite Control Flag
PAM控制标志,模块失败立即返回,不再检查后续模块
sufficient
Sufficient Control Flag
PAM控制标志,模块成功且前面没有required失败则立即返回成功
pam_pwquality
Password Quality Module
密码质量检查模块,强制密码复杂度策略,替代旧的pam_cracklib
pam_faillock
Fail Lock Module
登录失败锁定模块,统计失败次数并锁定账户,替代已废弃的pam_tally2
pam_limits
Resource Limits Module
资源限制模块,通过limits.conf控制用户的文件描述符、进程数等资源配额
pam_access
Access Control Module
访问控制模块,通过access.conf控制用户从哪些来源可以登录
authselect
Authentication Select
CentOS 8+的认证配置管理工具,替代旧的authconfig,统一管理PAM配置
SSSD
System Security Services Daemon
系统安全服务守护进程,用于对接LDAP/FreeIPA等集中认证服务
HBAC
Host-Based Access Control
基于主机的访问控制,FreeIPA中控制用户能登录哪些主机的策略

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-03-01 06:45:05 HTTP/2.0 GET : https://f.mffb.com.cn/a/475944.html
  2. 运行时间 : 0.080637s [ 吞吐率:12.40req/s ] 内存消耗:4,728.90kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=f31a30804b23e80e254350f996731c35
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000415s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000533s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000262s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000303s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000508s ]
  6. SELECT * FROM `set` [ RunTime:0.000222s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000585s ]
  8. SELECT * FROM `article` WHERE `id` = 475944 LIMIT 1 [ RunTime:0.000713s ]
  9. UPDATE `article` SET `lasttime` = 1772318705 WHERE `id` = 475944 [ RunTime:0.000474s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000250s ]
  11. SELECT * FROM `article` WHERE `id` < 475944 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000871s ]
  12. SELECT * FROM `article` WHERE `id` > 475944 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.001154s ]
  13. SELECT * FROM `article` WHERE `id` < 475944 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.002462s ]
  14. SELECT * FROM `article` WHERE `id` < 475944 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.002368s ]
  15. SELECT * FROM `article` WHERE `id` < 475944 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.000926s ]
0.083046s