技术文章 | 反序列化Payload生成框架(一)

admin 2021年12月27日01:38:09评论619 views字数 7287阅读24分17秒阅读模式



技术文章 | 反序列化Payload生成框架(一)

起 因

技术文章 | 反序列化Payload生成框架(一)



在某次测试的过程中,Burp插件扫描到了站点存在Shiro 默认Key,也可以触发DNS。但是尝试执行命令却总是失败,当时推测是因为依赖缺失导致的。

由于ysoserial默认的CommonsBeanutils利用链中依赖了CommonsCollections,可以通过改造原有的CommonsBeanutils利用链,做到仅依赖shiro-core自带的commons-beanutils触发反序列化命令执行漏洞。在尝试复现的过程中,遇到了serialVersionUID不同可能导致反序列化失败的情况。在尝试了多个工具均无法成功利用后,再加上一直觉得ysoserial不够灵活,索性自己写了一个小工具,于是就有了这篇文章。




技术文章 | 反序列化Payload生成框架(一)

需要解决的问题

技术文章 | 反序列化Payload生成框架(一)


一、动态修改Java依赖

当时手头的各种Shiro利用工具,大多都基于魔改或裁剪后的ysoserial生成payload,默认依赖1.9.2版本的commons-beanutils,但是目标环境的commons-beanutils是1.8.3,所以会因为serialVersionUID不一致导致利用失败。关于serialVersionUID的概念:
如果两个不同版本的库使用了同一个类,而这两个类可能有一些方法和属性有了变化,此时在序列化通信的时候就可能因为不兼容导致出现隐患。因此,Java在反序列化的时候提供了一个机制,序列化时会根据类的属性和方法,通过固定算法计算出一个当前类的serialVersionUID值,写入数据流中;反序列化时,如果发现对方的环境中这个类计算出的serialVersionUID不同,则反序列化就会异常退出,避免后续的未知隐患。
简单测试一下serialVersionUID不同的实际影响:
  • CB版本不同,serialVersionUID不同

客户端commons-beanutils : 1.9.2 

服务端commons-beanutils : 1.8.3 

结果:反序列化失败
Caused by: java.io.InvalidClassException: org.apache.commons.beanutils.BeanComparator; local class incompatiblestream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962 
  • CB版本不同,serialVersionUID相同

客户端commons-beanutils : 1.8.2 

服务端commons-beanutils : 1.8.3 

结果:反序列化成功 

技术文章 | 反序列化Payload生成框架(一)

所以,其实在反序列化漏洞的利用过程中,想要成功反序列化,序列化数据中各个类的serialVersionUID需要与服务端相同,否则反序列化这一步就会失败。我们在尝试利用反序列化漏洞的时候,如果利用链中包含没有显式指定serialVersionUID的类,就需要考虑不同serialVersionUID可能造成的影响。而serialVersionUID不同,本质上是Java工程依赖的版本不同导致的,针对某些特定情况,通过修改字节码的方式直接修改serialVersionUID,虽然可以利用成功,但是既然serialVersionUID不同,就说明类的方法或属性发生了改变,无法保证这些改变是否会对漏洞利用产生影响。所以为了从根本上解决serialVersionUID不同的问题,就需要想出一个办法能够在运行时动态修改依赖。

二、更灵活的构建Payload

笔者一直觉得ysoserial构建Payload的方式不太灵活,在今年公司内部的CTF比赛中,Fastjson的反序列化利用,出题人通过RASP拦截了常见的命令执行方法,当时就是通过自己改造ysoserial,添加了加载自定义类的利用方式,实现了bypass RASP执行命令以及动态卸载RASP。熟悉反序列化漏洞的读者可能都清楚,在我们寻找反序列化漏洞的时候,很少会有直接将漏洞代码写在readObject()等方法中的情况,大多数时候都需要去构造利用链来进行“任意代码执行”。就像ysoserial中的大部分RCE利用链,都是将“任意代码执行”降级成了“远程命令执行”,本来应该是可以通过更加灵活的方式,完成更多种利用方式的。所以,笔者希望能够通过更灵活的方式构建反序列化的Payload,实现更多样的利用。



技术文章 | 反序列化Payload生成框架(一)

通过自定义ClassLoader动态修改依赖

技术文章 | 反序列化Payload生成框架(一)



那么如何能够在运行时动态修改依赖呢?由于对Java没有太过深入的了解,所以请教了一下Java研发同事,才知道其实类似的需求在实际开发中也是比较常见的。不过大多是为了解决依赖冲突的问题,在实际开发过程中,Java工程依赖的不同Jar包,可能也各自依赖了同一Jar包的不同版本,这就会导致依赖冲突的问题。这个时候一般都是通过自定义ClassLoader来实现依赖隔离的,比如Tomcat就是通过自定义的WebAppClassLoader对每个WebApp进行依赖隔离的。

一、双亲委派模型

