☰
sudo提权原理与实战:从权限配置到渗透测试的完整指南
2026/10/10 7:03:10 网站建设 项目流程

先别急着打flag,把sudo读透再提权

CTF里最容易让人上头的瞬间,就是把sudo -l的黑框输出拉到屏幕上那一刻。权限提升,尤其是基于sudo命令的提权,几乎是每台Linux靶机绕不开的关卡。我见过太多新手在拿到一个www-data的shell后,盯着sudo -l的输出发愣,不知道该挑哪条命令下手,最后只能拖着一条不能读flag的权限反复撞墙。这篇文章把sudo提权从原理到实战拆开讲:怎么看配置、怎么选路径、怎么避免自己踩坑,同时也会站在出题人的角度解释这类题为什么存在。适合正在刷CTF的入门玩家,也适合刚接触内网渗透评估的同学参考,内容全部基于授权靶机和模拟环境中的常见设计。

sudo本身不是什么神奇东西,它只是一扇门。门开多大、给谁开、开多久,都写在/etc/sudoers里。CTF的出题人通常不会把门锁死,而是故意留一条缝,让玩家去发现“这条命令虽然不能随便跑,但可以借助它拿到一个root shell”。问题在于,这个“缝”有很多种形态,有的藏在命令参数里,有的藏在外挂动态库里,有的甚至藏在文件名的通配符里。下面这几节,我会把最常见的几种形态逐个说透,每一步都配上能直接拿来复现的操作。

1. 为什么sudo提权在CTF里总是避不开

1.1 sudo的职责边界

先把这个工具放回原位理解。sudo是Linux下授权普通用户执行特权命令的机制,它的核心逻辑就是:系统管理员在/etc/sudoers里定义“谁能用什么身份执行什么命令”,普通用户敲下sudo后,系统会按照既定规则去匹配这条请求,匹配成功就放行,失败就拒绝。

规则颗粒度可以很细。比如可以只允许某个用户执行/usr/bin/systemctl restart xxx,不允许执行/usr/bin/systemctl stop xxx;可以要求输入当前用户密码,也可以设置NOPASSWD全部免密;甚至可以把某个命令的某些参数固定死,比如只允许以root身份执行特定的备份脚本。这些规则的设计原本都是为了隔离权限、降低风险,但在CTF题目里,这些细颗粒度配置恰好成了提权的表演舞台。

从本质上看,sudo提权题考的是两件事:第一,你能不能读对sudoers规则的语法和含义;第二,你能不能找到规则所允许的命令与“无法无天的shell”之间的桥梁。只要桥搭对了,root就是你的了。

1.2 提权链路中的sudo定位

在常见的CTF提权链路里,一级权限通常是从Web漏洞拿到的,常见的是www-data这样的低权限账户。这个账户没有sudo规则配置,它想读/root/flag、想读写系统敏感文件,处处受挫。此时就需要找到一条路径切换到另一个具备权限的账户,或者直接提到root。

sudo在这条链路里的位置非常微妙。很多低权限账户的配置其实不直接可见,/etc/sudoers里允许哪个用户执行哪些命令,往往才是真正的突破口。一旦你发现当前用户被允许以root身份执行某个程序,就相当于拿到了一个“最小特权”的窗口。接下来要做的事,就是利用这个程序自身的功能,把它从一个黑盒工具变成一个能够执行shell命令的通道。这也是我坚持在拿到shell之后第一件事就跑sudo -l的原因:它往往能直接展示出题人预设的所有“善意”,也就是你最需要的信息。

2. 开局第一件事:把sudo -l读透

2.1 权限列表的解读套路

许多初学朋友输完sudo -l,看到一大堆英文输出就慌了。其实输出的结构很固定,记牢几个关键词就行。核心是当前用户被允许以哪些身份、免不免密、执行哪些命令。

$ sudo -l Matching Defaults entries for www-data on ctf-box: env_reset, mail_badpass, secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin User www-data may run the following commands on ctf-box: (root) NOPASSWD: /usr/bin/vim (ALL) NOPASSWD: /usr/bin/python3

解读第一步先看Matching Defaults entries。这块定义的是环境相关的默认行为,比如env_reset表示sudo会重置环境变量,secure_path表示sudo会使用指定的PATH来执行命令,而不是继承你当前的环境变量。这两行对后面判断能否利用LD_PRELOAD非常关键。

第二步看命令列表。(root) NOPASSWD: /usr/bin/vim表示当前用户能免密以root身份执行vim;/usr/bin/python3类似,但身份是所有主机上的所有用户。读懂这一条,就清楚自己可用的资源边界了。

