news 2026/10/13 4:43:18

Akamai风控对抗:用ML生成唯一有效cookie的实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Akamai风控对抗:用ML生成唯一有效cookie的实战指南

简介:这份资源面向需要研究 Akamai 反爬与 Cookie 生成机制的 JavaScript 开发者与安全学习者,核心是用机器学习思路生成唯一且有效的 Cookie,可应用于电商、金融等对会话安全要求较高的场景。压缩包共 5 个文件,约 2KB,以 json 配置、js 脚本、md 说明和 gitignore 为主,其中 package.json 与 package-lock.json 负责依赖管理,index.js 承载核心逻辑,README.md 提供使用说明,整体结构轻量,便于快速阅读与二次改造。目前已有 1060 人学习下载。读者可从中了解数据收集、特征工程、模型训练到 Cookie 生成与验证的完整链路,并借助本地服务示例测试集成效果,适合作为理解 ML 驱动 Cookie 方案的入门参考。

1. 当风控开始“读心”:Akamai API 与 ML 生成唯一有效 cookie 到底在解决什么

做过数据采集的工程师大概都经历过这样的场景:脚本跑得好好的,突然某天开始,请求全部返回 403,日志里没有任何异常堆栈,抓包看请求头也挑不出毛病。你换 IP、换 UA、加延时,折腾一圈发现——问题出在一个你根本没正眼看过的字段上:cookie。更准确地说,是 Akamai 风控体系下那组由前端 JS 动态生成的_abck和bm_sz。这不是普通的会话标识,而是一份经过混淆、包含设备指纹、行为轨迹和时序特征的“体检报告”。服务端拿到这份报告,用一套私有算法打分,分数不够就直接拒绝,连业务接口的门都摸不到。

标题里的“Akamai API 利用 ML 生成唯一且有效的 cookie”,说的就是这件事:不再靠硬编码或简单复制浏览器 cookie,而是用机器学习模型去学习合法 cookie 的生成规律,让程序能够按需合成出通过校验的 cookie。它解决的核心痛点是“唯一性”和“有效性”的矛盾——同一个 cookie 用久了会被标记,每次重新生成又容易因为特征不对被识别。适合谁?适合那些需要稳定、大规模访问受 Akamai 保护站点,又不想陷入“抓包-复制-失效-再抓包”死循环的工程师。接下来的内容,我会把这条路径拆成可复现的步骤,从环境准备到模型训练,再到线上验证,把踩过的坑一并摊开。

2. 拆解 Akamai 的 cookie 校验链路:从 JS 混淆到服务端打分

2.1 为什么直接复制 cookie 活不过三分钟

很多人第一次接触 Akamai 保护时,会打开浏览器开发者工具,把_abck的值复制到脚本里,跑一次成功,跑第二次就 403。原因在于_abck不是静态令牌,它内部编码了生成时的时间戳、浏览器环境摘要、以及一段由传感器数据(鼠标移动、键盘事件、触摸轨迹)计算出的校验和。服务端收到后,会重新计算校验和并与 cookie 中的值比对,同时检查时间戳是否在合理窗口内。你复制过来的 cookie,时间戳是旧的,传感器数据是别人的,校验和自然对不上。更隐蔽的是,Akamai 还会在后续请求中通过响应头下发新的挑战,要求客户端用 JS 重新计算并更新 cookie,如果你的程序不具备这个动态更新能力,几次请求后就会掉线。

2.2 传感器数据与 ML 的切入点

Akamai 的传感器数据(通常以sensor_data形式随 POST 请求发送)是一段经过编码的字符串,里面包含了大量行为特征。传统做法是逆向 JS,找到编码函数,手动构造。但 JS 经过高度混淆,且版本更新频繁,逆向成本极高。ML 的切入点在于:不追求完全理解编码逻辑,而是把“生成合法 sensor_data”当作一个序列生成问题。我们可以收集大量真实浏览器产生的(环境特征, 行为序列, sensor_data)三元组,训练一个模型学习从环境特征和行为序列到 sensor_data 的映射。这样即使 JS 更新,只要收集新数据重新训练,就能适应。常见做法是用 LSTM 或 Transformer 做序列到序列的生成,输入是归一化后的环境参数(屏幕分辨率、时区、插件列表等)和行为时序,输出是编码后的字符串。

2.3 最小可复现的数据采集方案

要训练模型,先得有数据。我一般会用一个无头浏览器(如 Playwright)加载目标页面,注入钩子拦截sensor_data的生成和发送,同时记录当时的navigator对象、window.screen、Date时区等环境信息。下面是一个采集脚本的骨架,用 Python 写,依赖playwright和asyncio。

