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密文
针对于网上搜集到的资料进行总结:
真的如网上所说的这些信息就够了么?开始调试
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;
}
}
你们直接运行不了,很多都是单独封装的模块,可以根据文中意思自己大概写一下。Sorry~ 娜扎
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);
}
方法 | 作用 |
---|---|
setKeyStore | 初始化密钥库 |
setKeyAlias | 设置获取密钥类型 |
setKeyPassword | 设置密钥库密码(changeit) |
方法 | 作用 |
---|---|
cipher.init() | 初始化密码(Cipher)对象,需要传入加密的 mode / key / iv |
cipher.doFinal() | 执行加密或解密操作 |
最终在找到在BufferedBlockCipherBean.class中,调用到了cipher.ini()
Tips:在步入得到过程中,有的方法[步入]功能不进去,需要使用[强制步入]功能
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是根据密文计算得来的
读取配置文件中的配置;默认无,则使用"changeit"这个固定pass初始化解密读取默认密钥库(spring-webflow-client-repo-1.0.0.jar!/etc/keystore.jceks),然后根据Alias(“aes128”)这个keyName遍历获取获取Aes真正的key
代码解读:
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、小严同学,虽然没给我解决任何问题:)