Web逆向:福建省公共资源交易电子公共服务平台
1 信息
| 网站 | URL |
|---|---|
| 福建省公共资源交易电子公共服务平台 | https://ggzyfw.fujian.gov.cn |

2 接口
点击交易信息,主要是获取下面的招标公告信息

观察页面可以发现,招标公告 上方可以通过选择类型和日期来筛选不同的公告
通常,这些选择也会体现在请求的参数中,网站将根据前端传入的请求参数选择性的响应内容
3 分析
数据接口是 TradeInfo,将数据接口转换为 requests 请求


import requests
import json
headers = {
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/json;charset=UTF-8",
"Origin": "https://ggzyfw.fujian.gov.cn",
"Pragma": "no-cache",
"Referer": "https://ggzyfw.fujian.gov.cn/business/list/",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
"portal-sign": "168aa8b909b0ac4f0866e5a0e4d32311",
"sec-ch-ua": "\"Google Chrome\";v=\"149\", \"Chromium\";v=\"149\", \"Not)A;Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
url = "https://ggzyfw.fujian.gov.cn/FwPortalApi/Trade/TradeInfo"
data = {
"pageNo": 1,
"pageSize": 20,
"total": 2288,
"AREACODE": "",
"M_PROJECT_TYPE": "",
"KIND": "GCJS",
"GGTYPE": "1",
"PROTYPE": "",
"timeType": "6",
"BeginTime": "2026-01-05 00:00:00",
"EndTime": "2026-07-05 23:59:59",
"createTime": "",
"ts": 1783255330909
}
data = json.dumps(data, separators=(',', ':'))
response = requests.post(url, headers=headers, data=data)
这里我们需要分析的是:
- 请求头中
portal-sign参数的生成算法 - 响应的密文如何解密
请求参数中不同字段的含义分析不在本文的内容范围内
3.1 portal-sign 参数分析
通过 搜索法 定位到 portal-sign 参数生成位置

可以看到 portal-sign 参数来自 getSign 函数,接下来就需要分析:
getSign的类型getSign的输入
当一个函数输出的字符串长度为 32,并且字符串中各个字符都是 十六进制字符 时,我们可以考虑这个函数使用了MD5哈希算法
我们通过一个固定的字符串 zephyr 来测试,该字符串经过 MD5 哈希后的值为779f8a3a8c60f325a2171a2653d27f6e

网站中对 zephyr 字符串哈希后的值与预期的不一致,那么我们就需要深入了解 getSign 函数

看来,网站将 JS 对象序列化成字符串搭配一个固定的 Salt 后使用了标准 MD5 算法进行哈希
序列化方法可以直接从网站JS中获取,至此我们已经明白了 portal-sign 参数的生成算法
3.2 响应解密分析
本章节主要讲解 JavaScript Hook 技术如何应用于定位响应解密代码位置
JavaScript Hook 技术是指在目标函数执行流程中嵌入自定义逻辑,不改变其对外行为的技术。本质是 拦截 → 增强 → 透传
通常网站解密过程中会使用到 JSON 相关的方法,我们可以通过 Hook 这些方法来获取其被调用时的输入和输出,并且通过 console.trace 还可以找到方法被调用时的具体位置
一种比较简单的 Hook JSON 代码是这样写的
(function() {
var _parse = JSON.parse;
var _stringify = JSON.stringify;
// Hook JSON.parse
JSON.parse = function(text, reviver) {
console.trace("[JSON.parse] input:", text);
return _parse.call(this, text, reviver);
};
// Hook JSON.stringify
JSON.stringify = function(value, replacer, space) {
console.trace("[JSON.stringify] input:", value);
return _stringify.call(this, value, replacer, space);
};
})();
[!CAUTION]
该
Hook代码并没有做防检测和绕过相关的处理,只是简单的演示Hook原理

将代码放在代码段中(或者直接在控制台运行)然后运行,整个网页的 JSON.parse 和 JSON.stringify 都被 Hook 了
当我们刷新整个网页时 Hook 会失效,此时必须重新运行 Hook 代码
所以,我们通过分页按钮触发网站数据更新(不要刷新网页)
当我们看到控制台自动输出 console.trace 的内容,这代表了我们成功 Hook JSON


console.trace 会输出方法的调用链,顺着这个调用链我们就可以找到响应解密的位置

解密代码很简单:JSON.parse(b(e.data)) ,分析 b 函数就行了

b 是标准的 AES 加密,使用了 CBC 模式
使用 CryptoJS 这个库,只需要找到 key 和 iv 就可以直接完成解密
4 结果
TODO