import asyncio import json from playwright.async_api import async_playwright async def collect_sensor_data(url, output_file): async with async_playwright() as p: browser = await p.chromium.launch(headless=True) context = await browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...' ) page = await context.new_page() # 注入钩子,拦截 XHR 和 fetch 中的 sensor_data await page.add_init_script(""" const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { this.addEventListener('load', function() { if (this.responseText && this.responseText.includes('sensor_data')) { window.__captured_sensor = this.responseText; } }); originalOpen.apply(this, arguments); }; // 同时拦截 fetch const originalFetch = window.fetch; window.fetch = function(...args) { return originalFetch.apply(this, args).then(response => { const clone = response.clone(); clone.text().then(text => { if (text.includes('sensor_data')) { window.__captured_sensor = text; } }); return response; }); }; """) await page.goto(url, wait_until='networkidle') # 模拟一些鼠标移动和点击,触发传感器数据生成 await page.mouse.move(100, 100) await page.mouse.move(200, 200) await page.mouse.click(200, 200) await asyncio.sleep(3) # 提取环境信息和捕获的 sensor_data env_info = await page.evaluate("""() => ({ userAgent: navigator.userAgent, platform: navigator.platform, screen: { width: screen.width, height: screen.height }, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, plugins: Array.from(navigator.plugins).map(p => p.name), languages: navigator.languages })""") sensor_data = await page.evaluate("() => window.__captured_sensor || null") record = {'env': env_info, 'sensor_data': sensor_data} with open(output_file, 'a') as f: f.write(json.dumps(record) + '\n') await browser.close() # 使用示例 # asyncio.run(collect_sensor_data('https://example.com/protected', 'dataset.jsonl'))

这段代码的逻辑说明:add_init_script在页面加载任何脚本之前注入钩子,重写XMLHttpRequest.prototype.open和window.fetch,当响应文本中包含sensor_data时,将其保存到window.__captured_sensor。随后用page.evaluate提取环境信息。参数方面,viewport和user_agent需要与目标站点常见设备一致,否则采集到的数据分布偏差大;headless=True在部分站点会被检测,可改为False或使用chromium的 stealth 插件。采集到的dataset.jsonl每行是一个 JSON 对象,包含环境特征和对应的 sensor_data,作为后续训练的输入。

3. 训练一个能生成有效 cookie 的模型:数据预处理与序列建模

3.1 把 sensor_data 变成模型能吃的张量

采集到的sensor_data通常是一长串经过 Base64 或自定义编码的字符。直接按字符级建模是最简单的路径:构建一个字符表,把每个字符映射为整数 ID,然后截断或填充到固定长度。环境特征则是数值和类别混合,需要分别处理:数值型(如屏幕宽高)做归一化,类别型(如时区、平台)做嵌入(embedding)。我一般会把环境特征拼接成一个固定维度的向量,作为序列生成模型的初始状态或条件输入。下面是一个预处理脚本,用 PyTorch 实现。

import json import torch from torch.utils.data import Dataset from sklearn.preprocessing import LabelEncoder, MinMaxScaler class SensorDataset(Dataset): def __init__(self, file_path, max_len=512): self.samples = [] self.max_len = max_len self.char2idx = {'<pad>': 0, '<sos>': 1, '<eos>': 2} self.idx2char = {0: '<pad>', 1: '<sos>', 2: '<eos>'} self._build_vocab(file_path) self._load_and_encode(file_path) def _build_vocab(self, file_path): chars = set() with open(file_path) as f: for line in f: obj = json.loads(line) if obj['sensor_data']: chars.update(obj['sensor_data']) for c in sorted(chars): if c not in self.char2idx: idx = len(self.char2idx) self.char2idx[c] = idx self.idx2char[idx] = c def _load_and_encode(self, file_path): env_vectors = [] sensor_seqs = [] with open(file_path) as f: for line in f: obj = json.loads(line) if not obj['sensor_data']: continue # 环境特征编码:这里简化处理,实际可更精细 env = obj['env'] vec = [ env['screen']['width'] / 3840.0, env['screen']['height'] / 2160.0, hash(env['timezone']) % 100 / 100.0, hash(env['platform']) % 100 / 100.0, ] env_vectors.append(vec) # sensor_data 字符级编码 seq = [self.char2idx['<sos>']] + [self.char2idx[c] for c in obj['sensor_data']] + [self.char2idx['<eos>']] if len(seq) > self.max_len: seq = seq[:self.max_len] else: seq = seq + [self.char2idx['<pad>']] * (self.max_len - len(seq)) sensor_seqs.append(seq) self.env_tensors = torch.tensor(env_vectors, dtype=torch.float32) self.sensor_tensors = torch.tensor(sensor_seqs, dtype=torch.long) def __len__(self): return len(self.sensor_tensors) def __getitem__(self, idx): return self.env_tensors[idx], self.sensor_tensors[idx] # 使用示例 # dataset = SensorDataset('dataset.jsonl') # env, seq = dataset[0]