2.2 需要留神的五种配置模式

见过足够多的题目之后,你会发现出题人常用的配置模式就那么几种。照着这个思路去检查sudo -l的输出,效率会高很多。

  • 配置了编辑器类程序:vim、nano、less、more、man这类程序自带交互式命令行或者翻页功能,很多可以直接通过内部命令逃逸到shell。
  • 配置了脚本解释器:python3、perl、ruby、node这些解释器天然就有获取shell的能力,只要给一句代码就能生成一个交互式shell。
  • 配置了文件管理类工具:find、tar、cp、rsync这类工具本身不支持直接执行命令,但它们支持参数、支持外部调用,存在被利用的可能。
  • 配置了二进制文件:某些系统自带小工具,可能有特殊参数能够调用系统命令,需要逐个尝试。
  • 配置了自定义脚本:出题人写了一个/usr/local/bin/backup.sh之类的脚本,虽然脚本本身看起来无害,但里面可能调用了内部命令,可以被符号链接或环境变量劫持。

上面这五类模式不是穷举,但覆盖了市面上大部分基于sudo的CTF提权题。看到输出先对照着分个类,再决定走哪条路,会比直接猜测高效得多。

3. 五个最常考的sudo提权路径

3.1 编辑器类的交互式逃逸

最常见的例子:sudo -l给出(root) NOPASSWD: /usr/bin/vim。先把命令跑起来,进入vim编辑器后,接着在普通模式下输入:进入命令行模式,再输入!bash即可弹出root shell。

$ sudo vim # 在vim里输入::!bash

原理很简单:vim的:!指令允许执行外部命令,而这个外部命令在执行时继承了vim进程的权限,也就是root。提示符变成#之后,可以直接读取flag,也可以进一步执行任意命令。

less和more相关的情况也类似。这两个分页器都内置了在浏览文件内容时执行命令的机制。进入界面后按!,再输入想执行的命令即可。比如:

$ sudo less /etc/passwd # 在less界面按! !id uid=0(root) gid=0(root) ...

有一个容易忽略的点是:less首次启动时会尝试打开文件,如果文件不存在会提示,这不影响后续的提权操作。另外,man命令本身也带有!转义功能,sudo man man进去后按!照样能执行命令。

3.2 让解释器帮忙起shell

sudo -l里出现解释器时,直接利用它们的功能即可。Python和Perl都有专门生成shell的模块:

$ sudo python3 -c "import pty; pty.spawn('/bin/bash')"
$ sudo perl -e 'exec "/bin/bash";'

Ruby则可以用:

$ sudo ruby -e "exec '/bin/bash'"

对很多人来说,记住这三条命令就够了。但如果题目环境里只有某个不常见的解释器,比如Lua,也别急着放弃。Lua同样可以执行系统命令:

$ sudo lua -e "os.execute('/bin/bash')"

用解释器提权有两个注意点。第一,有些解释器启动后不会直接变成交互式shell,比如python的pty.spawn()只会在当前终端内产生一个bash进程,这个bash还是挂在sudo进程下的,退出时会回到原始shell,这并不影响命令执行。第二,如果sudo配置了env_reset,环境变量被重置了,但解释器本身的功能不受影响。所以这个路径的成功率非常高。

3.3 环境变量注入实现提权

这是相对隐蔽的一类。常见前提是sudo -l里出现env_keep+=LD_PRELOAD,或者Defaults配置中没有启用env_reset。LD_PRELOAD的作用是:一个程序启动时,动态链接器会优先加载这个指定的共享库文件,哪怕程序本身没有显式要求加载它。

思路是:写一个包含自定义函数的共享库,把函数名写成程序运行时会调用的函数,然后通过LD_PRELOAD让目标程序加载这个库,最终在库的构造函数里执行我们想要的命令。

先写一个C文件:

#include <stdio.h> #include <sys/types.h> #include <stdlib.h> void _init() { remove("/tmp/pwned"); setuid(0); unsetenv("LD_PRELOAD"); system("/bin/bash"); }

然后编译:

$ gcc -shared -fPIC -o /tmp/pwn.so /tmp/pwn.c

运行目标程序时带上LD_PRELOAD:

$ sudo LD_PRELOAD=/tmp/pwn.so /usr/bin/find /tmp

利用的关键在于,sudo执行find时会以root身份启动,动态链接器先加载pwn.so,在_init构造器里直接起了bash,于是我们就得到了一个root shell。

