shiro反序列化漏洞分析

分析源码

我们去源码里面去找找,搜索rememberMe:

发现有一个 CookieRememberMeManager 这个类,看名字就知道他多半就是处理 RememberMe的逻辑,所以根据该类查看它干了什么

这里继承 AbstractRememberMeManager 类,AbstractRememberMeManager提供了创建和验证这些令牌的方法,以及配置cookie属性的方法。

这是CookieRememberMeManager 类的构造函数。首先创建一个名为"rememberMe "的SimpleCookie 对象,并将其设置为HttpOnly 。然后,将cookie的最大生存期设置为一年,以确保用户在一年内不需要重新登录。最后,将cookie对象赋值给类中的cookie属性。这个构造函数的作用是创建一个CookieRememberMeManager对象,并设置默认的"记住我"cookie配置。如果需要更改cookie的配置,可以使用其他构造函数或通过setter方法来更改属性。

继续往下看,感觉这里是重点有没有.......

看类名可以知道此类用于将序列化后的用户身份信息存储到cookie中。

在方法中,首先通过WebUtils.isHttp(subject)方法判断Subject对象是否为HTTP-aware实例。如果不是,则返回并忽略记住我操作。如果Subject对象是HTTP-aware实例,就可以获取到HttpServletRequest和HttpServletResponse对象,从而可以设置cookie。

接下来是对这些对象执行的操作:

  1. 通过WebUtils.getHttpRequest(subject)方法获取HttpServletRequest对象。
  2. 通过WebUtils.getHttpResponse(subject)方法获取HttpServletResponse对象。
  3. 将序列化后的用户身份信息进行Base64编码。
  4. 创建一个新的SimpleCookie对象,并将其值设置为Base64编码后的身份信息。
  5. 将新的cookie对象保存到HttpServletRequest和HttpServletResponse对象中。

所以这个方法的作用是将序列化后的用户身份信息存储到cookie中,并将其保存到HTTP响应中,以便在用户下一次访问网站时自动登录。

方法调用的跟踪

接下来去查看一下该方法在什么地方被调用(快捷键:Ctrl+Alt+Shift+F7)

左下角如果有弹框的话,将所以选项勾上,下拉范围选项框选择所有。

在这可以看到该类继承的AbstractRememberMeManager 类调用了该方法。

发现这个方法被 rememberIdentity 方法给调用了。

在这里会发现 rememberIdentity 方法会被 onSuccessfulLogin 方法给调用,跟踪到这一步,就看到了 onSuccessfulLogin登录成功的方法。

当登录成功后会调用AbstractRememberMeManage.onSuccessfulLogin 方法,该方法主要实现了生成加密的RememberMe Cookie, 然后将RememberMe Cookie 设置为用户的Cookie值。在rememberSerializedIdentity方法里面去实现了。

回到 onSuccessfulLogin 这个地方,这里看到调用了isRememberMe 很显而易见得发现这个就是一个判断用户是否选择了Remember Me选项。

另一方面 rememberIdentity 方法会调用 rememberSerializedIdentity方法,到这里为止,我们已经接触到序列化了。

继续跟踪,有一个叫getRememberedPrincipals 的方法调用getRememberedSerializedIdentity 。看名字就知道 getRememberedPrincipals是一个取得Remember验证的方法。

这里我们再跟进getRememberedPrincipals方法,(快捷键:Ctrl+Alt+h)

我们继续跟踪convertBytesToPrincipals方法

因为convertBytesToPrincipals 方法就是处理getRememberedPrincipals方法 的东西,看名字应该是进行字节转换的。

该方法首先检查是否存在加密服务,如果存在,则使用密钥对字节数组进行解密。接着,该方法使用"deserialize"方法将字节数组序列化。

我们可以先看反序列化

发现有一个反序列化入口 **readObject()**这里就是要利用的点

然后看解码那个地方他的逻辑是如何的

该方法名为"decrypt",有一个参数:"encrypted",代表需要解密的字节数组。

该方法首先将需要解密的字节数组赋值给一个名为"serialized"的新的字节数组。然后,该方法通过调用"getCipherService"方法获取加密服务,如果加密服务存在,则用"getDecryptionCipherKey"方法获取解密密钥,并使用加密服务对字节数组进行解密。解密后,将解密后的结果赋值给"serialized"字节数组。最后,该方法返回"serialized"字节数组,即解密后的结果。

在这里我们需要关注俩个点,接口的抽象方法,有两个参数

第一个是要解密的数据

第二个参数就是解密的key了,这个是我们十分关心的,所以我们跟进第二个参数

跟踪返回 decryptionCipherKey

跟踪 setDecryptionCipherKey方法

跟踪 setCipherKey方法

相关推荐
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
励志成为嵌入式工程师2 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
记录成长java4 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
前端青山4 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
睡觉谁叫~~~4 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
音徽编程4 小时前
Rust异步运行时框架tokio保姆级教程
开发语言·网络·rust
观音山保我别报错4 小时前
C语言扫雷小游戏
c语言·开发语言·算法