身边不止一个运维朋友,GRUB 设了密码就觉得服务器稳了。直到有同事拿 U 盘启动个 Live 系统,挂载硬盘改 root 密码,前后不到五分钟。值班的兄弟甚至不知道对方怎么进去的。

物理接触下的防线到底能挡住什么、挡不住什么,得先理清楚。GRUB 菜单加密码这事本身没问题, 生成加密串,塞进 /etc/grub.d/40_custom,指定 superusers,再跑 。一套下来,重启后按 e 改内核参数会弹密码框,进命令行模式也得先过认证。它能防住的就是这类通过 GRUB 界面直接修改启动流程的路径——比如单用户模式重置 root 密码、传递 rd.break 进 dracut shell、或者加 init=/bin/bash 跳过 init 进程。前提是:攻击者必须通过你的 GRUB 菜单启动。

一旦对方有物理接触——拆机、拔硬盘、插 U 盘——GRUB 密码就彻底失效。Live USB 启动的系统不读你硬盘上的 GRUB 配置,它自带一段全新引导程序。挂载 /dev/sda2/mnt,chroot 进去,passwd 一敲,密码就改了。别说 root,你想改哪个用户的都行。那个真实案例的运维后来复盘:他给 CentOS 7 设了 GRUB 密码,BIOS 没锁,Secure Boot 也没开。同事出差回来发现系统进不去(服务异常),直接拿自己的笔记本+U盘,重启进 PE 改密码,完事还不忘发个消息:“帮你修好了。” 全程没绕过 GRUB,人家根本就没用 GRUB。

要彻底堵住物理接触下的篡改,得叠三层:BIOS/UEFI 密码锁住启动设备顺序,禁止从 USB/CDROM 引导;Secure Boot + TPM 绑定对启动链上每个组件签名验证;LUKS 全盘加密让硬盘即使被拆走也解不开数据。三层都上确实麻烦——每次重启要多输一次 BIOS 密码、一次 LUKS 口令。但如果你的资料盘里存的是付费用户才该看见的内容,那这一步不能省。GRUB 密码只防“路过的好奇心”,防不了“带了螺丝刀的人”。

很多人做到第一步就觉得安全了,抱歉,这只是第一层。我们要做的是把“看得见的入口”也藏起来——让未付费的用户根本看不到那行菜单,连尝试输入密码的机会都没有。GRUB 的 password 指令能拦住编辑启动参数的人,但它不会替你隐藏菜单项。如果你直接把付费条目明晃晃地列在开机选择里,哪怕旁边标着“需密码”,也会劝退一大半潜在买家——谁都知道这里面有东西,只是进不去而已。更麻烦的是,有些发行版的默认超时策略会让这行菜单永远停在屏幕上,等于免费打广告。

所以我们换个思路:把整个资料盘做成一个独立镜像(ISO 或 raw disk image),挂载为 loop 设备。GRUB 可以通过 loopback 和 linux16 / initrd16 直接从这个文件启动内核与 initramfs,不需要先暴露给用户任何分区信息。未付费时,菜单里只显示“系统修复工具”这类无害选项;付费后,客户拿到一串触发词,在特定时机按下某个键组合,才会弹出真正的入口。

具体怎么藏?答案在 GRUB 的 insmod--hotkey 上。我们可以写一段这样的 custom 配置:

menuentry '系统修复 (按 R 进入高级模式)' --hotkey=r {
  insmod terminal_input
  insmod extcmd
  set pager=1
  echo "按 r 继续,其他键跳过..."
  terminal_input --interactive
  if [ "$grub_exit" = "r" ]; then
    menuentry '【付费资料盘】' --users admin {
      linux16 /boot/paid.iso loopback.loop0 root=/dev/loop0
      initrd16 /boot/paid.iso.initrd
    }
  fi
}

