本文作者 77@ QAX A-TEAM
![8867abc26ce728ea5a77b60ec1dc7c8f.gif](https://i-blog.csdnimg.cn/blog_migrate/72cbc2d6809ca773fc1c89ae7f2b9c07.gif)
Confluence的WebDAV插件(< 5.0.4版本)在接收到用户向其请求/webdav开头的url时,会对此请求做"反向代理",服务端将/webdav/xxx的url转换为/plugins/servlet/confluence/default/xxx之后向用户指定的Host发起请求,然后将结果返回给客户端。漏洞成因在于对用户提供的Host请求头没有做验证。
![8867abc26ce728ea5a77b60ec1dc7c8f.gif](https://i-blog.csdnimg.cn/blog_migrate/72cbc2d6809ca773fc1c89ae7f2b9c07.gif)
声明:本篇文章由 77@ QAX A-TEAM原创,仅用于技术研究,不恰当使用会造成危害,严禁违法使用 ,否则后果自负。
QAX A-TEAM背景
2019年4月看到这个Atlassian官方安全公告的时候,好像大家关注的主要是CVE-2019-3396,而忽略了CVE-2019-3395。而且到目前为止,除了官方的描述(以及对官方描述的翻译),网上没有关于这个漏洞的更多描述。(除了瞩目的Orange大佬的id
官方是这么描述的:
A remote attacker is able to exploit a Server-Side Request Forgery (SSRF) vulnerability in the WebDAV plugin to send arbitrary HTTP and WebDAV requests from a Confluence Server or Data Center instance.
简单来说就是,由于WebDAV插件的漏洞,攻击者可以从受影响的Confluence服务器发起任意HTTP/WebDAV请求。
信息搜集
拿到这个漏洞,先要知道是什么插件什么功能的漏洞。先熟悉了一下WebDAV的知识,知道它是一个类似HTTP的协议,或者说它扩展了HTTP协议。
通过搭建环境,先连接到Confluence的WebDAV服务, 体验了一下WebDAV服务的功能。通过Confluence的WebDAV服务,用户可以方便地使用本地自带的文件管理工具Finder(macOS)或者文件管理器(Windows)批量增、删、移动一些页面/附件等。
通过Finder增删改查这些文件和目录然后抓包发现,这种看似本地操作文件式的访问过程其实是使用WebDAV协议基于HTTP Basic认证通过网络通信来完成的。
后续知道了/webdav
这个url,随便发起了一个/webdav
开头的请求,在服务端抓包发现Confluence服务确实收到了两个HTTP请求。
然后开始定位漏洞点。开始猜测可能是这个插件使用了Apache Jackrabbit作为WebDAV插件, 而它之前出过一个XXE(CVE-2015-1833)https://www.exploit-db.com/exploits/37110 会不会是XXE导致的SSRF呢?尝试按照这个利用方式构造请求,但是发现并不能利用成功。后来查看webdav插件的内容,发现它使用的Jackrabbit是1.4版本,而CVE-2015-1833的影响范围是2.x。 于是搁置了一段时间。最近又捡起来,看看有没有新的线索。google搜索webdav ssrf关键词果然有新发现。这次找到了这个插件的官方git仓库。通过查找commit信息找到了专门修复这个issue的分支。
然后通过阅读commit信息,漏洞修复工作应该是从5.0.3版本开始的,从701ef5f60d1aceaa33f276ec95728a4c126208c5开始到8bbc105d5613b48d37f805f3c11256a7603e03ac修复完成然后发布5.0.4版本。
推测受此漏洞影响的WebDAV插件版本应该< 5.0.4
通过查看diff信息,发现改动了一个关键的Filter: com/atlassian/confluence/extra/webdav/servlet/filter/ReverseProxyFilter.java
漏洞调试
使用atlassian-confluence-6.9.3自带的5.0.0版本的WebDAV插件,配合其官方源代码(https://bitbucket.org/atlassian/confluence-webdav-plugin) 进行调试。在这个Filter的doFilter
下断点。主要流程都在这里了:
整体看一下,看到那个HttpClient了,那里是发起HTTP请求的地方,而想进入那里,需要跳过前面两个if条件判断。
先看第一个if,如果请求方法是OPTIONS
且url是根目录则不需要进行认证,直接返回OPTIONS /
的结果。
再看第二个if, 其条件是url不以
/webdav
开头,或者via
请求头的值含有localhost
。PS:判断via
请求头的值是否含有localhost
,来标识是否之前已经处理过(hasHandledBefore
)?(后来经过分析发现原来每次对/webdav
反向代理之后会在请求头中加上via: HTTP/1.1 localhost
,用于标识这个经过"反向代理"处理之后的请求)
所以为了跳过前两个if条件,需要url以/webdav
开头,且via
请求头不含有localhost
。
跳过前面两个if判断之后继续跟进。进入请求的构造过程。 先通过
getMethod(httpServletRequest)
将用户的/webdav
开头的url转换成对应的/plugins/servlet/confluence/default
开头的url。
下面分两步,接收用户发起的请求,然后将其作为参数通过prepareProxiedMethod
方法构造待发起的请求(主要是请求头)。
第一步,通过prepareProxiedMethodHeaders
方法将用户发起的请求头直接加入到发起下一个请求的请求头列表中。
这里信任了用户提供的Host头,并且没有验证合法性直接作为下一个请求的Host请求头!
第二步,通过prepareProxiedMethodProxyHeaders
加上自带的一些请求头,似乎是一般反向代理时需要加的请求头?反正对造成漏洞不是必要的。
最后是发起请求:
再来看看响应是如何构造的。在429行通过
httpClient.executeMethod(proxiedMethod);
发起请求后,在431行进入给客户端构造响应的步骤。
File[] responseTmpFiles = prepareResponse(proxiedMethod, httpServletResponse);
进入prepareResponse
方法,如图:
将HttpClient发起的请求的响应状态码、响应头分别设置为客户端的响应状态码和响应头,然后将响应内容写入到临时文件,计算这个临时文件的长度作为给客户端响应的响应头的Content-Length
字段的值,然后释放连接将响应发给客户端。 最后删除临时文件。
Demo
目前的这个PoC可以从Confluence对公网、局域网和本地地址的任意端口发起HTTP/WebDAV请求,会自带几个请求头,且可指定自定义的请求头,可指定一定的HTTP动词(不止GET和POST),有回显。不过path部分会固定以
/plugin/servlet/confluence/default
开头。
总结
Confluence的WebDAV插件(< 5.0.4版本)在接收到用户向其请求/webdav
开头的url时,会对此请求做"反向代理",服务端将/webdav/xxx
的url转换为/plugins/servlet/confluence/default/xxx
之后向用户指定的Host发起请求,然后将结果返回给客户端。而漏洞成因关键在于对用户提供的Host
请求头没有做验证,导致可通过HttpClient
向用户指定的Host建立TCP连接,然后发起了一次其Host请求头为这个用户指定的任意值的HTTP请求,造成了服务端请求伪造漏洞。由于用户的凭据是服务端向/plugins/servlet/confluence/default/ssrf
发起请求时用到的,而发起这个请求本身的条件并不需要任何登录凭据,所以这个漏洞是不需要认证即可触发的。
PS:NVD和Atlassian官方的issue里的影响版本和修复版本并不准确,经测试发现6.8.5
和6.9.3
使用的是受影响的webdav插件,是存在漏洞的版本。
不过我没有想通,为什么NVD给了9.8的高分?可能有更厉害的利用方式?
漏洞修复
使用自带webdav插件为5.0.4版本(修复版)的atlassian-confluence-6.13.4进行测试,发现将原来的ReverseProxyFilter
改成了WebdavRequestForwardFilter
。 从名字上看意思是原来的过滤器用于“反向代理”,而修改之后现在是用于“转发”。
这个Filter在doFilter方法中在处理/webdav
开头的url时,也先是将用户的/webdav
开头的url转换成对应的/plugins/servlet/confluence/default
开头的url。不过这次调用org/apache/catalina/core/ApplicationDispatcher.java
的forward
方法来对/plugins/servlet/confluence/default
开头的请求进行转发(forward)。
通过阅读源码的注释了解到ApplicationDispatcher
这个类主要进行的是应用内部的转发,不涉及到网络层面的转发。
然后通过几个Filter之后交给com/atlassian/confluence/extra/webdav/servlet/ConfluenceWebdavServlet.java
进行处理。下面就是普通的对/plugins/servlet/confluence/default
开头url进行处理的流程了。
进行正常的登录凭证认证,没有Cookie就看HTTP Basic认证头。如果都没有,则抛出DavException 然后被外部的catch捕获,
最后响应
401 Unauthorized
。
参考
https://jira.atlassian.com/browse/CONFSERVER-58070
https://jira.atlassian.com/browse/CONFSERVER-57971
https://confluence.atlassian.com/doc/confluence-security-advisory-2019-03-20-966660264.html
https://confluence.atlassian.com/doc/use-a-webdav-client-to-work-with-pages-200704169.html
https://confluence.atlassian.com/doc/configuring-a-webdav-client-for-confluence-148044.html
https://bitbucket.org/atlassian/confluence-webdav-plugin/commits/branch/issue/CONFSERVER-57488-pre-auth-ssrf?page=1
https://bitbucket.org/atlassian/confluence-webdav-plugin/commits/701ef5f60d1aceaa33f276ec95728a4c126208c5?at=issue/CONFSERVER-57488-pre-auth-ssrf
https://bitbucket.org/atlassian/confluence-webdav-plugin/commits/b338cd29bd01fc90c3637525c1a17eede02e079c