Hessian_Only_JDK
0x01 Apache Dubbo Hessian2 expection 2 deser
参考:Apache Dubbo Hessian2 异常处理时反序列化(CVE-2021-43297) (seebug.org)
虽是Dubbo的CVE,但是漏洞点和修复点都在Dubbo魔改的Hessian依赖包,原版的Hessian同样存在这个问题
Reproduce
Hessian2Input#readObject
这个方法是这么描述的👇
Reads an arbitrary object from the input stream when the type is unknown
也就是Hessian在反序列化时,根据输入流来判断类型

首先读取了输入流的一个字节(看到这里和0xff作与运算)
根据这个标记字节来决定反序列化的类型,67对应'C',进入readObjectDefinition -> readString
这里不止67可以用,只要最后让他匹配不到类型抛出expect即可

readString又读了一次标记字节(_buffer头两个元素都是相同的标记字节)
但这次没有找到对应67的case,进入default,抛出了expect异常
com.caucho.hessian.io.Hessian2Input#expect

这里继续对输入流进行反序列化,并将得到的对象obj拼接到异常错误信息中,妥妥的触发obj.String()
这里就和Dubbo那个Exported Service Not Found抛出异常的打法具有异曲同工之妙了
修复:Dubbo3.2.13中不进行obj的拼接

接下来就是ROME利用链了
toStringBean#toString -> getter -> JdbcRowSetImpl#getDatabaseMetaData -> InitialContext#lookup
但若目标环境没有ROME依赖呢
接下来就来学习一下大佬们挖到的链子orz
0x02 Different Path of XStream
XStream有一条原生JDK的链子(为什么能联想到XStream是因为XStream和Hessian一样不需要类实现Serializable接口)
这条链有两个限制
MultiUIDefaults的访问修饰符是default,只有javax.swing才能使用它,Hessian反序列化时会出错高版本JDK打不了JNDI
MultiUIDefaults实现了Map接口,获取到的反序列化器为MapDeserializer。
其对类进行实例化,会先检查该类是否可访问checkAccess


Class com.caucho.hessian.io.MapDeserializer can not access a member of class javax.swing.MultiUIDefaults with modifiers "public"
MimeTypeParameterList + MethodUtil
大佬们找到了另一个可利用的toString类javax.activation.MimeTypeParameterList
parameters成员是Hashtable类型,而UIDefaults也刚好继承了Hashtable
看看SwingLazyValue#createValue

createValue能够调用类的静态方法或对类进行实例化
(注意,这里用Class.forName加载类时,指定的类加载器是null,所以SwingLazyValue只能加载rt.jar下的类)
sun.reflect.misc.MethodUtil的invoke静态方法可以任意调用方法
因此我们得到一条新的链子
Unsafe Bypass blacklist
上面的POC打不出来,本地调试的Hessian版本是4.0.63
该版本下Hessian在获取反序列化器时会对类进行检查
com.caucho.hessian.io.ClassFactory#isAllow判断类是否允许被反序列化,其维护了一个黑名单

ClassFactory#load把黑名单中的类都转为HashMap

试了一下降到4.0.38就可以了
当然既然能调用任意方法了,我们也不必拘束于Runtime
利用Unsafe加载字节码,注意Unsafe#defineClass不会对类进行初始化,所以需要调用两次,在恶意类里添加一个静态方法,第二次调用之。
JNDI Breakthrough
高版本的JDK之所以有JNDI限制,是因为trustURLCodebase默认为false,禁用了RMI、CORBA和LDAP使用远程codebase的选项
JDK 6u132, JDK 7u122, JDK 8u113后
com.sun.jndi.rmi.object.trustURLCodebase = falsecom.sun.jndi.cosnaming.object.trustURLCodebase = falseJDK 6u211,7u201, 8u191, 11.0.1后
com.sun.jndi.ldap.object.trustURLCodebase = false

好巧不巧这个java.lang.System#setProperty就是个静态方法,可以用上面的sun.reflect.misc.MethodUtil#invoke去调用
稍微修改一下上面的POC
开启trustURLCodebase之后就可以发起JNDI请求了。
0x03 PKCS9Attributes + BCEL
和上面的MimeTypeParameterList + MethodUtil比较,这条链子就source和sink不同
source 需要调用
HashTable#get
sun.security.pkcs.PKCS9Attributes#toString -> PKCS9Attributes#getAttribute


this.attributes刚好是HashTable
sink 需要有可利用的静态方法或构造器
com.sun.org.apache.bcel.internal.util.JavaWrapper#_mian

实例化一个JavaWrapper之后进入wrapper.runMain

loader在实例化时被初始化为com.sun.org.apache.bcel.internal.util.ClassLoader(该ClassLoader继承了java.lang.ClassLoader)
但是BCEL的ClassLoader并不会对类进行初始化initial,所以不会马上执行静态代码块
得写一个_main让runMain走到后面的逻辑,调用恶意类的_main的时候才会执行静态代码块
设置_main方法为静态方法,才不会抛出异常
输出
static block
_main
注:BCEL Classloader在 JDK < 8u251之前还在rt.jar里面 原生JDK
0x04 ProxyLazyValue + DumpBytecode + System.load
jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode是静态方法,能够写class文件

但由于ClassLoader的原因,SwingLazyValue这里只能加载rt.jar里面的类,而DumpBytecode类在nashorn.jar里面
javax.swing.UIDefaults$ProxyLazyValue.createValue

获取到classloader就能加载nashorn.jar了
创建一个动态链接库文件
Linux生成so文件
gcc -c calc.c -o calc && gcc calc --share -o calc.so
Windows生成dll文件
先写so/dll文件,再通过System.load加载动态链接库
0x05 XSLT Transform
通过com.sun.org.apache.xml.internal.security.utils.JavaUtils#writeBytesToFilename静态方法写文件,然后通过com.sun.org.apache.xalan.internal.xslt.Process#_main去加载XSLT文件触发transform,达到任意字节码加载的目的。不需要出网。
既然org.springframework.cglib.core.ReflectUtils#defineClass是静态方法,为什么不直接调用呢?
试了一下ClassLoader在Hessian序列化时会出问题。。。
0x06 Reference
Last updated
Was this helpful?