这段代码做了几件事:默认只显示一条看似普通的修复选项;只有当用户按下 R 时,才会动态加载第二条子菜单——“【付费资料盘】”。而这条子菜单本身又被 --users admin 保护着,即使有人猜到它存在,没有正确用户名/密码依然无法选中。至于那个 ISO 文件,你可以用 ddgenisoimage 打包成一个不可修改的只读镜像,里面包含完整的 Linux 环境 + 自动挂载脚本。这样做的最大好处是:普通访客重启多少次都不会发现异常,他们看到的永远是“系统修复”。只有付费用户知道要在开机瞬间精准按下 R 才能触发隐藏入口。这种“肉眼不可见”的体验,比单纯加个密码更有仪式感,也更容易让人产生“我是不是错过了什么重要东西”的心理暗示。

所有花哨功能都得建立在稳定的基础上。我在一台 CentOS 7 测试机上曾因为漏装 grub2-tools 导致 报错退出,结果整个 /boot/grub2/grub.cfg 被清空,开机直接掉进 rescue shell。还有一次是在 Anolis OS 上忘了关闭 SELinux,导致 GRUB 无法读取自定义模块,日志里全是 avc: denied。这些问题单独看都不大,但在凌晨三点接到客户电话时足够让你冷汗直流。所以我的建议是:每次修改完 40_custom 后,务必手动执行 (CentOS/RHEL)或 update-grub(Debian/Ubuntu),并且立刻重启验证行为是否符合预期。如果你用了 dracut 生成 initramfs,记得也在其内部注入同样的 loopback 支持,否则 ISO 镜像可能挂在半路认不出根文件系统。这些细节不会写在任何官方文档的显眼位置,只能靠自己一次次踩坑总结出来。

说到底,这套方案的本质不是技术有多深奥,而是利用了人类对“未知”的好奇心。你以为自己在卖一份 Linux 学习资料,其实卖的是一种“只有少数人知道的秘密通道”。而这恰恰是社群运营最擅长的事情——制造稀缺感,然后静待愿意付费的人自己找上门来。

前面说了那么多怎么藏菜单、怎么设密码,但如果你亲自动手配过 GRUB,大概率迟早会遇到一个比密码输错更绝望的场景:重启后屏幕一黑,然后弹出一个干巴巴的 dracut:/# 提示符。我第一次碰到这玩意是在一台 CentOS 7 测试机上。当时刚改完 /etc/default/grub 里的 ,加了几个内核参数想支持 LUKS 解密,接着 跑完,自信满满敲下 reboot。结果呢?系统直接掉进 dracut emergency mode,根分区挂不上,所有数据像被封在玻璃柜里——你看得见,但它不给你用。

排查过程并不优雅。我先试着 ,空的。说明 LUKS 根本没被解密。又查 dmesg | grep crypt,输出里一行关于 dm-crypt 的日志都没有。这才反应过来:新编译的 initramfs 里压根没打包 crypt module。你问我怎么确认的?拆开 initramfs 看一眼就行:

mkdir /tmp/init_check && cd /tmp/init_check
xzcat /boot/initramfs-$(uname -r).img | cpio -idmv
ls -l usr/lib/modules/$(uname -r)/kernel/crypto/

输出里 dm-crypt.ko 根本没出现。那模块都不在,内核拿什么去解密?

解决方法不复杂,但有一个坑很多人会漏掉。单纯的 dracut -f 并不会自动补齐所有模块,dracut 默认只加载它认为当前系统启动所需的驱动。如果根分区在 LUKS 上,但 /etc/crypttab 里配置有误或压根不存在,dracut 就会认为“这个系统不需要 crypt”。你得显式告诉它:哥们,带上加密模块。

dracut --force --add crypt "crypt"

这条命令会强制将 crypt 模块打包进 initramfs。但我还栽过另一个跟头:跑完后重启,依然进 emergency mode。这次 dmesg 里倒是看到 crypt 了,却报错说找不到密钥设备。一查 /etc/crypttab,里边的 UUID 少写了一位——我复制粘贴时漏了最后一个字符。养成好习惯:每次改完 /etc/crypttab 后先 blkid 手动验证一遍,别直接重启。这招能省下至少半小时的抢救时间。

dracut 紧急模式本身是一个单人可用的 shell,拥有 root 权限。如果你只给 GRUB 菜单加了密码,却忘了限制进入 emergency target,那攻击者根本不用猜你的 superusers 密码——他只需要在 GRUB 命令行里敲一行 systemd.unit=emergency.target,就能绕过所有菜单密码直达 root shell。要堵这个口子,得在 /etc/grub.d/40_custom 里加上一个密码保护的 emergency 启动项,并把 --unrestricted 从默认项上拿掉。像这样:

