框架中的readObject方法来反序列化的例子
前言
Apache Shiro™是一个功能强大且易于使用的Java安全框架,可以执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序——从最小的移动应用程序到最大的web和企业应用程序。
Apache Shiro | Simple. Java. Security.
就比如一个前后端的blog系统,管理员需要进入后台来进行管理文章,这时候就需要登陆验证的框架,就比如Shiro当然Spring security也可以实现类似功能。
[SHIRO-550] Randomize default remember me cipher - ASF JIRA (apache.org)
- shiro对每次访问都会用到”记住我”的功能进行以下操作:
- 检索
rememberMe
cookie的值 //cookie中是否有这个参数 - Base 64解码 //对参数的值进行解码
- 使用AES解密 //对参数的值再进行解密
- 使用Java序列化(
ObjectInputStream
)反序列化。 //对解出的参数的值进行反序列化
Shiro的CookieRememberMeManager类里对漏洞参数rememberMe进行序列化,加密等操作,来实现在浏览器或服务器重启后用户不丢失登录状态,下次读取时进行解密再反序列化。
在Shiro 1.2.4版本之前内置了一个默认且固定的AES加密密钥,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞。
Demo
JavaThings/shirodemo at master · phith0n/JavaThings (github.com)
这个是p神的学习漏洞的小项目
依赖
- shiro-core、shiro-web,这是shiro本身的依赖。
- javax.servlet-api、jsp-api,这是JSP和Servlet的依赖,仅在编译阶段使用,因为Tomcat中自带这 两个依赖。
- slf4j-api、slf4j-simple,这是为了显示shiro中的报错信息添加的依赖。
- commons-logging,这是shiro中用到的一个接口,不添加会报错。
- commons-collections,为了演示反序列化漏洞,增加了commons-collections依赖。
使用 mvn package
将项目打包成war包,放在Tomcat的webapps目录下,再进入bin目录下点击startup.bat
运行项目。然后访问 http://localhost:8080/shirodemo/
进入登陆页面。
账号密码,root/secret,成功登录:
如果勾选了remember me会多一个rememberMe的Cookie
调试
IDEA配置调试shiro
1 | Run -> |
加入war包,IDEA很智能。
再点击运行即可
参数所在位置
也是可以登入,勾选remember me之后,cookie里面就有rememberMe这个名称,而shiro每次都会对cookie中的rememberMe来进行解密后反序列化操作来确定访问者权限,所以直接在cookie传输rememberMe参数就可以控制shiro反序列化的值
加密解密的方法
看看当作入口点的CookieRememberMeManager
类,ALT+7查看所有方法。
进入他的父类AbstractRememberMeManager
可以看到默认的key
在AbstractRememberMeManager
类函数名为encrypt
(加密)下断点
Drop Frame返回上一个方法,serialized的值是我刚才web端登录的用户名root序列化后的数据,shiro验证完了登录的账号密码,然后根据用户名生成序列化数据准备进行加密了。
再步过一步,回到断点的位置仔细看这个cipherService,模式为CBC,128位,填充方式为PKCS5Padding
步入
再步入,就是加密的过程了
planintext为序列化的用户名
key为密钥
iv是随机生成长度为16的字节
output是随机生成的,占用长度为16字节+AES加密结果
接着步过就回到断点的地方。
回到CookieRememberMeManager
类,看到队反序列化的内容又进行了base64编码
Shiro-550反序列化漏洞(CVE-2016-4437)
前面分析的就是Shiro-550
攻击流程
- 使用以前学过的CommonsCollections利用链生成一个序列化Payload
- 使用Shiro默认Key进行加密
- 将密文作为rememberMe的Cookie发送给服务端
就跟着p神来构造cc6的吧。
1 | import org.apache.shiro.crypto.AesCipherService; |
在之前的CommonCollections6类新建一个名为getPayload()的byte[]返回值方法,然后return一个新分配的字节数组。
登陆进去之后删除掉JSESSIONID,因为当存在 JSESSIONID 时,会忽略 rememberMe。然后替换remeberMe的值。
但是tomact报错了
Transformer方法出现了问题,看到最后一行的这个方法,重写了resolveClass方法。
进入方法看到注释:加载与指定流类描述等价的本地类。子类可以实现此方法,以允许从备用源获取类。读取序列化流的时候,读到一个字符串形式的类名,需要通过这个方法来找到对应的 java.lang.Class 对象。
在ClassResolvingObjectInputStream类抛出异常的地方打上断点看看。可以看到出异常时加载的类名为[Lorg.apache.commons.collections.Transformer;
[L
是一个JVM的标记,说明实际上这是一个数组,即Transformer[]
Shiro resovleClass使用的是ClassLoader.loadClass()
而非Class.forName()
,而ClassLoader.loadClass
不支持装载数组类型的class。
想要进入Tomcat要加入依赖,版本要与使用的Tomcat对应。
1 | <dependency> |
进到ClassUtils类的forName方法,
设置
运行到这步就报错了,
WebappClassLoaderBase (Apache Tomcat 9.0.87 API Documentation)
加载具有指定名称的类,使用以下算法进行搜索,直到找到并返回 类。如果找不到该类,则返回 。ClassNotFoundException
- 调用以检查类是否已加载。如果有,则返回相同的对象。
findLoadedClass(String)
Class
- 如果该属性设置为 ,则调用该方法 父类加载器(如果有)。
delegate
true
loadClass()
- 调用以在本地定义的存储库中查找此类。
findClass()
- 调用父类加载器的方法(如果有)。
loadClass()
如果使用上述步骤找到该类,并且标志为 ,则此方法 然后将调用生成的 Class 对象。
当本地cache或存储库中均无时,则通过父类加载器URLClassLoader.loadClass(),在URLClassLoader.loadClass()处添加断点。加载主角登场
可以看到并没有找到class,然后就进入他的父类方法里面了
一开始path虽然正常但是返回结果却是null,后面还出来了错误的path
进入findLoadedClass0这个path是根据name来获取的就会出现bug,肯定是找不到的。
1 | /[Lorg/apache/commons/collections/Transformer;.class |
Tomcat和JDK的Classpath是不公用且不同的,Tomcat启动时,不会用JDK的Classpath
- 数组形式会使得shiro想尝试从本地加载时,path也被赋上数组标识,导致无法从本地jar包中正常获取。
- 而URLClassLoader中是因为Tomcat和JDK的Classpath的不同,导致即使path正确,也无法找到对应class
构造不含数组的反序列化Gadget
通过上面的分析也是知道了问题所在,那么应该怎么避免数组呢?
置为TemplatesImpl对象
看到org.apache.commons.collections.functors.InvokerTransformer#transform
这个方法通过反射根据传入的input
对象,调用其iMethodName
。可以将iMethodName
置为newTransformer
来实现传入的input
为构造好的TemplatesImpl
对象这样就不是数组了。
传入到ConstantTransformer.transform
函数的input
,该transform
函数不依赖input
,而直接返回iConstant
TiedMapEntry
,其作为中继,调用了LazyMap
(map)的get
函数。
其中map
和key
我们都可以控制,而LazyMap.get
调用了transform
函数,并将可控的key
传入transform
函数
也就是LazyMap#get
的参数key,会被传进transform(),来传递TemplatesImpl
对象。
把CommonsCollections6改一下
1 | java.util.HashSet.readObject() |
poc
总结
shiro还有很多需要学习的东西,比如各种链子进行利用,因为Shiro是一个反序列化的入口,还有权限绕过问题。很老的洞了,实际生活中应该没了,可能一些竞赛会拿出来玩玩吧。主要在于锻炼思维。
参考
知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具 (zsxq.com)
Shiro反序列化分析带思路及组件检测笔记 - 先知社区 (aliyun.com)
redis未授权到shiro反序列化之session回显马|NOSEC安全讯息平台 - 白帽汇安全研究院
redis未授权到shiro反序列化 - 先知社区 (aliyun.com)
shiro反序列化漏洞原理分析以及漏洞复现 - FreeBuf网络安全行业门户
shiro-1.2.4反序列化分析踩坑 - W4nder - 博客园 (cnblogs.com)