省流
目前请求时需要添加四个关键头部参数用于验证,其中必需的是 x-vqd-4
和 x-vqd-hash-1
(不过都拿出来了):
参数
x-fe-signals
在浏览器控制台中执行:
1
|
window.__DDG_BE_VERSION__ + window.__DDG_FE_CHAT_HASH__,
|
大概是固定的
x-fe-version
这是一个点击控制信息,解码格式如下:
1
2
3
4
5
6
7
8
9
10
|
{
"start": 1748338159233,
"events": [
{"name": "onboarding_impression_1", "delta": 2},
{"name": "onboarding_impression_2", "delta": 345451},
{"name": "onboarding_finish", "delta": 346530},
{"name": "startNewChat", "delta": 346549}
],
"end": 352948
}
|
直接伪造并进行 Base64 编码即可
x-vqd-4
和 x-vqd-hash-1
这两个参数需要从 DuckDuckGo 的状态 API 获取:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
curl -X GET 'https://duckduckgo.com/duckchat/v1/status' \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36' \
-H 'accept-language: en-US,en;q=0.9' \
-H 'cache-control: no-store' \
-H 'priority: u=1, i' \
-H 'referer: https://duckduckgo.com/' \
-H 'sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "Windows"' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: same-origin' \
-H 'x-vqd-accept: 1' \
-H 'Cookie: dcm=3'
|
(x-vqd-accept 设为 1 时有效)
x-vqd-4
直接使用 API 返回的 x-vqd-4
值
x-vqd-hash-1
这部分可以玩玩
- 请求头的API解码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
{
"server_hashes": [
"sez/jUZEafdmWjGTy4HVDNZozO44dIEpSp2HvA4jigE=",
"DZG60hz+ay4TVu9z1fk2z5JIyCfrLEhq1CSQxmjsaGU="
],
"client_hashes": [
"W3E/w78cQGJhqE9wUYViQEE9dVr74wWbHL7AP1zYWoA=",
"Ey9ukEo8q2xSmzDx11eOZ+mm84S4jrSj3gzq6K3qU3A="
],
"signals": {},
"meta": {
"v": "3",
"challenge_id": "8f2d8c3d15195ef21b8426efccd558dc9fea43cdd986355f9ff7018053af76b3h8jbt",
"timestamp": "1749730629886",
"origin": "https://duckduckgo.com",
"stack": "Error\nat Cn (https://duckduckgo.com/dist/wpm.chat.652b7f6eac131ba0090b.js:1:62737)\nat async dispatchServiceInitialVQD (https://duckduckgo.com/dist/wpm.chat.652b7f6eac131ba0090b.js:1:87463)" // 非必需,不用纠结这个
}
}
|
- 对 上文API响应头 返回的
x-vqd-hash-1
进行 Base64 解码,得到一个 IIFE(立即执行函数表达式),反混淆后的代码结构如下:
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
|
(function () {
return {
server_hashes: [
"sez/jUZEafdmWjGTy4HVDNZozO44dIEpSp2HvA4jigE=",
"DZG60hz+ay4TVu9z1fk2z5JIyCfrLEhq1CSQxmjsaGU="
],
client_hashes: [
navigator.userAgent +
(navigator.userAgentData
? navigator.userAgentData.brands
.map((brand) => `"${brand.brand}";"v="${brand.version}"`)
.join(", ")
: ""),
(function () {
const div = document.createElement("div");
div.innerHTML = "<div><div></div><div></div";
return String(
0xb85 + div.innerHTML.length * div.querySelectorAll("*").length
);
})(),
],
signals: {},
meta: {
v: "1",
challenge_id:
"8f2d8c3d15195ef21b8426efccd558dc9fea43cdd986355f9ff7018053af76b3h8jbt",
},
};
})();
|
3.对应的俩加密函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
client_hashes: await Promise.all(
n.client_hashes.map((e) =>
(async function (e) {
const t = new TextEncoder().encode(e),
n = await crypto.subtle.digest("SHA-256", t),
a = new Uint8Array(n);
return btoa(
a.reduce((e, t) => e + String.fromCharCode(t), "")
);
})(e)
)
)
meta: Et(
Et({}, n.meta || {}),
{},
{
origin: Rt(),
stack: St(new Error()),
}
)
|
然后把返回的值替换掉再进行base64编码
其实,直接复用client_hashes就好了,
毕竟你看源码SHA-256 digest…它没办法复原的,好迷
(但是太离谱了仍然会伪掉,建议自己拿固定值digest一下,前者看着来,后者3000-5000即可)
- 将返回值转换为 Base64 编码
注意:每次调用 API 返回的 js 都会变化
成功截图
