专栏名称: 国舜股份
移动互联网时代的综合性网络安全解决方案供应商。专业的安全产品,专业的安全服务团队,全面的安全服务资质,安全不变,国舜同行。
目录
相关文章推荐
史事挖掘机  ·  1949年运钞机坠毁山野间, ... ·  昨天  
中国舞台美术学会  ·  通知丨文化和旅游部艺术司关于征集戏曲创作优秀 ... ·  5 天前  
51好读  ›  专栏  ›  国舜股份

【CVE-2019-8451】Jira未授权SSRF漏洞分析&复现(附超详细JDB调试过程)

国舜股份  · 公众号  ·  · 2019-10-01 00:01

正文

0x00 前言

Atlassian Jira是澳大利亚Atlassian公司的出品的项目与事务跟踪工具,被广泛应用于各大厂商任务跟踪、流程审批等系统。8月12号,Atlassian官方在其数据服务中心公布Jira系统中存在未授权SSRF漏洞,攻击者可以利用该漏洞未授权访问内网资源。

0x01 影响版本

Jira: version < 8.4.0 ,建议升级到8.4.0及以上版本;
Enterprise版: version < 7.13.9 ,企业版将在7.3.19版本中修复,但是该版本目前尚未发布;
官方发布的漏洞及整改详情参考:https://jira.atlassian.com/browse/JRASERVER-69793

0x02 漏洞分析

什么是SSRF

SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造好目标请求,通过控制服务器发起请求的安全漏洞。该漏洞把被控制的服务器作为跳板,借用服务器的身份访问未授权资源,通常,SSRF漏洞用于探测内网资源。概念可能有点抽象,具体攻击场景见下图,攻击者通过DMZ区的Web服务作为跳板,访问内网的其他服务。

环境准备

在Docker Hub中搜索到了Jira的container,所以可以直接使用Docker来搭建Jira应用环境,pull一个存在漏洞的Jira版本:

docker pull cptactionhank/docker-atlassian-jira:8.0.0

创建Jira应用的容器实例,把容器内Jira服务8080端口映射到本地8081:

docker run --detach --publish 8081:8080 cptactionhank/atlassian-jira:8.0.0

启动容器,访问 http://127.0.0.1:8081 ,完成Jira的初始化安装。根据官方披露的漏洞详情,存在SSRF漏洞的是/plugins/servlet/gadgets/makeRequest接口,先直接访问下接口,返回404...

既然不能直接访问,那就需要从代码中寻找返回404的原因了。进入Docker容器的shell,应用根目录为/opt/atlassian/jira:

先直接根据接口关键字 gadgets/makeRequest 搜索下对应的servlet,发现除了刚才请求的日志文件,并没有对应的class文件:

