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在反序列化时,根据输入流来判断类型

image-20230621132938822

首先读取了输入流的一个字节(看到这里和0xff作与运算)

根据这个标记字节来决定反序列化的类型,67对应'C',进入readObjectDefinition -> readString

这里不止67可以用,只要最后让他匹配不到类型抛出expect即可

image-20230621140853874

readString又读了一次标记字节(_buffer头两个元素都是相同的标记字节)

但这次没有找到对应67的case,进入default,抛出了expect异常

com.caucho.hessian.io.Hessian2Input#expect

image-20230621134047268

这里继续对输入流进行反序列化,并将得到的对象obj拼接到异常错误信息中,妥妥的触发obj.String()

这里就和Dubbo那个Exported Service Not Found抛出异常的打法具有异曲同工之妙了

修复:Dubbo3.2.13中不进行obj的拼接

image-20230621162909381

接下来就是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

image-20230621143916701
image-20231022134130033

Class com.caucho.hessian.io.MapDeserializer can not access a member of class javax.swing.MultiUIDefaults with modifiers "public"

MimeTypeParameterList + MethodUtil

大佬们找到了另一个可利用的toStringjavax.activation.MimeTypeParameterList

parameters成员是Hashtable类型,而UIDefaults也刚好继承了Hashtable

看看SwingLazyValue#createValue

image-20230621150105158

createValue能够调用类的静态方法或对类进行实例化

(注意,这里用Class.forName加载类时,指定的类加载器是null,所以SwingLazyValue只能加载rt.jar下的类)

sun.reflect.misc.MethodUtilinvoke静态方法可以任意调用方法

因此我们得到一条新的链子

Unsafe Bypass blacklist

上面的POC打不出来,本地调试的Hessian版本是4.0.63

该版本下Hessian在获取反序列化器时会对类进行检查

com.caucho.hessian.io.ClassFactory#isAllow判断类是否允许被反序列化,其维护了一个黑名单

image-20230621151515103

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

image-20230621151700349

试了一下降到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 = false

    com.sun.jndi.cosnaming.object.trustURLCodebase = false

  • JDK 6u211,7u201, 8u191, 11.0.1后

    com.sun.jndi.ldap.object.trustURLCodebase = false

image-20230621165952875

好巧不巧这个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

image-20230621193730227
image-20230621193741109

this.attributes刚好是HashTable

  • sink 需要有可利用的静态方法或构造器

com.sun.org.apache.bcel.internal.util.JavaWrapper#_mian

image-20230621194200153

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

image-20230621194718109

loader在实例化时被初始化为com.sun.org.apache.bcel.internal.util.ClassLoader(该ClassLoader继承了java.lang.ClassLoader

但是BCEL的ClassLoader并不会对类进行初始化initial,所以不会马上执行静态代码块

得写一个_mainrunMain走到后面的逻辑,调用恶意类的_main的时候才会执行静态代码块

设置_main方法为静态方法,才不会抛出异常

输出

static block

_main

注:BCEL Classloader在 JDK < 8u251之前还在rt.jar里面 原生JDK

0x04 ProxyLazyValue + DumpBytecode + System.load

jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode是静态方法,能够写class文件

image-20230621215018305

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

javax.swing.UIDefaults$ProxyLazyValue.createValue

image-20230621230622926

获取到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?