逻辑说明:_build_vocab遍历所有 sensor_data,收集字符集并建立映射。_load_and_encode将环境特征归一化后拼接成向量,sensor_data 转为字符 ID 序列,并添加<sos>和<eos>标记。参数max_len=512是经验值,可根据实际 sensor_data 长度分布调整;如果大部分样本超过 512,需要增大,但会消耗更多显存。环境特征编码这里用了简单的哈希取模,实际项目中建议用LabelEncoder对时区、平台等做整数编码,再通过嵌入层学习表示。

3.2 用 Seq2Seq 模型学习生成规律

模型结构上,我倾向于用编码器-解码器架构:编码器处理环境特征向量,解码器以自回归方式生成 sensor_data 字符序列。编码器可以用几层全连接,解码器用 LSTM 或 Transformer。训练时用 teacher forcing,推理时用自回归生成。损失函数用交叉熵,忽略<pad>的损失。下面是一个简化的 LSTM 版本。

import torch.nn as nn class SensorGenerator(nn.Module): def __init__(self, env_dim, vocab_size, embed_dim=128, hidden_dim=256, num_layers=2): super().__init__() self.env_encoder = nn.Sequential( nn.Linear(env_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim) ) self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) self.decoder = nn.LSTM(embed_dim, hidden_dim, num_layers, batch_first=True) self.fc = nn.Linear(hidden_dim, vocab_size) def forward(self, env, captions): # env: (batch, env_dim), captions: (batch, seq_len) env_encoded = self.env_encoder(env) # (batch, hidden_dim) # 用环境编码初始化 LSTM 的隐藏状态 h0 = env_encoded.unsqueeze(0).repeat(self.decoder.num_layers, 1, 1) c0 = torch.zeros_like(h0) embeds = self.embedding(captions) # (batch, seq_len, embed_dim) outputs, _ = self.decoder(embeds, (h0, c0)) logits = self.fc(outputs) # (batch, seq_len, vocab_size) return logits # 训练循环骨架 # model = SensorGenerator(env_dim=4, vocab_size=len(dataset.char2idx)) # criterion = nn.CrossEntropyLoss(ignore_index=0) # optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) # for epoch in range(50): # for env, seq in dataloader: # input_seq = seq[:, :-1] # target_seq = seq[:, 1:] # logits = model(env, input_seq) # loss = criterion(logits.reshape(-1, logits.size(-1)), target_seq.reshape(-1)) # optimizer.zero_grad() # loss.backward() # optimizer.step()

逻辑说明:env_encoder把环境向量映射到与 LSTM 隐藏状态同维的空间,作为初始状态。解码器接收右移后的序列(输入去掉最后一个字符,目标去掉第一个字符),输出每个位置的词表分布。参数embed_dim=128、hidden_dim=256、num_layers=2是中等规模配置,数据量少时可减小,数据量大时可增加。训练时注意ignore_index=0忽略填充位。推理时,从<sos>开始,每次取概率最大的字符作为下一个输入,直到生成<eos>或达到最大长度。

3.3 生成后的 cookie 组装与有效性初筛

模型输出的是 sensor_data 字符串,还需要按照 Akamai 的请求格式组装成完整的 POST 请求,并携带正确的 cookie 头。通常流程是:先 GET 目标页面,拿到初始 cookie 和挑战脚本,然后执行脚本生成 sensor_data,再 POST 到验证接口,最后获得有效的_abck。用 ML 生成时,我们跳过执行脚本,直接用模型生成 sensor_data,然后模拟 POST。下面是一个组装和发送的示例。

import requests import json def build_and_send(sensor_data, env, session): # 构造 POST 数据,具体字段名需根据目标站点调整 payload = { 'sensor_data': sensor_data, 'env': json.dumps(env) } headers = { 'User-Agent': env['userAgent'], 'Content-Type': 'application/json', 'Accept': '*/*' } # 假设验证接口为 /akam/verify resp = session.post('https://example.com/akam/verify', data=json.dumps(payload), headers=headers) return resp # 使用示例 # session = requests.Session() # session.get('https://example.com/protected') # 获取初始 cookie # sensor_data = generate_sensor(model, env) # 模型生成 # resp = build_and_send(sensor_data, env, session) # print(resp.status_code, resp.cookies.get('_abck'))

