Part1 前言
大家好,我是ABC_123。这是我的第101篇原创技术文章,好久没有给大家分享代码审计类的分析了。前一阵子网友发来一个从流量设备中抓到的0day漏洞,不理解其绕过CAS单点登录及Shiro组件的权限校验的原理,我也好奇分析了一下,不禁感叹这位漏洞挖掘者的决心和毅力,各种因素结合完美绕过5层的权限校验。
建议大家把公众号“希潭实验室”设为星标,同时关注我的视频号"希水涵一讲堂"。
Part2 技术研究过程
这里给出这个POC的权限校验的关键部分,URL及参数都做了模糊化处理,大家可以先思考一下:
POST /file/changeUpload.png
VersionMetadata:clientCategory
Host: xxxxxxx
第1关:URL后缀.png的路由映射问题
这个 POC 我一看就有疑问,.png 这个url能在 SpringMVC 中正常执行java应用吗?首先看一下web.xml配置文件,通过 mvc-dispatchcer 得知,该web应用是SpringMVC 应用。发现该 web 应用未对.png、.gif、.css等静态文件配置默认的 servlet 处理类。
所以当访问 /file/changeUpload.png 时,这个URL首先会被解析为 /changeUpload,然后是 .png 扩展名。@RequestMapping 注解用来定义控制器应该处理哪些URL,但是如果没有指定请求方法或扩展名,那么这个方法将处理所有以 /changeUpload 开头的 URL,无论它们是否有扩展名。由于.png没有被明确映射到其它servlet,且 /base64FileUpload 部分与根路径/匹配,因此由 mvc-dispatcher 将处理这个请求。
第2关:程序本身的权限校验绕过
接下来看下一个权限校验的 filter,这个filter是Web应用程序自己写的,注意看2个 <init-param> 标签定义的值,很明显有可能是白名单url关键字。
跟入相关的 filter 类,查看 doFilter 方法的实现,经过分析,isIgnoreUrl(httpRequest)方法是关键。
分析如下代码,在请求头中添加 VersionMetadata:clientCategory 可以使 isIgnoreUrl 条件判断返回true,则上述 fitler 代码会直接跳到 chain.doFilter(request, response); 这段代码,将请求和响应传递到下一个 filter 过滤器。
第3关:CAS权限校验绕过
接下来看一个filter类,来到了CAS单点登录的权限校验过程,跟进相关filter的类进行分析。
经过分析,这一大串 if 条件非常复杂,将其复制出来放到本地idea中调试一下,发现只要http请求头包括 VersionMetadata:clientCategory 的时候,该 if 条件包裹的一堆权限校验代码直接全部跳过。
跳过 if 权限校验语句之后,程序会执行 filterChain.doFilter(request, response); 代码,从而传递请求去下一个filter类进行权限校验,至此就完美绕过了CAS的单点登录校验。
第4关:Shiro组件第一道权限校验
接下来 filterChain.doFilter 会将request对象及 response 对象传递到如下这个Shiro组件的 shiroFilter 过滤器。
接下来从applicationContext-shiro.xml中找到了shiro组件的关于权限方面的配置文件,里面配置了很多url路由及权限校验的实现类。
查看shiro的安全过滤链配置项filterChainDefinitions,发现如下关键代码:/**表示匹配所有url路由,指定了两个自定义的过滤器来处理http请求,用作权限校验,这两个过滤器分别是 LoginAuthFilter、uapStatelessAuthcFilter。
首先分析第1个 LoginAuthFilter 过滤器,代码如下:
经过一系列的 if 条件判断,发现上述EXP的上传数据包都不符合这些 if 条件判断,因而权限判断会走到最后一步,默认返回为true,表示通过了第一个过滤器的权限校验。
第5关:shiro组件的第2道权限校验
接下来看shiro组件的第二个权限校验过滤器 uapStatelessAuthcFilter,它的实现类是 com.xxx.shiro.filter.StatelessAuthcFilterNC。
接下来查看这个类的具体实现,主要查看onAccessDenied方法,如果返回true,则表示当前用户被允许访问请求的资源。
这个类代码特别繁杂,我跟了一晚上也没发现这个exp是怎么通过这个类的权限校验的。在即将放弃的时候,发现了这段代码 super.include(hReq),由于代码很短,我跟了好几次都没仔细看,后来才发现,这里才是权限校验绕过的关键点。这小段代码调用了父类的 include 方法,并传递一个 request 对象。
如下代码通过遍历 this.esc 的方式实现了对 URL 扩展名的校验。
this.esc内容如下,定义了权限校验的白名单的扩展名:.jpg、.png、.gif、.css、.js、.jpeg,这几个扩展名都是用户登录校验的。跟到这里就非常明显了,/file/changeUpload.png的url请求中的.png符合权限校验的白名单,onAccessDenied返回为true,所以权限校验通过,无需用户名密码登录。至此,作者精心构造的EXP完全绕过5层权限校验。
Part3 总结
1. 解决.png路由访问的问题:可以在 @RequestMapping 注解中指定请求方法和扩展名,或者在 web.xml 中添加更具体的 servlet-mapping 来处理特定的扩展名。例如,可以添加一个 servlet-mapping 来处理所有 .png 文件。这将确保所有 .png 文件由默认的 servlet 处理,而不是 mvc-dispatcher。
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>.png</url-pattern></servlet-mapping>
2. 权限校验方面,对于白名单要慎之又慎。
3. 后续ABC_123会继续给大家分享java代码审计的权限校验方案的安全问题。
公众号专注于网络安全技术,包括安全咨询、APT事件分析、红队攻防、蓝队分析、渗透测试、代码审计等,99%原创,敬请关注。
Contact me: 0day123abc#gmail.com
(replace # with @)