判断能否走这条路,需要额外确认两件事:一是sudo配置是否启用了env_keep+=LD_PRELOAD;二是目标环境是否对C编译做了限制。CTF靶机一般不会阉割gcc,所以这条路在训练环境里非常稳定。如果是真机评估,要额外注意目标是否需要编译工具链。

除了LD_PRELOAD,PYTHONPATH也是一个切入点。如果sudo允许在当前环境变量下运行python,而python脚本导入了某个自定义模块,我们可以通过覆盖PYTHONPATH来劫持模块。不过CTF里相对少见,属于进阶路径,遇到再深入去看也不迟。

3.4 通配符与文件匹配陷阱

这一条考的是Linux shell的通配符展开机制。假设sudo -l里允许root执行/usr/bin/tar,而常见的备份脚本可能是这种形态:

$ sudo tar -czf /tmp/backup.tgz *

关键点在于:通配符*由当前shell展开,展开后的文件名会传给tar。如果当前目录下有一个文件名为--checkpoint=1,另一个文件名为--checkpoint-action=exec=/bin/sh,tar就会把它们当成参数来解读,而不是普通文件名。

构造文件:

$ touch -- --checkpoint=1 $ touch -- --checkpoint-action=exec=/bin/bash $ sudo tar -czf /tmp/backup.tgz *

生成的bash是root权限。tar会按参数解析自己的行为,遇到--checkpoint参数后,在执行到检查点时调用exec指定的命令。这个手法在旧版tar上非常稳定,新版tar对这个参数的控制更严格,但CTF题目里通常刻意保留旧版环境,所以值得掌握。

同理,rsync也有类似的利用方式。rsync的--e参数可以指定远端的shell,如果匹配到特殊的文件名,也能触发命令执行。这类题的价值在于提醒玩家:不要把过长的注意力放在命令本身,要看到文件名的OS层面展开机制。匹配规则是由shell做的,命令本身只是被动的接收者。

3.5 从文件读取到明文密码的被动提权

之前的几条路径都是主动冲击shell,还有一类是被驱动的。某些sudo配置允许你以root身份读取某个文件,该文件里可能直接存放了root的密码哈希,或者是某个高权限用户的SSH私钥。

举个例子,sudo -l输出(root) NOPASSWD: /bin/cat /etc/shadow,这条规则虽然看起来限制了cat只允许读取/etc/shadow,但/etc/shadow里本身就有所有用户的密码哈希。把root的哈希拿出来,用在线工具或者本地john跑字典,如果root的密码恰好是弱口令,那就白拿了一台root权限。

这看起来不像是“提权”,但却是最省事的方式。很多CTF题目把flag藏在root家目录下,而读取权限就藏在sudo的cat规则里,先读一波敏感文件,再去决定是否硬提,成功率更高。

另一个常见组合是sudo -l允许/usr/bin/tee,tee可以把内容附加到任意文件。此时可以往/etc/passwd里塞一个自定义的root权限用户,或者往/root/.ssh/authorized_keys里塞一把自己的公钥,然后平滑登入root。这类方式需要的操作更多,但在无shell的题目里反而是关键解法。

4. 出题人视角:这个洞是怎么被设计出来的

4.1 一个常规模拟题的设计流程

很多玩家困惑“为什么会允许一个用户用vim,这配置也太蠢了”。但如果你站到出题人的角度看,这个“蠢”本身就是核心考点。

一个常规的sudo提权题,设计流程大致是:先确定玩家已经处于什么权限,默认www-data;然后确定目标,是读取root的flag;最后在两者之间搭建一条可发现、可利用的路径。路径的选择遵循一个原则:命令是常见的,配置是错误的,利用是不需要复杂工具的。

出题人会在/etc/sudoers里写一行:

www-data ALL=(ALL:ALL) NOPASSWD: /usr/bin/vim

这行配置放到真实服务器上确实危险,但在CTF里就成了一场“发现并利用”的智力游戏。为了让玩家有迹可循,出题人一般会在sudo -l的输出里保留明显的痕迹,比如权限项里没有使用通配符去固定参数,或者完全忽略env_reset对环境变量的影响。只要你能读懂输出,就等于竞赛开始。

另一种设计思路是把命令伪装得很严格。例如:

www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx

看起来只允许重启nginx服务,但systemctl本身支持调用外部脚本,或者可以通过trace、inspect等方式接触到内部执行逻辑,一样能扩展成shell。出题人把“看似安全”的命令作为陷阱,考的是选手是否会去查手册、查在线提权清单。

4.2 防御侧与加固思路

