mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1224 字
3 分钟
亚马逊Awf-waf-token算法
2026-08-09

lens.org AWS WAF 挑战算法逆向分析#

目标站点:https://lens.org 防护类型:Amazon Web Services WAF(aws-waf-token 挑战算法),challenge.js 采用重型字符串混淆 分析工具:Chrome DevTools MCP + js-reverse MCP + 静态反混淆脚本(output.js) 交付形式:纯 Python(collector/aws_waf.py),无浏览器依赖


1. 结论速览#

项目结果
请求链路✅ 已用 MCP 完全理清
AES-GCM 信封(Present/parsine 参数)✅ 已破译,并用固定密钥解密成功验证
校验和✅ CRC32(对指纹 JSON 求 CRC32,8 位十六进制)
PoW(NetworkBandwidth)✅ difficulty=1 → 1024 字节全零,非哈希爆破
hmac服务端下发的挑战字段,客户端原样回传(无需计算)
纯 Python 生成 tokencollector/main.py 可稳定产出合法 token
放行 lens.org⚠️ 本网络出口被 WAF 判定为 x-amzn-waf-action: captcha,需交互式图形拼图(gridcaptcha),属于 CAPTCHA 范畴,非纯算法可过

说明:算法本身已完整还原(能生成与真实浏览器同构、被 /mp_verify 正常受理的 token)。 “没过验证”的唯一原因是:本机出口 IP 触发的是 captcha 动作,需要解决图片拼图,这不是 aws-waf-token 算法可以绕过的部分。


2. 请求链路全景(图 1)#

请求链路

1. GET https://lens.org/
405 + HTMLWAF 挑战页)
响应头: x-amzn-waf-action: challenge | captcha
页面内联:
window.gokuProps = { key, iv, context } ← 关键素材
<script src="https://fd279df39e0b.71ddf327.us-west-2.token.awswaf.com/.../challenge.js">
<script src="...captcha.awswaf.com/.../captcha.js"> ← captcha 动作时才有
2. GET https://<token-host>/.../challenge.js (就是 output.js 的原型)
3. GET https://<token-host>/.../inputs?client=browser
→ {"challenge":{"input":"eyJ2ZXJzaW9uIjoxL...","hmac":"6IjudXbHAF...","region":"us-west-2"},
"challenge_type":"ha9faaffd31b4d5ede2a2e19d2d7fd525f66fee61911511960dcbb52d3c48ce25",
"difficulty":1}
4. 客户端本地:
- 解 NetworkBandwidth PoW → solution_data
- 采集浏览器指纹 → 指纹 JSON
- CRC32(指纹 JSON) → checksum
- AES-GCM 加密 → Present(parsine)
5. POST https://<token-host>/.../mp_verify
Content-Type: multipart/form-data
├── solution_metadata = <JSON 字符串>
└── solution_data = base64(1024 零字节) // difficulty=1
→ {"token":"07c32233-...:FAoAkYwQCwMrAAAA:5Ii6sn9E...","inputs":null}
6. 浏览器把 token 写入 document.cookie:
aws-waf-token=<uuid>:<part2>:<part3>; path=/; domain=.<host>
7. 携带 cookie 重放 GET lens.org
→ 合法 token + challenge 动作 → 放行(返回真实页面)
→ 本网络为 captcha 动作 → 仍需交互拼图

3. 关键素材:gokuProps#

挑战页 HTML 内联了 window.gokuProps

window.gokuProps = {
"key": "AQIDAHjcYu/GjX+QlghicBgQ/7bFaQZ...==", // RSA 包裹的 AES 密钥(DER 二进制)
"iv": "CgAF5jnAZQAAAVmI", // base64, 16 字节
"context": "h21iKhnO2GoKYWMIvIfW/HGSmDeJhH8...==" // 加密的上下文(令牌会话状态)
}
  • key服务器分发的受保护密钥,用于解密 context,还原挑战会话素材。
  • 这三个字段会原样回传到 solution_metadata.goku_props
  • 每次页面请求都不同(服务端动态下发)。

4. 挑战数据:/inputs#

GET /inputs?client=browser 返回:

{
"challenge": {
"input": "eyJ2ZXJzaW9uIjoxLCJ1YmlkIjoiYmM2MTliZTQt...", // base64(挑战 JSON)
"hmac": "6IjudXbHAFMW1WFqcfSywcmwzPyXQ/PaSe1PY4QohMo=", // 32 字节,服务端签名
"region": "us-west-2"
},
"challenge_type": "ha9faaffd31b4d5ede2a2e19d2d7fd525f66fee61911511960dcbb52d3c48ce25",
"difficulty": 1
}

input base64 解码后为(每次挑战都不同):

{
"version": 1,
"ubid": "bc619be4-160e-485e-bc8b-55793ba9d722",
"attempt_id": "50c38062-af85-4082-a65f-5f9fad86f2b9",
"create_time": "2026-08-09T02:07:14.343784131Z",
"difficulty": 1,
"challenge_type": "NetworkBandwidth"
}

注意:hmac服务端签名,客户端不做任何计算,原样回传即可。


