
你有没有想过一台服务器上写着的sudo规则可能就是整个内网沦陷的起点前阵子我帮一个朋友排查开发机发现/etc/sudoers里赫然写着一行dev ALL(ALL) NOPASSWD: ALL。那一刻我突然意识到很多所谓的“简单权限绕过”根本不涉及什么高级漏洞纯粹是sudo的配置本身把大门敞开了。今天这篇文章我就从权限绕过的视角把sudo重新拆一遍——不聊那些概念化的理论就聊具体规则、命令和环境变量是怎么被“绕过”的以及作为管理员我们该怎么在十分钟内发现自己系统里的隐患。这篇文章适合所有碰过Linux的运维、开发和安全测试朋友不管你是刚入门还是已经写过无数条sudoers规则都能从中找到一些自己没注意到的细节。1. 为什么sudo是“简单权限绕过”的重灾区1.1 一个让我重写sudoers的真实案例还是回到刚才那个例子。朋友说“我就是为了方便让dev用户能执行所有命令”。听起来省事实际呢dev用户只要执行一条sudo -i或者sudo bash就直接拿到root shell。这个权限绕过的“成本”低到什么程度低到攻击者连漏洞都不需要找只需要知道你允许了这个用户运行sudo然后敲两个字母权限就到手了。这种案例我在各种环境里见过太多次了。不要以为只有小团队才犯这种错大型企业的内部系统里同样躺着大量“为了省事而写宽”的sudo规则。sudo原本的粒度非常细细到可以精确控制某个用户、以某个身份、执行某条命令甚至限制到参数级别的范围。但绝大多数人只会用最粗的那一层允许谁、以什么身份、执行什么命令。一旦习惯了粗放配置权限绕过就变成了一马平川。1.2 sudo模型里藏着的三个“默认信任”sudo本身的授权模型其实很简单核心就是一个三元组who、where、what。who是哪个用户where是哪些主机what是哪些命令。sudoers文件按顺序解析规则一旦命中后面的规则就不再看了。这个模型看似清晰但里面藏着三个“默认信任”恰恰是权限绕过的主要来源。第一个信任规则匹配到的命令等于安全命令。实际上完全不是这样。允许用户执行vimvim命令模式下输入:!bash就能弹shell允许执行less、more按!也能执行系统命令。这类“编辑器逃逸”是老生常谈的话题但真实环境里的sudoers规则依然随处可见这类程序。第二个信任命令的参数不会改变命令行为。sudoers里写着允许执行某个脚本可脚本本身如果对参数处理不严攻击者就能在参数里注入额外选项让程序执行配置者根本没想过的操作。第三个信任环境变量是干净的。环境变量恰恰是最容易被忽略的旁路。PATH、LD_PRELOAD、LD_LIBRARY_PATH这些变量一旦被sudo环境意外继承问题就会以非常隐蔽的方式出现。把这三个“默认信任”想清楚你再去审自己的sudo配置思路会完全不同。后续要讲的每一种绕过手法本质上都是对这个三元组模型的某个环节做了利用。2. 规则写得越“宽”门就开得越大2.1 (ALL) NOPASSWD: ALL 这类规则的拆解我们先把最危险的那条规则拆开看user ALL(ALL) NOPASSWD: ALL。第一列user授权对象也就是后续命令的发起者。第二列ALL所有主机。只要主机名在hosts或DNS里能解析匹配这条规则就生效。第三列(ALL)以任意身份运行本质就是允许切换到任何系统用户。NOPASSWD连密码都不需要。最后的ALL所有命令。把这条规则翻译成大白话就是系统给这个用户发了一张“免密root通行证”。从权限绕过视角看这已经谈不上“绕过”了因为权限是直接送出去的。还有一些看起来稍微克制一点的写法user ALL(ALL) ALL。多了一层密码验证似乎安全些并不是。sudo验证的密码就是你当前用户的登录密码如果这个用户的密码被钓鱼、被键盘记录、或因为弱口令被猜出来那么拿到这个账号的人就等于拿到了root。更麻烦的是sudo默认会缓存凭证5分钟timestamp_timeout这5分钟里再次执行sudo根本不需要输入密码。攻击者只要能在这5分钟内复用你的终端会话同样畅通无阻。所以审sudoers的时候凡是出现ALL的规则都必须追问一句这个范围真的有必要吗绝大多数场景下你真正需要的只是某些特定命令而不是“全部”。ALL这个词会让权限绕过直接变成权限直通。2.2 可逃逸到shell的“程序白名单”另一类高发配置是管理员只允许用户执行某个程序但这个程序本身就具备逃逸到shell的能力。我把最常出现在sudoers白名单里、实则是后门的程序列一下方便你对照自查程序逃逸示例说明vim / vi / nano:!bash编辑器万能逃逸入口less / more!bash分页器也能执行命令findfind / -exec bash \;自带执行参数man!bash帮助页里的shellpython / python3 / perl / ruby / php交互式解释器即shell脚本语言最危险gitgit help config进入 less 后逃逸git子命令多路径复杂tar--checkpoint-actionexec/bin/bash归档工具也能执行命令这些程序被授权使用sudo时权限绕过的成本基本就是一次交互操作。我在实际环境中见过不少“只允许用户编辑nginx配置”的规则授权模式是vim /etc/nginx/*管理员以为这样就能限制住编辑范围结果vim本身既能打开任意文件也能直接进入shell。所以在给用户授权编辑器时必须确认shell逃逸行为被关闭或者干脆不让用户直接执行vim改用后面我要讲的sudo -e方案。2.3 通配符与参数组合的漏洞sudoers是支持通配符的比如/usr/sbin/nginx *看起来是允许nginx带任意参数。问题在于通配符匹配的是“参数的个数”而不是“参数的内容”。攻击者传递的参数可以完全突破配置者的预期。举个例子如果授权了/usr/bin/apt *这几乎等于允许执行任意命令因为apt在处理某些包时会运行包里的维护脚本包括postinst这类可能包含任意命令的脚本。更隐蔽的是tar。很多人以为只允许tar某个指定目录就安全了但tar本身支持--checkpoint-action这样的参数可以指定在执行到某个checkpoint时运行任意命令。这类技巧在GTFOBins网站上都有现成收录我每次自查sudoers时都会打开这个网站逐条对照。安全领域有个共识一个工具只要能传参就不能默认它是“无害”的。另外还要注意一个解析顺序问题sudoers是“先到先得”前面的规则先匹配了一个宽泛命令后面的限制性规则根本轮不到执行。我见过有管理员写一条“允许所有命令但禁止vim”的规则满心以为能兜底结果系统默认先匹配了前边的ALL后面的Deny完全没生效。这就是“规则越写越多、风险却不见下降”的典型案例。3. 环境变量、PATH、LD_PRELOAD最容易被忽视的旁路3.1 secure_path的保护范围sudo默认会重置环境变量env_reset并把PATH设置成sudoers里secure_path指定的值。这个设计的初衷是好的让你sudo执行命令时命令解析路径来自系统目录而不是当前用户可能被篡改的PATH。但它保护的只是“程序本身的解析路径”。如果你sudo执行的是一个脚本比如/usr/local/bin/deploy.sh这个脚本内部如果调用了某个外部命令而该命令没有使用绝对路径那么脚本在运行时依然会去查找调用者的PATH。什么意思攻击者可以在这个用户的~/.bashrc里把自己的目录加到PATH最前面然后放置一个同名恶意程序。当sudo运行的deploy.sh脚本内部执行tar、cp或curl这样的命令时就会先找到恶意程序并按root权限执行。这就是一条非常典型的“看起来安全、实际上绕过了sudo限制”的路径。审计时不仅要看sudoers规则本身还要检查被允许执行的脚本内部是否对每个外部命令都使用了绝对路径调用。3.2 env_reset 与 LD_PRELOAD如果sudoers里出现了!env_reset或者显式的env_keep LD_PRELOAD问题就会被放大很多倍。LD_PRELOAD可以让动态链接器在程序启动时优先加载你指定的.so文件。这个机制如果在sudo环境下被允许攻击者只需准备一个so文件在里面实现一个同名函数覆盖目标程序里的标准函数就能在root权限下执行自己的代码而sudoers规则里只写了“允许执行某个看似安全的程序”。这个手法在安全社区里早就不是什么新鲜事了但它恰恰是“简单权限绕过”的典型代表不需要系统漏洞不需要内核漏洞只需要一个配置项放开了环境变量控制。我在实际审计中见过的最离谱配置是为了调试方便在sudoers里给某用户开了setenv结果别人拿到那个用户账号后直接用LD_PRELOAD就实现了权限提升。那条sudoers规则看起来只是“方便调试”实际上等于给root开了一个侧门。所以我的建议很明确默认的env_reset务必保留setenv这类关键字出现在sudoers里就要高度警惕更不要为了某个软件或者某个调试场景去关闭环境变量隔离。3.3 从“无sudo make 编译”看sudo习惯的副作用热搜词里有“无sudo make 编译 github”这个说法我猜可能是很多开发者在执行make install时被权限卡住。这里我想先说一个非常普遍的错误习惯很多人的教程第一步就是sudo make install于是大家养成了“编译也要sudo”的习惯。实际上make编译本身根本不需要root权限只有把产物安装到系统目录那一刻才需要。以root身份编译生成的中间文件、缓存文件全部归属root这会导致你的项目目录权限变得很诡异后续编辑需要反复sudo越陷越深。更严重的是以root身份运行编译脚本时如果脚本内部调用了外部工具而这些工具又被当前PATH劫持那么权限绕过的风险就直接落到了root身上。正确的做法是源码编译一律用普通用户身份只有make install那一步才通过sudo执行。这个习惯从权限绕过视角看尤其重要因为它能直接缩小root环境中“脚本调用外部命令”的暴露面。4. 热搜场景里的合规用法与常见误区4.1 “sudo需要tty”到底加固了什么搜索里“sudo需要tty”出现频率很高对应的sudoers配置是Defaults requiretty。很多管理员以为开了这个就更安全因为攻击者在没有tty的情况下没法执行sudo。这个理解只对了一半。requiretty限制的是sudo必须在交互式终端里运行它防的是cron、at这类非交互环境调用sudo也防一部分web shell场景。但真正的攻击者拿到用户权限之后用ssh登录本身就有tty用python起一个pty也有tty所以requiretty对他们基本构不成障碍。它更像一道针对“无人值守场景”的约束而不是针对权限绕过的加固。另外requiretty会给合法自动化带来不小的麻烦cron任务里需要执行sudo的命令经常会因为“没有tty”而直接报错。排查报错时不要只看字面意思要先判断这条配置是否真的有必要。如果确实需要保留可以对特定用户或特定命令做例外比如Defaults!/path/cmd !requiretty只放开某一条命令的tty限制而不是全局去掉。4.2 sudo apt、sudo systemctl 的常规操作边界sudo apt install jmeter和sudo systemctl restart ssh这类命令是sudo最日常的用途。它们的共性是操作对象都在系统级资源上apt要写/var/lib、/etc/aptsystemctl要发D-Bus信号给systemd这些操作确实需要root权限。单纯看命令本身它们不是“权限绕过”的例子反而可以借它们理解权限边界。这里有个容易被忽略的点并不是“能执行systemctl”就等于能控制系统服务还要看具体授权的子命令。如果sudoers里写的是systemctl restart *那么攻击者能重启服务但没法直接执行任意命令。可如果授权范围扩大到了systemctl daemon-reload或者systemctl edit攻击者就能通过修改unit文件来获得任意命令执行能力。所以不管是apt还是systemctl授权时都要把子命令写具体权限的最小化不能只停留在“哪个程序”这个层面要细化到“哪些子命令”“哪些参数”的层面。4.3 sudo rosdep init error 等报错背后的排查思路热搜里的sudo rosdep init error: cannot download default sources list from url看起来很像权限问题实际上是rosdep初始化时下载网络资源失败通常是网络不通或者源地址不可达。把sudo当成“万能权限药”来解决所有问题很容易把排查方向带偏。我总结过一个sudo报错的排查顺序省了很多弯路先看错误信息里有没有权限关键词比如permission denied、command not found、not in sudoers。再看是不是网络错误比如连接超时、下载失败、证书问题。用sudo -l检查当前用户实际被授予的命令集合别凭感觉假设“sudo一定可以执行任何命令”。最后检查sudoers语法用visudo -c验证。这个顺序能快速把“权限不足/权限绕过”和“环境问题”区分开。很多你以为的权限问题其实是网络问题也有反过来的时候网络下载不了绕了一大圈才发现是代理配置问题。把概念分开排查效率会高很多。5. 10分钟自查找出你的sudo盲区5.1 从sudo -l看你的真实暴露面我每次登录一台新接手的服务器第一件事就是执行sudo -l。它会列出当前用户被授予的所有命令并且标明是否需要密码。审计的思路很简单把列表里的每个命令都放到“攻击者视角”下去过一遍。这个命令能弹shell吗能读任意文件吗能修改系统配置吗任何一个答案是“能”这条命令就不该出现在授权列表里或者必须加上参数限制。有一个实用技巧给sudoers里的命令补上绝对路径同时把参数白名单写清楚。比如dev ALL(ALL) /usr/bin/systemctl restart ssh, /usr/bin/systemctl status ssh而不是systemctl *。参数白名单会增加一点维护成本但能从授权层面堵住大部分“参数穿越”类的绕过手法。5.2 sudoers文件里的“危险模式”快速审sudoers时我通常直接grep几个关键词基本能定位高危规则关键词风险说明NOPASSWD免密执行风险等级最高ALL(ALL)可以切换任意身份执行(ALL) ALL等价于直接给root能力setenv/env_keep环境变量相关高风险/usr/bin/或/bin/下的非常规命令可能是攻击者预留的后门如果一条规则同时命中NOPASSWD和ALL(ALL)基本不用犹豫这条规则就是“权限绕过”的入口。除了人工审还可以用visudo -c检查语法配合版本管理工具把sudoers文件纳入git每次改动都留痕。这比直接改一个没有备份的大文件稳妥得多。5.3 日志与审计线索自查不只是改规则还要知道日志里能看到什么。sudo的日志默认走authprivDebian系在/var/log/auth.logRedHat系在/var/log/secure。关键字段包括哪个用户、从哪个tty、在哪个pwd目录、执行了什么命令。从权限绕过视角看日志里最值得留意的模式有几类短时间内出现大量sudo -l探测同一个非root用户在不同tty上频繁执行sudo先sudo一个低危命令紧接着又sudo一个高危命令的组合。这些不一定代表攻击但值得追查。新版sudo还支持把日志转发到独立的sudo_logsrvd日志服务与系统日志分开存放这样即使系统日志被清理sudo的审计线索还在。6. 加固sudo的四个具体动作6.1 最小化规则、显式路径、禁用逃逸程序第一件事把所有宽泛的ALL规则改成最小集合。原则是能指定命令就指定命令能指定参数就指定参数能用绝对路径就不用PATH解析。像vim这类有shell逃逸能力的编辑器如果必须授权可以考虑用vim -Z受限模式或者干脆不直接授权vim。每改一条规则之前先问自己一个问题攻击者如果拿着这条命令能干什么这个问题问多了你会自然倾向于写更窄的授权。6.2 用sudoedit代替直接编辑如果你需要给用户“编辑某个配置文件”的权限正确做法不是授权vim去打开那个文件而是在sudoers里使用sudoedit这个指令。用户在终端执行sudo -e 文件名时sudo会把这个文件复制成一个临时文件再用用户自己配置的编辑器打开保存退出后sudo再把内容写回原文件。整个过程编辑器运行在受限环境里shell逃逸的路径被封掉了。我实测过很多次把sudo vim /etc/nginx/nginx.conf改成sudoedit /etc/nginx/nginx.conf之后既满足了编辑需求又堵掉了vim弹shell的绕过路径。这个动作很小收益却很直接。6.3 集中管理sudoers.d与版本化把规则分散在/etc/sudoers.d/下的独立文件里单独维护比全部堆在一个大文件里好读得多。每个文件命名要有意义比如20-web-team。文件开头用注释写清楚这个文件允许谁做什么、为什么授权、最后修改日期。配合git做版本管理任何改动都能回溯谁在什么时候加了一条规则一目了然。长期维护下来sudoers就是一份“谁有权做什么”的可审计资产。权限绕过漏洞的高发地往往就是那些没有注释、没有审计、层层堆叠的庞杂sudoers文件。6.4 从权限绕过视角复盘把sudo当边界而不是习惯最后说一个我自己的体会。sudo是运维里最常用的命令之一但它本质上是“边界控制工具”不是“万能提权按钮”。每一次sudo的使用都在越过一次权限边界。边界被越过太多次系统的可信度就会下降。所以我建议身边同事养成三个习惯不是所有命令都需要sudo特别是编译、查看日志这类操作普通权限完全可以搞定。写sudoers规则时先写最小允许集合再在确有需要时一点点加。定期用sudo -l检查所有生产服务器的授权暴露面把它当成和检查磁盘空间一样的例行工作。即便不考虑安全性把sudo用得越克制排障时就越容易定位问题。这个习惯比我见过任何一键加固脚本都更值得长期执行。