摘要:本文面向运维与安全工程师,拆解 Linux 与国产操作系统登录链路中 PAM 可插拔认证模块的工作机制,给出将国密双因子无缝接入 SSH 与图形桌面登录的完整改造路径,涵盖 PAM 模块编排、gdm/lightdm 集成、会话级因子校验、失败锁定策略与统一策略下发,并对比单机、联网、SaaS 三类部署形态在离线应急与审计上的取舍。 正在上传图片...
关键词:操作系统双因素认证, 国密USBKey, Linux 指纹登录, 等保2.0, 远程登录安全, PAM 模块
在等保 2.0 三级及以上系统中,"仅口令认证"早已不满足身份鉴别项的强度要求。对运行 CentOS、Ubuntu、麒麟 V10、统信 UOS 的服务器与办公终端而言,真正的难点不在于"有没有第二因子",而在于"第二因子能不能接进既有的登录链路,且不影响既有运维习惯"。本文以 Linux/国产操作系统的 PAM(Pluggable Authentication Modules,可插拔认证模块)栈为切入点,逐步拆开从 SSH 远程接入到图形桌面会话的认证改造过程,并讨论失败锁定、统一策略下发与离线应急等工程化问题。
PAM 是类 Unix 系统统一身份鉴别的事实标准,在应用程序(login、sshd、gdm、sudo 等)与具体认证实现之间插入一层配置化接口。每个调用 PAM 的程序按顺序执行四种类别的栈:
栈类别 | 触发时机 | 常见用途 |
|---|---|---|
auth | 鉴别用户身份 | 口令校验、USBKey 挑战应答、OTP 比对、指纹仪校验 |
account | 鉴别通过后 | 账号是否过期、是否允许此时此地登录 |
password | 改密时 | 口令复杂度、旧口令校验 |
session | 登录前后 | 挂载家目录、环境变量、拔 Key 锁屏钩子 |
理解这一点很关键:我们要把"国密双因子"塞进 auth 栈,把"拔 Key 自动锁屏"挂到 session 栈,把"失败锁定"通过 account 或 auth 的失败计数实现。只要编排正确,应用层(无论 SSH 还是桌面)几乎零改动,这也是双因子方案大多选 PAM 而非重写登录器的原因——改造面小、回滚快、跨应用一致。
更进一步看,PAM 的"可插拔"本质是一份栈式责任链:每个模块返回 success、ignore、die、reset 等状态,控制字如 required、requisite、sufficient、optional 决定状态如何汇总。掌握控制字语义才能兼顾安全与体验,后文会反复用到。

SSH 是运维主入口,改造风险最敏感——认证栈配错可能把自己挡在门外。稳妥做法:先用跳板机灰度,保留带口令的兜底账号与带外控制台,确认双因子链路稳定再铺开。
典型的 /etc/pam.d/sshd 改造如下(示意,非某产品原生命令):
# 先尝试国密 USBKey 挑战应答
auth sufficient pam_sm3_challenge.so try_first_pass
# Key 缺失或失败,则回落到 OTP
auth [success=1 default=ignore] pam_otp.so
# 两者都失败才用口令,且口令仅在第二因子通过后被采纳
auth requisite pam_unix.so try_first_pass
# 任意一次失败即记录并计数
auth required pam_faillock.so preauth这里有几个工程细节:
sufficient,带着 Key 插在机器上就能直接过、不打扰日常;Key 不在时自动回落 OTP,适合远程接入场景。requisite 与 required 的语义差异。requisite 失败立即返回,不再执行后续;required 即便失败也继续跑完再汇总。失败锁定计数建议用 required 的 pam_faillock,避免把正常回落误判为暴力破解。UsePAM yes 与 ChallengeResponseAuthentication。OpenSSH 服务端须开 PAM 通道,键盘交互式认证才能把自定义提示透传客户端,否则用户只看到口令框。服务器 SSH 改造相对直白,但带显示器终端要走图形登录器。麒麟 V10 默认 gdm,统信 UOS 与部分 Ubuntu 定制版用 lightdm,二者对 PAM 调用点不同,改造易踩坑。
gdm 在 /etc/pam.d/gdm-password 与 /etc/pam.d/gdm-fingerprint 两处分别处理密码与生物特征。若要"Linux 指纹登录",正确做法是在 gdm-password 的 auth 栈里串联指纹仪校验模块,而非另起独立登录器——否则会丢失调制解调器式的会话恢复能力。
# gdm-password:先指纹后口令,二选一即可
auth [success=done default=ignore] pam_print_device.so dev=/dev/fp_touch
auth requisite pam_unix.so try_first_pass
# 会话建立后挂拔 Key 锁屏
session required pam_lock_on_remove.so注意:此处指纹仪为外接触摸式设备,走 /dev 输入节点,校验在本地 PAM 模块内完成,模板不出终端;它与"指纹 USBKey"是两类东西——前者仅采集,后者兼有存储与签名,选型时易混为一谈。
lightdm 多一层 greeter(登录界面)进程,PAM 对话由 greeter 转发。改造时除改 /etc/pam.d/lightdm,还要确认 greeter 支持"自定义提示文案"(如"请触摸指纹仪""请输入动态口令"),否则用户面对黑框不知所措。
# lightdm:国密 OTP + 掌纹仪双因子
auth [success=1 default=ignore] pam_palm_device.so dev=/dev/palm_touch
auth sufficient pam_otp.so
auth requisite pam_unix.so try_first_pass掌纹仪同属外接触摸式设备,适合无接触、卫生要求高的场景(如医疗、食堂终端),在 PAM 层与指纹仪仅设备节点与比对库不同,编排一致。
很多方案双因子只校验登录那一刻,成功后拔 Key 毫无影响——工位离座场景下的巨大安全洞。真正会话级保护要把钩子挂到 session 栈:
session required pam_lock_on_remove.so \
lock_cmd="loginctl lock-sessions" \
poll_interval=2该模块在会话存活期间轮询设备节点,一旦国密 USBKey 消失,立即触发锁屏。对工控、柜台这类"人走机不停"的场景尤其重要;实现上轮询间隔过短占 CPU、过长留空窗,锁屏命令要适配 systemd-logind 与老旧 ConsoleKit,否则老系统拔 Key 不锁屏。

