0x01 前言
CS作为目前最流行的远控工具,其爆出的远程命令行漏洞CVE-2022-39197号称脚本小子杀手神器。之前看了@漂亮鼠大佬的文章《最新CS RCE曲折的复现路》,对文章的内容非常感兴趣,文章中故意对关键利用链进行忽略,如图1.1所示,这也勾起了大家浓烈的研究兴趣。最近比较忙断断续续对该漏洞进行了复现,这里想分享一些复现心得给大家。
图1.1 大佬隐藏的关键利用链
本来以为按照作者的提示这会是比较简单的一次复现,但是在实际过程中还是遇到很多困难,最终在二哥@gainover的帮助下完成了复现,这里也特别感谢下二哥。
在阅读本文之前需要首先阅读原文,原文中很多内容已经很详细了,我会尽量不写原文已经有的内容,主要是分享下自己的心得体会。
0x02 初探
第一次准备寻找相应的利用链的时候,听到旁边的同事说除了CS受影响,哥斯拉也受到这个漏洞影响,我下意识的以为这是一个通用的JDK的利用链。所以最开始的很长一段时间我都是直接在JDK中调试的,完全忽略了Cobalt Strike本身,虽然走了很多弯路,但是还是积累了一些基本的方法。
在第一步复现的时候,就遇到了问题。跟着作者的思路,object标签会自动调用对应的setXXX方法,如图2.1所示。我直接拷贝了作者给的代码,但是我无论如何都进不去setText方法,难道我连最基本的原理都理解错了?
图2.1 原文中对setXXX方法的简单利用
仔细调试之后才发现这里的单词写错了,不是parame,而是param。后面又在别的群里看到了这样的聊天记录截图,不得不感慨一句:“大佬套路深,我要回农村”。
图2.2 大佬的套路
按照文章中给的思路要找到利用链,需要满足四个条件。
1)classid传入需要实例化的类,类必须继承与Component
2)必须有无参构造方法,貌似是因为newinstant是调用的无参构造方法
3)必须存在一个setXXX方法的XXX属性
4)setXXX方法的传参数必须是接受一个string类型的参数
我们不可能人工来发现满足条件的类和方法,可以直接通过idea发现所有继承自Component的类,保存类名,并通过反射的方式来筛选满足其他条件的所有的类和方法。
从JDK中发现所有继承自Component接口的类,如图2.3所示。
图2.3 JDK中继承自Component类的类
这些类只是满足了条件1,把所有继承自Component类的类道出保存成文件。但是要满足其他条件,还需要继续对这些类进行筛选。为了更方便的找出满足条件的类和方法,我写了一个自动化遍历的代码,其主要功能是读取刚才保存的继承自Component类信息的文件,然后通过反射的方式来判断类是否满足其他几个条件,如图2.4所示。其中classFullName代表需要便利的完整的类名,主要是通过正则的方式从文件中读取,限于篇幅有限,就不展示了,过程中主要的步骤我都以注释的方式解释代码。
图2.4 自动化便利筛选满足条件的类
按照这种方式我们很容易就找到了很多满足条件的类,如图2.5所示。总计大概有160个。
图2.5 在JDK中筛选的满足条件的类及对应的方法
但是按照这种方式筛选的满足条件的方法最终的数据量还是很大,筛选到的方法中有大量无用的方法,比如setName、setLabel、setToolTipText、setAsText等。这些方法都很简单,一般只是对应swing最终界面展示内容的功能,我们先暂时不考虑这些功能。所以我们对上面的代码进行了改进,增加黑名单方法,如图2.6所示。
图2.6 增加黑名单方法
按照这种方式筛选之后JDK中可能满足的方法就只剩下19个,如图2.7所示。
图2.7 筛选之后JDK中可疑的19个方法
这些方法都需要进行人工研判,其中最能吸引人眼球的是setDocumentBase方法,这种方法名总是给人一种像是JNDI注入设置路径的感觉,但是实际测试来看根本就不会调用这个方法,因为documentBase属性不是writeMethod,而仅仅是一个readMethod。在ObjectView类中也明确要求,满足条件的属性必须是writeable,而这应该也算是第5个条件吧,如图2.8所示。
图2.8 判断属性是否是属于Writeable property
又对其他的方法依次都进行了查看,总归是没有找到哪一个方法能导致命令执行。
0x03 峰转
在我还在对着JDK的源码死磕的时候,看到朋友圈里面二哥发了一张利用object标签执行命令的图,然后问了一下二哥“是不是setContentType”,因为站在我当时的角度,只有setContentType里面有较复杂的逻辑,设计到了动态类加载的过程。但是二哥回复说“不是,不要看JDK的,可以看看CS的类”。
这时候我才突然醒悟过来,这个漏洞虽然说影响了Cobalt Strike和哥斯拉,但是实际上大佬们在平时一直都是说这是一个CS RCE漏洞,并没有人说这是JDK SWING的RCE,Cobalt Strike和哥斯拉的利用链根本就不通用。
赶紧把好久不用的CS掏出来,然后把对应的jar包加入liabrary。按照之前的方式来自动化发现可能利用的方法,结果如图3.1所示。
图3.1 在CS的jar包中发现的可能被利用的方法
按照这种方式,确实找到一些新的可能利用的方法,依次对方法进行筛选之后之后,最终我确定这里面可能利用的方法只可能是两个setTeamServerAlias和setURI。只有这两个方法中有比较复杂的代码逻辑,并且涉及到了动态类加载相关的代码,但是在实际debug的时候发现CS的所有的类断点都只能下在方法名,不能把断点下载方法体里面,并且跟踪到方法名之后F7也不能进入方法体。相当于我们调试只能到方法名,不能到方法体调试,这是一件很痛苦的事情,如图3.2所示。
图3.2 在方法体中的断点式无法进入的
仔细研究后发现CS的class文件为了反调试都去掉了行号,没有行号之后就不能在方法体内部进行调试,只能调试到方法名,而且这种机制目前并没有什么好的办法解决。
Java的代码都是很复杂的,内部逻辑很多,如果不能有效的调试,特别是涉及到内部还有多线程并发的操作,就会显得尤其痛苦。因为一些其他的事情,本来好多天都没再看这个代码了,后来二哥又给提示说,最终的利用链就是setURI,并且不需要用很复杂的分析技术,只是看了一些官方的demo就复现成功了,这个意料之中又让人惊喜的结果让我又重拾了对这个漏洞复现的兴趣。
事实上这个官方的demo我到现在都没找到,但是却受到了一些思路启发,单纯的看代码并不是最高效的解决方案,可以和一些实际demo相结合,特别是对于当前调试有一些技术上障碍的情况下。
首先我们明确最终的利用链是
org.apache.batik.swing.JSVGCanvas-->setURI
这个方法的功能是设置SVG图片的地址,远程加载SVG图片。熟悉前端攻防的人其实对SVG是不陌生的,这是一个经常用于特殊场景下XSS绕过的标签。如何来利用SVG来加载执行JAVA代码呢?这在网上是肯定搜索不到的,但是如何利用SVG来加载Javascript代码,这个是很容易找到的。毕竟java和javascript的关系好比雷锋和雷峰塔的关系
,如图3.3所示,
图3.3 通过SVG来加载javascript代码
这个代码很容易让我们想到,把JS的代码按照ScriptEngineManager的方式转化为java代码是不是可以执行呢,如图3.4所示?
图3.4 把JS代码转化为JAVA代码
如果我在独立的环境中来执行对应的代码,是可以正常执行,确实是可以弹出计算器的,如图3.5所示。
图3.5 本地通过script标签来执行java代码
但是把这个代码放在CS的环境中来运行,却发现并不能正常运行,如图3.6所示。
图3.6 在CS环境中运行上面的代码报错
从上面的代码报错可以看出CS环境中并没有运行代码所需的javascript相关的库,所以爆了ClassNotFoundException的异常,这样当然是没有办法直接利用的。但是这个方式仍然可以帮助我们在debug的时候发现整个利用链中最关键的一个方法调用
org.apache.batik.bridge.BaseScriptingEnvironment类的loadScript方法,如图3.7所示。
图3.7 loadScripts方法加载执行代码
从上面代码的逻辑可以看出通过获取的type来走不通的分支流程,默认为text/ecmascript。这种方式可以通过javascript库来执行命令,但是由于默认CSjar包中并没有javascript库,导致这种方式并不能执行利用,但是从代码中我们可以发现还有一个分支流程是type=application/java-archive的情况,如果进入这个分支流程,会是一个什么逻辑呢,如图3.8所示
图3.8 application/java-archive分支的代码加载流程
从代码中可以看出这里逻辑大体上是加载一个远程URL地址的jar包,然后loadClass加载var13对应的类。如果整个过程完成可控,那么我们就可以通过URLClassLoader来达到RCE的效果,那么我们整个利用链就活了。
跟踪getXLinkHref来看具体的URL地址是如何取出的,如图3.9所示。
图3.9 获取远程待加载的URL地址
从图中可以看出URL地址来自于namespaceURL为http://www.w3.org/1999/xlink的href属性。按照相似的办法来构造SVG文件的内容,如图3.10所示。
图3.10 改造后的SVG文件内容
在此之后我们也只是进入了对应的流程中,还需要解决几个对应的条件判断,如图3.11所示。
图3.11 要最终RCE需要满足的条件
其中条件1是checkCompatibleScriptURL,跟进对应的方法,一直对应的跟下去,就会发现最终会进入到DefaultScriptSecurity,如图3.12所示。其中var2和var3分别对应远程svg文件地址和远程jar包地址,要求这两个地址必须host相同,这就很好办到了。
图3.12 checkCompatibleScriptURL检查的核心逻辑
条件2和条件3可以一起来看,都是检查配置文件META-INF/MANIFEST.MF中的配置项,我下意识的以为META-INF/MANIFEST.MF是指CS的jar包中的配置,所以一直觉得这里的判断是不可能绕过的,后来才知道原来这个META-INF/MANIFEST.MF是指远程jar包中的配置文件,和CS无关,属于我们可控的部分,那么整个攻击链就完全打通了。我们需要修改远程jar包中的META-INF/MANIFEST.MF文件,配置其中Script-Handler的值为需要远程加载的恶意类的类名,如图3.13所示。
图3.13 指定要远程加载的恶意类名称CodeSource
至于CodeSource.class文件,则是经过编译之后的恶意类,只需要在静态代码块或者无参构造函数中填写对应的恶意代码即可,如图3.14所示。
图3.14 最终执行的恶意类CodeSource
最后我们完整的来总结一下整个利用过程,需要准备好恶意的SVG文件和恶意的jar包,jar包中包含恶意类CodeSource(其他名字也可以,但是必须和配置文件中的名字对应),放置在一台服务器上,恶意的jar包中需要修改META-INF/MANIFEST.MF配置文件。最后我们来看一下运行的效果,如图3.15所示。这里面会有一些报错,但是实际上不影响执行,想要不报错把恶意类继承ScriptHandler就可以了。
图3.15 利用object标签加载远程加载恶意类
0x04 回归
为了保持故事的完整性,我还是继续对CS利用部分进行了复现,这部分的内容事实上漂亮鼠大佬已经在文章中给出了,我只是简单的说几句。
文中给出了两种可能的利用方式,一种是在首页通过frame标签来绕过117个字节的长度限制。但是我在实际测试的过程中发现无论我怎么写都会在setParent方法中报异常转换的错,如图4.1所示。本来想继续死磕一下这个frame的原理,但是后来发现另一种方式直接就成功了,所以这里暂时忽略。
图4.1 使用farme标签报错
另外一种方式是通过hook windows api的方式来传输恶意payload,如图4.2所示。
图4.2 复现最终CS RCE合影留念
0x05 结论
这个漏洞号称是脚本小子杀手,喜欢用CS的朋友们瑟瑟发抖。复现这个漏洞也是一个很有意思的事情,让我学到了很多关于Swing和SVG的知识,这篇文章也希望能帮助大家对此类漏洞的调试、复现等过程有更深入的理解。