Apereo CAS反序列化漏洞中数据加解密研究

0x01、简介

Apereo CAS,全称为 Central Authentication Service,是一种开源的单点登录(SSO)解决方案。它提供了一个可扩展的、可定制的平台,用于统一身份验证和访问控制,支持多种认证协议和技术。Apereo CAS 可以轻松地集成到现有的应用程序和服务中,为用户提供单点登录和数据交互能力。该系统具有高度的安全性和可靠性,同时也支持多种操作系统和编程语言。Apereo CAS 是一个成熟的、高度稳定的 SSO 解决方案,已经被广泛地应用于大型机构、企业和政府机构中。

4.1.7版本之前存在AES默认密钥的问题,利用这个默认密钥我们可以构造恶意信息触发目标反序列化漏洞,进而执行任意命令。

最近在写攻防中用到的工具(红队版:一键开天门;蓝队版:一键守天门)

其中蓝队版的一个功能:一键解密流量工具,为实现一键智能化

(毕竟蓝队猴子怎么会看得明白什么是shiro、什么是cas、什么是哥斯拉\蚁剑\冰蝎等等呢(对不起,不是所有人但是真的有))

因此需要研究一下CAS漏洞利用中execution值的加解密算法!结果网上没有一个深入研究构造结构体的,大多都是误导文章,而且都是直接套用cas原jar包

结论:UUID + _ + 头部长度标识(7byte) + iv长度标识(1byte) + iv值(16byte) + keyName(10byte) + AES密文
在这里插入图片描述

0x02、网上获取资料

针对于网上搜集到的资料进行总结:

  1. execution值处理流程过程为: 剔除头部UUID字段+base64解密+AES解密+Gzip解密
  2. AES存在默认密钥
  3. AES加密模式为 AES/CBC/PKCS7
  4. 【很多文章里都没有提到】 AES加解密相关的配置会 先去配置文件中获取 ,没有配置密钥信息的会使用 jar包默认的密钥信息
  5. 密钥库 的密码为 changeit
  6. 默认 keystore文件 位于 spring-webflow-client-repo-1.0.0.jar!/etc/keystore.jceks
  7. 网上有很多exp工具,直接使用cas工程的相关jar包、java文件,来调用加密函数,不需要自己写
  8. cas工程使用的是 Cryptacular 第三方库进行的AES加解密。

真的如网上所说的这些信息就够了么?开始调试

0x03、初步运行失败

1、分析:

  1. 剔除头部uuid;
  2. AES\CBC\PKCS7Padding解密,key为changeit;
  3. iv向量为多少?网上没提到,猜测随机生成;