menuentry 'Emergency Mode (protected)' --users admin {
    linux /vmlinuz-$(uname -r) root=/dev/mapper/root ro systemd.unit=emergency.target
    initrd /initramfs-$(uname -r).img
}

同时确保默认的 rescue 选项也被同样的 --users 锁住。别以为没人会从 rescue 切入——在物理接触环境下,这就是最快拿到磁盘内容的方式。至于 BIOS/UEFI 密码和 Secure Boot,那是另一层防线。GRUB 密码只能拦住键盘前的人,拦不住拿螺丝刀拆硬盘挂到另一台机器上读数据的人。如果你这套“付费资料盘”里真的有敏感内容(比如你自己的 SSH 密钥或客户端备案信息),建议你至少做到:UEFI 设密码,禁止从 USB 启动,并且用 LUKS 对整个根分区加密。只有这三层都到位了,你藏在 GRUB 菜单后面的那个付费入口才算真正安全。

那次翻车后,凌晨改 GRUB 然后直接重启这种事,我是真不敢再干了。要么先在虚拟机里走一遍流程,要么老老实实准备串口控制台——万一挂了,还能摸进去救场。毕竟靠“稀缺感”吃饭的人,自己先别变成那个稀缺的翻车案例。

只给 GRUB 设个密码?说实话,遇到手快的人,启动时狂按 e 就能直接绕过去。真正想让物理接触者也死心,防线得拉到磁盘和硬件那一层。GRUB 菜单能防住改内核参数、拦下单用户模式,但它压根不负责加密磁盘本身。对方把硬盘拔走,挂到另一台机器上读——你的“付费资料盘”基本等于裸奔。用 LUKS 把根分区全盘加密之后,离线状态下读出来的全是一堆密文块,看着就踏实。装系统时选“加密整个系统”最省事;要是系统已经装好,也能用 (设备名千万核对清楚)先初始化,再把原有分区迁进去。随机盐和迭代次数记得留足,不然输完密码等开机的那几十秒,足够你怀疑人生。

cryptsetup luksAddKey --key-file - /dev/sdaX
# 输入现有口令后,会把新口令写入同一扇区

/etc/crypttab 里别漏了 blkid 引用,否则重启后映射可能起不来;dracut 也要重新生成 initramfs,让早期用户态能拿到 unlock key。LUKS 虽强,但每次都输长密码很烦人。把解锁钥匙交给 TPM,意味着这盘只能在原机硬件上解密——搬走也没用。常见做法是用 tpm2-tools 把 LUKS 的 master key 密封到 PCR0..7,或者直接用 systemd-cryptsetup 的 TPM2 绑定。好处很明显:开箱即用;代价同样明显:一旦主板或 TPM 芯片更换,你得现场重置,没有“万能恢复码”。如果你卖的是社群涨粉资料盘,这种“只能在我这台机子上打开”的特性,反而能把二次转售的路堵死。

tpm2_createprimary -C e -c primary.ctx
tpm2_evictcontrol -c primary.ctx 0x81010001
tpm2_loadexternal -C primary.ctx -G rsa2048 -u key.pub -r key.priv
tpm2_encryptdecrypt -I key.priv -o wrapped.key

有了 LUKS 和 TPM,别忘了启动链条里的薄弱环节还在:救援模式、紧急 shell、内核参数注入。把默认 rescue 改成受 GRUB 密码保护的 menuentry,并去掉 --unrestricted;必要时禁用 GRUB 命令行(set superusers=admin,只在需要的 menuentry 上加上 --users admin)。如果还不放心,可以在 dracut 生成的 initramfs 里启用 rd.neednet,让网络也成为解锁前置条件——没有网线,即使插 Live USB 也别想轻易落脚。整套流程下来,从“只设 GRUB 密码”到“LUKS+GRUB+TPM 三合一”,确实要多花几个小时;但当你半夜收到机房告警,却发现机器只是被拔掉电源而不是被人顺走数据时,你会明白这些时间花得值。