在运维工作中,一台暴露在公网或者多人能接触到的CentOS物理服务器,最大的安全漏洞往往不是复杂的网络攻击,而是物理接触或带外管理(IPMI/KVM)导致的单用户模式提权。只要能在GRUB启动菜单按“e”进入编辑模式,在内核启动行末尾加上“single”或“init=/bin/bash”,攻击者就能在几秒内拿到root权限。这种操作完全绕过了操作系统层面的密码验证,让所有基于用户密码的安全策略瞬间失效。要堵死这条路,必须给GRUB引导加载程序设置独立的加密密码,确保任何试图修改启动参数的行为都需要先通过验证。

GRUB密码保护的底层逻辑

CentOS 7及之后版本默认使用GRUB2作为引导器,其配置文件不再是传统的/boot/grub/menu.lst,而是/boot/grub2/grub.cfg。这个文件由grub2-mkconfig命令根据/etc/default/grub模板和/etc/grub.d/目录下的脚本自动生成。因此,设置密码的正确姿势不是直接去改grub.cfg,而是在/etc/grub.d/下的规则脚本中嵌入认证指令,这样即便内核升级触发grub2-mkconfig重新生成配置,密码保护依然会保留。

GRUB2的密码保护分为两种粒度:一种是保护所有引导条目,另一种是只保护特定菜单项。对于防止单用户模式入侵的场景,必须保护所有引导条目,或者至少保护救援模式与普通内核条目。GRUB2支持明文密码和PBKDF2加密密码,生产环境绝对禁止使用明文密码,因为grub.cfg文件默认权限是644,任何能登录系统的用户都能读到明文密码。必须使用grub2-mkpasswd-pbkdf2命令生成密文,这样即便配置文件泄露,攻击者也无法逆向出原始密码。

生成加密密码的详细步骤

以root身份登录CentOS系统后,首先执行以下命令生成PBKDF2加密字符串:

grub2-mkpasswd-pbkdf2

系统会提示输入密码并确认,然后输出一段以“grub.pbkdf2.sha512.”开头的长字符串。这段字符串包含了哈希算法标识、迭代次数、盐值和密文,是GRUB2用来验证密码的唯一凭证。务必把这段完整字符串复制下来,包括“grub.pbkdf2.”前缀,后续配置中需要完整粘贴。建议在本地加密笔记中备份这个哈希值,但不要与服务器密码混用,GRUB密码应独立设置。

配置GRUB全局密码保护

编辑/etc/grub.d/01_users文件,这个文件专门用于定义GRUB2的超级用户和密码。如果该文件不存在则手动创建,内容如下:

#!/bin/sh -e
cat <<EOF
set superusers="root"
password_pbkdf2 root 粘贴刚才生成的PBKDF2哈希字符串
EOF

这里“root”是GRUB2中的超级用户名,可以自定义为其他字符串,比如“admin”或“grubadmin”,但必须记住这个用户名,因为重启后在GRUB界面输入密码时需要同时提供用户名和密码。保存文件后赋予执行权限:

chmod +x /etc/grub.d/01_users

接下来还需要修改/etc/grub.d/10_linux文件,这个脚本负责生成普通内核引导条目。默认情况下生成的菜单项没有密码保护,需要在生成menuentry时加上“--users”参数。找到文件中所有以“menuentry”开头的行,在“--class”参数前插入“--users superusers”,修改后的典型行如下:

menuentry 'CentOS Linux' --users superusers --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-4.18.0-348.el8.x86_64-advanced-xxx' {

注意“--unrestricted”参数表示该条目在未认证状态下可见但不可执行,而“--users superusers”则强制要求只有superusers列表中的用户通过密码验证后才能启动该条目。如果希望某些条目完全隐藏而非仅锁定,可以将“--unrestricted”替换为“--users ""”,但这样会让菜单项在认证前不可见,可能影响正常使用,因此生产环境建议保留“--unrestricted”配合“--users”使用。

针对CentOS 7和CentOS 8的差异处理

