折腾CTF靶场这几年我最喜欢的环节不是初始拿shell那一刻而是从普通用户一路摸到root的过程。很多时候你手里就一个低权限的用户粗糙的命令都跑不了但只要能读到sudo -l的输出整个局面就可能彻底反转。sudo几乎算是CTF里出镜率最高的提权入口没有之一。这篇文章就围绕sudo命令实现权限提升这条路把常见手法、原理、配置细节和踩坑点一次性说透适合刚开始打CTF的选手也适合想回头补一补系统权限管理知识的运维朋友。很多人会觉得sudo不就是“临时借管理员权限跑一条命令”吗有什么可讲的真到了靶场里你才发现一条配置不当的sudo规则就是给你留好的登云梯。与其到处碰运气不如把sudo提权的底层逻辑吃透让它变成你工具箱里最顺手的一把钥匙。1. 认识sudo一把通往最高权限的钥匙1.1 sudo到底在做什么简单来说sudo是Linux/Unix系统里用来临时提升执行权限的命令。默认情况下系统管理员可以通过配置允许指定用户以root或其他用户身份执行某些命令。放在CTF场景里就很好理解了你通过某个漏洞拿到一个低权限webshell下一步通常想读/root/flag或者执行只有root才能跑的指令这时候如果当前用户恰好被sudo配置允许执行某些命令提权就变成了一道现成的程序题。我习惯把sudo机制比作小区的门禁卡。普通用户只有单元门卡只能在公共区域活动sudo是物业发的临时授权卡能进机房、能开设备间但具体能进哪个房间、能操作哪台机器全写在授权表里。CTF出题人往往就是在授权表上做文章故意给你留几个“看起来不太危险”的命令入口实际操作空间却很大。1.2 sudoers规则怎么看授权表就是/etc/sudoers文件正常不会让你直接编辑系统也提供了visudo命令专门改它目的就是防止语法错误把人锁死。规则的通用格式长这样用户名 主机列表(被切换的用户) 标签:命令比如ctf ALL(ALL) NOPASSWD: /usr/bin/vim这句话的意思是用户ctf在任意主机上可以不需要输入密码以任何用户的身份执行/usr/bin/vim。这里的ALL是最常见的通配符第一个ALL是主机名第二个ALL是目标用户。NOPASSWD是标签代表执行sudo时免密CTF里特别常见因为出题人通常不希望你卡在密码上而是希望你关注命令本身能不能做文章。还有一种特殊写法要格外留意就是(ALL, !root)、(root)这类权限范围限定。有些规则看似限制了你能以谁的身份执行实际上可能被某些参数绕过。后边讲CVE的时候会提到。我们看自己的sudo权限最实用的命令就是sudo -l。它会列出当前用户被允许执行的所有命令以及对应规则。如果加了-U参数还可以查看其他用户的sudo权限不过通常没有权限这么做。1.3 为什么CTF里绕不开sudo提权不止一个朋友问过我CTF里提权手段那么多为什么偏要看sudo原因有三点第一sudo配置错误太常见了。靶机作者为了让题目有解往往会在业务脚本里顺手给某用户加一条sudo规则比如“允许运行某个备份脚本”“允许运行某个编辑器”这正好变成了考点。第二sudo的可利用面很大。绝大多数常用命令都可以在root权限下逃逸出shell不需要你懂内核漏洞也不需要写很复杂的exploit一条命令就够。第三sudo提权是很多复杂利用链的基础设施。你可能先用sudo拿到了root shell之后才方便去读敏感文件、改内核模块、甚至横向移动。没有这一步后面的戏全都唱不了。2. 从sudo -l开始找到你脚下那块垫脚石2.1 sudo -l的输出不是天书打靶时我拿到低权限shell的第一件事就是敲sudo -l。很多人不看重这一步以为输出就是几条路径其实里面藏着大量信息。假设你看到这样一个输出User ctf may run the following commands on this host: (root) NOPASSWD: /usr/bin/vim这就意味着你可以用root身份执行vim。看起来好像没什么用但vim本身有执行shell的能力。进入vim之后按下:!sh它会直接起一个以root权限运行的shell。因为命令是sudo启动的进程已经是root而vim只是替你打开了一扇门。更普遍的思路是看到sudo -l列出了某个命令先别急着跑想想这条命令有没有可能交互式执行其他程序或者有没有参数可以直接调用系统命令。凡是能打开shell的工具都有可能成为你的跳板。2.2 命令逃逸到shell的经典姿势我把常见的几条命令和对应的逃逸方式整理成了一个速查表基本覆盖了CTF里最常见的套路命令逃逸方法说明vim:!sh进入vim后冒号模式下执行外部命令less / more输入!sh分页器内部支持执行shell命令findfind / -exec /bin/sh \;用-exec执行任意命令pythonpython -c import os; os.system(/bin/sh)脚本解释器天然能执行系统调用perlperl -e exec /bin/sh;同上awkawk BEGIN {system(/bin/sh)}awk里直接调system函数gitgit help -a后输入!/bin/shgit pager机制也能逃逸envenv /bin/shsudo运行env本身无意义但可以带命令nmap交互模式或--interactive执行!sh老版本nmap适用新版有限制这些逃逸手法的底层逻辑其实都一样sudo以root身份启动了某个程序而这个程序内部又提供了执行外部命令的接口于是你在当前进程里拿到root权限的shell。它不是“爆破”更像“借壳”。2.3 不会ESC这些组合就是你的盾牌光记住几个命令还不够你得掌握怎么判断“这条命令有没有戏”。我提供一个简单的概念性判断流程先看命令类型。编辑器、分页器、脚本解释器、包安装工具、搜索工具这些大概率都有逃逸方式。再看参数面。命令是否支持-exec、--interactive、-c这类执行参数最后看交互面。有些工具需要进入交互模式后才能输入shell指令比如less里的!。如果你对这些命令不够熟就去搜一下“GTFOBins”这是一个专门汇总Unix二进制文件提权方式的公开项目CTF玩家应该人手一份。有意思的是很多选手到了这一步反而不习惯“一条命令直接拿root”的感觉总觉得是不是漏了什么。实际上这就是sudo提权的魅力出题人想考的就是你对命令细节的熟悉程度。所以平时多练习多尝试不同的参数组合才会越来越有感觉。3. 环境变量与动态库隐藏的提权后门3.1 sudo保留的环境变量机制有时候sudo -l里列出的命令看起来平平无奇比如只有/usr/bin/find但你还是可以提权。原因不在命令本身而在环境变量配置。默认情况下访问控制会把环境变量重置避免恶意变量污染高权限进程但如果在sudoers里配置了SETENV标签或者某些变量被env_keep保留情况就完全不同了。你可以把环境变量理解成进程的“随身行李”。sudo在过安检时默认把大部分行李扣下清空成一套安全的默认值但有些特殊情况下出题人允许你带着自己的行李过安检甚至允许在行李里夹层藏了动态库。这个时候就能利用动态库加载机制做文章了。3.2 LD_PRELOAD劫持提权原理与实操Linux有一个环境变量叫LD_PRELOAD它可以让一个动态库被提前加载到进程里抢先执行其中的构造函数。如果我们能够通过sudo以root身份运行任何一条命令同时sudo又允许保留LD_PRELOAD环境变量那么加载一个我们自己写好的恶意动态库就能让root进程替我们执行任意代码。这个过程我拿一个经典例子分解一下。先写一个C文件比如shell.c#include stdio.h #include stdlib.h #include sys/types.h void _init() { unsetenv(LD_PRELOAD); setresgid(0,0,0); setresuid(0,0,0); system(/bin/sh); }然后编译成动态库gcc -fPIC -shared -o shell.so shell.c -nostartfiles最后在sudo执行命令时手动指定这个动态库sudo LD_PRELOAD./shell.so find / -type f如果你看到find真的以root权限执行了同时动态库里的_init函数也被调用你就会直接掉进一个root shell。为什么能生效因为sudo在高权限状态下启动进程时加载器先加载了LD_PRELOAD指定的库构造函数抢先执行而后才运行find这个程序。我们借的是加载机制跟find本身无关。当然有个前提必须反复强调sudo规则里得允许设置环境变量比如Defaults env_keepLD_PRELOAD或者规则里带有SETENV标签。如果默认清空环境变量这招就是无效的。所以我在实际打靶时会刻意检查sudo -l的输出里是否出现SETENV字样有的话就先想到LD_PRELOAD。3.3 PATH劫持与脚本类命令的玩法另一个利用环境变量的思路是PATH劫持。假设sudo保证你可以运行某个系统脚本而脚本内部又调用了外部命令比如#!/bin/bash ls -la /root/data如果这个脚本没有使用绝对路径调用ls而我们又可以通过sudo设置PATH环境变量就可以制造一个假的ls放到当前目录里面写上/bin/sh再把PATH改成优先搜索当前目录。这样sudo运行脚本时找到的ls实际是我们的恶意脚本用户虽然原本没权限执行任意命令但通过脚本间接达到了目的。不过PATH劫持在CTF里成功率往往没有LD_PRELOAD那么高因为sudo默认对PATH做了严格限制常常会重置成系统安全路径。所以第七感告诉我看到脚本提权时先看脚本能不能写再看环境变量能不能控制最后才考虑路径劫持。顺序反了会浪费不少时间。4. 真实场景中的组合拳从普通用户到root4.1 一个模拟靶场里的完整流程理论说再多不如走一遍流程。这里我虚构一个某常见模拟靶场里反复出现的套路用来展示sudo提权的组合打法。场景是这样的通过Web漏洞拿到的webshell。反弹回来之后当前用户是dummy家目录里有一个user.txt但没有读取/root/flag的权限。老规矩先执行sudo -l得到如下输出User dummy may run the following commands on this host: (root) NOPASSWD: /usr/bin/python3 /opt/config.py这里能执行python3但是限定了只能执行/opt/config.py这个脚本。乍一看好像把代码固定死了但脚本内容往往有可乘之机。我赶紧看/opt/config.py果然它只是做了配置解析支持一个--include参数用到了Python的某个模块加载。这不就是“间接命令执行”吗如果你能在脚本的运行路径下放置一个有特定文件名的恶意模块或者通过参数影响搜索路径最后就能让python3以root身份加载我们的恶意代码。类似地常见的还有sudo限定只能执行某个备份脚本但脚本里有tar、zip等命令调用同样可以借机绕过。我在这个模拟场景里最后是通过构造恶意模块文件再利用sudo运行/usr/bin/python3 /opt/config.py成功以root身份执行了反弹shell命令直接拿到root权限并读取了flag。整个过程只用了不到十分钟关键就在于充分利用了配置文件对模块路径的控制。4.2 版本漏洞带来的意外惊喜除了配置层面的滥用sudo自身的历史漏洞也是CTF里完全绕不开的考点。其中比较有名的两个我建议你把原理记牢。第一个是CVE-2019-14287。这个漏洞本质上是对sudo用户ID处理不当。当sudoers规则里写的是(ALL, !root)这种排除root的授权时攻击者如果指定用户ID为-1即4294967295的无符号整数sudo会在某些版本里错误地把它当成root来处理从而绕过“不允许root身份执行”的限制。利用方式很简单sudo -u#-1 id或者sudo -u#4294967295 id只要规则中存在排除root的写法就能以root身份运行命令。这个洞在好几年前很火直到现在一些靶机还喜欢把它当彩蛋。第二个是CVE-2021-3156代号叫Baron Samedit。它是sudo堆缓冲区溢出漏洞影响了很多年前的多个版本利用方式比前者复杂但效果直接可以完全绕过配置限制获得root shell。CTF里遇到老旧系统时如果找不到任何配置层面的提权点我就会去查sudo版本然后对应排查漏洞。这些漏洞的共性在于它们不是配置错误而是程序本身的逻辑缺陷。但从攻击者视角看它们和配置错误是同样的资产都属于“拿来就能用”的提权路径。不过我要多说一句了解漏洞是为了防护和打靶千万不要拿到真实生产环境里去乱试。4.3 提权的防御建议与自查清单自己搭靶场或者做系统加固时我会按下面这套清单过一遍能挡掉大部分sudo提权问题坚持最小权限原则。不该给sudo的普通用户一条命令都不给。可执行命令列表里尽量避免vim、less、python、find这类能逃逸到shell的工具。如果业务必须用到某些命令能用绝对路径就用绝对路径并在规则里限定参数减少灵活性。避免滥用NOPASSWD关键字每一条免密规则都是风险点。保留环境变量重置机制不要轻易配置SETENV或env_keep。定期更新sudo版本至少关注安全公告别让系统腐烂在远古版本上。定期让运维同事跑一遍sudo -l检查每个用户的实际权限面。这套清单是我自己每次配置完系统后会立刻执行的“安全检查”虽然简单但很多入侵者撞到这些点都会被卡住。5. 常见坑位与排错手册5.1 为什么提权经常翻车sudo提权看似简单实战时踩坑的几率却不低。我粗略总结过大部分人翻车集中在下面几个地方一是sudo -l的信息没有被充分解读。输出里明明写了SETENV你没看到明明写的是(ALL) NOPASSWD你却在纠结密码。二是场景不对。LD_PRELOAD并不是所有sudo配置下都能用很多人拿着这个技巧硬套结果失败。三是对shell版本和工具版本不敏感。比如老版本的nmap有交互模式新版本早就砍掉了这个功能拿老命令去新环境当然不灵。5.2 一条条对号入座的排查技巧我把自己在靶场里踩过、甚至在某些真实系统加固时复盘过的典型问题整理成了一张排错表看起来会更直观症状可能原因排查与处理sudo -l提示找不到命令当前用户没有sudo权限或命令路径不在PATH里先确认用户是否在sudo组再看/etc/sudoers语法提示必须输入密码但不知道密码规则中没有NOPASSWD或者sudo缓存已过期回看信息收集阶段拿到的密码哈希尝试密码复用LD_PRELOAD提权没生效环境变量被重置或规则不允许保留该变量检查sudoers里的env_keep和SETENV标签逃逸命令执行后没反应有些程序进入的是受限交互模式尝试换一种逃逸方式或查看/bin/sh是否被替换成受限shell拿到了shell但操作异常进程权限可能还是普通用户或目标系统限制了shell立刻执行id确认uid必要时用exec bash替换当前进程规则里写了/usr/bin/find却跑不了sudoers可能限制了精确参数比如只允许特定路径查看完整规则不要只看命令名排错的核心思路永远是先确认权限面再确认环境变量再确认版本和交互方式。不要在一个点上死磕换一条路径往往更快。另外一个细节sudo本身会记录执行日志到/var/log/auth.log不同发行版路径略有差异有时候日志里的格式能帮你反推规则细节。靶场里这不是你该操心的但做防御视角自查时这些日志是判断是否有异常sudo调用的重要依据。5.3 我个人最想提醒你的三件事第一不要把提权限定在“必须拿到root shell”这一个目标里。很多时候只要你能通过sudo读取某个敏感文件甚至执行一条特定管理命令任务就已经完成了。比如直接sudo cat /root/flag.txt虽然不算“获得shell”但在CTF里你已经得分了。第二多看sudo -l里的细节。NOPASSWD、SETENV、(root)、(ALL:ALL)这些关键词直接决定走哪条路。用习惯之后我会下意识地先把输出复制到本地高亮这些关键词再动手。第三别忘了sudo其实还有“临时切换用户”的作用。如果你能sudo到某个服务账户而这个服务账户本身又有特殊权限那也算一种提权路径。有时候横向比纵向更好用视野打开一点思路就不会被“只能变root”限制死。说实话玩了这么多年CTFsudo提权是我觉得性价比最高的内容之一。它不像内核漏洞那样高门槛也不像密码爆破那样看运气它纯粹考验你对系统配置和命令细节的理解。把这套东西吃透你在靶场里的操作会顺畅很多同时对真实系统的权限管理风险也会有更具体的感知。最后再唠叨一句所有技巧都是在授权范围内做安全测试时才值得练习。别拿到生产的机器去试也别把靶场里的经验变成灰色的习惯。守住这条线技术这条路才走得更稳、更安心。