SCTF 2020 两道Login Me预期解的核心技术
Login Me - cas 4.x excution rce(黑盒)
详细的漏洞分析可以参考Apereo CAS 4.X execution参数反序列化漏洞分析这里不在赘述。文章提到了,前后两个版本区间的encode方法是不一样。
在cas4.x-cas.4.1.5中的加密伪代码如下
1 | payload = gzip(Java Serialized data) |
CAS 4.1.7 ~ 4.2.X的加密伪代码如下
1 | cipher = aes128_cbc_encode(iv + gzip(Java Serialized data)) |
因为encode的变化excution是不一样的亦可作为判断版本的指纹。
- cas4.x-cas.4.1.5之前特征:execution base64解码出来以\x00\x00\x00\x22\x00\x00\x00\x10开头。
- 4.1.6之后特征:execution两次base64解码出来不是乱码而是jws格式(header.body.sign)的字符串。
解密题目的execution不难发现,环境是4.x-4.1.5。此外看到,前后两个版本的encode的方式唯一的差异是4.1.6之后execution的需要进行加密签名,联系到它使用的是aes/cbc说到这应该很熟悉了吧padding oracle!
这里padding oracle,仍然需要讲究技巧,直接生成cc链一类的payload进行padding大约需要padding 114组左右数据(题目两小时重启一次,gadget还需要fuzz,这是一个难以完成的任务),但是如果环境能出网的话用jrmp就需要padding 14组数据左右了,这里视环境情况仍然需要跑1h-3h不等,但是通过的分析过cve-2018-2628之后发现jrmp的payload的可以更短只需要7组,我在同区域的阿里云上多线程跑不到20分钟就有了结果(这也是题目描述Time is Flag的暗示233333)。
1 | from jose import jws |
通过jrmp出来fuzz gadget也很方便,这里用的是JDK7u21(在自己做的时候发现统一端口请求一次jrmp之后,后面的再一次请求会变得很慢,这里可以选择再跑一个端口出来交替使用)。接下来就是常见的读取数据库连接字符串查用户登陆的操作了,在此不细表。
Login Me Again - shiro rce && shiro bypass acl(白盒)
这道题,由我和@leixiao合作完成,前半部分shiro不出网rce利用由leixiao负责完成,后半部分shiro bvpass acl部分由我负责完成。
先贴一个当时构思这道题时候的速记(有删改):
环境:外网一个有shiro rce的不出网应用(打包成jar),内网有一个spring+最新版shiro写一个只允许图的上传功能(打包成war),上传功能需要管理员权限(shiro鉴权)部署在有ajp漏洞的tomcat7上。
攻击思路
1.通过注入有socks5代理功能的webshell代理到内网。
2.找shiro新的权限绕过方法或者谷歌搜到我之前找的shiro ajp越权:https://issues.apache.org/jira/browse/SHIRO-760,越权上传文件或者用c0ny1师傅的姿势。
3.用ajp漏洞包含刚才上传的图片rce
利用难点:1.市面上还没有socks5代理功能的无文件webshell,需要选手自己从已有的jsp构造转换成无文件的webshell。2.自己挖越权或者搜到我之前提交的那个越权issue或者用其他办法。3.市面ajp协议的介绍较少,需要选手自己研究如何用ajp协议上传文件。
下面就从利用难点,逐一说明
无文件socks5代理
因为这里是shiro,shiro本身也是一个filter,所以内存马最好也搞成filter(优先级最高),内存马的思路可以看基于Tomcat无文件Webshell研究。至于具体filter的逻辑,改一下reg就好了,下面贴一下leixiao师傅的代码。
1 | package reGeorg; |
ajp越权的shiro acl
这一点后面的提示也给出来了,可以用how-to-detect-tomcat-ajp-lfi-more-accurately提到的办法,也可以用我之前提交的SHIRO-760。poc在issue里面已经给了,漏洞的demo环境在我github上可以找到,这里借这个机会分享一下当时挖掘的思路。
通过分析前人的文章可以知道,我们可以知道在org.apache.shiro.web.util.WebUtils#getPathWithinApplication内部会对requestUri进行提取并交给patchMatches匹配以判断是否需要鉴权。
多次步入后,可以看到具体的获取uri的实现是其中的getRequestUri。getRequestUri首先会获取javax.servlet.include.request_uri的值如果获取到了就不会进入 if (uri == null)
。
而如果有师傅看过shiro上一次对越权的修复的话会发现,补丁是打在if (uri == null)
中的,通过ajp控制javax.servlet.include.request_uri
相当于绕过上一次的补丁点。
接着这里提取出来的uri/;/admin/page
会进入decodeAndCleanUriString中进行清洗。decodeAndCleanUriString会取分号前的内容返回。
在这里返回的就是/
,后面shiro的正则/admin/*
自然也就拦截不了。
此外,光绕过shiro还不行,spring不解析这条路由也没用,一个开始我也为用前人文章中的 /xxxx;/../
可以轻松绕过,黑盒发现并不行。分析ajp漏洞的时候我们知道,tomcat先调用对所有filter进行过滤然后会调用对应的servlet,而在spring都是统一由DispatcherServlet进行统一调度的。所以一开始我选择把断点打到org.springframework.web.servlet.FrameworkServlet#doGet(_DispatcherServlet继承FrameworkServlet_)。又因为spring是通过HandlerMapping来找对应的控制器,所以步入断点之后就开始找哪个地方有这个逻辑。最后在/org/springframework/web/servlet/DispatcherServlet.class:484找到。
步入之后spring把已经注册过Mapping轮询一次。在代码中我们用的@GetMapping这里就对应ReuqestMappingHandlerMapping。
步入ReuqestMappingHandlerMapping之后再多次步入,最后来到org.springframework.web.util.UrlPathHelper#getPathWithinApplication
这里三个箭头是关键的三个点,第一个箭头会对uri提取并“消杀”,第二个箭头会去pathWithinApp中servletPath之后的内容。第三个箭头返回path交给HandlerMapping匹配。
我们先来看第一个箭头“消杀”的步骤。
上图removeSemicolonContent会移除uri中;
,/;/admin/page
变为//admin/page
。getSanitizedPath会对移除重复的/
, //admin/page
变为 /admin/page
(_ps:这里并不会处理..及.这也是为啥老payload /xxx;/../无法用的原因,虽然可以绕过但是之后spring handlerMapping匹配不到。_)
再来第二个箭头,这个getRemainingPath会提取处Uri中conextPath之后的部分。举个反例如果我们把javax.servlet.include.servlet_path
设置为/
,那么返回给HandlerMapping将会是 admin/page
,而HandlerMapping只会匹配/admin/page
这也是为什么javax.servlet.include.servlet_path
需要置为空的原因。
回过头看漏洞本质还是在于spring和shiro在规范消杀url时标准不一致造成的问题。因为最新版的tomcat已经默认把ajp关了,并且在反代情况下tomcat 8009也不会对外开放所以这个洞的利用还是受很大限制的。
ajp上传文件
因为网上ajp协议讨论较少,和exp有关的只有CVE-2020-1938,不过payload的构造比较单一并不涉及到上传文件的请求,网上应该也没有介绍相关的文章。那要怎么通过ajp传?我预想的思路是选手通过阅读相关类库来解决比如AJPy,在tomcat.py中提供了一种部署war包getshell的操作,这里面就有上传文件的操作,可以借鉴。
1 | import sys |
SCTF 2020 两道Login Me预期解的核心技术
https://cl0und.github.io/2020/07/17/SCTF-2020-两道Login-Me预期解的核心技术/