反序列化漏洞与Shiro-550

反序列化漏洞与Shiro-550

本文先从零介绍了反序列化漏洞,并紧接着介绍Shiro 550反序列化漏洞的形成原理、利用方式和防护方法。

(不涉及对利用链的具体分析)

第一部分:前置知识

第二部分:Apache Shiro 550反序列化漏洞的形成分析

第三部分:Apache Shiro反序列化漏洞的利用&防护

1.Shiro反序列化漏洞的前置知识

1.1.序列化与反序列化

序列化是什么,解决了什么问题?

  • 序列化:将程序代码中的对象转换为字节流序列的过程。

  • 反序列化:即序列化的逆过程,即将字节流序列转化为对象的过程。

  • 应用情景:由于程序中的对象是复杂且极具平台特征的,所以在网络传输持久化存储跨平台兼容等方面有天然的不足,所以程序员通过序列化,将复杂各异、立体的程序对象转换为通用、扁平字节流序列进行传输或存储,在特定时间对其进行反序列化,以尽可能地还原出原始的对象。

    举一下以上三个情景的例子:

    • 网络传输(B/S架构的在线商城)

      • 客户端

        1. 用户在客户端中填写了订单信息;

        2. 客户端(JS)使用序列化技术,将订单对象转为字节流;

        3. 客户端将字节流放在HTTP请求体中进行相应的请求。

      • 服务端

        1. 服务端将HTTP请求体中的相关字节流取出,并将其反序列化为订单对象;
        2. 接着,服务端就订单对象进行一系列相关操作(如:验证订单号、计算金额、拉起支付等);
        3. 处理完毕后,服务端将订单信息存入数据库中作为记录。
    • 持久化存储(如Cookie)

      1. 服务端将用户的相关会话对象进行序列化后得到一个JSON字符串,并将其放入响应报文的Set-Cookie字段中(Ps:JSON是一种数据格式,它并非只能代表序列化之后的字符串,也可以是未经序列化的对象);
      2. 客户端将该JSON字符串反序列化得到一个Cookie对象,在进行请求时带上相关的Cookie值。
    • 跨平台兼容

      比如Python向php传递对象:

# Python将person对象序列化为JSON字符串,并将其编码后传给php处理
      import json
      import base64
      
      # 定义一个person对象
      person = {
          "name": "John",
          "age": 25
      }
      
      # 将该对象转换为JSON字符串(序列化)
      json_str = json.dumps(person)
      # 值为{"name": "John", "age": 25}
      
      # 将JSON字符串进行Base64编码
      base64_str = base64.b64encode(json_str.encode()).decode()
      # 值为eyJuYW1lIjogIkpvaG4iLCAiYWdlIjogMjV9
# php将收到的数据解码后得到JSON字符串,将JSON字符串反序列化后得到person对象
      <?php
      
      // 获取Base64编码的JSON字符串
      $base64_str = "eyJuYW1lIjogIkpvaG4iLCAiYWdlIjogMjV9";
      
      // 将Base64字符串解码为JSON字符串
      $json_str = base64_decode($base64_str);
      // 值为{"name": "John", "age": 25}
      
      // 将JSON字符串解码为PHP对象(反序列化)
      $person = json_decode($json_str);
      // 值为:object(stdClass)#1 (2) {
      //  ["name"]=>
      //  string(4) "John"
      //  ["age"]=>
      //  int(25)
      //}
      
      // 在PHP中使用解码后的对象或数组
      echo "Name: " . $person->name . "\n";
      echo "Age: " . $person->age . "\n";
      // 执行结果:
      // Name: John
      // Age: 25
      ?>

1.2.反序列化漏洞

反序列化漏洞的形成:如果服务端未对客户端的输入进行严格、全面的过滤,便使得攻击者有机会传入精心构造的Payload,当服务端尝试对包含这些POP链的数据进行反序列化时,就可能会触发恶意代码的执行。

  • 简单的例子:

    <?php
    // 弱点1
    class VunerableClass {
        public $command;
        
        public function __construct($command) {
            $this->command = $command;
        }
        
        public function execute() {
            // 执行恶意操作
            exec($this->command);
        }
    }
    
    // 弱点2
    class Server {
        public function handleRequest($serializedData) {
            // 反序列化数据
            $user = unserialize($serializedData);
            
            // 如果反序列化的对象是 VunerableClass 类的实例,则立即执行恶意命令
            if ($user instanceof VuneableClass) {
                $user->execute();
            } else {
                // 处理正常逻辑
                echo "处理正常逻辑";
            }
        }
    }
    
    // 假设$serializedData存储了攻击者传来的恶意字符串(Payload)
    $serializedData = 'O:14:"VunerableClass":1:{s:7:"command";s:8:"rm -rf /";}';
    
    $server = new Server();
    $server->handleRequest($serializedData);
    ?>
    