双因子引入后,失败组合变多(Key 没插、OTP 输错、口令拼错),锁定策略必须精细,否则运维会被自己锁死。
# 在 auth 前预检
auth required pam_faillock.so preauth silent deny=5 unlock_time=900
# 在 auth 后记录
account required pam_faillock.so要点:
deny=5 表示连续 5 次失败锁定;SSH 远程接入建议收紧到 3 次,本地带显示器终端可放宽到 10 次,因本地有物理在场性。unlock_time=900 是临时解锁,超时自动放开;同时应保留管理员手动 faillock --reset 的应急通道,避免分支机构无人能救。当终端规模从几台涨到几百台,"逐台改 /etc/pam.d/"就不可持续了。三部署形态在此分化:
部署形态 | 策略来源 | 离线表现 | 适合场景 |
|---|---|---|---|
单机 | 本地配置文件 | 完整可用,OTP 需预生成应急码 | 隔离网、工控、涉密机 |
联网 | 中心平台下发 | 断网时回落到本地缓存策略 | 企业内网、分支机构 |
SaaS | 云端租户策略 | 同样依赖本地缓存,断网不中断 | 跨地域、外包终端 |
通用方案通常把因子组合、失败锁定次数、是否启用拔 Key 锁屏抽象为平台侧策略对象,经代理下发到各终端本地 PAM 配置生成器,使安全团队改一处策略即可让数百台麒麟、统信、CentOS 登录行为同步收敛,避免脚本漂移。
需要强调的是,无论平台多强,终端本地的 PAM 配置必须自洽可离线生效。一旦中心平台不可达就拒绝登录,等于把可用性命门交给网络,这在等保的"韧性"要求上是倒退。平台价值是"集中定义、统一收敛",而非"运行时强依赖"。
"离线双因子"是远程登录安全里最易被忽视的环节。设想分支机构终端与中心平台网络中断,此时:
从攻击面看,离线应急码须足够随机、一次性、绑定具体终端与有效期,纸质码入柜保管并定期轮换。
更进一步,应急码的使用须被审计:哪台终端、哪个账号、何时用应急码登录应记为高亮事件并事后复核,因其天然绕过常规第二因子。运维应建立"应急码使用即告警、七日内复盘"机制,将其视为可控安全事件而非普通登录。
等保 2.0 的"安全审计"项要求记录关键安全事件。双因子改造后,审计字段应至少包含:
审计字段 | 说明 | 价值 |
|---|---|---|
时间戳 | 登录/登出/锁屏时刻 | 行为回溯 |
终端标识 | 主机名/序列号 | 定位资产 |
用户标识 | 登录账号 | 责任界定 |
因子类型 | USBKey/OTP/指纹/掌纹 | 鉴别强度证明 |
因子结果 | 成功/失败及原因 | 攻击画像 |
会话事件 | 拔 Key 锁屏、异常结束 | 离座风险 |
这些日志应同时落本地与上报平台(联网/SaaS 形态),本地留存保证断网不丢证据,平台汇聚便于跨终端关联;日志须防篡改,至少仅追加(append-only)并定期哈希校验,否则可被清除以掩盖横向移动。
在等保测评时,审计字段的"因子类型"与"因子结果"是证明落地双因子的关键证据;若日志只记"登录成功"而不记用何种第二因子成功,测评员很难采信。
实务中常见的几个坑:
sufficient 因子若放在 requisite 口令之前且无约束,可能"随便一个因子成功就放行"。务必明确第二因子与口令是"与"还是"或"关系,并在注释里写清。/etc/pam.d/ 片段。通用方案通常提供"配置预检 + 灰度回滚":先在新 PAM 配置上模拟认证,失败自动回退上一版,降低第 4 类事故概率。该思路具备通用性,自研方案也应保留"配置版本化 + 自动回滚"。
麒麟 V10 与统信 UOS 的 PAM 栈与上游基本一致,但有两点要注意:
pam_sm3_challenge.so 无法加载。CentOS 7 老内核尤需打补丁,部分裁剪镜像甚至缺国密引擎。工控终端登录环境更苛刻:很多工控机跑裁剪版系统,glibc 版本老、缺依赖。改造前应先在镜像里用 ldd 全量扫描所有 PAM 模块的符号依赖,避免现场"登录界面转圈"却查不出原因;同时工控机常无显示器,需确认双因子在纯串口/无界面环境下的回落路径(如仅 USBKey + 应急码)。
理解改造,最好动手写一个最小 auth 模块。PAM 模块核心是实现 pam_sm_authenticate 函数,签名约定固定,返回值用 PAM_SUCCESS / PAM_AUTH_ERR 等宏;编译为 .so 放到 /lib64/security/ 后即可在 /etc/pam.d/ 里引用。
#include <security/pam_modules.h>
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh,
int flags, int argc, const char **argv) {
const char *user = NULL;
pam_get_user(pamh, &user, NULL);
/* 这里接你的第二因子校验,如读取 /dev 设备做比对 */
if (second_factor_ok(user)) return PAM_SUCCESS;
return PAM_AUTH_ERR;
}写模块时务必处理"用户取消""设备不存在"与"超时"三种非成功态并映射到不同返回值,否则上层会把"设备没插"误当"认证失败"而触发锁定,这也是自研方案在真实环境翻车的主因。
双因子常被吐槽"慢",瓶颈主要在:USBKey 的 SM2 签名(几十毫秒,可接受)、OTP 网络校验往返(联网形态建议本地校验兜底)、指纹/掌纹仪采集比对(触摸式通常 < 1 秒)。优化上:把第二因子是否必要做成上下文感知(内网带 Key 免 OTP、公网强制双因子);会话级轮询改用 inotify 而非忙等;登录后会话钩子尽量异步,避免阻塞 greeter 显示。
登录双因子只守住"进门"这一关,但等保同样关注"进门之后提权"的鉴别强度。默认 sudo 只校验调用者口令,攻击者拿到低权账号后仍可凭口令提权到 root。正确做法是在 /etc/pam.d/sudo 的 auth 栈里复用同一套第二因子模块:
# sudo:提权也要第二因子,且与登录策略同源
auth [success=1 default=ignore] pam_otp.so
auth sufficient pam_sm3_challenge.so
auth requisite pam_unix.so try_first_pass这里的关键收益是"策略同源"——sudo 与 sshd、gdm 共用同一份因子定义与失败计数,不会出现"登录要双因子、sudo 却只认口令"的木桶短板。su 同理,建议一并收敛。对于审计而言,sudo 的每次提权也应带上因子类型与结果,便于事后追溯"谁在何时以何种强度拿到了 root"。
此外,运维自动化(如 Ansible、定时脚本)常依赖密钥或口令做非交互提权,强行双因子会打断流水线;应把"服务账号/自动化通道"按来源与账号白名单豁免,而非整体关闭提权保护——这是策略精细度体现,也最易与业务方扯皮。
在把国密双因子接进 Linux/国产 OS 登录链路时,可参考以下通用落地建议与选型要点:
auth、锁屏进 session、失败计数进 account 或 auth 的计数模块,编排顺序直接决定安全性与可用性,建议先用 pamtester 类工具逐栈验证。sufficient/requisite 误用导致口令被绕过。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。