
先直接说结论DEF CON CTF 这类比赛里我最喜欢的就是这种“表面人畜无害、实际上全是权限细节”的题。“Sudo Make Me a Sandwich”从标题看是个段子 xkcd 那个经典漫画里一个人喊“sudo make me a sandwich”另一个人回“make it yourself”你自己做然后第一人打出“sudo”对方只能无奈地说“Fine”。但命题人把这个玩笑搬进了真实的 Linux 权限体系里你确实被允许用 sudo 执行一条命令而且看起来这条命令什么危险事都干不了。可最后你能不能从这条命令出发一路打通到 root、读到 /root/flag.txt就完全取决于你对“权限边界”和“特权执行链”这两个概念的理解深度了。这篇文章是我对这道题的一次完整复盘适合三类人读。第一类是正在打 CTF 的选手尤其是卡在 Linux 提权环节的新人里面每一步都有可直接复现的命令第二类是日常跟 sudo、Linux 权限打交道的运维和研发因为题里暴露的配置问题在真实服务器上同样存在第三类是安全爱好者想搞清楚为什么“一条 sudo 授权”能被拆成一条完整的攻击链。我会从侦察讲起拆解 sudoers 里的每个细节再逐步演示两条提权跳板的完整打法最后聊聊我踩过的坑和从攻击视角反推出来的防御清单。1. 先把这道题的来龙去脉说清楚1.1 一句玩笑话背后的真实考点很多不熟悉 CTF 的人会觉得这类题目要么是打二进制漏洞要么是挖 Web 漏洞而这题名字里带个“sudo”听起来像个运维题。其实它考的是 Linux 权限体系里最核心的一层sudo 并不是“让你以 root 身份跑一条命令”那么简单而是一套完整的授权边界。你被允许执行什么命令、以什么身份执行、通过什么环境去执行这些“边界条件”里任何一处没守住都可能演变成一条通往 root 的特权执行链。题目里那道 sudo 规则给的是NOPASSWD也就是说不需要密码就能执行这本身就很可疑。真实的渗透测试或 CTF 提权里sudo -l是第一步必查的命令因为只要管理员在 sudoers 里配了一个“能执行代码”的程序比如编辑器、解释器、make、find、less 这些基本上就能拿到对应身份的 shell。这题的高明之处在于它没有被配置成直接允许sudo /bin/bash这种一眼看穿的规则而是给了一个看似固定的构建命令让新手觉得“我只能执行这一条应该没得搞”。但实际上攻击者真正需要的是一个“执行引擎”而不是一个“明显的高权限命令”。只要这条 sudo 规则背后的程序具备执行外部命令、读取额外文件、被环境变量影响的能力权限边界就已经裂开了。这道题第二个层面的考点是“特权链”。很多人拿到一个低权限 shell 后就只会到处找 SUID 文件找不到就懵了。而这题明确展示了提权不一定一步到位完全可以是先拿下中间用户再通过定时任务、可写脚本、错误配置的组权限逐步爬上 root。这种多跳的思维方式恰恰是真实内网渗透里最常用的。你在一个 50 台机器的内网里往往不会一上来就打穿域控而是先拿一台跳板机再横向、再纵向。CTF 题目把一个完整思路封装进了一个容器里从这个角度看“Sudo Make Me a Sandwich”本质上是一门浓缩的权限边界课程。1.2 从标题拆解命题者的设计意图把题目名拆开看三个词都值得琢磨。“Sudo”指向的是 Linux 里最常用的提权入口也意味着这道题的所有漏洞都围绕 sudo 的授权机制展开。“Make Me”表面上是在说“帮我做”实际暗指make这个构建工具——在 sudo 规则里允许你执行的就是/usr/bin/make。至于“a Sandwich三明治”命题人借它指代一个“交付物”对应到题目里就是你需要让 make 命令帮你产出一个文件或者说帮你执行一段构建流程。换句话说你想得到一个“三明治”但 sudo 规则决定了你能用哪些原料和工具来做攻击者的目标就是往原料里掺毒。我从标题里读到的命题逻辑是这样的第一步让参赛者通过sudo -l发现一条 make 授权第二步让参赛者意识到 make 在解析 Makefile 的过程中会执行 shell 命令而且受环境变量影响第三步利用环境变量残留例如 MAKEFILES在固定 Makefile 的解析阶段注入恶意指令突破“只允许固定命令”的边界第四步拿到中间身份后再利用定时任务脚本的写权限完成最后一级提权。整道题下来几乎把 Linux 权限边界上最常见的问题点都覆盖了一遍授权粒度、环境变量、解析执行顺序、定时任务权限、SUID 位。这种“从权限边界到特权执行链”的设计在真实世界里每天都在发生。很多公司内部会有类似的“运维脚本越权”问题管理员为了省事在 sudoers 里直接授权了某个脚本或某个工具觉得“反正别人只能执行这个”结果攻击者只需要利用环境变量、符号链接、配置文件注入就把执行权扩大到了任意命令。所以虽然题目是一个 CTF 关卡但如果你只把它当成游戏来打就浪费了命题人的良苦用心。2. 开局从登录靶机到摸清权限边界2.1 拿到靶机后的第一轮侦察逻辑上这道题会给你一个低权限账号的 SSH 登录凭证或者直接提供一个容器环境你以ctf用户登录进去flag 在/root/flag.txt普通用户读不了。拿到 shell 之后我习惯先做一轮最基础的侦察命令不多但每一条都有明确目的。id whoami uname -a cat /etc/os-release pwd env sudo -lid和whoami是确认当前身份的别看这两个命令简单很多人在提权过程中打着打着忘了自己是谁误以为某个文件属于自己结果白折腾半天。uname -a和/etc/os-release用来确认内核版本和发行版信息这决定了后面能不能套用一些内核漏洞或者特定包管理器的手法。env非常关键后面我踩过一个特别隐蔽的坑就跟环境变量有关这里先记一笔sudo 的env_reset和env_keep配置会决定你的哪些环境变量能带进高权限进程这往往是整道题的突破口。sudo -l则是整个侦察环节的重头戏它的作用是列出当前用户被允许执行的 sudo 命令。在实际操作中我不会只是把这些命令的输出扫一眼而是会把它们记录到本地笔记里。尤其是sudo -l的输出必须逐字逐句地读。很多 CTF 选手拿到 shell 后第一件事是急着找 SUID 文件忽略了sudo -l结果把最简单的路径绕过去了。反过来真实环境里也可能因为太信任sudo -l忽略了 sudo 本身版本漏洞比如之前爆的 Baron Samedit那是另一个故事这题不需要。2.2 sudo -l 结果里藏着整道题的线索在这道题里sudo -l的输出大致是这个样子Matching Defaults entries for ctf on challenge: env_reset, env_keepDISPLAY HOME LANG PATH MAKEFILES mail_badpass User ctf may run the following commands on challenge: (sandwich) NOPASSWD: /usr/bin/make -f /opt/sandwich/Makefile这段输出要拆成两块看。第一块是 “Matching Defaults entries”也就是当前用户生效的 sudo 默认配置。env_reset说明 sudo 会重置环境变量但env_keepDISPLAY HOME LANG PATH MAKEFILES又列出了五个例外其中MAKEFILES这个变量立刻引起了我的注意。如果你不是经常写 GNU Makefile可能对这个变量不熟它告诉 make 在解析普通 Makefile 之前额外读取哪些文件。一个普通用户能控制自己 shell 里的环境变量如果这个变量能传入 sudo 启动的 make 进程那“额外读取哪些文件”就变成了“攻击者决定”。第二块是实际的授权命令你可以以sandwich这个用户身份免密执行/usr/bin/make -f /opt/sandwich/Makefile。注意这里有两个关键信息。第一命令带了-f /opt/sandwich/Makefile说明你不能随便sudo make让 make 默认去找当前目录的 Makefile你必须使用固定的这个文件看起来攻击面被锁死了。第二sudo 的-u sandwich意味着 make 进程将以 sandwich 用户身份运行不是 root。很多新手看到这里会直接放弃觉得“我最多也就变成 sandwich又不是 root有什么意义”这个想法正好落入了命题人的陷阱——权限链是可以一步一步往上爬的。2.3 侦察阶段容易漏掉的信息点回到第一轮侦察除了sudo -l我还会额外确认几个容易被忽略的点。首先是/etc/sudoers本身能不能读有时候配置里会直接存在敏感注释或者额外规则虽然一般情况下只有 root 能读但值得试一下cat /etc/sudoers和ls -la /etc/sudoers.d/。其次是/opt/sandwich/这个目录的权限我执行ls -la /opt/sandwich/确认 Makefile 是否可读可写。结果显示 Makefile 只有 root 可写但可读于是我把它的内容拉出来看了一遍。SHELL : /bin/bash CC : gcc TARGET : /tmp/sandwich .PHONY: all clean all: $(CC) -o $(TARGET) main.c chmod 755 $(TARGET) clean: rm -f $(TARGET)我盯着这个 Makefile 看了一会儿发现了值得追问的细节。第一CC : gcc表示在解析时就把 gcc 固定下来但 make 在执行 recipe 时仍然会通过 PATH 去查找gcc这个外部命令第二目标文件是/tmp/sandwich而/tmp是任何人都能写的目录这让我对执行链条有了想法第三整个构建过程是在 make 进程启动后、以 sandwich 用户身份执行的。看起来这条 sudo 规则限制很死固定 make、固定 Makefile、固定目标用户。但如果MAKEFILES环境变量真的能传入 make 进程那么不管-f指定了哪个 Makefile我都可以让 make 在解析阶段先去读一个我自己写的文件这个文件里的内容会在固定 Makefile 生效之前就被处理。侦察阶段最后一步我确认了/tmp目录可写也确认了当前用户 shell 环境里可以自由设置环境变量。到这里整道题的攻击面已经清楚了授权命令是 make环境变量残留是 MAKEFILES中间身份是 sandwich最终目标要靠下一步继续摸。可以说侦察这一步做得越细后面打起来越顺很多选手卡关不是因为技术不行而是因为开局就把输出里的关键信息漏掉了。3. 权限边界分析sudo 配置里的魔鬼细节3.1 sudoers 语法的正确打开方式既然这题的核心是 sudo那就必须先弄懂 sudoers 文件里的每一处配置到底在控制什么。sudoers 的基本语法是一条条的授权规则格式大致是用户 主机(可切换到的身份) 标签: 允许执行的命令例如ctf ALL(sandwich) NOPASSWD: /usr/bin/make -f /opt/sandwich/Makefile的意思就是用户 ctf 在任意主机上可以切换到 sandwich 用户身份不需要输入密码执行/usr/bin/make -f /opt/sandwich/Makefile这条命令。这里的“主机”填的是ALL表示不限制来源主机这在 CTF 容器里很常见但在真实环境里是个隐患——如果一台跳板机被攻破等于所有授权命令都暴露了。更危险的是命令参数。sudoers 里如果命令后面跟了参数sudo 会做“精确匹配”。也就是说你只能原封不动地执行/usr/bin/make -f /opt/sandwich/Makefile不能加别的参数也不能少参数。这条限制本身是合理的很多管理员也认为“我限制了参数命令就只能干我允许的事”。但这道题恰恰想说明参数限制了命令的“外壳”限制不了命令的“内核”。make 这个程序在执行时会读取 Makefile会解析其中的变量、函数、shell 命令会接受环境变量的影响。你堵死了命令行的参数入口但环境变量、文件内容、当前工作目录这些侧信道仍然敞开着。3.2 这道题暴露的三处边界裂缝复盘的时候我把这题里的权限边界裂缝总结成了三处每一处单独看都不致命但串起来就是完整的提权链。第一处裂缝是“授权了一个代码执行引擎”。/usr/bin/make本质上是把 Makefile 里的规则翻译成 shell 命令去执行。任何人都能写出一个 Makefile规则里只写一行/bin/bash然后让 make 以目标用户身份执行。所以只要 sudo 规则允许你执行 make而你又能让 make 读到你自己控制的规则文件那就等价于允许你执行任意命令。GTFOBins 上记录得非常清楚在特定条件下sudo make可以直接给你一个 root shell。现实中很多管理员只禁止了 vim、python、perl 这类“看起来能弹 shell”的程序却忽略了 make 也是同等的执行引擎。第二处裂缝是环境变量残留。env_keep... MAKEFILES等于告诉 sudo不要清掉 MAKEFILES把它原样带进高权限进程。环境变量本应是调用方和被调用方之间传递参数的一种方式但在安全上它经常成为注入点。这就像你让外卖小哥帮你送一个密封好的盒子结果你还在盒子的提手上拴了一根绳绳子的另一端握在陌生人手里陌生人一拉你盒子里的东西就变了。MAKEFILES 就是那根绳子。第三处裂缝是固定 Makefile 里使用了外部命令而未用绝对路径例如gcc、chmod、rm。如果环境里的 PATH 也被保留输出里确实有 PATH攻击者就可以在 PATH 里优先放入自己的目录伪造一个名叫gcc的恶意脚本让 make 在“干活”的时候不知不觉地执行你的代码。这道题我用 MAKEFILES 做了主攻PATH 劫持属于备用路线。3.3 make 在提权语境里为什么如此危险很多人不理解make 不就是个“自动化构建工具”吗它有什么危险的关键在于 make 的解析和执行机制。第一make 支持$(shell ...)函数这个函数在 Makefile 被解析的时候就会执行而且是在 make 的权限下执行的不需要等你运行某个 target。第二make 支持include和-include指令可以读取其他文件里的规则配合MAKEFILES环境变量你根本不需要修改被授权的那份固定 Makefile。第三make 在执行 recipe 时每一条规则都相当于一个 shell 脚本片段你可以直接在规则里塞入任意 shell 命令。用一个生活化的类比sudo 授权 make就像你雇了一个厨师并且告诉厨师“你只准按照我写好的菜单做菜”。但厨师有个习惯进厨房前会先翻一翻门口的便签本看有没有别人留给他的额外提醒。你现在没法改菜单但你可以往便签本上写“端菜之前先给顾客一刀”。厨师不知道便签本上的内容是谁写的它只是照做。MAKEFILES 就是这个便签本make 就是那个厨师$(shell) 就是你写的那句话。至于“固定参数”的限制它防得住你去改 make 的命令行但防不住 make 自己去读文件、去执行规则。命令参数是攻击面的“开关”而 makefile 内容是攻击面的“水龙头”管理员只关了开关忘了关水龙头水照样流得满屋子都是。理解到这一步权限边界就已经不是“你能执行什么命令”的问题了而是“你控制的什么数据能进入高权限进程的处理流程”。4. 特权执行链从“做一个三明治”到读取 flag4.1 第一跳MAKEFILES 注入拿下 sandwich 用户整道题最关键的一步就是把 MAKEFILES 环境变量变成一个恶意 Makefile 的入口。我先在/tmp下创建了恶意文件内容是让 make 在解析阶段执行一段 shell 命令复制一份 /bin/bash给它加上 SUID 位放到 /tmp/sandwichsh。这一步的目的是留下一个后门 shell后续随时可以用它切换到 sandwich 身份不需要反复去触发 sudo。cat /tmp/evil.mk EOF $(shell cp /bin/bash /tmp/sandwichsh chmod 4755 /tmp/sandwichsh) EOF export MAKEFILES/tmp/evil.mk sudo -u sandwich /usr/bin/make -f /opt/sandwich/Makefile运行完这条 sudo 命令之后我以为 make 会正常去构建那个“三明治”但实际发生的是make 启动后第一时间读到了 MAKEFILES 指向的/tmp/evil.mk在解析这个文件的过程中执行了$(shell ...)里的命令。于是/bin/bash被复制成了/tmp/sandwichsh并加上了 SUID 位。随后 make 才继续去读/opt/sandwich/Makefile构建那个真正的三明治程序。整个过程看起来只是一次普通的构建但攻击载荷已经在解析阶段落地了。/tmp/sandwichsh -p id # output: uid1000(ctf) gid1000(ctf) euid1001(sandwich) egid1001(sandwich)执行/tmp/sandwichsh -p后我获得了 euid 为 sandwich 的 shell。这里必须解释一下-p参数的作用一个 SUID 的 bash 在启动时默认会把有效用户 ID 重置为真实用户 ID也就是说如果你直接运行/tmp/sandwichsh它虽然文件上带了 SUID但 bash 会因为“安全考量”而把自己降权回 ctf。只有用了-p参数bash 才会保留 SUID 赋予的有效用户 ID。很多新手做到这一步会误以为 SUID 没生效其实是忘了加-p。为什么chmod 4755能成功因为 make 是以 sandwich 用户的身份运行的/tmp/sandwichsh这个文件的属主就变成了 sandwich而 ctf 用户执行这个 SUID 文件后获得的就是 sandwich 的有效用户 ID。这一步属于“权限边界”的第一次突破我虽然还是 ctf但我已经能以 sandwich 的身份做任何 sandwich 能做的事了。4.2 第二跳借 root 定时任务补完最后一段提权链拿到 sandwich 身份的 shell 之后我开始以这个身份继续侦察。首先确认 sandwich 有没有额外的 sudo 权限执行sudo -l发现没有有价值的授权。接着我检查了系统的定时任务因为定时任务经常以 root 身份周期性地执行某些脚本而脚本的写权限可能并没有被收干净。ls -la /etc/cron.d/ /etc/crontab cat /etc/cron.d/order输出里出现了一个名为 order 的定时任务内容大意是* * * * * root /usr/local/share/app/update.sh这个任务每分钟执行一次/usr/local/share/app/update.sh而且是以 root 身份。我立刻检查了这个脚本和它所在目录的权限ls -la /usr/local/share/app/update.sh ls -la /usr/local/share/app/脚本的属主是 root但所属组是 sandwich权限是-rwxrwx---。也就是说sandwich 组的成员可以写这个脚本。这又是一个典型的边界裂缝定时任务本身是 root 的“特权执行通道”但脚本文件的写权限却向一个低权限组开放了。攻击者完全没有必要去破解 root 密码只需要在这个脚本里写一段自己的命令等它被 root 执行即可。我替换了脚本内容让它创建一个 root 所有的 SUID bashcat /usr/local/share/app/update.sh EOF #!/bin/bash cp /bin/bash /tmp/rootbash chmod 4755 /tmp/rootbash EOF chmod x /usr/local/share/app/update.sh等待最多一分钟定时任务触发root 账户以脚本权限执行了这段命令。于是/tmp/rootbash出现了它属于 root 且带 SUID 位。我执行/tmp/rootbash -p id # output: uid1000(ctf) gid1000(ctf) euid0(root) egid0(root) cat /root/flag.txt到这里整条特权执行链完成了。从 ctf 到 sandwich再到 root中间没有使用任何内核漏洞也没有密码破解纯粹靠的是三处权限边界没有守好sudo 授权了执行引擎、环境变量残留、定时任务脚本可被低权限组写入。如果我一开始就只盯着“怎么直接变成 root”可能就卡死了因为第一跳只给了我 sandwich。但把视角放宽把“提权”理解成“沿着任意一条可控的高权限执行路径往上爬”突破口就出现了。4.3 整条攻击链的时间线与转化关系复盘的时候把每一步的输入、输出、身份变化整理成表格会非常清晰步骤当前身份利用的缺陷获得结果侦察ctf读取 sudo -l 与配置文件发现 make 授权与 MAKEFILES 残留第一跳ctf → sandwichMAKEFILES 环境变量注入make 解析期执行 $(shell)sandwich 身份的 SUID shell第二跳sandwich → rootroot 定时任务脚本可被 sandwich 组写入root 身份的 SUID shell收尾root读取受限文件/root/flag.txt这张表的意义在于它展示了“权限边界”和“特权执行链”的关系。权限边界是你和更高权限之间的那道闸门一个边界裂缝就是一个未闭合的闸口而特权执行链则是从你当前位置出发逐个通过这些闸口的过程。如果每个闸口都闭合了提权就无从谈起。但这题里第一道闸口的“门锁”虽然看起来很结实固定命令、固定参数门框却是软的环境变量第二道闸口干脆连锁都没上定时任务脚本可写。从防御的角度回看这张表你会发现所有问题都出在“配置细节”上sudo 规则没有收敛到绝对必要的最小集合、环境变量没有彻底清理、定时任务脚本放在了一个低权限组可写的目录里。这三个问题在真实的生产服务器里几乎每天都在发生。5. 实战踩坑与 sudo 高频问题速查5.1 “sudo 需要 tty”是怎么一回事在复盘这道题之外我想把日常跟 sudo 打交道时最常遇到的几个问题也一并梳理一下毕竟这些“坑”在 CTF 环境里同样会突然冒出来而且比题目本身更容易让人手足无措。第一个高频问题是这条报错sudo: sorry, you must have a tty to run sudo这个错误出现的原因是 sudoers 里配置了Defaults requiretty意思是 sudo 只允许在有终端tty的情况下执行。设计这个选项的初衷据说是为了防止某些通过 web 请求、cron 脚本等方式滥用 sudo 的场景但它在现代实践中常常弊大于利你在 CI 流水线、自动化脚本、远程执行环境里都会因为没有 tty 而直接碰壁。解决思路有几种。如果你是管理员并且确认requiretty给自己带来的只是麻烦而不是防护可以执行visudo把这一行注释掉或者改成Defaults !requiretty。如果你只是普通用户临时要在一个没有 tty 的会话里执行 sudo可以借助ssh -t分配伪终端或者用script -qec sudo ...包一层。这里面有个容易混淆的点sudo -S解决的是“密码从 stdin 输入”的问题它并不会帮你创造一个 tty所以别指望它能绕过 requiretty。我见过不少人在这上面浪费时间。5.2 没有 sudo 权限的机器上怎么编译 GitHub 项目这道题里 make 是提权工具但在日常开发里make 更多时候是无权用户编译源码的“老朋友”。我经常遇到这样的场景你在一台共享服务器上手头没有 sudo却要从 GitHub 克隆一个 C 项目、编译、使用它。默认情况下make install会把文件装到/usr/local/bin这一步必然会因为权限不足而失败。正确的姿势是使用用户级安装前缀。大多数用 autotools 构建的项目支持./configure --prefix$HOME/.local然后用make和make install把程序装到自己的家目录git clone https://github.com/example/some-tool.git cd some-tool ./configure --prefix$HOME/.local make -j$(nproc) make install装完之后记得把$HOME/.local/bin加入 PATH把$HOME/.local/lib或$HOME/.local/lib/pkgconfig加入库搜索路径和 pkg-config 搜索路径否则程序启动时找不到共享库。这一步我踩过很多次尤其是只配了 PATH、忘了LD_LIBRARY_PATH的情况程序能启动但瞬间报error while loading shared libraries。对于不依赖 configure 的纯 Makefile 项目也可以直接读 README看它有没有友好的变量支持例如make install PREFIX$HOME/.local。5.3 几个常见的 sudo 操作翻车现场除了 tty 和编译安装我再把几个经常在社区里被反复提问的 sudo 场景集中列出来。这些排查思路不深但关键时刻能省半小时。sudo apt install jmeter报Unable to locate package jmeter通常不是你输错了包名而是当前发行版默认仓库里根本没有这个二进制包。JMeter 在 Debian/Ubuntu 的官方源里并不总是直接提供尤其是精简的容器镜像。处理办法是先把源更新一遍再搜sudo apt update apt-cache search jmeter如果还是没有就直接去 Apache JMeter 官网下载二进制压缩包解压到本地运行没必要跟 apt 死磕。sudo rosdep init报ERROR: cannot download default sources list from: https://...是 ROS 环境里很经典的问题。这个命令要做的其实是拉取一份默认的 rosdep 源列表文件放到/etc/ros/rosdep/sources.list.d/20-default.list。报错的原因通常很简单无法访问该 URL。这时候先确认网络连通性例如curl -I那个地址看看是不是能通如果用了企业代理确认http_proxy、https_proxy环境变量已经正确设置。如果网络实在受限也可以手动写文件自己创建/etc/ros/rosdep/sources.list.d/20-default.list把 rosdistro 官方文档里那行yaml https://.../20-default.list的内容放进去再执行sudo rosdep update。这个思路本质上就是“让工具不需要下载直接提供本地文件”。sudo systemctl restart ssh报Failed to restart ssh.service: Unit not found多半是服务名不对。在 Debian/Ubuntu 的 OpenSSH 服务通常叫sshd.service而部分发行版或者某些教程里习惯写成ssh。所以先systemctl list-units | grep ssh实际看一眼再操作。如果服务名没问题却报Access denied那就和 systemd 的权限策略有关需要检查改服务当前用户是否有权限通过 systemd 管理该单元这类问题在非 root 用户的 sudo 链路上经常表现为“你明明有 sudo但仍然被拒”。现象常见原因排查方向sudo: must have a ttysudoers 启用了 requiretty修改 Defaults !requiretty 或分配伪终端make install 失败默认安装前缀需要 root使用 --prefix$HOME/.localUnable to locate package软件源里没有该包apt update、启用对应源或用官方二进制包rosdep init 下载失败无法访问源列表 URL检查连通性、代理配置或手动写入列表文件systemctl restart ssh 失败服务名错误或策略拒绝查单元名确认 polkit/systemd 权限5.4 提权链排查的快速检查清单回到 CTF 场景如果你拿到一个低权限 shell 但一时间不知道从哪下手我建议按照下面这个顺序快速过一遍它能帮你找到绝大多数常规突破口sudo -l find / -perm -4000 -type f 2/dev/null ls -la /etc/cron* /etc/at* /etc/systemd/system/ find / -path /proc -prune -o -type f -name *.sh -perm -0022 -exec ls -la {} \; 2/dev/null env | sortsudo -l看授权find -perm -4000看 SUID/etc/cron*和 systemd 目录看有没有低权限用户可写的定时任务最后env看环境变量里有没有可疑的注入点MAKEFILES、LD_PRELOAD、PATH 等。这套清单不仅适用于 CTF也适用于授权范围内的渗透测试。要特别提醒一句以上所有命令都只在你自己拥有授权的环境里使用。把这套东西拿到不属于自己的系统上去试性质就完全不同了这是安全从业者的基本底线。6. 复盘之后从攻击链反推出来的防御清单6.1 最小权限原则不是口号是配置细节打完这道题我最深的体会是最小权限原则不是一句挂在安全文档里的口号而是可以拆成一条条具体配置细节的实践准则。在 sudo 授权上它至少意味着三层约束。第一层能授权命令就不要授权解释器或执行引擎。make、vim、python、perl、less、find 这些程序在输入可控的前提下都具备执行任意命令的能力。在 sudoers 里给它们开白名单就相当于给执行引擎开了绿灯。管理员应该先问自己一个问题这个命令在执行时用户能控制它的哪些输入如果用户能控制文件内容、环境变量、配置文件、工作目录中的任何一个那么这个授权本质上就是“允许执行任意命令”。第二层能加参数限制也要警惕参数之外的注入面。固定参数只是限制了“从命令行传什么”并没有限制命令自己“会去读什么”。所以对 make 这类工具而言光写NOPASSWD: /usr/bin/make -f /opt/sandwich/Makefile是不够的你还要确保环境变量被清理干净确保 Makefile 所在目录不可被非授权用户写入确保它引用的外部命令都是绝对路径确保目标文件的产物目录也不可控。第三层能切换身份就要评估中间身份的价值。这题里-u sandwich看似把提权截断了但 sandwich 用户依然拥有可写的定时任务脚本。所以sudo 规则里允许切换到的那个身份本身也要纳入最小权限的评估范围。一个“看起来没什么用”的中间用户可能恰好通过某个文件权限变成通往 root 的跳板。6.2 一条可落地的 sudo 配置审计清单复盘不能只停留在思想上我把常用的审计命令整理成了一份可以照做的清单建议每隔一段时间在服务器上跑一遍。第一检查当前有效 sudo 规则。用sudo -l或直接看/etc/sudoers和/etc/sudoers.d/下的所有文件把每条授权命令与 GTFOBins 对照一遍看看有没有“sudo 可执行引擎”的危险组合。判断标准很朴素如果这条命令能读文件并能执行命令假设攻击者拿到了你的账号他能通过这条命令做什么第二检查环境变量处理方式。执行sudo env看看哪些变量被保留进了高权限进程。重点关注 LD_PRELOAD、LD_LIBRARY_PATH、PATH、PYTHONPATH、MAKEFILES 这类会影响程序行为的变量。原则是能不留就不留。配置上明确Defaults env_reset按需设置Defaults secure_path/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin很多时候 secure_path 一行就能封死 PATH 劫持。第三检查定时任务和其他特权执行路径。find /etc/cron* -type f -exec ls -la {} 跑一遍看有没有 root 所有的任务引用了一个低权限用户可写的脚本。脚本本身必须 root 所有、root 组、且权限不高于 755所在目录也要检查组权限避免出现“脚本是 root 的但目录是其他用户可写”的尴尬情况。systemd 服务里同样存在这类问题systemctl list-unit-files和find /etc/systemd/system -type f -exec ls -la {} 值得一起查。第四检查 SUID 和文件系统权限。find / -perm -4000 -type f 2/dev/null列出现有的 SUID 文件逐个确认它们是否必要不必要的 SUID 位应尽快去掉。对于/tmp这类所有人都能写的目录不要在里面放置受信任的脚本或可执行文件也不要让高权限进程去执行或加载/tmp里的任何东西。6.3 一点我的个人体会这道题打完之后我在笔记里写了一段话CTF 最好玩的地方不是它比真实世界更酷而是它把真实世界里那些零散的、不起眼的配置问题浓缩成了一条可见的因果链。你在生产服务器上可能永远不会同时遇到 MAKEFILES 残留和定时任务脚本可写但这两个问题却各自真实地存在于无数系统里。正因为它们平时不凑在一起很多人才会忽略其中的任何一个。而 CTF 把它们串起来强迫你从攻击者的视角把整条路走一遍你才会切身体会到“一个环境变量也能成为突破口”这件事不是危言耸听。我个人在实际操作中还有一个习惯每次打完一道提权题都会把“攻击路径”和“防御对策”两列并排写下来。攻击路径是“MAKEFILES 注入 → make 解析期执行 → SUID 后门 → 定时任务脚本 → root”防御对策就是这条路径上每一个节点对应的封堵动作清理环境变量、不授权执行引擎、定时任务脚本权限最小化。这样下次做别的题或者审视真实系统时我脑海里就有一个“路径检查表”遇到新系统先按图索骥比临时想快很多。最后再分享一个容易被忽略的小技巧CTF 复盘时不要只记录“怎么成功的”也要记录“我怎么卡住的”。我最初在这个题里卡了大概二十分钟误以为固定 Makefile 就是铁板一块直到我偶然注意到env_keep里那个不起眼的 MAKEFILES才猛然意识到环境变量才是真正的钥匙。这种“被卡住的瞬间”恰恰是最宝贵的经验来源记录下来它会在你下一次面对类似难题时成为灵感的跳板。