Java安全攻防之从wsProxy到AbstractTranslet

本文主要是围绕在Java反序列化利用过程中,默认使用 ysoserial 带来的一些问题和局限性。通对代码的二次改造,最终能完成序列化数据的体积的减少和Websocket类型正向代理的无文件植入。

常用改造 ysoserial 任意类加载执行方式

在使用ysoserial的时候,部分gadget最终的命令执行都是通过Gadgets.createTemplatesImpl实现的。
在原始的Gadgets.createTemplatesImpl方法中,使用了Javassist来构建templates的_bytecodes属性,在构建时设置了父类为AbstractTranslet,设置了static方法内容为Runtime命令执行。
由于单纯的命令执行还是非常局限,通常需要转换为代码执行。大家为了更方便的实现命令执行回显或中内存马,在ysoserial中新增了codefile、classfile等逻辑。
在classfile逻辑中,不再需要麻烦的使用javassist来构建_bytecodes属性,而是直接传入class文件路径。在编写codefile时,第一步就需要继承AbstractTranslet类,然后在构造方法或者static代码块中编写利用代码。
反序列后成功执行了代码,移除继承AbstractTranslet后,
也产生了异常但是没有执行代码,虽然需要继承AbstractTranslet才能利用,但在之前的各种利用中都不会带来什么影响,所以一直没去研究过为什么要继承AbstractTranslet才能利用。但在利用最近出的WebSocket内存马技术时,因为要继承AbstractTranslet带来了一些不便。

反序列化植入 WebSocket Proxy

wsMemShell/Tomcat_Spring_Jetty at main · veo/wsMemShell
作者提供了反序列化中wsCmd的代码,不过没有提供反序列中wsProxy的代码,自己就尝试去改了改。
作者提供了wsProxy.jsp,https://github.com/veo/wsMemShell/blob/main/Tomcat_Spring_Jetty/wsproxy.jsp 直接照着jsp改一份java版本。
刚开始就遇到了问题,以前中filter/listener内存马比较多,因为javax.servlet.filter、ServletRequestListener都是接口,那么可以继承AbstractTranslet同时实现这些接口,就不需要defineClass了,个人也不太喜欢defineClass(因为要改代码的时候略微麻烦),
中websocket内存马的时候,变成了抽象类javax.xml.ws.Endpoint,由于java不允许实现多继承,继承了AbstractTranslet后,无法再继承Endpoint。
实在没办法,只能老老实实defineClass了,先将wsProxy.jsp中的ProxyEndpoint抽出来改为java。
正常流程是将ProxyEndpoint编译为class后,再对class文件base64编码,但是在编译后会发现生成了多个class文件,ProxyEndpoint$Attach.class、ProxyEndpoint.class、ProxyEndpoint$2.class、ProxyEndpoint$1.class,这里因为ProxyEndpoint里面有多个内部类,导致生成了多个class文件。
这时候要defineClass就比较麻烦了,需要把这些内部类也一起defineClass,写了个servlet测试四个defineClass后,代理确实能够正常使用。
虽然能用但是十分不优雅,为了解决这个问题 想了两个优化步骤
1、去除所有内部类,一个defineClass实现利用
2、研究反序列化的codefile能否不继承AbstractTranslet使用,不需要defineClass实现利用

步骤1 单独类实现 Websocket 代理

步骤1实现比较简单,去除内部类后,只会生成一个class文件,利用起来优雅了一丝。

步骤2 反序列化codefile不继承AbstractTranslet

为什么不继承AbstractTranslet代码执行会失败?
部分gadget通过调用getter方法能够调用到com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties
getOutputProperties->newTransformer->getTransletInstance,在getTransletInstance中调用了defineTransletClasses方法来生成一个类然后实例化。
在defineTransletClasses方法中,通过loader.defineClass(_bytecodes[i]);生成了类后,回到getTransletInstance方法后实例化。
在defineTransletClasses方法中,会判断defineClass生成的类的父类是否为AbstractTranslet,如果不是那么就会执行到_auxClasses.put(_class[i].getName(), _class[i]);
观察_auxClasses属性发现被transient修饰,无法通过反序列化控制值,如果直接调用_auxClasses.put会抛出空指针异常,导致代码执行中断。
再观察defineTransletClasses方法可以发现,当classCount > 1时,会对_auxClasses属性赋值HashMap,这时候put就不会再空指针异常了。
另外还存在一个_transletIndex< 0时,就会抛出异常中断的限制,_transletIndex默认为-1。 在for循环时,只有当生成类的父类为AbstractTranslet时,才会对_transletIndex属性赋值。
查看_transletIndex属性时发现该属性并没有被transient修饰,那么即使父类不是AbstractTranslet也可以通过反序列化控制该属性绕过限制。
所以只要满足两个条件即可实现去除AbstractTranslet
1、classCount也就是生成类的数量大于1
2、_transletIndex >= 0
在defineTransletClasses生成类后,后续会用到_transletIndex属性指定从_class属性数组中实例化哪个类,那么需要将_transletIndex属性指定为恶意类的索引。
再回到原始ysoserial的createTemplatesImpl方法中,可以发现 ysoserial在设置templates的_bytecodes属性时确实传了两个类的bytes进去。但是在之前缩短payload的浪潮中,我移除了Foo.class。
还原Foo.class后再加上_transletIndex即可。
Codefile 不继承AbstractTranslet,
依然实现了代码执行。
在解决了AbstractTranslet的问题之后,反序列化的codefile就可以直接继承Endpoint了,同时部分安全产品有检测AbstractTranslet关键字,去掉之后也许也能起一些作用。
最终反序列化中wsProxy的codefile:
Copyright © 2022北京边界无限科技有限公司 京ICP备20007775号 京公网安备11010502040438号