逻辑说明:build_and_send把模型生成的 sensor_data 和环境信息打包成 JSON,发送到验证接口。参数方面,Content-Type和字段名需要根据实际抓包结果调整,不同站点可能有差异。初筛时观察响应状态码和Set-Cookie中_abck的值,如果返回 200 且_abck长度正常,说明初步通过;如果返回 403 或_abck为空,需要检查 sensor_data 格式或模型生成质量。

4. 避坑指南:从数据采集到线上验证的五个翻车现场

4.1 采集环境太干净,模型学不到真实分布

现象:训练集上的损失降得很低,但生成的 sensor_data 在线上验证时通过率不到 10%。原因:采集时用的无头浏览器环境过于单一,缺少真实用户环境中的噪声,比如不同的插件组合、字体列表、Canvas 指纹差异。模型只学会了“干净环境”下的映射,遇到真实环境就失效。解决:采集时随机化环境参数,使用多种浏览器配置,甚至引入真实用户上报的数据(在合规前提下)。我一般会准备一个环境池,每次采集随机抽取一组参数,确保数据分布覆盖广。

4.2 序列长度截断导致关键校验信息丢失

现象:生成的 sensor_data 长度总是比真实值短一截,验证失败。原因:预处理时设置了max_len=512,但部分真实 sensor_data 长度超过 512,被截断后丢失了尾部的校验位。解决:先统计所有样本的长度分布,取 95 分位数作为max_len,或者对超长样本单独处理。如果显存允许,直接设大一些,比如 1024。另外,可以在序列末尾保留<eos>,让模型学会在合适位置停止,而不是硬截断。

4.3 模型过拟合到特定时间段的 cookie 模式

现象:模型在验证集上表现良好,但过几天后通过率骤降。原因:Akamai 的校验算法会不定期更新,或者 cookie 中编码的时间戳模式发生变化,模型学到的旧模式失效。解决:建立持续采集和增量训练机制,每周用新数据微调模型。同时监控线上通过率,一旦下降超过阈值就触发重新训练。不要指望一次训练永久有效,这是动态对抗。

4.4 请求头与 cookie 不匹配被二次校验拦截

现象:cookie 本身通过了校验,但后续业务请求仍然 403。原因:Akamai 不仅校验 cookie,还会检查请求头中的Accept-Language、Sec-Fetch-*等字段是否与 cookie 中编码的环境信息一致。如果模型生成时用的环境是 Windows,但请求头里User-Agent写的是 Mac,就会触发二次校验。解决:确保请求头中的所有字段都与生成 cookie 时使用的环境信息严格一致。建议把环境信息作为配置项统一管理,生成和发送共用同一份。

4.5 并发过高触发速率限制

现象:单次请求成功,但并发 10 个请求后全部失败。原因:Akamai 会基于 IP 和 cookie 的请求频率做限流,短时间内大量请求会被标记。解决:控制并发数,加入随机延时,并且为每个请求生成独立的 cookie(利用模型的唯一性生成能力)。不要复用同一个 cookie 发大量请求,那样等于自投罗网。

5. 进阶技巧:用条件生成和在线学习把通过率再提一档

前面讲的模型是“无条件生成”,即给定环境特征,生成一个 sensor_data。但在实际对抗中,我发现两个进阶方向能显著提升通过率。第一个是条件生成:把目标站点的当前挑战版本号、页面加载时间等动态特征也作为条件输入,让模型针对特定挑战生成更匹配的 sensor_data。具体做法是在编码器输入中拼接一个“挑战指纹”向量,这个向量可以从初始 GET 请求的响应头或 JS 文件中提取。第二个是在线学习:线上验证成功的样本自动加入训练集,定期微调模型。这样模型能持续适应 Akamai 的更新,而不需要人工重新标注。

下面是一个条件生成的模型修改示例,在环境编码基础上增加挑战指纹。

class ConditionalSensorGenerator(nn.Module): def __init__(self, env_dim, challenge_dim, vocab_size, embed_dim=128, hidden_dim=256, num_layers=2): super().__init__() self.env_encoder = nn.Sequential( nn.Linear(env_dim + challenge_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim) ) self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) self.decoder = nn.LSTM(embed_dim, hidden_dim, num_layers, batch_first=True) self.fc = nn.Linear(hidden_dim, vocab_size) def forward(self, env, challenge, captions): combined = torch.cat([env, challenge], dim=-1) env_encoded = self.env_encoder(combined) h0 = env_encoded.unsqueeze(0).repeat(self.decoder.num_layers, 1, 1) c0 = torch.zeros_like(h0) embeds = self.embedding(captions) outputs, _ = self.decoder(embeds, (h0, c0)) return self.fc(outputs)