在上述的简单示例中,攻击者传入了恶意的字符串序列,而服务端在未对其进行充分验证的情况下就进行了反序列化,从而使得攻击者的代码被执行。

1.3.HTTP的会话管理

HTTP是无状态的,所以Web应用在设计时会引入Cookie、Session等会话管理技术。

就Cookie而言,客户端在登录或设置偏好时会向服务端请求Cookie,服务端会在处理后将Cookie写入响应报文的Set-Cookie字段中,客户端存储Cookie,并在进行相应请求时附上所需的Cookie字段。
既然Cookie的使用情景涉及到网络传输和存储,那么自然和序列化技术联系上了:
在Shiro框架中,服务端发给客户端的Cookie中便是经序列化之后的字节流,服务端在收到客户端发来的Cookie后需要对其进行反序列化。

1.4.AES

AES是一种可靠的对称密码算法(加解密使用相同的密钥)。

注意,AES的“可靠”不是指使用AES加密算法就没有安全风险:

  • 作为对称密码算法,AES必然面临着这类算法无法回避的问题——密钥管理与分发,而早期版本的Shiro(<=1.2.4)中是使用硬编码的方式将AES密钥存储在了源代码中,攻击者便能够在收集到多个版本的Shiro中AES密钥后进行枚举,得知目标Shiro框架所使用的AES密钥。【这点很重要,是实战中能利用Shiro反序列化漏洞的前置条件
  • AES是对称密码算法中的分组密码算法:即将明密文拆分为多个长度固定的分组,AES本身只是对各个分组进行加解密(AES的可靠到此为止,它只能保证分组的密文不被直接破解),所以引入了ECB、CBC及CTR等加密模式作为“引擎”控制AES对各个分组的操作,而ECB这种加密模式本身有较大的安全风险(ECB模式中各个分组之间相互独立,这会导致猜测明文攻击和重放攻击等,具体细节欢迎阅读鄙人这篇:《密码学——现代密码体制总结》)【在Shiro中的AES默认使用CBC加密模式(后期使用GCM),相对ECB来说安全得多】

2.Apache Shiro框架的反序列化漏洞

2.1.关于Apache Shiro:

Shiro(发音为“shee-roh”,日语中“castle(堡垒)”的意思)是一个强大易用的Java安全框架,可执行身份验证、授权、加密和会话管理,可用于保护任何应用程序的安全,从命令行应用程序、移动应用程序到最大的web和企业应用程序。

Shiro有三大概念:Subject、SecurityManager和Realms

Subject(主体):Subject代表当前正在与应用程序交互的用户。它可以是一个人、一个设备或者其他实体。Subject可以执行身份验证和授权操作,并且可以维护与用户相关的会话状态。Subject是Shiro的核心概念,它封装了与当前用户相关的信息和操作。

SecurityManager(安全管理器):SecurityManager是Shiro的核心组件,负责管理所有的Subject、Realm和权限操作。它是应用程序与Shiro之间的桥梁,处理身份验证、授权和会话管理等安全相关的操作。SecurityManager协调Subject与Realm之间的交互,确保安全策略的执行。

Realm(域):Realm是Shiro用于获取安全数据(如用户、角色和权限)的组件。它负责从数据源(如数据库、LDAP等)中获取用户的身份验证和授权信息,并将其提供给SecurityManager进行处理。Realm可以自定义实现,以适应不同的身份验证和授权需求。应用程序可以配置一个或多个Realm,以支持多种身份验证和授权方式。

简单来说,安全管理器SecurityManager会对主体Subject的相关操作进行控制,并向域Realm进行获取安全数据等。

这和MVC有些许相似之处,域Realm对应MVC中的模型Model安全管理器SecurityManager对应MVC中的控制器Controller,具体的web应用(如Shiro的登录界面)对应MVC的视图View

2.2.Shiro序列化与反序列化的过程

Shiro在会话管理中提供了一个“记住我”功能,用户可以在登录成功前时选择启用“记住我”功能,该功能实现如下:

  1. 初次登录
    • 对用户的主体数据(Subject)进行序列化
    • 对序列化值进行AES加密
    • 对密文进行Base64编码
    • 将编码值(shiro令牌)写入HTTP响应包的Set-Cookie中,返还给用户存储
  2. 再次访问
    • 取出用户Cookie中的Remember字段值(shiro令牌)
    • 使用Base64进行解码
    • 对编码值进行AES解密
    • 对得到的序列化值进行反序列化

2.3.Shiro反序列化漏洞的形成

由于使用了AES加密,Shiro完全相信了请求Cookie中RememberMe字段的内容(如果能正常解密的话);

但只要攻击者知道了AES的加密细节(密钥、加密模式),便能控制服务器所反序列化的内容,如果知道了反序列化的利用链,那么便能进行RCE;

在Shiro版本<=1.2.4时,AES密钥被硬编码在源代码中,攻击者便可通过收集各版本源码来枚举出目标Shiro系统所使用的AES密钥,从而控制反序列化内容。