import com.hotboy.utils.aesUtils;
import com.hotboy.utils.strUtils;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class casDecryptTest {

    public static byte[] decrypt(String input,aesUtils aes){
		String input = "7b951c2a-e78f-4286-95fe-970782352a84_AAAAIgAAABDE2HZ3uiB2bzFHXNO5uObYAAAABmFlczEyOCPJEIAG4U8FA%2b%2bSoqcRzovVlpSWfd/raZVfVf3gXUc8f9Xz%2beN25UhRBwwmMRAv%2byjSVLKbWeRzPkGeVvof/44rS4PcFfF9pzwo%2bEbqJz6ZBCo5%2blAczVCAp8UBjRy7R/jkb/YSZj6YBHDMJ0ejjqFly779A9b3opzyMwCIJod0yvs0qtYNd0qXhd8yY/XpatlelngVKxqLDp0lrwXmP7W3YblsIV/r3bJv2mHk1qAgVL54yTX4en4i17z37qKv6CBkRxZN5ORAERYUm4E%2bsmckjnQEKcjb2bqMWSi7WKxc57DPIBwnjmUJ8plB8aoNsjKxuOYri%2bsBpkZDcFucuyiTOwPOlGm4CaUYHxpoCiaLSJb%2bsj/0Iml107F8L%2bH/pzwtH1BHQef1eVtcml0AKGpVf0YzR9UUua8PysUxbqQpFa36nC3RgIoz97v9Hi6oCkBg9WlarS0QVE7lUSG6SquiT/hzPz9TvP%2b3Yw48BrU04JltH76rboR07zDvgMy3sBk32hKb8w2qbBk5Vo3xVRqwS8Z6fUm1Zl9BnRXr1/kIFP58dLbkwraq%2bD4/%2bbtAMb4F7LB8c%2b1jihZU3vHI7gvpQTtrLr4z%2bqtm8C8NN4rkcbT77QLfKM%2bMCqRhSFzoGyy9Qs%2bhO3Xi34tHUh6oANU3FPP/Cd%2b3/B6w%2bw9W13ecESXG8H6w374I64UiWRQRSnWqciO%2b4BVhkdRtfOF2d7UCw9zL/vqwrTcMgUtbCPtbD1Wj/ucxvut5oeeBPlfPicGz0Ohr9FI0C9j1myRP6DZ6Uv2SlomPnNhrY/z7C6SbjkSF9CLvOhN92XA9OklEkm1HOIm7uFhQ5JL1Fv60muZW/ifqjUrR2kkhUv/LA4nixpcjUpOYRdbT2O7/wIXs7jhInwlFVACx3TL4KrRO6Zb79uGJJdMlHdWREfPr81dxJ9G8u%2bYNYz2djvrbTV%2b/BgZxMRmVugXLH7%2bQRAOhozyvDm5XMnGusjga/NLBFQO4A341puj2QzTdG2R%2bzUexFLeXc2TZ5XgrKqVvjIKeNYPECSKDiZZc%2bj1ivpQt4bUNS2Nx7Hup2%2bUhiwxA8pVtxiVY0YU3QWvphUXUSdu5nFp7qOGz0Yy9m5wOU4kIKyIlJnEeaKVMTETd7TtzhQ5sYEATKpzGUrezaaHei0%2bbkRjGAgK7q8/Wkb/tueJZ2af3IeHOeyulA/%2bHRpvDDMzS0AiGvhvLMVu7PNUKpIKrGuPgIPuXTy6N2WoYQiewnAKekaS03DukE2g%2bTTMFVJ2OXUBpF5MHxAs68NoJCw0s/UgdzKaYWUHH6dS9AjMiYx0eSS7RtUf6bbgMZkLQzL%2bW4CV8gRvacJ7Jn0bsg3zZBGt/8CaVfhNpXU4g3MqAYz9w0iinx0mUPgP7%2bYwf2D2Qg/KkGeY8Qg3sJH4T7oEjs4PjeqbYpPGxjQZd7Uhlv%2b8TkorNmfGXBrl6M9Deow3lWGX/zl1u9uH%2bbNTiSRJAb1dGqqiVtGE3j1Ld/RRgxCI9/TB93dBdovtdKVmhZyjfsIWQS5ypneaYgFbv/l0WBG8GvwGs3QBbYDQbNC8lF5OQ7OS0xCYWBecHqvOlEjU68Yb8crUe3f0q9YXkDum%2b3OlDwmg/SQylqrmO9taYI%2brU8JByMu/ZnTjiWPMOyUx9Codsj5ml6PxMK3OcZHBj7G9BLJJz4XHPisOZxt0LUjKMeH/0itQmxeEnPn%2blvcOWp%2bYNsF6UjmYdnKkSEd9s61jN9lPwCB3m10w%2bRWEIPbbgm6Gn/Yelf1dd4T%2b4e70tJeTWI%2bImR%2be97HxOLEyw3D7aCSzS0LSzTvtGsdk7XrvmiYTzK3aR/i/SQypZCNgpi4vncxLAQ8iJ9xT541Z0gqhWYfJB7XS087RhmnhGfwISZ5rkjYBZPr12Ho5M4xaRKfpu2qWgbnvuBBqMNJWu5JOPMbgVMm3C3DzcRnTs9oVnI81go4j1yJ0tOAguj8iviikAk1/2s3EIgFGnA6gxSZV6XlDwhqpBFzMwQNne/O0bvezmLwQ/Rx%2bjbNSo8ZhmdQ%3d%3d";
		byte[] res = null;
        String code = "";
        input = strUtils.urlDecode(input);

        Pattern pattern = Pattern.compile("([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})_(.*)");
        Matcher matcher = pattern.matcher(input);
        if (matcher.find()) {
            code = matcher.group(2);
        }
        if( code == null || code == "") {
            return res;
        }
		try {

            byte[] encryptData = strUtils.base64Decode(code.getBytes(StandardCharsets.UTF_8));

            byte[] key = "changeit".getBytes(StandardCharsets.UTF_8);
            byte[] iv = aes.generateRandomBytes(16);
            
            String mode = "CBC";
            String padding = "PKCS7Padding";

            byte[] result = aes.decrypt(
                    encryptData,
                    key,
                    iv,
                    mode,
                    padding
            );
            
            aes.mode_AES.set(mode);
            aes.padding_AES.set(padding);
            aes.key_AES.set(Arrays.toString(key));
            aes.iv_AES.set(Arrays.toString(iv));
            res = result;
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return res;
    }
}

2、Tips:

你们直接运行不了,很多都是单独封装的模块,可以根据文中意思自己大概写一下。Sorry~ 娜扎

0x04、分析原因

  • 1、自己写解密算法 / 直接使用cas工程的相关jar包、java文件,调用解密函数

  1. 工程内,自己已经使用了基于java标准API的crypto封装了更完善的AES加解密方法,并且支持自动检测结果是否存在gzip\class\反序列化特征并再处理,不同于Cryptacular,支持低级别的数据加密。
  2. 作为高级软件开发工程师,怎么能容忍自己的代码变成屎山呢?
  • 2、为什么会解密失败?

  1. 突然想起来,自己傻Der了~,Aes的Key怎么可能是changeit,Key应该是16位
  2. 全网都在提 “changeit” 硬编码,以下代码,被误导了:
public EncryptedTranscoder() throws IOException {
    final BufferedBlockCipherBean bufferedBlockCipherBean = new BufferedBlockCipherBean();
    bufferedBlockCipherBean.setBlockCipherSpec(new BufferedBlockCipherSpec("AES", "CBC", "PKCS7"));
    bufferedBlockCipherBean.setKeyStore(createAndPrepareKeyStore());
    bufferedBlockCipherBean.setKeyAlias("aes128");
    bufferedBlockCipherBean.setKeyPassword("changeit");
    bufferedBlockCipherBean.setNonce(new RBGNonce());

    setCipherBean(bufferedBlockCipherBean);
}
  1. 因为没有用过Cryptacular库,学习一波才明白,使用"changeit"这个pass初始化密钥库,然后根据Alias(“aes128”)这个keyName获取Aes真正的key。
方法作用
setKeyStore初始化密钥库
setKeyAlias设置获取密钥类型
setKeyPassword设置密钥库密码(changeit)
  1. 那必然是需要部署相关jar包、java文件调试了(Tips:直接使用网上exp工具项目,他们已经抽取完毕了)

0x05、断点调试

  1. java中进行AES解密,基本上所有第三方包,都会进入AES解密时的java标准api
方法作用
cipher.init()初始化密码(Cipher)对象,需要传入加密的 mode / key / iv
cipher.doFinal()执行加密或解密操作
  1. 想要获取key及iv向量,关键的就在cipher.init(),找到位置打断点获取key/iv的值,但是无法直接全局搜索cipher.init(因为该方法在jar包中,编辑器不支持搜索jar中class的内容)
  • 手动点开jar,根据可能的文件名点开查看寻找;(不推荐)
  • 调用外层java中的解密方法,手动根据方法调用找方法(如果开发者再次封装的不繁杂的话可以)
  • 调用外层java中的解密方法,打断点先大跳(步过)粗略走流程,然后根据可能位置逐步步入判断
  1. 采用了第二种方案,结果发现封装的裂开,很容易走错。采用第三种方案:

最终在找到在BufferedBlockCipherBean.class中,调用到了cipher.ini()

Tips:在步入得到过程中,有的方法[步入]功能不进去,需要使用[强制步入]功能

在这里插入图片描述

0x06、代码分析

1、key/iv初始化函数确定

key初始化方式的确如之前分析的根据keyName在密钥库中寻找lookupKey

key初始化位置在:params = new ParametersWithIv((cipherParameters)params, header.getNonce())

通过打断点获取到了key真正的值(byte[]):[78, -47, -80, -25, 76, 55, -57, -111, -81, -3, -54, 62, 118, 15, 113, 0]
在这里插入图片描述

iv初始化位置在:params = new ParametersWithIv((cipherParameters)params, header.getNonce())

这一步会获取IV向量值存放在params中,通过多次断点调试,发现针对于相同密文,他的IV是不变的,不同密文,他的IV值是会变的。因此得到一个结论:

key是固定的,iv是根据密文计算得来的

2、key初始化函数分析

  • keys固定的,就不带着函数挨个进了,流程如下:

读取配置文件中的配置;默认无,则使用"changeit"这个固定pass初始化解密读取默认密钥库(spring-webflow-client-repo-1.0.0.jar!/etc/keystore.jceks),然后根据Alias(“aes128”)这个keyName遍历获取获取Aes真正的key

3、iv初始化函数分析

在这里插入图片描述
代码解读:

  • 1、将原始密文读取如bb变量; // 如 [0,0,0,34,0,0,0,16,…]
  • 2、大端序重新排列; // 不变
  • 3、从bb根据第二部的大端序读取带4个字节兵解释为带符号的整数值,作为header头部长度; // 34
  • 4、创建byte[] nonce,大小为iv长度标识; // byte[]{0,0,0,0,0,0…} 大小为16
  • 5、从bb的iv长度标识后中读取对应长度放入nonce;
  • 6、header头部处理完剩余的部分放入keyName字段; // aes128

0x07、总结

以此可以推断出,密文的实际构造情况为

UUID + _ + 密文

UUID + _ + header头部(34byte) + AES密文

UUID + _ + 头部长度标识(7byte) + iv长度标识(1byte) + iv值(16byte) + keyName(10byte) + AES密文

运行通过

package com.hotboy.content.blueTeam;

import com.hotboy.utils.aesUtils;
import com.hotboy.utils.strUtils;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Potato
 */
public class casDecrypt {

    public static byte[] decrypt(String input,aesUtils aes){
        byte[] res = null;
        String code = "";
        input = strUtils.urlDecode(input);

        Pattern pattern = Pattern.compile("([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})_(.*)");
        Matcher matcher = pattern.matcher(input);
        if (matcher.find()) {
            code = matcher.group(2);
        }
        if( code == null || code == "") {
            return res;
        }

        try {

            byte[] encryptData = strUtils.base64Decode(code.getBytes(StandardCharsets.UTF_8));

            byte[] key = {78, -47, -80, -25, 76, 55, -57, -111, -81, -3, -54, 62, 118, 15, 113, 0};
            byte[] iv = new byte[16];
            System.arraycopy(encryptData, 8, iv, 0, 16);
            String mode = "CBC";
            String padding = "PKCS7Padding";

            // 剔除header头部34个标志性字节
            byte[] tmpEncryptData = new byte[encryptData.length - 34];
            System.arraycopy(encryptData, 34, tmpEncryptData, 0, encryptData.length - 34);
            encryptData = tmpEncryptData;

            byte[] result = aes.decrypt(
                    encryptData,
                    key,
                    iv,
                    mode,
                    padding
            );

            aes.mode_AES.set(mode);
            aes.padding_AES.set(padding);
            aes.key_AES.set(Arrays.toString(key));
            aes.iv_AES.set(Arrays.toString(iv));

            res = result;

        } catch (Exception e) {
            e.printStackTrace();
        }

        return res;
    }
}

运行成功,成功获取源代码:

在这里插入图片描述


感谢Allan、Songqb、小严同学,虽然没给我解决任何问题:)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Fastjson是一款Java语言编写的高性能JSON处理器,被广泛应用于各种Java应用程序。然而,Fastjson存在反序列化漏洞,黑客可以利用该漏洞实现远程代码执行,因此该漏洞被广泛利用。 检测Fastjson反序列化漏洞的方法: 1. 扫描源代码,搜索是否存在Fastjson相关的反序列化代码,如果存在,则需要仔细检查反序列化的过程是否安全。 2. 使用工具进行扫描:目前市面上有很多漏洞扫描工具已经支持Fastjson反序列化漏洞的检测,例如:AWVS、Nessus、Burp Suite等。 利用Fastjson反序列化漏洞的方法: 1. 利用Fastjson反序列化漏洞执行远程命令:黑客可以构造一个恶意JSON字符串,通过Fastjson反序列化漏洞实现远程命令执行。 2. 利用Fastjson反序列化漏洞实现文件读取:黑客可以构造一个恶意JSON字符串,通过Fastjson反序列化漏洞实现文件读取操作。 3. 利用Fastjson反序列化漏洞实现反弹Shell:黑客可以构造一个恶意JSON字符串,通过Fastjson反序列化漏洞实现反弹Shell操作。 防范Fastjson反序列化漏洞的方法: 1. 更新Fastjson版本:Fastjson官方在1.2.46版本修复了反序列化漏洞,建议使用该版本或更高版本。 2. 禁止使用Fastjson反序列化:如果应用程序不需要使用Fastjson反序列化功能,建议禁止使用该功能,可以使用其他JSON处理器。 3. 输入验证:对所有输入进行校验和过滤,确保输入数据符合预期,避免恶意数据进入系统。 4. 序列化过滤:对敏感数据进行序列化过滤,确保敏感数据不会被序列化。 5. 安全加固:对系统进行安全加固,如限制系统权限、加强访问控制等,避免黑客利用Fastjson反序列化漏洞获取系统权限。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

土豆.exe

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

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

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

打赏作者

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

抵扣说明:

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

余额充值