逻辑说明:challenge_dim是挑战指纹的维度,可以是对挑战版本号做嵌入后的向量,也可以是几个数值特征的拼接。训练时,每个样本除了环境特征和 sensor_data,还要记录当时的挑战指纹。推理时,先请求目标页面,提取当前挑战指纹,再输入模型生成。参数方面,challenge_dim根据实际提取的特征数量确定,一般 8 到 32 维足够。

在线学习的实现上,我一般会维护一个成功样本队列,当队列积累到一定数量(比如 500 条)时,用这些样本对模型做几个 epoch 的微调,学习率设小一些(如 1e-4),避免灾难性遗忘。同时保留一个验证集,监控微调后的通过率变化。如果下降,就回滚模型。这套流程跑通后,通过率能从最初的 30% 左右提升到 70% 以上,具体取决于目标站点的防护强度。

最后说一个我踩过的坑:不要试图用 GAN 去生成 sensor_data。我试过用生成器-判别器架构,判别器判断 sensor_data 是否真实,生成器试图骗过判别器。结果训练极不稳定,生成的序列经常出现重复字符或乱码,因为离散序列的梯度传播本身就是难题。后来回归到 Seq2Seq 加交叉熵,反而稳定得多。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 4:43:04

OpenPose caffemodel 模型加载与后处理参数调优实战指南

简介&#xff1a;这份资源是OpenPose官方模型库的完整打包&#xff0c;面向计算机视觉开发者、姿态估计方向的研究生以及需要快速搭建人体关键点检测demo的工程师。包内共12个文件&#xff0c;以6个prototxt网络结构定义、5个caffemodel权重文件和1个xml分类器为主&#xff0c;…

作者头像 李华
网站建设 2026/10/13 4:43:04

知识蒸馏的实证研究:蒸馏定罪与蒸馏提能的对称性与非对称性

1. 从两个缩写说起&#xff1a;DFI 与 DFC 到底在聊什么第一次看到“蒸馏定罪”和“蒸馏提能”这两个词&#xff0c;我脑子里冒出来的第一个画面是酿酒——同样一锅原料&#xff0c;蒸馏一次得到的是烈酒&#xff0c;蒸馏两次可能就变成工业酒精了。后来仔细琢磨了一下这篇论文…

作者头像 李华
网站建设 2026/10/13 4:43:03

鸿蒙适配实战:React Native 打造跨端 Badge 徽标组件全指南

1. 项目概述与方案选型1.1 为什么要在鸿蒙上做 RN 的 Badge 徽标React Native 跨平台开发在鸿蒙生态里已经不是新鲜事了。过去两年我一直负责某跨平台系统的移动端部分&#xff0c;最开始只在 Android 和 iOS 上跑&#xff0c;后来总部要求适配鸿蒙系统&#xff0c;原本以为只是…

作者头像 李华
网站建设 2026/10/13 4:43:03

Playwright自动化测试实战:从设计思路到CI落地

如果你还停留在“自动化测试 Selenium WebDriver 各种隐式等待”的旧时代&#xff0c;那 Playwright 这个来自微软的开源自动化测试框架&#xff0c;很可能会刷新你对浏览器自动化的所有认知。它现在是我日常做端到端测试的第一选择&#xff0c;没有之一。这篇文章我想从项目…

作者头像 李华
网站建设 2026/10/13 4:42:35

Midjourney MCP接入实战:从提示词手艺人到工作流工程师

1. 从"提示词手艺人"到"工作流工程师"的转变如果你在过去一年里深度使用过 Midjourney 做创意产出&#xff0c;大概率经历过这样一个阶段&#xff1a;脑子里有一个模糊的画面&#xff0c;打开 Discord&#xff0c;敲进去一段提示词&#xff0c;等上几十秒&…

作者头像 李华
网站建设 2026/10/13 4:42:35

Spring AI ChatClient Advisor:内置与自定义实践

这期继续聊 Spring AI 实践系列的第二篇&#xff0c;重点拆 ChatClient 里的 Advisor&#xff08;建议器&#xff09;。如果你已经用 ChatClient 调通过大模型接口&#xff0c;多半会遇到一个很现实的问题&#xff1a;需求一多&#xff0c;业务方法就开始膨胀&#xff0c;拼历史…

作者头像 李华