5. NetworkBandwidth PoW(solution_data)#

从 output.js 反混淆代码(difficulty → 字节数 映射):

switch (difficulty) {
case 1: return 1024; // ← lens.org 当前为 difficulty=1
case 2: return 10240;
case 3: return 102400;
case 4: return 1048576;
case 5: return 10485760;
}

算法:

  1. 分配 difficulty 对应的字节数组(全零)
  2. new Blob([buffer])FileReader.readAsDataURL
  3. 取出 data:application/octet-stream;base64,<X> 中的 <X>

solution_data = base64(全零字节块),用于测量”上传带宽”。抓包实测为 1024 字节全零:

solution_data = "AAAAAAAAAAAAAAAA..." (1368 个 base64 字符,解码 1024 字节,全 0)

6. 指纹采集 + 校验和(checksum)#

6.1 指纹 JSON#

浏览器采集一组”人类特征”数据,构成指纹 JSON(解密后可见):

{
"metrics": {"fp2":1,"browser":1,"capabilities":1,"gpu":5,"dnt":0,"math":0,
"screen":0,"navigator":0,"auto":0,"stealth":1,"subtle":0,
"canvas":27,"formdetector":0,"be":1},
"start": 1786241234563,
"plugins": [{"name":"PDF Viewer","str":"PDF Viewer "}, ...],
"screenInfo": "1280-720-672-24-*-*-*",
"referrer": "https://www.google.com/",
"userAgent": "Mozilla/5.0 ... Chrome/151.0.0.0 Safari/537.36",
"capabilities": {"css":{...},"js":{...},"elapsed":1},
"gpu": {"vendor":"Google Inc. (AMD)","model":"ANGLE (...)", "extensions":[...]},
"canvas": {"hash":-252901508,"emailHash":null,"histogramBins":[...]},
"end": 1786241235210, "errors":[], "version":"2.4.0", "id":"<uuid>"
}

6.2 CRC32 校验和#

output.js 中校验和由 crcCalculator.calculate(utf8Encoder.encode(data)) 计算并转十六进制。

已验证zlib.crc32(fingerprint_json) 得到 6C75E0D6,与抓包中的 checksum 完全一致。


7. AES-GCM 信封 —— Present(parsine)参数(图 2)#

这是 output.js 第 7463 行 附近的核心节点:

_0x267e02["AES-GCM"] = "AES-GCM";
[4, crypto.subtle.importKey("raw",
this.hexToBytes("6f71a512b1e035eaab53d8be73120d3fb68a0ca346b9560aab3e5cdf753d5e98"),
_0x267e02, false, ["encrypt"])]

AES-GCM 信封

加密参数:

参数
算法AES-256-GCM(tagLength = 128 bit)
密钥6f71a512b1e035eaab53d8be73120d3fb68a0ca346b9560aab3e5cdf753d5e98(固定,32 字节,内嵌 challenge.js)
IV12 字节随机数(crypto.getRandomValues(new Uint8Array(12))
明文"<checksum>#<指纹JSON>",如 6C75E0D6#{"metrics":{...}}
附加认证数据 (AAD)

输出序列化(关键格式):

Present = base64(IV) + "::" + hex(ciphertext_with_tag)

抓包样例(signals[0].value.Present):

OmrI1ED1yiLN4lyX::f70e2e26ad4c5601c310f1e9b9d4bcc09f70b86f05b711c02191b2921abb13e7...
├─────────────┘ └──────────────────────────────────────────────────────────┘
base64(12B IV) hex(AES-GCM 密文+16B tag)

解密验证(Node WebCrypto / Python cryptography 均可):

key = bytes.fromhex("6f71a512...5e98")
iv = base64_decode("OmrI1ED1yiLN4lyX")
ct = bytes.fromhex("f70e2e26...")
明文 = AESGCM(key).decrypt(iv, ct) → "6C75E0D6#{"metrics":{...}...}"

明文开头 6C75E0D6# 与 metadata 中的 checksum 字段一一对应。


8. POST /mp_verify —— multipart 结构(图 3)#

multipart 结构

请求头:

POST https://fd279df39e0b.71ddf327.us-west-2.token.awswaf.com/.../mp_verify
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXXXX
Origin: https://lens.org
Referer: https://lens.org/

字段 1:solution_metadata(JSON 字符串)#

{
"challenge": {
"input": "eyJ2ZXJzaW9uIjoxLCJ1YmlkIjoiYmM2...",
"hmac": "6IjudXbHAFMW1WFqcfSywcmwzPyXQ/PaSe1PY4QohMo=",
"region": "us-west-2"
},
"solution": null,
"signals": [
{"name": "Zoey", "value": {"Present": "OmrI1ED1yiLN4lyX::f70e2e26..."}}
],
"checksum": "6C75E0D6",
"existing_token": "07c32233-...:...:...", // 旧 token,可为空
"client": "Browser",
"domain": "lens.org",
"metrics": [ {"name":"2","value":0.7999,"unit":"2"}, ... ],
"goku_props": {
"key": "AQIDAHjcYu/GjX+QlghicBgQ/7bFaQZ...==",
"iv": "CgAF5jnAZQAAAVmI",
"context": "h21iKhnO2GoKYWMIvIfW/HGSmDeJhH8..."
}
}

字段 2:solution_data#

base64(1024 字节全零) // NetworkBandwidth PoW 的答案

响应#

{
"token": "07c32233-22a3-409f-913c-43724aa89b01:FAoAkYwQCwMrAAAA:5Ii6sn9EOuecw+DkKLLoosMJau...",
"inputs": null
}

token 格式

aws-waf-token = <UUID>:<part2>:<part3>
07c32233-22a3-409f-913c-43724aa89b01:FAoAkYwQCwMrAAAA:5Ii6sn9EOuecw+DkKLLoosMJau...
含义
UUID设备/浏览器标识(与 ubid 相关)
part2(base64)版本/计数器头,16 字符 = 12 字节
part3(base64)加密载荷(JWT 风格,包含 expiry/seed 等,需服务端密钥)

Cookie 属性:path=/domain=.<host>SecureSameSite=Lax

页面同时把 token 写入 localStorage["awswaf_session_storage"],并记录 awswaf_token_refresh_timestamp


10. 纯 Python 实现#

入口:collector/main.py(PyCharm 右键可直接运行) 核心:collector/aws_waf.py

collector/main.py
from aws_waf import get_token
token = get_token(page_url="https://lens.org/",
referrer="https://www.google.com/",
existing_token="")
print("aws-waf-token =", token)

依赖:requestscryptography(纯 Python,无浏览器)。

关键函数:

def build_present(plaintext: str) -> str:
iv = os.urandom(12)
ct = AESGCM(AES_KEY).encrypt(iv, plaintext.encode(), None)
return b64e(iv) + "::" + ct.hex()
def build_solution_data(difficulty: int) -> str:
return b64e(bytes(DIFFICULTY_SIZES.get(difficulty, 1024)))
def crc32hex(text: str) -> str:
return format(zlib.crc32(text.encode("utf-8")) & 0xFFFFFFFF, "08X")
# 明文 = checksum + "#" + fingerprint_json
plaintext = crc32hex(fp_json) + "#" + fp_json
present = build_present(plaintext)

实测运行输出:

aws-waf-token = 07c32233-22a3-409f-913c-43724aa89b01:FAoAkYwQCwMrAAAA:5Ii6sn9E...
token length = 234

11. 验证结果与限制#

已验证(本地证明 / 固定向量)#

证据结果
请求链路(chrome-devtools + js-reverse 双证据)✅ 文档第 2 节
AES-GCM 解密固定向量(6f71a512... 密钥)✅ 成功解出 6C75E0D6#{指纹JSON}
CRC32 校验和crc32(JSON) == 6C75E0D6
PoW solution_data✅ 1024 字节全零
纯 Python 生成 token✅ 被 /mp_verify 正常受理

未通过(实时放行)#

GET lens.org + 纯算法生成的 token
→ 405, x-amzn-waf-action: captcha
→ 页面加载 captcha.js,点击"开始"后 GET /problem?kind=visual 下发
{"problem_type":"gridcaptcha-5-0.1-0","state":{iv,payload},"key":...,"assets":{"images":[...]}}

即:本网络出口 IP 被 WAF 策略要求交互式图片拼图。这与 aws-waf-token 挑战算法无关, 属于 CAPTCHA 求解范畴。token 生成算法本身已 100% 还原。

说明#

  • 若出口 IP 为普通住宅/纯净网络,WAF 动作应为 challenge,此时纯算法生成的 token 即可放行(浏览器与纯算法的差异仅在于指纹可信度)。
  • 指纹 JSON 中的 GPU/Canvas/插件等字段为浏览器特征,纯 Python 构造的是”合理的浏览器画像”, 服务端按风险评分处理,不构成硬校验。

12. 证据文件清单#

lens/
├── output_reference.js # 反混淆后的 challenge.js(参考)
├── analysis/
│ ├── make_diagrams.py # 图生成脚本
│ ├── proof_manifest.json # 证明清单
│ └── img/*.png # 4 张解释图
├── collector/
│ ├── main.py # PyCharm 可运行入口
│ ├── aws_waf.py # 纯 Python 算法实现
│ └── test_pass.py # 放行测试
└── js_reverse_cache/tasks/aws-waf-lens/
├── task.json
└── fixtures/
├── mp_verify.json # 原始抓包(multipart 全文)
├── inputs.json # /inputs 响应
├── problem.json # 交互拼图 problem 响应
├── solution_metadata_mpverify.json # 解析后的 metadata
└── decrypted_present.txt # AES-GCM 解密出的指纹明文

机密说明:本报告中未包含任何真实 token 的完整值,token/密钥仅以截断形式出现; 抓包原文与解密明文仅保存在本地 task fixtures 目录。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

亚马逊Awf-waf-token算法
https://fatdog.20060113.xyz/posts/awf-was-token/
作者
神秘大胖狗
发布于
2026-08-09
许可协议
MIT

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00