zoukankan      html  css  js  c++  java
  • shiro550反序列化分析

    拖了很久的shiro分析

    漏洞概述

    Apache Shiro <= 1.2.4 版本中,加密的用户信息序列化后存储在Cookie的rememberMe字段中,攻击者可以使用Shiro的AES加密算法的默认密钥来构造恶意的Cookie rememberMe值,发送到Shiro服务端之后会先后进行Base64解码、AES解密、readObject()反序列化,从而触发Java原生反序列化漏洞,进而实现RCE。

    该漏洞的根源在于硬编码Key。

    漏洞复现

    使用shiroattack工具

    Dnslog接收到请求

    执行命令

    漏洞分析

    远程调试

    用idea连vulhub的docker环境来进行调试
    首先进入容器
    docker exec -it 34db756dfcfc /bin/bash

    可以看到是用jar包起的环境,把文件拷贝出来(也可以docker-compose up -d之后使用docker ps --no-trunc来查看容器默认的启动命令)

    docker cp 34db756dfcfc:/shirodemo-1.0-SNAPSHOT.jar ~/

    然后把jar包解压了之后用idea打开

    libraries里导入

    在module中添加BOOT_INF这个目录

    另外还需要改一下dockerfile
    idea远程调试docker
    需要增加一组端口供调试用,这里我们用idea默认的5005

    vulhub的shiro环境是java -jar xxx.jar的形式运行的,那么添加对jar程序启动的调试命令即可,在启动docker时用自定义的COMMAND替换默认的COMMAND

    version: '2'
    services:
     web:
       image: vulhub/shiro:1.2.4
       ports:
        - "8080:8080"
        - "5005:5005"
       command: java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar /shirodemo-1.0-SNAPSHOT.jar
    

    然后配置好remote

    下好断点,输入登录账户密码测试,看到以下界面说明成功了

    原理分析

    从官方的 issue 上来看,存在几个重要的点:

    • rememberMe cookie
    • CookieRememberMeManager.java
    • Base64
    • AES
    • 加密密钥硬编码
    • Java serialization

    首先正常登录

    返回的cookie值中的rememberme值如下:

    o5NA+QAjgJpe6uKkIJ1li/WLqOAR2KfLIY3BzwfAUFbbkBSEfs3/259i4Qc2jq6lxUpLabYK2c0oR4faB3l8m0GDhzMvVYjFOR2TPKRtQmqlUAdaiJT06biAC56EMu6UbBJAujAgP1msHuJYkV1fDuhdLN5wGK+IlEpr1Xf+aa6oNkPqBkRG7+B/3bXQNTqmLFlarZUWxB6TwZyshplhx0ckyIfc0qJuf/f5Tt60gK/D28JUI93Gp3vGi/P7UUiwv2Qyzpz4hXZSUocGlC73qE+62ZvQ1ryzVRjpfTkG4Hat6sst04wbpFwdUSJxh6t4FLJ2i9bs5eIm/1UJpVHP9Eia5WaAShQa45qr5yIMA+q1rYxtz0WufvOi67fpqY3qi8LQ/ZnGwXUY+o6dLu2qmqHwTXbRTRKP4G5d3e5SA9FNvXUhYWRhcwo2zJ2lS2JK/D6S0u3HAak04+3wpbZm0UCJxafXlaFPUDhmiXBtIULcEELqBOaqLr1n7LV+F1Cl7kYNU6GozLVPvNlqW5UtLA==
    

    跟一下登录生成cookie的过程

    生成cookie

    shiro会提供rememberme功能,可以通过cookie记录登录用户,从而记录登录用户的身份认证信息,即下次无需登录即可访问。而其中对rememberme的cookie做了加密处理,漏洞主要原因是加密的AES密钥是硬编码在文件中的,那么对于AES加密算法我们已知密钥,并且IV为cookie进行base64解码后的前16个字节,因此我们可以构造任意的可控序列化payload。

    处理rememberme的cookie的类为org.apache.shiro.web.mgt.CookieRememberMeManager

    它继承自org.apache.shiro.mgt.AbstractRememberMeManager,其中在AbstractRememberMeManager中定义了加密cookie所需要使用的密钥,当我们成功登录时,如果勾选了rememberme选项,那么此时将进入onSuccessfulLogin方法

    之后进入serialize,对登录认证信息进行序列化

    然后进行加密

    org.apache.shiro.mgt.AbstractRememberMeManager中的encrypt方法如下

    protected byte[] encrypt(byte[] serialized) {
        byte[] value = serialized;
        CipherService cipherService = getCipherService();
        if (cipherService != null) {
            ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
            value = byteSource.getBytes();
        }
        return value;
    }
    

    ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());调用的即为AES算法

    可以看到使用CBC模式的AES加密算法,其中Padding规则是PKCS5。
    具体实现在org/apache/shiro/crypto/JcaCipherService.java

    private ByteSource encrypt(byte[] plaintext, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
    
        final int MODE = javax.crypto.Cipher.ENCRYPT_MODE;
    
        byte[] output;
    
        if (prependIv && iv != null && iv.length > 0) {
    
            byte[] encrypted = crypt(plaintext, key, iv, MODE);
    
            output = new byte[iv.length + encrypted.length];
    
            //now copy the iv bytes + encrypted bytes into one output array:
    
            // iv bytes:
            System.arraycopy(iv, 0, output, 0, iv.length);
    
            // + encrypted bytes:
            System.arraycopy(encrypted, 0, output, iv.length, encrypted.length);
        } else {
            output = crypt(plaintext, key, iv, MODE);
        }
    
        if (log.isTraceEnabled()) {
            log.trace("Incoming plaintext of size " + (plaintext != null ? plaintext.length : 0) + ".  Ciphertext " +
                    "byte array is size " + (output != null ? output.length : 0));
        }
    
        return ByteSource.Util.bytes(output);
    }
    

    IV(初始化向量)是随机生成的,将IV放在crtpt()加密的数据之前然后返回

    加密结束后,在org/apache/shiro/web/mgt/CookieRememberMeManager.java的rememberSerializedIdentity方法中进行base64编码,并通过response返回

    这里的byte是前16位随机的IV+AES密文,然后经过base64编码

    解析cookie

    org/apache/shiro/web/mgt/CookieRememberMeManager.java中会将传递的base64字符串进行解码后放到字节数组中,因为java的序列化字符串即为字节数组

    byte[] decoded = Base64.decode(base64);
    

    然后进入解密流程

    先解密后进行反序列化

    AES是对称加密,加解密密钥都是相同的,并且shiro都是将密钥硬编码

    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);
    }
    
    public AbstractRememberMeManager() {
        this.serializer = new DefaultSerializer<PrincipalCollection>();
        this.cipherService = new AesCipherService();
        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
    }
    

    org/apache/shiro/crypto/JcaCipherService.java的decrypt()方法中进行解密从cookie中取出iv与加密的序列化数据

    调用crypt方法利用密文,key,iv进行解密

    解密完成后进入反序列化,看上面的public AbstractRememberMeManager()这里用的是默认反序列化类

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

    readobject()触发反序列化

    至此,Shiro对Cookie的rememberMe的处理流程已整体调试分析结束。

    漏洞修复

    Apache Shiro 1.2.5版本的源码,修复方法就是将使用默认Key加密改为生成随机的Key加密:https://github.com/apache/shiro/commit/4d5bb000a7f3c02d8960b32e694a565c95976848

    参考

    https://ares-x.com/2020/04/20/IDEA远程调试Docker中程序的方法/
    https://paper.seebug.org/shiro-rememberme-1-2-4/
    https://xz.aliyun.com/t/6493?accounttraceid=052d6170a05a4736a42c47de607a2766sdut#toc-6
    https://www.mi1k7ea.com/2020/10/03/浅析Shiro-rememberMe反序列化漏洞(Shiro550)/

  • 相关阅读:
    敏捷之一:以终为始
    Rails non browser app高级篇-capistrano/daemon部署
    ios 5下设置屏幕方向为landscape
    Android敏捷开发实践(连载)
    Rails+Mongodb的一个重要技巧:如何得到last N Records?
    在macos x上 编译jzmq 3.x
    计划缓冲区
    转载程序员的十层楼
    特权账户是什么?
    如何管理公司的共享iPad?
  • 原文地址:https://www.cnblogs.com/twosmi1e/p/14279403.html
Copyright © 2011-2022 走看看