JS逆向-数据包解签名实战案例
字数 1598 2025-08-10 16:34:25
JS逆向-数据包解签名实战案例教学文档
0x00 前言
本教学文档基于FreeBuf上的JS逆向实战案例,详细讲解如何分析并破解数据包中的签名机制。签名机制常用于API请求的身份验证和防篡改,常见签名字段如sign、appsign等。
免责声明:本文涉及的所有技术仅用于学习交流,严禁用于非法用途。未经授权请勿进行非法渗透测试,否则后果自负。
0x01 签名机制分析流程
1. 识别签名字段
通过抓包分析,确定目标网站的签名字段为sign。
2. 定位签名代码
在浏览器开发者工具的Sources面板中搜索sign字段,通常签名逻辑会出现在app.*.js这类文件中。在本案例中,签名代码位于app.72b81572.js文件中。
3. 分析签名函数
找到的关键签名函数如下:
function k(e) {
const t = g(),
n = m();
let a;
a = e ? b(e) : {};
const r = e ? `${c.a.stringify(a.hasParams)}&${t}&time=${n}` : `${t}&time=${n}`;
return e = Object.assign({}, a.params, {
sign: h()(decodeURIComponent(r)),
time: n
}), e
}
4. 参数分析
参数t
通过跟进g()函数,发现t的值为:
当前年份 + "5616" + 当前月份(两位数) + 当前日期(两位数)
例如:202356160812
参数n
通过跟进m()函数,发现n的值为当前时间戳(本案例中可不减去e值)。
参数a
a的值与传入参数e相同,e的内容即请求参数(GET)或请求体(POST)。
参数r
r是签名的明文,格式为:
请求内容 + "&" + t + "&time=" + n
注意:POST请求的JSON格式数据需要转换为key1=value1&key2=value2...的形式。
0x02 加密算法分析
通过调试发现加密类名为Md5,签名过程是对r字符串进行MD5哈希计算。
验证方法:
- 在签名代码行设置断点
- 获取r的值
- 计算r的MD5值
- 与代码执行的sign结果比较
0x03 mitmproxy脚本编写
mitmproxy是一个中间人代理工具,可以实时拦截和修改HTTP/HTTPS请求。以下是本案例的完整脚本:
from datetime import datetime
from mitmproxy import ctx
import hashlib
import random
import json
import time
class Modify:
def __init__(self):
self.plaintext = ''
self.new_sign = ''
current_datetime = datetime.now()
self.today = f"{current_datetime.year}5616{current_datetime.month:02d}{current_datetime.day:02d}"
def md5(self):
md5_hash = hashlib.md5()
md5_hash.update(self.plaintext.encode('utf-8'))
self.new_sign = md5_hash.hexdigest()
def request(self, flow):
if flow.request.method == "POST":
ctx.log.info(f'\n原POST请求体:{flow.request.text}')
data = json.loads(flow.request.get_text())
if data != '{}' and 'sign' in data:
del data['sign']
del data['time']
for k in data:
self.plaintext += f"{k}={str(data[k])}&"
timestamp = int(time.time() * 1000)
self.plaintext += f"{self.today}&time={timestamp}"
self.md5()
data["sign"] = self.new_sign
data["time"] = timestamp
flow.request.set_text(json.dumps(data).replace(" ", ""))
ctx.log.info(f'\n新POST请求体:{flow.request.text}')
self.plaintext = ''
else:
ctx.log.info('无参数,无需改签')
elif flow.request.method == "GET":
ctx.log.info(f'\n原GET请求体:{flow.request.query}')
query = flow.request.query
if query != '{}' and 'sign' in query:
del query['sign']
del query['time']
if query:
for k in query:
self.plaintext += f"{k}={str(query[k])}&"
else:
self.plaintext = "&"
timestamp = int(time.time()) * 1000
self.plaintext += f"{self.today}&time={timestamp}"
self.md5()
query["sign"] = self.new_sign
query["time"] = timestamp
ctx.log.info(f'\n新GET请求体:{flow.request.query}')
self.plaintext = ''
else:
ctx.log.info('无参数,无需改签')
@staticmethod
def response(flow):
if '签名校验失败!' in flow.response.text:
ctx.log.error(f'\n签名异常:\nurl => {flow.request.path}\n异常信息 => {flow.response.text}')
addons = [
Modify()
]
mitmproxy常用命令
mitmdump -p 777 -s ./mitmscript.py --flow-detail 0
mitmproxy常用属性和方法
上下文对象(ctx)
ctx.log.info(): 在mitmproxy日志中输出信息ctx.options: 访问mitmproxy配置选项ctx.master: mitmproxy的Master对象ctx.proxy: ProxyConfig对象ctx.client: ClientConnection对象ctx.server: ServerConnection对象ctx.protocol: ProtocolHandler对象
回调函数
request(flow): 拦截请求时调用response(flow): 拦截响应时调用error(flow): 请求/响应出错时调用clientconnect(flow): 客户端连接时调用serverconnect(flow): 连接服务器时调用
flow.request常用属性
method: 请求方法(GET/POST等)scheme: 协议(http/https)host: 主机名port: 端口号path: 路径部分url: 完整URLheaders: 请求头(字典)cookies: Cookie信息(字典)query: 查询参数(字典)content: 请求内容(字节)text: 请求内容(文本)urlencoded_form: URL编码表单数据(字典)multipart_form: 多部分表单数据(列表)
0x04 总结
本案例完整展示了如何:
- 识别签名字段
- 定位签名代码
- 分析签名参数生成逻辑
- 确定加密算法(MD5)
- 编写mitmproxy脚本实现自动签名
关键点:
- 签名明文格式:请求参数 + 时间相关字符串
- POST请求需要将JSON转换为键值对格式
- 时间戳和日期字符串是签名的重要组成部分
- MD5是常见的签名算法之一
通过掌握这些技术,可以更好地理解Web应用的安全机制,并用于合法的安全测试和研究。