为什么可以通过自定义ClassLoader实现依赖隔离呢?简单介绍一下Java的类加载机制,Java默认的ClassLoader使用了双亲委派模型(Parents Delegation Model) 。除了顶层的启动类加载器之外,其余的类加载器都应当有自己的父类加载器。其工作过程是:如果一个类加载器收到了类加载请求,首先不会自己去尝试加载,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。 

技术文章 | 反序列化Payload生成框架(一)

所以,为了实现依赖的动态修改,我们就需要破坏双亲依赖模型,在我们自定义的类加载器中加载可能需要修改的依赖。

二、具体实现

在自定义ClassLoader前,需要先了解ClassLoader的几个重要方法:

关键方法

① loadClass

loadClass(String, boolean)函数即实现了双亲委派模型

  1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

② findClass

抽象类ClassLoaderfindClass函数默认是抛出异常的。而loadClass在父加载器无法加载类的时候,就会调用我们自定义的类加载器中的findeClass函数,因此我们必须要在loadClass这个函数里面实现将一个指定类名称转换为Class对象,Java正好提供了defineClass方法,通过这个方法,就可以把一个字节数组转为Class对象。

③ defineClass

defineClass主要的功能是将一个字节数组转为Class对象。

所以,我们只需要通过重写以上的三个方法的逻辑,就可以让自定义ClassLoader按照我们的需求加载依赖。不过,在具体实现的过程中,还是遇到了下面几个问题:

④ 依赖存储

为了避免不必要的麻烦,笔者不太希望将依赖存储在Jar包外部。所以最好的选择就是将所有可能需要的依赖都打包到最终的Jar包中,但是由于我们可能需要存储多个版本互相冲突的Jar包,而Maven本身会进行依赖仲裁,无法同时将多个相同依赖引入工程,所以这里选择把依赖的Jar全部作为resources。

⑤ 类加载

前面也说到了,针对我们需要加载的类,可以通过重写defineClass直接将字节数组转为Class对象,但是问题是,这个方法只能define "Class",而我们随便一个依赖的Jar包,里面都是不计其数的Class。我们固然可以通过遍历Jar包中的Class,将他们挨个define出来,但是还是太过麻烦。而URLClassLoader是可以直接加载Jar包的,如果我们自定义的ClassCloader继承URLClassLoader,不就可以直接借用父类的加载方式,方便的加载依赖了吗?事实上确实可以,但是因为我们的依赖都打包到了resources当中,而resources中的文件,虽然可以通过getResourceAsStream等方法访问到,但是这些文件实际上并没有落地,而是被Jvm加载到了内存中的,URLClassLoader是无法通过file,jar等协议,访问到内存中的对象的。

⑥ 自定义协议

场面一度陷入了尴尬的境地,笔者当初甚至想过放弃一开始的想法,尝试通过落地文件进行依赖加载。但是在网上冲浪的过程中,发现有前辈提出过一种思路,就是通过URLStreamHandlerFactory自定义协议,伪造一个可以访问内存内容的URL,这样就可以通过URLClassLoader进行加载了。通过查阅Java官方文档关于java.net.URLStreamHandlerFactory的描述,我们可以发现,Java对URL的大致处理流程为:

  1. 如果已经设置了一个URLStreamHandlerFactory实例,那么Java会优先把protocol字符串作为入参,调用该URLStreamHandlerFactory的createURLStreamHandler方法。
  2. 如果未设置URLStreamHandlerFactory,或者createURLStreamHandler方法返回null,那么构造器就会去寻找package以及System default package中是否有能处理对应协议的handler,如果都找不到,那么会抛出一个MalformedURLException异常。
  3. http,https,file,jar这四种协议的handlers是默认一定存在的。
所以,我们只要实现一个URLStreamHandlerFactory,并且在传入包名的时候,控制URLConnection的getInputStream,返回对应Jar包的内容,就可以伪造一个URLClassLoader可用的协议了。贴一下代码:
public static List<URL> init(String[] resourceJars) throws Exception {        List<java.net.URL> urls = new ArrayList<>();        java.net.URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {            public URLStreamHandler createURLStreamHandler(String urlProtocol) {                if ("kuro".equalsIgnoreCase(urlProtocol)) {                    return new URLStreamHandler() {                        @Override                        protected URLConnection openConnection(URL url) {;                            String key = url.toString().split(":")[1];                            return new URLConnection(url) {                                public void connect() {                                }
public InputStream getInputStream() { return new ByteArrayInputStream(map.get(key).toByteArray()); } }; } }; } return null; } });
for (String resourceJar : resourceJars) { InputStream in = KuroClassLoader.class.getResourceAsStream(resourceJar); int len = -1; byte[] bytes = new byte[1024]; ByteArrayOutputStream jarBytes = new ByteArrayOutputStream(); while ((len = in.read(bytes)) != -1) { jarBytes.write(bytes, 0, len); } map.put(resourceJar, jarBytes); urls.add(new URL("kuro:" + resourceJar)); }
return urls; }

