问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
从挖矿木马事件到Confluence 远程代码执行漏洞(CVE-2021-26084)分析
漏洞分析
客户服务器感染挖矿木马,分析后发现是利用Confluence RCE漏洞获取服务器权限,近几个月已经遇到了好几起利用Confluence RCE漏洞进而挖矿的事件,于是准备分析一波漏洞成因。
挖矿木马事件 ====== 1、在态势感知查看事件,发现有攻击者利用CVE-2021-26084漏洞进行攻击。 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-feb95e0ec38b6b785f39296800ca84b67a0ae4e2.png)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-feb95e0ec38b6b785f39296800ca84b67a0ae4e2.png) 2、进入服务器中,查看进程该服务器存在定时计划任务,会向一个服务器下载脚本文件http://45.145.185.85/ldr.sh [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-99008c5b67c9ca60b420354e7b4cb4ea017658de.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-99008c5b67c9ca60b420354e7b4cb4ea017658de.jpg) 3、服务器中存在恶意进程kthreaddi,可以确认为挖矿程序 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-c0ec86a7bf3fe24abc4503f2447bf33c38ad0012.png)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-c0ec86a7bf3fe24abc4503f2447bf33c38ad0012.png) 4、在进程以及其它信息中可以发现更多挖矿病毒信息 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-872a2de1a27058611048aaccb2f88e37e0c88f96.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-872a2de1a27058611048aaccb2f88e37e0c88f96.jpg) 5、通过开源情报信息和这些些特征信息做对比,明确该机器被Sysrv-hello僵尸网络所感染。通过对ip:45.145.185.85对威胁情报进行信息查询,发现该ip有Sysrv-hello僵尸网络标签,而且该服务器属于主控服务器,用于提供远程挖矿脚本服务。 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-34cd03b977a25843e8a8241ea71d79c5ca06ba03.png)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-34cd03b977a25843e8a8241ea71d79c5ca06ba03.png) 漏洞复现 ==== 1、成功执行运算。 ```html POST /pages/doenterpagevariables.action HTTP/1.1 Host: ip:8090 Accept-Encoding: gzip, deflate Accept: */* Accept-Language: en User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 90 queryString=%5cu0027%2b%7b3*3%7d%2b%5cu0027 &linkCreation=%5cu0027%2b%7b3*4%7d%2b%5cu0027 ``` `/pages/doenterpagevariables.action`该接口是不需要登录的,有两个参数是可以利用的,一个是`queryString`,一个是`linkCreation`,初步来看两个的成因应该都是一样的 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-f1df83c33c8f6c6bafaf3c553ca70d761b05ac3c.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-f1df83c33c8f6c6bafaf3c553ca70d761b05ac3c.jpg) 2、poc:[https://github.com/r0ckysec/CVE-2021-26084\_Confluence](https://github.com/r0ckysec/CVE-2021-26084_Confluence) 代码执行成功 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-07cc1272153b2c79d553b5c16c0c672100e8c8ed.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-07cc1272153b2c79d553b5c16c0c672100e8c8ed.jpg) 漏洞分析 ==== 近几个月已经遇到了好几起因为CVE-2021-26084而引发的挖矿木马事件,之前一直就想要分析一下,就趁这次机会使用debug模式对docker下的Confluence进行调试分析漏洞成因。 DEBUG教程 ------- 保姆式Confluence docker镜像DEBUG教程 ```php 拉取centos镜像 [root@localhost ~]# sudo docker pull centos 查看本地镜像 [root@localhost ~]# docker images 启动相应镜像 [root@localhost ~]# sudo docker run -d -p 8090:8090 -p 5050:5050 --privileged=true --name confluence_7.12.4_privileged 5d0da3dc9764 /usr/sbin/init 列出所有容器 [root@localhost ~]# docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a6a5a6729cc5 5d0da3dc9764 "/usr/sbin/init" 4 minutes ago Up 3 minutes 0.0.0.0:5050->5050/tcp, 0.0.0.0:8090->8090/tcp confluence_7.12.4_privileged 进入相应容器 [root@localhost ~]# sudo docker exec -it a6a5 bash 下载JDK [root@a6a52ef8aaf8 /]# yum install java-11-openjdk-devel -y 设置JAVA_HOME [root@a6a52ef8aaf8 /]# vim ~/.bashrc 添加到最后一行 JAVA_HOME=/usr/lib/jvm/java-11-openjdk-11.0.12.0.7-0.el8_4.x86_64 下载mysql [root@a6a52ef8aaf8 /]# yum install -y mysql mysql-server 启动mysql服务 [root@a6a52ef8aaf8 /]# service mysqld start 进入数据库 [root@a6a52ef8aaf8 /]# mysql 创建数据库 mysql> create database confluence; 设置编码 mysql> ALTER DATABASE confluence CHARACTER SET utf8 COLLATE utf8_bin; 设置事务隔离级别 mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED; 更改事务隔离级别 [root@a6a52ef8aaf8 bin]# vim /etc/my.cnf 将下面这段话添加到文件末尾 transaction-isolation=READ-COMMITTED 重启mysql服务 [root@a6a52ef8aaf8 bin]# service mysqld restart 下载Confluence [root@a6a52ef8aaf8 /]# wget https://product-downloads.atlassian.com/software/confluence/downloads/atlassian-confluence-7.12.4.tar.gz 解压到opt路径下 [root@a6a52ef8aaf8 /]# tar -xzvf atlassian-confluence-7.12.4.tar.gz -C /opt/ 更改home目录 [root@a6a52ef8aaf8 atlassian-confluence-7.12.4]# vim confluence/WEB-INF/classes/confluence-init.properties 将下面这段话添加到文件末尾 confluence.home=/opt/confluence/data 添加debug端口 [root@a6a52ef8aaf8 atlassian-confluence-7.12.4]# cd bin [root@a6a52ef8aaf8 bin]# vim setenv.sh 将下面这段话添加到文件倒数第二行 CATALINA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5050 ${CATALINA_OPTS}" 下载mysql驱动 [root@a6a52ef8aaf8 lib]# wget https://cdn.mysql.com//Downloads/Connector-J/mysql-connector-java-8.0.26.tar.gz 解压添加到lib目录 [root@a6a52ef8aaf8 lib]# tar -xzvf mysql-connector-java-8.0.26.tar.gz -C /atlassian-confluence-7.12.4/lib/ 运行启动脚本 [root@a6a52ef8aaf8 atlassian-confluence-7.12.4]# cd bin [root@a6a52ef8aaf8 bin]# sh startup.sh 访问ip:8090 申请试用 数据库选择mysql,地址172.0.0.1,数据库confluence,账号root,密码为空 confluence创建完成 将源代码从容器中复制 [root@localhost ~]# docker cp a6a52ef8aaf8:/opt/atlassian-confluence-7.12.4/confluence confluence 然后传到本地,使用IDEA打开 配置remote Host为服务器IP。 Port为5050端口。 将WEB-INF目录下的lib、atlassian-bundled-plugins、atlassian-bundled-plugins—setup加入依赖库 ``` 官方修复 ---- 官网关于漏洞的通报https://confluence.atlassian.com/doc/confluence-security-advisory-2021-08-25-1077906215.html 下载漏洞补丁 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-8151d0c2b5200d1d7f8b513ab3023cffbbb9d488.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-8151d0c2b5200d1d7f8b513ab3023cffbbb9d488.jpg) 将脚本文件内容提取一下,发现主要更新了以下五个文件 ```php # File 1 of 5 sed $SEDFLAGS 's/(Enable dark feature.+value=)[^"]+"/\1featureKey"/' confluence/users/user-dark-features.vm; # ###################################### # File 2 of 5 sed $SEDFLAGS 's/("Hidden" "name=.token." "value=)[^"]+"/\1token"/' confluence/login.vm; # ###################################### # File 3 of 5 sed $SEDFLAGS 's/("Hidden" "name=.([a-zA-Z]+)." "value=).\$[!l][^"]+"/\1\2"/' confluence/pages/createpage-entervariables.vm; # ###################################### # File 4 of 5 sed $SEDFLAGS 's/("Hidden" "name=.([a-zA-Z]+)." "value=).\$[!l][^"]+"/\1\2"/' confluence/template/custom/content-editor.vm; sed $SEDFLAGS 's/("Hidden" "id=sourceTemplateId.*value=)[^"]+"/\1templateId"/' confluence/template/custom/content-editor.vm; # ###################################### # File 5 of 5 sed $SEDFLAGS 's/("Hidden" "id=syncRev.*value=)[^"]+"/\1syncRev"/' $TMP_EXTRACT_DIR/templates/editor-preload-container.vm; ``` 修改取消了所有漏洞点参数中的$符号,程序不会再对前端获取到的值进行Ognl表达式计算,也就不存在Ognl表达式注入导致命令执行的漏洞了。 ```php confluence/users/user-dark-features.vm value='$!action.featureKey' -> value=featureKey confluence/login.vm value='$!action.token' -> value=token confluence/pages/createpage-entervariables.vm 重点重点重点!!! #tag ("Hidden" "name='queryString'" "value='$!queryString'") -> #tag ("Hidden" "name='queryString'" "value=queryString") #tag ("Hidden" "name='linkCreation'" "value='$linkCreation'") -> #tag ("Hidden" "name='linkCreation'" "value=linkCreation") confluence/template/custom/content-editor.vm 将多个value值的$!删除 confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader*.jar中的templates/editor-preload-container.vm value='$!{action.syncRev}' -> value=syncRev ``` OGNL ---- ### 先简单了解一下什么是OGNL OGNL具有三要素:expression、根对象root和context。 ```php expression:expression表达式告诉OGNL需要执行什么操作; root:OGNL可以对root进行取值或写值等操作, context:context可以理解为对象运行的上下文环境,context以MAP的结构、利用键值对关系来描述对象中的属性以及值; ``` ### OGNL表达式注入 Ognl 的`getValue 和 setValue` 都可以用来执行代码 OGNL表达式的`getValue()`解析过程就是先将整个OGNL表达式按照语法树分为几个子节点树,然后循环遍历解析各个子节点树上的OGNL表达式,其中通过`Method.invoke()`即反射的方式实现任意类方法调用,将各个节点解析获取到的类方法通过`ASTChain`链的方式串连起来实现完整的表达式解析、得到完整的类方法调用。 简单地说,在`getValue`中形如 `()(a)(b)` 的表达式在 ognl 处理的时候会先计算`()(a)`,然后返回带有 payload 的 `ASTEval` 树,再以`b` 为`root`计算 `AST` 树,最终执行命令。 ### 常用 paylaod: ```php //获取context里面的变量 #user #user.name //使用runtime执行系统命令 @java.lang.Runtime@getRuntime().exec("calc") //使用processbuilder执行系统命令 (new java.lang.ProcessBuilder(new java.lang.String[]{"calc"})).start() //获取当前路径 @java.lang.System@getProperty("user.dir") ``` ### 分析调用 看到OGNL表达式注入,就找`xwork-core-*.jar`,然后去寻找`getValue()`函数,我们在`Confluence的xwork-core-*.jar`包中找到了两个文件中存在三个`getValue()`函数 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-8de302f4dfb8f305ce1697a44f05792f1205603d.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-8de302f4dfb8f305ce1697a44f05792f1205603d.jpg) 我们给这三处打上断点进行调试,发现最终停在了OgnlValueStack.findValue()处。 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-d44bac254fc41be8b77aa650877504d93e3c2f59.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-d44bac254fc41be8b77aa650877504d93e3c2f59.jpg) 这里简单调试分析下`Ognl.getValue()`解析OGNL表达式的过程。 在`Ognl.getValue`代码处打下断点,往下调试,调用了`OgnlUtil.compile(expr)` [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-6706632c589f17c68c0321c22ab61d295f39a4a5.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-6706632c589f17c68c0321c22ab61d295f39a4a5.jpg) 进入到`OgnlUtil.compile(expr)`中,看到调用了`parseExpression()`函数,该函数将传入的String类型的字符串解析为OGNL表达式能理解的`ASTConst`类型: [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-197f42e10936818ec2ff13d93d6fd4f5360411d4.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-197f42e10936818ec2ff13d93d6fd4f5360411d4.jpg) 往下,将传入的`ASTConst`类型的tree参数转换成Node类型(`ASTConst继承自SimpleNode、SimpleNode继承自Node`)再调用其`getValue()`函数继续解析: [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-ecf26740a2d40bd5c735b30ea4ec0c377fcd931c.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-ecf26740a2d40bd5c735b30ea4ec0c377fcd931c.jpg) 由于tree变量就是表达式解析来的东西,因此接下来的调用中局部环境中的this变量的值就是我们的OGNL表达式的内容。往下就是调用的`SimpleNode.getValue()`函数,其中调用了`evaluateGetValueBody()`函数: [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-12e9fed6bd2362e9b7d57f7d6f57c09967b6e155.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-12e9fed6bd2362e9b7d57f7d6f57c09967b6e155.jpg) `evaluateGetValueBody()`函数,用于计算getValue体中OGNL表达式的值。跟进看是直接调用了`getValueBody()`函数 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-33a1acdc684d9f33e52a5287c6c17d00dc863508.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-33a1acdc684d9f33e52a5287c6c17d00dc863508.jpg) 在SimpleNode中会对node节点进行处理,这里中间就会发生OGNL表达式注入问题,然后传入AbstractTagDirective中,关于OGNL表达式注入的调试就先结束了。 Velocity -------- ### 1、Velocity是什么 Debug完成后,根据公开的payload,进入idea搜索一下`doenterpagevariables.action`,看到是在`pages/createpage-entervariables.vm`下 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-8326e864e795c9bf8ab11ca7ba69ec79769612e9.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-8326e864e795c9bf8ab11ca7ba69ec79769612e9.jpg) \*.vm 后缀的文件,是velocity的文件。 ```php velocity是基于java的一种页面模板引擎,支持#if #else #foreach等写法的前台文件。 Velocity 的工作原理: $开头指引用,表明一个变量或对象。 #开头指脚本语句 !一般和$一起使用,表明变量没有值时,将输出一个空字符串 {}表示声明一个velocity变量 例如:当表单加载并$email仍然没有值时,将输出一个空字符串。 <input type="text" name="email" value="$!{email}"/> 例如:Hello world <html> <正文> #set( $foo = "Hello" ) $foo world! </正文> </html> ``` ### 2、#tag Velocity 中的#tag用于生成“输入”标签,例如 `#tag ("Hidden" "name='queryString'" "value='$!queryString'")` html 输出将是: `<input type=" hidden " name=" queryString " value="<value>"/>` ```php #tag的用法大致是这样的: #tag (“attribute1” “attribute2” “attribute3”) 期间#tag的这些属性将在AbstractTagDirective 中处理 ``` ### 3、分析调用 在上面OGNL调试中,我们调试到了`AbstractTagDirective` ,我们继续。 在`AbstractTagDirective` 中首先调用`applyAttributes(contextAdapter, node, object)`处理参数,其中`AbstractTagDirective.createPropertyMap()`创建Map,保存键值对。 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-f0b4b674c2aa725b01faa30961fe68e36aecb882.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-f0b4b674c2aa725b01faa30961fe68e36aecb882.jpg) 然后`AbstractTagDirective.render()`调用`processTag()`去处理#tag [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-44098627a5a7a2b70d58f5deef6711ea80c18883.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-44098627a5a7a2b70d58f5deef6711ea80c18883.jpg) `AbstractTagDirective.applyAttributes()`会进入到`AbstractUITag`中,`AbstractUITag.doEndTag()` 去调用 `AbstractUITag.evaluateParams()`获取value值 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-af57ffeea736427781d3bb2123aa8015e013a336.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-af57ffeea736427781d3bb2123aa8015e013a336.jpg) 这个value值会被传递到`WebWorkTagSupport.findValue()`中,然后会以expr(表达式)的形式被传递到`OgnlValueFinder.findValue()`中,然后调用`SafeExpressionUtil.isSafeExpression()`进行安全检查。 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-102d4b9f3aac490102668ffeedf32f0a7cf26eb7.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-102d4b9f3aac490102668ffeedf32f0a7cf26eb7.jpg) 进入到`isSafeExpression()`中,该方法会通过`containsUnsafeExpression()`处理表达式 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-10ab376d5d89b2d80bd608b23f10d994af33d2bb.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-10ab376d5d89b2d80bd608b23f10d994af33d2bb.jpg) 我们先不管过滤机制,继续跟踪发现进入到了`OgnlValueStack.findValue()`中,最终调用`Ognl.getValue`计算表达式,就是这里引起了`OGNL表达式注入` [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-2a1278352446ea4c0ca4e5e9043e368917e363b4.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-2a1278352446ea4c0ca4e5e9043e368917e363b4.jpg) 我们上面用的payload是`测试`,然后最终到`Ognl.getValue`计算表达式里面是`"'测试'"`,那么使用`payload'测试'->"''测试''"`,不就可以造成`OGNL表达式注入`了 但是当我使用`'`的时候,我发现被转义为 html 实体形式,成为了`'` [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-aa829d05d0ae02f9210ae0a84ab716f546cc1d91.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-aa829d05d0ae02f9210ae0a84ab716f546cc1d91.jpg) 在`HtmlAnnotationEscaper`中调用`HtmlEntities`被转义 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-f685100aebc2669f5d79e0cb6677008a889de1eb.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-f685100aebc2669f5d79e0cb6677008a889de1eb.jpg) 那我们为什么可以使用“ `\u0027`”来进行绕过呢? 因为在`OgnlParserTokenManager.getNextToken()`中的 “`this.input_stream.readChar()`”会读取1个字符然后返回,当遇到“`\u` ”时,`JavaCharStream.readChar()`将其转换为字符,所以可以使用“ `\u0027`”来进行绕过html转义 然后我们的payload只需要再绕过静态方法`SafeExpressionUtil.isSafeExpression`的黑名单安全检查就可以了 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-94da9afad9af5d45ed76cfd0ba62e5f1c628c9a5.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-94da9afad9af5d45ed76cfd0ba62e5f1c628c9a5.jpg) 黑名单进行了一个简单的字符串匹配, 1. 第一个hashset限制了构造函数的关键字 2. 第二三个hashset限制了获取classloader 3. 第四个hashset限制了编译后的结果中不能出现特定的变量 那么我们就可以使用类似数组的运算符来绕过这里绕过方式可以使用数组进行绕过, 只是改变: `"".getClass().forName("java.lang.Runtime")` 为 `""["class"].forName("java.lang.Runtime")` 就可以绕过黑名单机制了 总结: === Confluence 远程代码执行漏洞的过程基本如下 [![](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-81117bd52dd11a66070e67e22118ef51bdff6076.png)](https://shs3.b.qianxin.com/attack_forum/2021/12/attach-81117bd52dd11a66070e67e22118ef51bdff6076.png)
发表于 2021-12-13 11:56:07
阅读 ( 11748 )
分类:
漏洞分析
3 推荐
收藏
0 条评论
请先
登录
后评论
苏苏的五彩棒
25 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!