从防守方角度看,这些题反过来就是一份加固检查表。给sudoers配置时,先问自己几个问题:这条命令是否需要普通用户以root执行?执行时是否需要保留完整参数?是否真的需要NOPASSWD?默认情况下,Defaults env_reset必须开启,且不要轻易把环境变量加入env_keep。

在真实生产环境里,优先考虑的做法是:把需要特权的操作封装成独立脚本,并用sudoers限制该脚本的参数,不要直接把vim、python、perl这类交互式程序暴露给用户。如果一个运维同学真需要在服务器上编辑某个配置文件,正确的姿势是让他使用sudo -e以便调用策略编辑器,而不是直接放权给vim。

在CTF题目设计里,防御思维的考察也会反向出现。出题人可能会做成“限制参数”的题目,比如只允许/usr/bin/less /var/log/app.log,看玩家能不能通过路径绕过拿到其他文件。实测下来,less读取别的文件虽然会被规则拦,但绕过方式同样存在,例如利用软链接指向敏感文件。

5. 现场排错与复盘记录

5.1 常见问题速查表

在实操过程中,我经常在几个节点卡住。把这些经验整理成一张表,建议存下来对照排查。

现象可能原因排查思路
执行sudo命令提示当前会话没有tty新拿到的shell为只读终端,sudo默认要求tty使用python -c 'import pty; pty.spawn("/bin/bash")'获取一个伪终端;或用script /dev/null占用一个tty
sudo -l输出里没有可执行命令当前用户权限很低,可能不在sudoers里尝试其他低权限账户切换文件,如~/.bash_history、数据库配置文件、web目录里的备份文件
按:!bash后命令未执行编辑器版本较新或配置限制了转义尝试更直接的路径:在vim里执行/bin/bash的绝对路径;或换用python等其他工具
LD_PRELOAD注入后没反应sudo启用了env_reset且未保留该环境变量检查sudoers配置中Defaults env_keep部分是否含有LD_PRELOAD,没有的话此路不通,换路
tar通配符触发失败文件名参数顺序或tar版本校验不匹配确认两个文件名称准确;确认通配符展开后的第一个特殊参数能被tar识别
找不到可写目录执行临时脚本系统将临时目录挂载为noexec尝试写入/tmp以外的目录,如/var/tmp、/dev/shm,用mount查看挂载选项

这个表只收录了我在模拟环境下踩过的高频坑。每一条背后都对应着一次试错,如果你在自己的靶机里遇到类似报错,先走表格里的排查路线,往往几分钟就能解开。

5.2 一次失败案例的复盘

之前在某台模拟靶机里,我拿到www-data权限后跑sudo -l,看到允许执行/usr/bin/find。当时第一反应是直接跑:

$ sudo find / -exec /bin/bash \;

但执行后返回了一个权限错误。我立刻去查/etc/sudoers,发现规则写的是:

www-data ALL=(ALL) NOPASSWD: /usr/bin/find /var/www -exec *

意思是只允许在/var/www目录下使用-exec,参数列表虽然用了通配符,但无法对系统根目录执行。此时如果硬跑,就会被规则拒绝。后来我转换思路,利用find的-exec在当前用户允许的目录里把/bin/bash以root身份启动,实际上还是被限制住,因为target不是root权限下的find吗?

复盘时我意识到,这里真正有效的方式可能是利用find的-exec加载一个具有SUID位的副本,或者结合其他文件权限漏洞。传统的find -exec /bin/bash用法在这个规则约束下失效,说明出题人专门卡了这个点。这个失败案例给我的教训是:不要只背命令,要先读懂sudoers里对参数范围的限制。

最终这台靶机的解法换成去检查web目录里的备份文件,从数据库配置里翻到一个高权限用户的密码,再用su切换过去,最终通过该用户的sudo规则读到flag。整个过程虽然绕,但更贴近真实评估场景,普通玩家容易在第一步就卡住。

结尾

个人在实际操作中最深的体会是,sudo提权题拼的不是命令数量,而是对“权限配置边界”的判断力。拿到sudo -l输出后不急着抄命令,先花三分钟想想:这个命令能碰文件系统吗?能碰环境变量吗?能交互吗?如果一个都不沾,思考它是否有子命令能间接完成这些动作。多经历几次“差一步就成功”的失败,反而比顺风顺水拿多少台靶机都更有收获。

最后再分享一个小技巧:在自己的练习环境里,把所有提权路径整理成一张速查笔记,记录每条命令生效的前提条件和典型报错。实际比赛时会特别赶,笔记能帮你跳过一半的试错过程。老话重提,但真的管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询