Ubuntu PAM配置错误致sudo失效?从原理到恢复的完整排查指南
前几天给一台Ubuntu服务器做登录加固顺手在/etc/pam.d/common-auth里加了一行双因素认证配置。当时测试是正常的我还以为一切顺利。结果退出SSH重新登录sudo就无论如何都过不去了密码输入得再准确也一样。日志里没有任何密码错误的记录sudoers文件也没问题。那一刻我反应过来问题出在PAM栈上说白了就是自己写错了pam.d文件把本机的sudo权限卡死了。这篇文章就把这次排查和恢复的完整过程写下来。包括PAM文件为什么会影响到sudo、怎么一步步确认故障范围、在无法使用sudo的情况下如何绕进系统修复文件以及修复之后怎么验证、怎么避免下次再翻车。对经常折腾Ubuntu的朋友来说这篇内容实操性很强建议先收藏。1. PAM文件写错为什么直接让sudo罢工1.1 先弄清楚PAM认证链的工作方式PAM的全称是Pluggable Authentication Modules翻译过来就是可插拔认证模块。Linux下几乎所有需要认证的程序包括sudo、su、SSH、图形登录界面、甚至一些FTP服务最终都会调用PAM框架来完成身份校验。PAM的配置集中在/etc/pam.d/一个服务一个文件比如sudo对应的就是/etc/pam.d/sudosshd对应/etc/pam.d/sshd。但真正容易被改动坑死人的是那些公共文件最典型的就是/etc/pam.d/common-auth、common-account、common-password、common-session。Ubuntu的设计里sudo、sshd、login这些服务文件往往不会自己写认证逻辑而是通过include common-auth这样的指令把公共配置引入进来。也就是说你只要在common-auth里写错一行sudo、su、SSH、本机登录可能全部跟着完蛋。PAM的认证语句不是随便写的每一条由“模块类型、控制标记、模块路径、参数”四部分组成。控制标记最容易让人犯迷糊常见的有required这个模块必须返回成功认证才算成功但失败不会立刻中断整个认证链。requisite和required类似但失败会立刻中断后面的模块不再执行。sufficient如果该模块返回成功整个认证链直接通过后续模块可以跳过。optional可有可无一般不左右结果主要用来记录信息。可以用流水线来理解。每个PAM模块就是流水线上的质检员required相当于“必须合格”requisite是“一个不合格立刻停产”sufficient则是“有人合格就整批放行”。sudo调用PAM认证时如果某个模块加载失败、或者在验证密码环节一直失败那整个流水线就会停住表现为“输对密码也进不了sudo”。这里还要注意PAM不只是管“认证”这一件事。配置文件里通常有四类指令auth确认“你是谁”。account确认“你能否使用这个服务”比如账户是否过期、是否被锁定。password修改密码时的校验逻辑。session会话开始和结束时的钩子比如挂载家目录、写入日志。sudo最关心的是auth和account。如果这两类配置写错即使你密码正确、也在sudo组里sudo依然会拒绝执行命令。这篇文章说的“sudo权限丢失”准确讲不是权限被撤销而是认证环节被PAM配置错误卡死了。1.2 我这次事故的“案发现场”当时我想给服务器加一层登录保护于是打算在common-auth里加一行 Google Authenticator 的双因子校验。本意是这样的# /etc/pam.d/common-auth 里的新增行 auth required pam_google_authenticator.so问题是这台机器上根本没有安装libpam-google-authenticator这个软件包用户的验证密钥也没生成。PAM解析到这一行时会尝试加载/lib/x86_64-linux-gnu/security/pam_google_authenticator.so结果模块文件不存在。加载失败后整个认证链被判定为失败所有走PAM认证的服务都废了。这种错误非常隐蔽。因为配置看起来语法没错只是模块不存在。如果不是经常看PAM日志第一次遇到可能完全摸不着头脑。除了模块不存在还有一些更蠢但同样致命的手误比如把pam_unix.so写成pam_unixs.so把required拼成requiered或者删除common-auth时不小心把include common-auth从 sudo 文件里注释掉了。这些属于另一个极端语法直接不合法但给排错留了更明确的线索。1.3 容易写错的几种配置及其后果我把这次事故中遇到过的、以及身边朋友踩过的典型PAM配置错误整理成一张表方便对照错误写法典型现象影响范围auth required pam_unixs.so nullok_secure模块名拼错PAM加载失败认证必定失败common-auth全局所有服务遭殃加了pam_google_authenticator.so但包没装登录后sudo提示再次认证失败common-auth全局或sudo单点auth [success1 defaultignore] ...写错控制语法PAM解析出错服务启动或认证失败直接修改的那个服务文件把include common-auth注释掉sudo没有认证模块任何用户都过不去仅sudo服务chmod 600 /etc/pam.d/common-auth其他进程无读权限认证直接失败全局在common-auth最前面放auth required pam_permit.so所有密码都能过形同裸奔全局且是非常危险的操作这里多说一句pam_permit.so。它只负责“放行”不做任何验证。应急调试时可以临时放在认证链最前面让系统先能进入但只要放到公网服务器上就等于开门迎客。后面我会单独讲怎么用但记住修完之后必须立刻移除。2. 从“密码错误”到定位PAM故障的完整排查链路很多人遇到sudo用不了第一反应是去查/etc/sudoers。但如果你确定用户名在sudo组里、sudoers文件也没被动过那问题大概率不在授权而在认证。下面是我当时从“一无所知”到“锁定PAM”的完整排查过程。2.1 先别急着怀疑密码看看sudo到底想说什么我重新打开一个SSH会话输入正确密码登录成功说明SSH服务的认证链当时还可能正常。但执行任何一个sudo命令比如sudo -v它提示“抱歉请重试”或者Authentication failure。连续试了几次都明确报认证失败。这里要特别注意sudo提示认证失败并不等于密码错误。PAM认证链中任何一个环节失败最终都会以“身份验证失败”的形式呈现在用户面前哪怕模块压根没执行到密码比对那一步。所以看到这个提示先别急着换密码、改sudoers浪费时间的可能性很大。合理的下一步是先清掉sudo缓存再手动触发一次完整认证sudo -k sudo -v-k是让当前用户之前的sudo认证缓存失效强制下一次sudo重新走PAM认证。如果缓存导致误判这一步就能纠正。如果清掉之后依然失败就基本可以排除缓存问题。2.2 用su、SSH、本机终端做个影响面测试排查“权限丢失”类问题最重要的思路是判断影响范围到底只是sudo坏了还是整个系统的认证机制都被破坏我当时依次执行了下面几组测试。先测su -su -如果su也要求输root密码而且同样失败说明异常很可能已经蔓延到common-auth这类全局文件而不是单独的sudo文件出了问题。如果su能成功进入root说明认证链中针对root的某些路径还活着损坏范围相对局部。再测本机SSH登录是否受影响ssh localhost这里有个前提sshd得在运行。如果新开的SSH连接也无法通过认证那说明sshd相关的PAM配置也被拖下水了。最后切到本机TTY看看用CtrlAltF3或者F4切换到字符终端用普通用户登录。如果TTY登录是正常的但sudo一直失败说明系统登录链路和sudo链路之间出现了差异如果TTY也登录不进去说明全局认证栈已经彻底坏了。不同组合情况对应的结论可以用一张表总结sudosu -SSH新连接本机TTY大概率判断失败失败失败失败common-auth/account等全局文件损坏范围最大失败成功成功成功只是sudo的PAM配置损坏失败失败成功成功和TTY/SSH使用的认证栈有差异需要细查具体配置失败成功失败失败涉及login类服务的公共配置有问题我当时遇到的是第一种sudo、su、SSH全部凉了只剩一个已经登录的SSH窗口还活着。这种情况基本确定是common-auth或common-account出了问题必须从正常通道绕进去修。2.3 在/var/log/auth.log里找真正的根因系统其实已经把所有线索写在日志里了只是很少有人第一时间去看。在有root权限的会话里执行grep -i sudo /var/log/auth.log或者看最近几十行tail -n 80 /var/log/auth.log我在日志里看到的关键字是这样的sudo[3121]: pam_unix(sudo:auth): authentication failure; logname... uid... euid... sudo[3121]: PAM unable to dlopen(/lib/x86_64-linux-gnu/security/pam_google_authenticator.so): sudo[3121]: PAM adding faulty module: /lib/x86_64-linux-gnu/security/pam_google_authenticator.so看到unable to dlopen和adding faulty module的时候整个故障原因就非常清晰了。PAM在加载我新加的那个模块时找不到.so文件于是这个模块被标记为“有缺陷”继续执行后导致整个认证链失败。如果日志里出现的是pam_unix(sudo:auth): authentication failure但没有模块加载错误那可能是密码比对本身失败或账户被锁定这时候要检查是不是pam_tally2或pam_faillock这类锁定模块在起作用。如果日志里有permission denied或者类似无法读取配置文件的字眼那就要检查/etc/pam.d下的文件权限是否被改了。排查时用journalctl也可以特别是Ubuntu 16.04之后某些版本默认没有独立的auth.log需要用sudo journalctl -u ssh --since 10 minutes ago多关注unable to dlopen、pam_authenticate、authentication failure这三个特征词。它们分别对应模块加载失败、认证调用失败、密码比对失败三种完全不同的原因。2.4 一张“症状矩阵”帮你快速判断损坏范围结合上面的排查我最后整理了一张快速判断表事后复盘觉得非常有用也分享出来你看到的现象优先怀疑对象下一步动作所有服务认证都失败日志有模块加载错误common-auth文件里新加的模块有问题进recovery或Live环境修common-auth只有sudo失败su仍然正常/etc/pam.d/sudo文件损坏检查和恢复sudo的PAM配置登录时提示账户锁定日志有pam_faillock/tally2锁定策略触发重置faillock/tally2计数偶尔能过、偶尔不能过PAM配置顺序或sufficient/required搞混检查认证链顺序把放行模块放对位置密码正确但sudo拒绝配置中恰好跳过/破坏了pam_unix对用户校验在common-auth里对比默认配置这张表适合在所有修复动作之前先看一眼。范围的判断决定了你接下来是改一个文件还是改一串文件也决定了你能不能靠recovery模式一把梭出来。3. 绕开sudo的恢复路线Recovery Mode与LiveUSB双方案既然sudo已经用不了常规手段就到此为止了。剩下的修复思路是暂时绕开sudo和正常登录以另一种方式进入系统文件系统直接改掉错误的PAM配置。3.1 方案ARecovery Mode里的root shell最方便的方式是重启进入Ubuntu自带的Recovery Mode。它的优势在于不需要外部介质而且进入的root shell通常不依赖PAM登录认证所以哪怕PAM整体挂掉也能进去。具体操作重启服务器或物理机。开机时按住Shift键或者在UEFI启动时快速连点Esc直到出现GRUB菜单。有些机器的GRUB菜单只会显示一两秒反应要快。如果第一遍没抓住重启再来一次多试几次。在GRUB菜单里选择“Advanced options for Ubuntu”。里面会列出当前系统的多个内核版本选带有“(recovery mode)”字样的一项例如Linux 6.8.0-45-generic (recovery mode)。进入Recovery Menu后有一个“root Drop to root shell prompt”选项选中它。进入root shell之后可能会发现文件系统是只读挂载这时直接编辑文件会提示无法保存。先执行mount -o remount,rw /然后再去看/etc/pam.d/common-auth。我在这个阶段直接看到了自己加的那行问题配置使用nano或者vim将其注释掉vim /etc/pam.d/common-auth删除或注释错误行后执行reboot重启系统。如果还有别的问题比如文件权限不对先一并修正。这个方案我在实验环境里验证过很多次80%的PAM误配置都能靠它活过来。唯一的限制是如果故障机器是云服务器且服务商没有提供重启进入Recovery Mode的入口那这个方案就用不了。这时候要转向方案B或者使用云服务商提供的救援模式。3.2 方案BLiveUSB进入Live环境挂载修复LiveUSB是通用性最强、也最稳妥的方案。它能让你在另一个独立的系统环境里直接查看和修改原硬盘上的文件完全绕开故障系统自带的PAM。第一步准备启动盘。在另一台正常的电脑上去Ubuntu官网下载和你原系统版本对应的ISO镜像用Etcher或Rufus写入U盘。U盘容量建议2GB以上。第二步插上U盘开机进入BIOS/UEFI启动菜单。不同机器快捷键不一样一般是F11、F12或Esc。选择从U盘启动。第三步在GRUB引导界面选择“Try Ubuntu或者叫“试用Ubuntu”进入Live桌面或Live命令行。这一步非常关键Live系统本身有自己独立的PAM配置不会受故障系统影响。第四步打开终端查看磁盘分区sudo lsblk假设原系统根分区是/dev/sda2EFI分区是/dev/sda1那么先创建挂载点并挂载根分区sudo mkdir -p /mnt/system sudo mount /dev/sda2 /mnt/system sudo mount /dev/sda1 /mnt/system/boot/efi如果你的服务器用了LVM先sudo vgscan和sudo vgchange -ay激活逻辑卷再用逻辑卷设备路径挂载比如/dev/ubuntu-vg/root。挂载完成后可以直接编辑原系统文件sudo vim /mnt/system/etc/pam.d/common-auth由于Live系统里当前用户就是root权限不需要借助故障系统的sudo修改起来非常顺滑。把错误配置注释掉或改回来保存退出。第五步如果想在chroot环境里执行一些系统级命令比如重新生成默认PAM配置可以这样做for i in /dev /dev/pts /proc /sys /run; do sudo mount --bind $i /mnt/system$i done sudo chroot /mnt/system /bin/bash进入chroot后可以直接跑pam-auth-update --package它会根据libpam-runtime的默认规则重新生成common-auth、common-account这类公共文件。不过要注意这会把你手动的定制内容全部重置回软件包默认状态。如果你只希望修复一行错误还是先手动编辑比较稳妥。第六步卸载并重启。记得不要在挂载状态下拔U盘按顺序卸载exit # 退出chroot sudo umount /mnt/system/boot/efi sudo umount /mnt/system/dev/pts sudo umount /mnt/system/dev sudo umount /mnt/system/proc sudo umount /mnt/system/sys sudo umount /mnt/system/run sudo umount /mnt/system sudo reboot拔掉U盘正常进系统。3.3 修复文件时的三个关键细节在Live环境或Recovery Mode下手动修文件时有几个细节建议一起检查否则可能修了等于没修。第一改文件前留备份。我已经吃过很多次亏现在养成习惯动手前先cp /etc/pam.d/common-auth /etc/pam.d/common-auth.bak.$(date %F)如果修改过程中发现自己改错方向或者想先恢复到某一版再重新修这个备份能救命。第二检查文件权限。PAM配置文件必须让所有需要认证的服务都能读取。一般权限是644属主root。如果你看到的是600或者更严格比如ls -l /etc/pam.d/common-auth输出显示-rw-------那就要赶紧改回来chmod 644 /etc/pam.d/common-auth我见过有人为了“安全”把整个/etc/pam.d目录权限收紧结果所有认证程序全部翻车。第三留意不可变属性。极少数情况下文件可能被chattr i锁住了。保存时报Operation not permitted看起来像是没有root权限其实是被immutable属性挡住了。检查方法lsattr /etc/pam.d/common-auth如果输出里有i就解除锁chattr -i /etc/pam.d/common-auth改完再决定要不要重新加锁。这个坑比较冷门但一旦遇到真的会卡住你很长时间。4. 修复后的验证动作与防止再翻车的几道保险4.1 验证sudo是否真的恢复正常文件改对了不代表立刻就能用。重启后第一次登录建议先做一轮完整验证别直接开干其他操作。验证步骤按顺序来sudo -v如果提示密码正确并缓存了认证说明sudo认证栈的第一关已经通了。然后sudo -i直接进入root交互环境。如果这步成功说明认证和会话两个阶段都正常了。再执行sudo -l看看能否正确列出授权规则。最后回头看一轮日志确认没有新的PAM报错grep -iE pam.*error|unable to dlopen|authentication failure /var/log/auth.log如果没有命中基本可以放心使用。我还建议再开一个新的SSH会话测试一遍因为有些PAM session问题只在全新会话创建时出现。只靠已经活跃的会话验证不一定能暴露session阶段的问题。4.2 重置账户锁定状态faillock与pam_tally2有一个和PAM误配置经常同时出现的问题是账户被锁定。有些人原本在common-auth里加了pam_faillock或pam_tally2密码输错几次后账户就锁定了。表现症状和“sudo权限丢失”非常像都是登录失败但日志会有明显记录pam_faillock(sudo:auth): Consecutive login failures for user ... account temporarily locked这种情况下即使修好了配置文件锁定计数还在用户依然过不去。通过Recovery或Live环境进入root shell后重置方法如下。老版本Ubuntu常用pam_tally2pam_tally2 --user your-username --reset新版本Ubuntu尤其是22.04以后系统默认用pam_faillock执行faillock --user your-username --reset如果不确定用户名可以先执行faillock --user来看锁定列表。重置之后再重新登录测试一次。记住锁定是PAM配置错误的次级灾情修配置的同时一定要检查锁定状态否则会误以为还没有修复。4.3 防止再翻车备份、pam-auth-update与安全底线这次事故之后我把服务器的PAM维护流程彻底改了一遍现在就分享几个实操经验。第一改PAM前先整体备份。不要再只备份单个文件了直接打包整个目录sudo cp -a /etc/pam.d /etc/pam.d.bak.$(date %F)把备份留在同分区复活后可以直接对比变更。也有人会顺手把整个目录提交到本地git仓库后续看diff很方便出问题一条git checkout -- /etc/pam.d/common-auth就能还原这个习惯真的很香。第二能用pam-auth-update就不要手写common-auth。Ubuntu官方其实提供了一个安全的生成工具sudo pam-auth-update它会用多选菜单的方式让你勾选启用的模块然后自动生成规范的配置。以后需要启用指纹、双因子等功能优先考虑用它而不是手动往common-auth里塞字符。它最大的好处是减少语法错误率生成的配置结构也符合当前系统发行版的标准。第三只修改需要的服务文件不要动全局文件。大部分场景下你只需要调整sudo的认证行为那就修改/etc/pam.d/sudo设置单独的认证规则。全局common-auth一旦写坏所有服务一起陪你遭殃。能有针对性地改是最稳妥的。第四永远保留一个已验证的root会话窗口。这是老运维的基本功了。在修改PAM之前先开一个root终端并确认有正常权限修改之后不要急着关掉它留着当“逃生舱”。万一新配置有误旧会话还保持着有效的认证状态可以马上回头改回来。如果旧会话也关掉了那就只能启动LiveUSB了代价完全不同。第五紧急情况下pam_permit.so只能用来临时放行。如果在修复过程中发现系统已经连TTY登录都进不去了可以在common-auth最前面临时加一条auth sufficient pam_permit.so这条规则会让所有用户不做验证直接通过。它只是为了让系统恢复可进入状态但绝不等于修复。成功进入系统后要立刻移除这条规则再处理真正的认证问题。生产环境、公网服务器绝对不要带着它过夜否则等于裸奔。最后再分享一个小技巧每次改完PAM相关文件顺手执行一下sudo -k sudo -v强制重新认证主动触发一次认证链路才有可能暴露潜在问题。别等退出系统之后才验证那个代价就是今天文章里写的这一整套抢救流程。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →