js逆向-某省公共资源交易平台(头部加密)

声明:本文仅供学习交流,请勿暴力爬取数据,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

网站
抓包 -> 测试能否请求
请求头中有加密参数

1
2
3
4
x-dgi-req-app:ggzy-portal
x-dgi-req-nonce:r2jEe2MRLPliMvi9
x-dgi-req-signature:f7427ad322e3dba49bb28154d20876ef7e37b082fceb06ba944e1018dee207eb
x-dgi-req-timestamp:1726214333104

路径XHR断点 -> 跟栈找到加密参数
进入函数h[d++]

1
c = c.then(h[d++], h[d++]);

目标函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function u(o) {
_o.inc();
const r = dr()
, {params: s, url: i} = o
, a = Date.now()
, l = hne(16)
, c = qu([8, 28, 20, 42, 21, 53, 65, 6])
, d = {
[qu([56, 62, 52, 11, 23, 62, 39, 18, 16, 62, 54, 25, 25])]: qu([11, 11, 0, 21, 62, 25, 24, 19, 20, 15, 7]),
[qu([56, 62, 52, 11, 23, 62, 39, 18, 16, 62, 60, 24, 5, 2, 18])]: l,
[qu([56, 62, 52, 11, 23, 62, 39, 18, 16, 62, 40, 23, 6, 18, 14, 20, 15, 6, 25])]: a
};
if (o.method.toLowerCase() === "get") {
(s == null ? void 0 : s.siteCode) === void 0 && !i.includes("?siteCode=") && (s ? o.params.siteCode = r.currentSite : o.params = {
siteCode: r.currentSite
});
const p = t1({
p: s,
t: a,
n: l,
k: c
});
d[[qu([56, 62, 52, 11, 23, 62, 39, 18, 16, 62, 53, 23, 11, 5, 15, 20, 22, 19, 18])]] = p
} else {
const p = t1({
p: sC.stringify(o.data, {
allowDots: !0
}),
t: a,
n: l,
k: c
});
d[[qu([56, 62, 52, 11, 23, 62, 39, 18, 16, 62, 53, 23, 11, 5, 15, 20, 22, 19, 18])]] = p,
e === "mhyy" && (o.data ? o.data.noSiteCode ? delete o.data.noSiteCode : o.data.siteCode === void 0 && (o.data.siteCode = r.currentSite) : o.data = {
siteCode: r.currentSite
})
}
const f = rp("token");
return f && (d.token = f),
o.headers = {
...d,
...o.headers
},
o
}

在开发者工具观察得知d包含了所有加密参数,删除多余的代码并补全缺失的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const { SHA256 } = require("crypto-js");
lF = 'zxcvbnmlkjhgfdsaqwertyuiop0987654321QWERTYUIOPLKJHGFDSAZXCVBNM';
function dne(e, t) {
switch (arguments.length) {
case 1:
return parseInt(Math.random() * e + 1, 10);
case 2:
return parseInt(Math.random() * (t - e + 1) + e, 10);
default:
return 0
}
}
function hne(e) {
return [...Array(e)].map(() => lF[dne(0, 61)]).join("")
}
function pne(e) {
let t = "";
return typeof e == "object" ? t = Object.keys(e).map(n => `${n}=${e[n]}`).sort().join("&") : typeof e == "string" && (t = e.split("&").sort().join("&")),
t
}
//通过定位调试,得知使用了SHA256加密算法
function t1(e = {}) {
const { p: t, t: n, n: u, k: o } = e
, r = pne(t);
return SHA256(u + o + decodeURIComponent(r) + n)
}
function test(page) {
const a = Date.now()
, l = hne(16)
, c = 'k8tUyS$m'
, d = {
['X-Dgi-Req-App']: 'ggzy-portal',
['X-Dgi-Req-Nonce']: l,
['X-Dgi-Req-Timestamp']: a
};
//page实现分页功能
const p = t1({
p: 'type=trading-type&openConvert=false&keyword=&siteCode=44&secondType=A&tradingProcess=&thirdType=%5B%5D&projectType=&publishStartTime=&publishEndTime=&pageNo=' + page + '&pageSize=10',
t: a,
n: l,
k: c
});
//toString()方法用于将ArrayBuffer转换为十六进制字符串
d['X-Dgi-Req-Signature'] = p.toString();
return d;
}

python代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import requests
import execjs

cookies = {
'_horizon_uid': '45dc0714-78fc-4996-8a4a-c3f9719ff41a',
'_horizon_sid': 'fc6e9db1-c635-4a4e-b046-222841daf6bb',
}

headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Connection': 'keep-alive',
'Content-Type': 'application/json',
# 'Cookie': '_horizon_uid=45dc0714-78fc-4996-8a4a-c3f9719ff41a; _horizon_sid=fc6e9db1-c635-4a4e-b046-222841daf6bb',
'Origin': 'https://ygp.gdzwfw.gov.cn',
'Referer': 'https://ygp.gdzwfw.gov.cn/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0',
'sec-ch-ua': '"Not)A;Brand";v="99", "Microsoft Edge";v="127", "Chromium";v="127"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}

# 切换pageNo即可分页
json_data = {
'type': 'trading-type',
'openConvert': False,
'keyword': '',
'siteCode': '44',
'secondType': 'A',
'tradingProcess': '',
'thirdType': '[]',
'projectType': '',
'publishStartTime': '',
'publishEndTime': '',
'pageNo': 2,
'pageSize': 10,
}

with open('test.js', 'r', encoding='utf-8') as f:
js_code = f.read()
d = execjs.compile(js_code).call('test', json_data['pageNo'])
headers['X-Dgi-Req-Signature'] = d['X-Dgi-Req-Signature']
headers['X-Dgi-Req-Nonce'] = d['X-Dgi-Req-Nonce']
headers['X-Dgi-Req-Timestamp'] = str(d['X-Dgi-Req-Timestamp'])

response = requests.post('https://ygp.gdzwfw.gov.cn/ggzy-portal/search/v2/items', cookies=cookies, headers=headers, json=json_data)
print(response.json())