前言
Jira是Atlassian公司出品的项目与事务跟踪工具,被广泛应用于缺陷跟踪、客户服务、需求收集、流程审批、任务跟踪、项目跟踪和敏捷管理等工作领域。
近日,Jira 官方网站上公布了一个代号为 JRASERVER-69793 的 issue,CVE 编号 CVE-2019-8451,描述指出,Jira 的 /plugins/servlet/gadgets/makeRequest 资源存在 SSRF 漏洞,原因在于 JiraWhitelist 这个类的逻辑缺陷,攻击者通过此漏洞在未授权的情况下访问 Jira服务器所处的内网资源。
漏洞分析
根据描述,漏洞位于 /plugins/servlet/gadgets/makeRequest 页面,直接访问后返回 404
![404](https://jenisec.nos-eastchina1.126.net/Jira/404.png)
应该是该接口在访问上做了某些限制,查看 web.xml 后发现,所有 /plugins/servlet/* 链接都交由 ServletModuleContainerServlet 类处理了
![web-xml](https://jenisec.nos-eastchina1.126.net/Jira/web-xml.png)
找到 ServletModuleContainerServlet,反编译后发现它继承了父类的逻辑
![SMCS](https://jenisec.nos-eastchina1.126.net/Jira/SMCS.png)
其父类在 lib/atlassian-plugins-servlet-x.x.x.jar 中,其中 service 看起来应该是处理 http 请求的方法了。在函数首下断
![service](https://jenisec.nos-eastchina1.126.net/Jira/service.png)
请求 makeRequest 接口,成功断在了断点处,this.getServletModuleManager() 不为空,因此进入 else 代码块,单步调试。在 servlet 赋值处调用了 getPathInfo 方法获取了请求中的接口地址
![pathinfo](https://jenisec.nos-eastchina1.126.net/Jira/pathinfo.png)
返回结果作为参数传递给 getServlet 方法,该方法通过接口地址获取到了对应 Servlet 的描述信息,传递给 getServlet 方法去获取最终的 Servlet
![descriptor](https://jenisec.nos-eastchina1.126.net/Jira/descriptor.png)
描述信息中的 Servlet class 指向 com.atlassian.gadgets.shindig.servlet.XsrfMakeRequestServlet,反编译 XsrfMakeRequestServlet 类,查看其代码逻辑
![XSRF](https://jenisec.nos-eastchina1.126.net/Jira/XSRF.png)
可以看出,doGet 方法检查了请求头中是否存在 X-Atlassian-Token: no-check 项,不存在则返回 404 错误。这也就解释了为什么直接请求 makeRequest 接口会提示找到了死链接。
在请求头中添加 X-Atlassian-Token: no-check 后再次请求,页面返回 400 错误,提示缺少 url 参数
![400](https://jenisec.nos-eastchina1.126.net/Jira/400.png)
添加 url 参数,设置一个地址,请求后返回 403 错误,提示请求中的链接不被允许。
![403](https://jenisec.nos-eastchina1.126.net/Jira/403.png)
其实这里找到 JiraWhitelist 类下断就可以直接看出问题出在了哪,但还是追一下代码,梳理一下代码逻辑吧(实际操作时我是从 JiraWhitelist 逆着推的,这里正向写是为了方便理解)。
绕过 XSRF 验证之后,成功进入 else 分支,此时调用父类的 doGet 方法。
![super_doget](https://jenisec.nos-eastchina1.126.net/Jira/super_doget.png)
跟入 fetch,这个方法是用来生成响应的。传入的 request 通过 buildHttpRequest 构造完整的 HTTP 请求,之后传入 contentFetcherFactory.fetch 方法获取响应。继续跟入,寻找处理 request 的方法
![contentFetcherFactory](https://jenisec.nos-eastchina1.126.net/Jira/contentFetcherFactory.png)
![contentFetcherFactory_fetch](https://jenisec.nos-eastchina1.126.net/Jira/contentFetcherFactory_fetch.png)
![super_fetch](https://jenisec.nos-eastchina1.126.net/Jira/super_fetch.png)
![execute](https://jenisec.nos-eastchina1.126.net/Jira/execute.png)
这里对 response 进行了赋值,说明请求流程在 execute 方法中
![ShindigApacheClientAdapter](https://jenisec.nos-eastchina1.126.net/Jira/ShindigApacheClientAdapter.png)
![mapResponse](https://jenisec.nos-eastchina1.126.net/Jira/mapResponse.png)
![HR_execute](https://jenisec.nos-eastchina1.126.net/Jira/HR_execute.png)
这个 validateRequestTargetAgainstWhitelist 就是用来验证链接是否在白名单中的方法
![validateRequestTargetAgainstWhitelist](https://jenisec.nos-eastchina1.126.net/Jira/validateRequestTargetAgainstWhitelist.png)
跟进 whiltelist.allows 方法
![allows](https://jenisec.nos-eastchina1.126.net/Jira/allows.png)
![fin_allows](https://jenisec.nos-eastchina1.126.net/Jira/fin_allows.png)
uriString 的值是从请求中获取的,canonicalBaseUrl 的值是通过 getCanonicalBaseUrl() 方法获取到的 Jira 服务的请求链接。allows 函数在 return 位置做了或关系的断言,uriString.startsWith(canonicalBaseUrl) || this.whitelistService.isAllowed(uri),其中 startsWith 方法用于获取 url 中的 host。也就是说,只要满足 urlString 以 canonicalBaseUrl 开头或者 uri 在白名单中,allows 方法就会返回真,服务器则会正常请求这个 url。因此这里对第一个条件进行恶意构造,将请求中的 url 参数值以 Jira 服务的 host 作为开头,然后使用 @ 将请求指向其他地址,即可完成 SSRF 攻击。
![success](https://jenisec.nos-eastchina1.126.net/Jira/success.png)
参考
https://jira.atlassian.com/projects/JRASERVER/issues/JRASERVER-69793?filter=allissues
https://www.seebug.org/vuldb/ssvid-98074
作者: JenI 转载请注明出处,谢谢
Comments !