代码主要做下面这两件事:

  1. 遍历传入的Jar包列表,读取字节码并存入Map中,同时为每个Jar生成一个kuro协议的url,返回url列表。
  2. 通过URL的setURLStreamHandlerFactory方法,扩展一个URL协议,可通过kuro:/lib/javassist-3.26.0-GA.jar直接访问map中的jar包。
自定义的类加载器大致流程如下: 

技术文章 | 反序列化Payload生成框架(一)

以上,我们就解决了动态依赖修改的问题。下面来解决Payload生成的问题。



技术文章 | 反序列化Payload生成框架(一)

Payload生成

技术文章 | 反序列化Payload生成框架(一)



前面有说过,ysoserial实际上是把很多“任意代码执行”降级为了“任意命令执行”。大部分RCE的Gadget链,实际上都能够做很多其他的事情。而在ysoserial中,任意代码执行利用链的终点主要有两个,一个就是鼎鼎大名的TemplatesImpl了,另一个在CommonsCollections系列利用中也经常出现,那就是ChainedTransformer。所以只要在生成payload的时候,替换这两个类中的恶意类,理论上就可以修改最终的利用效果。所以笔者把payload拆分为了gadget和exploit两个部分,exploit代表了payload最终的利用效果,而gadget则是用于触发exploit的利用链:

技术文章 | 反序列化Payload生成框架(一)

例如,我们想通过CommonsBeanutis利用链执行命令,那么只需要执行下面的代码即可:
byte[] exploit = new ExecCommand().getExploit(“open -a Calculator”);byte[] payloads = new CommonsBeanutils().getPayload(exploit);
exploit类的getExploit方法,负责返回恶意类的字节码,而gadget类的getPayload则负责将恶意类字节码插入利用链中,并返回完整的payload。

Gadget改造

  • TemplatesImpl

针对TemplatesImpl的改动比较小,其实只要将_bytecodes属性替换即可。
  • ChainedTransformer

ChainedTransformer通过一系列Transformer的链式调用执行恶意操作,所以一开始笔者是想通过链式调用,首先通过defineClass将传入的恶意类字节码还原为Class,然后调用恶意类的newInstance触发操作,但是这种利用只有在Weblogic反序列构造回显的时候有看到过,原因是满足defineClass外部可访问的ClassLoader比较少,而Weblogic中正好有一个满足利用条件的ClassLoader,DefiningClassLoader。为了保证适配性,在暂时没有找到通用性比较强的ClassLoaderde的情况下,参考了CC3的实现,在TemplateImpl外面包一层ChainedTransformer,实际上仍然是通过TemplateImpl触发的。
由于利用方式多种多样,所以笔者没有补充太多,但是添加了一个LoadReqClass的功能,可以通过HTTP请求加载恶意类,非常的自由,例如可以通过LoadReqClass加载冰蝎内存马: 

技术文章 | 反序列化Payload生成框架(一)

技术文章 | 反序列化Payload生成框架(一)



技术文章 | 反序列化Payload生成框架(一)

结 语

技术文章 | 反序列化Payload生成框架(一)



在整个项目过程中,发现自己有非常多的知识盲区,也参考了大量资料,深感受益匪浅。同时也感谢喜大师和芳哥在Java方面提供的指导和帮助。最后,笔者才疏学浅,且行文仓促,如有错漏,敬请斧正。



技术文章 | 反序列化Payload生成框架(一)

参考资料

技术文章 | 反序列化Payload生成框架(一)



  • CommonsBeanutils与无commons-collections的Shiro反序列化利用

    (https://www.leavesongs.com/PENETRATION/commons-beanutils-without-commons-collections.html)

  • Java自定义类加载器与双亲委派模型

     (https://www.cnblogs.com/wxd0108/p/6681618.html)

  • 如何实现Java类隔离加载 

    (https://zhuanlan.zhihu.com/p/351378828)

  • Java 自定义 ClassLoader 实现隔离运行不同版本jar包的方式

    (https://blog.csdn.net/t894690230/article/details/73252331)

  • 使用resource中的jar包资源作为URLClassloader

     (https://www.cnblogs.com/silyvin/articles/12178528.html)

  • URL(Java Plat form SE 8)

    (https://docs.oracle.com/javase/8/docs/api/java/net/URL.html#setURLStreamHandlerFactory-java.net.URLStreamHandlerFactory-)

  • ysoserial 

    (https://github.com/frohoff/ysoserial)


技术文章 | 反序列化Payload生成框架(一)
技术文章 | 反序列化Payload生成框架(一)

关注我

掌握最新动态




你这么好看,一定要点个在看

技术文章 | 反序列化Payload生成框架(一)

本文始发于微信公众号(58安全应急响应中心):技术文章 | 反序列化Payload生成框架(一)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月27日01:38:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   技术文章 | 反序列化Payload生成框架(一)http://cn-sec.com/archives/470263.html

发表评论

匿名网友 填写信息