CentOS 7的GRUB2版本较老,/etc/grub.d/10_linux文件中menuentry生成逻辑相对简单,直接按上述方法修改即可。CentOS 8及后续的Stream版本中,10_linux脚本结构更复杂,menuentry是通过变量拼接生成的,直接修改脚本容易出错。更稳妥的做法是在/etc/default/grub中添加全局变量来控制密码行为。编辑/etc/default/grub,在文件末尾添加:

GRUB2_PASSWORD=粘贴PBKDF2哈希字符串

但这种方式在较新版本中已逐渐被废弃,官方推荐的做法仍然是修改/etc/grub.d/下的脚本。对于CentOS 8,建议直接编辑/boot/grub2/grub.cfg文件顶部添加超级用户定义,然后通过chmod将grub.cfg权限改为600,防止非root用户读取。虽然直接改grub.cfg会在下次grub2-mkconfig时被覆盖,但可以通过定时任务或配置管理工具确保修改持久化。更工程化的做法是写一个Ansible playbook或shell脚本,在每次内核更新后自动重新应用密码配置。

重新生成GRUB配置并验证

修改完上述文件后,执行以下命令重新生成主配置文件:

grub2-mkconfig -o /boot/grub2/grub.cfg

对于使用UEFI引导的系统,输出路径可能是/boot/efi/EFI/centos/grub.cfg,需要根据实际分区情况调整。命令执行成功后,用grep命令检查生成的grub.cfg中是否包含password_pbkdf2字样:

grep password_pbkdf2 /boot/grub2/grub.cfg

如果返回了包含哈希字符串的行,说明密码配置已生效。此时还需要检查每个menuentry段落中是否都带有“--users”参数,确保没有遗漏任何引导条目。特别要注意救援模式条目,它通常在/etc/grub.d/10_linux中生成,也可能在独立的/etc/grub.d/10_rescue脚本中,务必一并加上用户限制。

重启测试与应急回滚方案

配置完成后必须重启服务器进行验证,这是整个操作中最关键也最容易出问题的环节。在重启前务必确保有服务器的带外管理权限(如IPMI、iDRAC、iLO)或者云主机的VNC控制台访问权限,因为一旦GRUB密码配置错误导致无法正常引导,物理接触或远程控制台是唯一的恢复途径。重启后在GRUB菜单界面按“e”键尝试编辑启动参数,系统应弹出用户名和密码输入提示。输入之前设置的GRUB超级用户名和密码,验证能否成功进入编辑模式。

如果密码验证失败或忘记密码,恢复流程如下:通过IPMI挂载CentOS安装ISO镜像,选择“Troubleshooting”进入救援模式,挂载原系统的/boot分区和根分区,然后chroot进去删除/etc/grub.d/01_users文件并将/etc/grub.d/10_linux恢复原状,重新执行grub2-mkconfig。这也是为什么物理服务器必须配置带外管理卡并设置独立强密码的原因,否则GRUB密码反而可能成为锁死自己的双刃剑。

加固单用户模式的第二道防线

GRUB密码保护虽然能阻止攻击者通过编辑启动参数进入单用户模式,但安全防护应该遵循纵深防御原则。即使GRUB密码被突破或者因配置失误失效,操作系统层面的单用户模式仍然可以设置额外障碍。编辑/etc/systemd/system/rescue.service.d/override.conf文件(目录不存在则创建),添加以下内容:

[Service]
ExecStartPre=-/bin/sh -c "/sbin/sulogin; /usr/bin/systemctl --no-block rescue.target"

这会让系统在进入救援模式或单用户模式前强制调用sulogin命令,要求输入root密码。sulogin是Linux系统中专门用于单用户模式密码验证的工具,它读取/etc/shadow中的root密码哈希进行验证。这样即便攻击者绕过了GRUB进入了单用户模式,仍然需要面对root密码这一关。同时建议在/etc/sysconfig/init文件中设置SINGLE=/sbin/sulogin(CentOS 7)或在/etc/sysconfig/init中确认PROMPT=no,防止单用户模式直接给出shell。

