Sec Hotspot 首页  排行榜  收藏本站  技术博客  RSS
统计信息
已收录文章数量:15724 篇
已收录公众号数量:90 个
本站文章为爬虫采集,如有侵权请告知
已收录微信公众号
网安寻路人 网信中国 区块链大本营 白说区块链 区块链投资家 区块链官微 区块链铅笔Blockchain HACK学习呀 二道情报贩子 合天智汇 小白帽学习之路 小米安全中心 弥天安全实验室 SAINTSEC SecPulse安全脉搏 TideSec安全团队 360安全卫士 游侠安全网 计算机与网络安全 安全祖师爷 安全学习那些事 腾讯安全联合实验室 黑客技术与网络安全 安全圈 腾讯御见威胁情报中心 Python开发者 Python之禅 编程派 Python那些事 Python程序员 安全威胁情报 吾爱破解论坛 行长叠报 安在 i春秋 嘶吼专业版 E安全 MottoIN 网信防务 网安杂谈 数说安全 互联网安全内参 漏洞战争 安全分析与研究 邑安全 ChaMd5安全团队 天融信阿尔法实验室 安全牛 SecWiki 安全学术圈 信安之路 漏洞感知 浅黑科技 Secquan圈子社区 奇安信集团 奇安信 CERT 国舜股份 雷神众测 盘古实验室 美团安全应急响应中心 瓜子安全应急响应中心 顺丰安全应急响应中心 蚂蚁金服安全响应中心 携程安全应急响应中心 滴滴安全应急响应中心 字节跳动安全中心 百度安全应急响应中心 腾讯安全应急响应中心 网易安全应急响应中心 OPPO安全应急响应中心 京东安全应急响应中心 Bypass CNNVD安全动态 安恒应急响应中心 天融信每日安全简报 奇安信威胁情报中心 看雪学院 黑白之道 水滴安全实验室 安全客 木星安全实验室 云鼎实验室 绿盟科技安全预警 白帽汇 深信服千里目安全实验室 腾讯玄武实验室 长亭安全课堂 FreeBuf 绿盟科技 nmask
【CVE-2019-8451】Jira未授权SSRF漏洞分析&复现(附超详细JDB调试过程)
本文来自公众号:国舜股份   2019.10.01 00:01:15


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(uri);  }

ImmutableSet.of(this.whitelist, this.messageBundleWhiteList)创建一个不可变的set集合,然后和this.optionalWhitelists对象作为参数传入Iterables.concat方法,concat方法的作用是把传入的多个Iterable对象串联成懒视图,返回一个Iterable对象。最后把这个对象和Predicate对象作为参数传入Iterables.any方法,any方法的作用是判断Iterable中是否有元素满足断言条件,如果满足,就返回true并立即停止后续判断。
简单理解就是,传入Iterables.any方法的参数A是个迭代器对象,会依次迭代元素和参数B进行断言,参数B是个Predicate对象,断言条件声明在apply方法里面,下面写段demo代码帮助理解:

理解了Google Guava,我们回到代码中,可以看见其声明的断言条件如下图第44行代码,迭代出Whitelist元素对象,调用它的allows方法进行判断,从第29行的Iterables.concat方法可知,需要迭代的元素分别是this.whitelist、this.messageBundleWhiteList和this.optionalWhitelists:

在第44行代码下断点,调试一下,发现前两个元素断言返回false,符合条件的是第三个this.optionalWhitelists迭代器中的Whitelist对象(

第三次cont命令才使程序正常运行

):


Whitelist对象是动态代理生成的,接下来就是需要找出对象的具体实例了,这样才能分析它allows方法的具体实现,直接print动态对象的变量,获取到Whitelist的具体实例 com.atlassian.jira.dashboard.JiraWhitelist :

反编译com.atlassian.jira.dashboard.JiraWhitelist.class,位置在WEB-INF\classes目录下,其allows方法详情如下:

在26行下个断点,获取到uriString和canonicalBaseUrl的值,return的时候有一个逻辑或的断言,如果满足请求的目标url是以应用的canonicalBaseUrl前缀开始,则返回true,所以,如果url参数值是以 http(s)://应用服务IP:端口 打头,那就可以让服务器正常请求这个url目标了。

0x03 漏洞复现

根据漏洞分析,我们知道请求的目标url只要是以应用的host开头,就可以通过白名单验证,使服务器正常发起http请求。构造poc如下:

/plugins/servlet/gadgets/makeRequest?url=http://ip:port@www.baidu.com

测试一下,成功绕过白名单检测,使服务器访问攻击者指定的url目标:

官方在8.4.0版本中已经修复该漏洞,修复后的代码逻辑如下,不再是简单的字符串前缀比较:

0x04 结语

漏洞本身比较简单,但是整个分析过程还是挺有意思的,文章内容写的比较啰嗦,希望看见文章的同学不留疑惑,避免踩坑。笔者也是个小菜鸟,很多不专业的地方,如有错误,还请指教。

References:

https://www.seebug.org/vuldb/ssvid-98074


发现 | 发掘

为随时发生的网安动态发声



资讯|干货|案例|威胁|动态