2.3.1.代码分析1:宏观设计

前文提到Shiro主要是依靠各安全控制器SecurityManager来实现安全控制,本文所涉及的安全控制器则是DefaultSecurityManager.java(Shiro框架的安全控制器默认实现)

在这里插入图片描述

在安全管理登录会话时,DefaultSecurityManager会指向RememberMeManager.java

RememberMeManager接口对RememberMe的相关操作进行了宏观设计,它指定了一系列的方法待后面的类进行具体实现。

在这里插入图片描述

进一步看,RememberMeManager中的各方法由AbstractRememberMeManager基本实现;

而为了实现Cookie中的“记住我”功能,Shiro写了个CookieRememberMeManager来继承了AbstractRememberMeManager,并添加了一些用于操作Cookie的成员。

在这里插入图片描述

2.3.2.代码分析2:调试跟进
2.3.2.1.初次登录——生成持久化令牌

对用户传来的数据进行序列化、AES加密及Base64编码后写入Cookie中:

AbstractRememberMeManager.java中的convertPrincipalsToBytes=>序列化、加密

CookieRememberMeManager.java中的RememberSerializedIdentity=>编码并写Cookie

更完整的调用链大致如下:

onSuccessfulLoginDefaultSecurityManager.java

​ =>rememberMeSuccessfulLoginDefaultSecurityManager.java

​ =>onSuccessfulLoginRememberMeManager.java

​ ==实际由onSuccessfulLoginAbstractRememberMeManager.java实现

​ =>rememberIdentityAbstractRememberMeManager.java

​ =>convertPrincipalsToBytesAbstractRememberMeManager.java

​ =>序列化:serializeAbstractRememberMeManager.java

​ =>加密:encryptAbstractRememberMeManager.java

​ =>编码并写Cookie:rememberSerializedIdentityAbstractRememberMeManager.java

​ ==实际由rememberSerializedIdentityCookieRememberMeManager.java实现

2.3.2.1.1.序列化、AES加密

在这里插入图片描述

步入encrypt查看:

在这里插入图片描述

向该函数传入序列化后的字节数组serialized,接着对密码算法进行一定的初始化配置,并在加密后返回密文值

在这里插入图片描述

初始化(指定密码算法的相关参数):这里使用了CBC加密模式的128bit密钥长度的AES加密算法(对明文采用PKCS5进行填充)

在这里插入图片描述

在这里插入图片描述

进行加密(传入明文和密钥):这里以字节数组形式传入密钥

在这里插入图片描述

2.3.2.1.2.Base64编码并写Cookie

来到CookieRememberMeManager.javaRememberSerializedIdentity方法

在这里插入图片描述

在这里插入图片描述

2.3.2.2.再次访问——鉴权

从请求Cookie中读出RememberMe值进行Base64解码,将密文进行AES解密后进行反序列化:

CookieRememberMeManager.java中的getRememberedSerializedIdentity=>读Cookie并解码

AbstractRememberMeManager.java中的convertBytesToPrincipals=>解密、反序列化

更完整的调用链大致如下:

resolvePrincipalsDefaultSecurityManager.java

​ =>getRememberedIdentityDefaultSecurityManager.java

​ =>getRememberedPrincipalsRememberMeManager.java

​ ==实际由getRememeberedPrincipalsAbstractRememberMeManager.java实现

​ =>读Cookie并解码:getRememberedSerializedIdentityAbstractRememberMeManager.java

​ ==实际由getRememberedSerializedIdentityCookieRememberMeManager.java实现

​ =>convertBytesToPrincipalsAbstractRememberMeManager.java

​ =>解密:decryptAbstractRememberMeManager.java

​ =>反序列化:deserializeAbstractRememberMeManager.java

2.3.2.2.1.读Cookie、Base64解码

进入getRememberedSerializedIdentity从请求包中拿cookie进行处理

在这里插入图片描述

对获取到的cookie进行解码

在这里插入图片描述

2.3.2.2.2.AES解密并反序列化

使用AES解密并反序列化

在这里插入图片描述

在这里插入图片描述

3.Shiro反序列化漏洞的利用&防护

3.1.Shiro-550的利用

利用Shiro-550需要两个数据:①AES密钥 ②反序列化时的利用链

以下使用GUI工具进行漏洞利用演示:https://github.com/j1anFen/shiro_attack/

  1. 找AES密钥

在这里插入图片描述

在这里插入图片描述
2. 找利用链

在这里插入图片描述
在这里插入图片描述

  1. RCE或注入内存马

在这里插入图片描述在这里插入图片描述

在这里插入图片描述

3.2.Shiro-550的防护

  • 不使用硬编码的AES密钥
    • 将Shiro框架升级到最新版本
    • 或不使用Shiro默认的AES密钥,而是自己生成一个密钥并妥善保管
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Neonline

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值