BIOS/UEFI密码与启动顺序锁定

GRUB密码只能保护引导加载程序本身,如果攻击者能通过U盘启动或修改BIOS启动顺序,仍然可以绕过整个GRUB保护链。因此服务器必须设置BIOS或UEFI固件密码,并锁定启动顺序,禁止从USB、光驱等外部介质启动。在固件设置中将硬盘设为第一启动项,关闭PXE网络启动(除非有专门的PXE安全策略),并设置固件管理密码。对于云服务器,这些由云平台底层保障,但裸金属服务器和自建机房服务器必须手动配置。

此外,/boot分区如果单独挂载,建议在/etc/fstab中为其添加nosuid、nodev、noexec挂载选项,虽然这会略微影响内核更新时的脚本执行,但能有效防止/boot分区被植入恶意内核模块。对于安全要求极高的环境,还可以考虑使用UEFI Secure Boot功能,它通过签名验证机制确保只有受信任的引导加载程序和内核能被启动,从固件层面彻底杜绝未授权修改。

自动化运维场景下的密码管理

在大规模服务器集群中,手动为每台机器配置GRUB密码显然不现实。可以通过配置管理工具如Ansible、Puppet或SaltStack来批量部署。以Ansible为例,核心思路是预先生成统一的PBKDF2哈希(或为每台机器生成独立哈希),通过模板渲染/etc/grub.d/01_users文件,然后执行grub2-mkconfig命令。需要注意的是,PBKDF2哈希中包含了随机盐值,因此同一密码每次生成的哈希字符串不同,这并不影响验证,但意味着不能通过比对哈希字符串来确认密码一致性。

对于使用kickstart自动化安装的场景,可以在%post段直接写入GRUB密码配置,确保系统从安装完成的那一刻起就处于保护状态。示例kickstart %post脚本片段:

%post
cat > /etc/grub.d/01_users <<'EOF'
#!/bin/sh -e
cat <<INNEREOF
set superusers="opsadmin"
password_pbkdf2 opsadmin grub.pbkdf2.sha512.10000.XXXXXXX...
INNEREOF
EOF
chmod +x /etc/grub.d/01_users
grub2-mkconfig -o /boot/grub2/grub.cfg
%end

这种预置方式避免了首次启动后到配置管理工具生效之间的安全真空期,是构建安全基线的重要一环。

常见问题排查与误区澄清

配置完成后如果重启发现GRUB菜单直接消失,通常是因为/etc/grub.d/01_users文件格式错误导致grub2-mkconfig执行中断。检查该文件是否以“#!/bin/sh -e”开头,cat命令的EOF定界符是否正确配对。另一个常见问题是修改了/etc/default/grub中的GRUB_CMDLINE_LINUX变量后忘记重新生成配置,导致密码配置被旧版grub.cfg覆盖。

不少运维人员误以为给root账户设置强密码就足够安全,这是对单用户模式工作原理的严重误解。单用户模式启动时,内核直接执行/sbin/sulogin或/bin/bash,完全跳过了login认证流程,root密码再复杂也无济于事。还有人认为禁用Ctrl+Alt+Del重启或者设置屏幕锁就能防止物理攻击,这些措施在能够物理接触电源按钮和硬件复位跳线的攻击者面前形同虚设。GRUB密码是防范这类物理接触攻击的唯一有效技术手段。

最后需要强调的是,GRUB密码与全盘加密(如LUKS)配合使用才能构成完整的物理安全防护。单独设置GRUB密码只能防止启动参数篡改,无法阻止攻击者拆卸硬盘后挂载到其他系统读取数据。如果数据敏感性极高,务必在系统安装时启用LUKS加密根分区,这样即使硬盘被物理窃取,没有LUKS解密密钥也无法读取任何数据。GRUB密码保护的是系统完整性,LUKS保护的是数据机密性,两者是互补关系而非替代关系。