观察web.xml部署描述符文件,发现接口为 /plugins/servlet/* 的请求都会被 com.atlassian.jira.plugin.servlet.ServletModuleContainerServlet 处理:

把Docker容器中的代码复制到本地,使用jd-gui反编译ServletModuleContainerServlet.class如下:


代码中没有处理http请求的方法逻辑,那就应该在其父类中。在WEB-INF的classes目录下没有找到其父类,最后搜索发现其父类在atlassian-plugins-servlet-5.0.0.jar文件中:

反编译jar,com.atlassian.plugin.servlet.ServletModuleContainerServlet.class代码如下:


关键看第37行代码,这里先调用getPathInfo方法,代码详情如下,返回pathInfo信息,然后把pathInfo作为参数传入getServletModuleManager().getServlet()方法,这里应该是通过传入的pathInfo信息在getServlet方法里面获取到接口对应的servlet,具体JDB调试看看。

  private String getPathInfo(HttpServletRequest request) {    String pathInfo = (String)request.getAttribute




    
("javax.servlet.include.path_info");    if (pathInfo == null) {      pathInfo = request.getPathInfo();    }    return pathInfo;  }

JDB动态调试

想要调试tomcat中运行的Web程序,需要先修改tomcat启动参数,让tomcat JVM工作在debug模式。Linux环境直接修改catalina.sh文件:

CATALINA_OPTS="-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8899"


重启容器,8899端口开启说明可以使用jdb调试了。

先attach目标进程,使用stop命令设置断点,然后在burpsuite的Repeater模块重放/plugins/servlet/gadgets/makeRequest接口,程序停止到com.atlassian.plugin.servlet.ServletModuleContainerServlet的第31行(

如果发现jdb没有自动停止到断点,可以使用resume命令恢复线程

):


使用jd-gui反编译atlassian-plugins-servlet-5.0.0.jar文件,结合反编译出来的伪代码,next命令继续往下调试,执行到第37行,如下:

第37行代码比较关键,需要知道getPathInfo的返回信息和getServlet方法里面获取到的servlet,具体调试过程如下,pathInfo的值等于/gadgets/makeRequest:

step命令继续步入,得到调用getServlet()方法的实现类如下:

反编译该class,方法内部逻辑比较简单,先获取一个key,然后通过key拿到descriptor,猜测descriptor就描述了接口servlet的相关信息,具体调试看一看:

调试结果如下

这里descriptor变量是个ServletModuleDescriptor对象,看看该对象的详细信息,发现它描述了servlet class信息:

这个class应该就是gadgets/makeRequest接口对应的servlet了,grep一下,发现该servlet定义在atlassian-gadgets-opensocial-plugin-4.3.9.jar文件中:

反编译jar文件,具体代码如下:

分析代码,可以看见在拿到请求信息后,会先获取请求头的 X-Atlassian-Token 属性,检查其值是否等于 no-check ,如果不等于,就直接返回404。到这里,我们好像找到访问/plugins/servlet/gadgets/makeRequest接口返回404的原因了,添加X-Atlassian-Token属性头,绕过if判断,重放数据包,状态码从404变成了400:

下断点,继续调试,跳转到父类org.apache.shindig.gadgets.servlet.MakeRequestServlet的doGet()方法:

grep搜索父类所在的jar包,没有结果,通过findjar.com在线搜索,结果显示该类包含在shindig-gadgets-xxx.jar的jar包中,然后搜索jar包名,结果如下:

原来org.apache.shindig.gadgets.servlet.MakeRequestServlet.class所在的jar包被包含在另外一个jar包里面:


解压出来,反编译shindig-gadgets-2.1.3.jar文件,对着反编译的伪代码继续调试,最后跟到org.apache.shindig.gadgets.servlet.MakeRequestHandler.fetch()方法,代码详情如下:


继续跟踪81行的buildHttpRequest方法,代码详情如下:


114行,先获取参数url的值,然后传入validateUrl方法验证,validateUrl方法实现在其父类,代码详情如下,先判断urlToValidate是否等于null,然后验证urlToValidate是不是一个http(s)协议请求:

在validateUrl方法下个断点,urlToValidate的值等于null:


因为我们测试时发起的请求没有携带url参数,所以 request.getParameter("url") 结果肯定等于null,最后在validateUrl方法中验证失败,抛出GadgetException异常,导致返回400的错误状态码。知道返回400的原因了,构造一个满足validateUrl方法验证的url参数,重放/plugins/servlet/gadgets/makeRequest接口请求,返回成功状态码200:


继续分析代码,ProxyBase.validateUrl方法返回一个Uri对象,接着MakeRequestHandler.buildHttpRequest方法拿着Uri对象new了一个HttpRequest对象,这个HttpRequest对象的请求目标就是/plugins/servlet/gadgets/makeRequest接口中url参数的值,最终把这个HttpRequest对象传入fetch方法:


接着继续调试分析,跟踪到atlassian-gadgets-opensocial-plugin-4.3.9.jar中的 com.atlassian.gadgets.renderer.internal.http.WhitelistAwareHttpClient.execute方法:

从方法名可知,validateRequestTargetAgainstWhitelist方法的意思就是检测请求的目标是否在白名单内,代码详情如下,如果不在白名单内,就抛出IllegalHttpTargetHostException异常:

继续跟踪,定位到com.atlassian.gadgets.renderer.internal.http.DelegatingWhitelist.allows()方法,代码详情如下,这个方法需要好好看看,有点学问在里面。

public boolean allows(URI uri) {    return Iterables.any(Iterables.concat(ImmutableSet.of(this.whitelist, this.messageBundleWhiteList), this.optionalWhitelists), allowsP((URI)Preconditions.checkNotNull(uri, "uri")));}

Google Guava

什么是Guava?Google Guava是对Java API的补充,已经进化为Java开发者的基础工具箱,详情见官方WIKI。回到DelegatingWhitelist.allows()方法,一起分析下这句很长很长的代码。
Preconditions.checkNotNull(uri, "uri"),检测URI对象是否为空,如果为空就抛出异常信息"uri",返回URI对象作为参数传入allowsP方法,然后new了一个WhitelistAllows对象返回放在Predicate里面:

  private Predicate<Whitelist> allowsP(URI uri) {    return new WhitelistAllows(






请到「今天看啥」查看全文