搞过风控对抗或者Bot防护的人,估计都见过这场景:某天早上站点忽然涌进大量客服投诉,说正常用户总是被弹验证、登录死活过不去。问题查来查去,最后定位到页面里多了一段来自shape.security或f5-dfcs.com的脚本。F5 Shape这套东西在美西南这类航旅站点、以及我那代号xbk的测试环境里,出现的频率是真的高。标题里说的“逆向分析”,不是要绕过它去搞什么灰产,而是作为安全研究员或者甲方风控,必须搞清楚它到底采集了什么、决策链路长什么样、误杀是怎么来的。这篇文章就是我近期对F5 Shape最新版SDK做机制拆解的完整记录,从抓包到解混淆,再到美西南和xbk两种场景下的信号差异,最后落到防御协作和误杀排查。
1. 为什么会盯上F5 Shape:一次“被误封”的排查现场
1.1 误封现象与第一反应
凌晨三点,告警群突然响起来,接口成功率从99.9%掉到70%出头。第一反应是回源出问题了,或者是CDN的某个节点抽风。但打开流量看,发现大量来自住宅宽带的正常会话都拿到了一个challenge响应,紧接着页面就被插入了一段图灵测试。这种“大规模误杀”的典型特征,基本就在提示我们:机器人防护系统的策略被某个信号击穿了,导致正常用户被连带拦截。
排查初期的常见错误是盯着应用日志找异常,但应用层根本没有错误——请求都正常到达了后端,只是在更前置的Bot防御层被扣下了。再到响应头一看,Set-Cookie里出现了__shape_a、__shape_b这一串东西,才确认是F5 Shape在起作用。平时它是隐形防守者,一旦误杀,就成了最大的背锅侠。
1.2 Shape不是单点,而是一条完整决策链
很多刚从应用防护转过来的人,会把F5 Shape理解成一个“JS脚本”,以为去掉脚本就绕过了。这是最大的误区。我前前后后分析过几次,Shape这套东西拆开来看至少四层:
- 采集层:下发到浏览器的SDK负责采集设备指纹、行为轨迹、DOM环境等信息。
- 保护层:SDK自带代码混淆、反调试、反模拟机制,防止被人直接抄作业。
- 决策层:采集到的数据上传到F5的云端模型,服务端结合IP信誉、历史行为、会话风险等给出 allow / challenge / block 的结论。
- 执行层:结论通过Set-Cookie、页面注入、API响应头传回浏览器,决定是放行、弹验证还是拒绝。
这套结构的核心在于:前端SDK只是收集端,真正的判定逻辑在云端。只盯着JS看,等于只拿到了整条链路的20%。很多做逆向分享的人喜欢把SDK里的字符串解出来贴一堆,看起来很厉害,但离“搞懂这套东西”还有很长的距离。
2. Shape系最新版SDK的可观察特征:从入口脚本到下发链路
2.1 SDK入口的加载方式与识别要点
被Shape保护的站点,入口脚本不一定叫shape.js,新旧版本差异不小。老版本常见的是直接加载https://*.shape.security/script/v1/...,而新版本统一走F5分布式云体系,域名经常是*.f5-dfcs.com,有时还会套一层CloudFront这类CDN做分发,脚本名有时还带随机化参数。单凭脚本文件名判断版本,很容易翻车。
观察入口要从三个层面入手:
- 加载方式:同步加载还是异步加载、是否带
defer、是否在DOMContentLoaded之后才动态插入。 - 版本探测:SDK初始化时会向服务端发一个配置请求,响应里通常包含当前下发版本号、启用的检测模块列表、灰度开关等。这是判断“最新版”最直接的途径。
- 脚本内容特征:主脚本里会有分片加载逻辑,新版手感和旧版完全不一样。
提示:新版SDK的版本控制是灰度制的。美西南这种大流量站点,同一时间线上可能同时在跑两三个不同版本,不同地区、不同会话拿到的脚本也不一样。分析时如果只缓存了一份样本,得出的结论可能就是错的。
2.2 新版与老版的核心差异
结合我这段时间对美西南站点和xbk环境的观察,最新版相对上一代,至少有四个明显变化:
第一,脚本从单文件变成了分片加载。老版一个shape.js从头到尾,新版的模块被拆成多个chunk,运行时按需拉取,哪些模块被加载取决于页面类型和环境特征。这在分析时要多看好几个请求,工作量直接翻倍。
第二,遥测接口的路径变成了动态签名。老版上报数据的URL相对固定,可以比较容易地抓到完整数据包。新版上报路径带一次性签名参数,并且report、config、challenge走不同的端点,光靠抓包把所有请求拼齐就需要不少耐心。
第三,检测点大量挪进了Web Worker。新版把一部分采集逻辑放到了独立的Worker线程里跑,主线程代码被混淆后基本看不出采集逻辑。调试时如果不开Worker的线程面板,很容易漏掉关键上报点。
第四,关键数据不再直接写进Cookie。老版本里有一段时间,部分特征会被编码塞进Cookie,存在本地。新版Cookie里存的多半是临时票据或一次性token,真正关键的状态全部服务端掌握。这意味着想靠纯前端分析伪造出一个“合法Cookie”来直接过关,基本是走不通的,新版的判定核心已经彻底云端化了。
2.3 服务端决策的响应形态
SDK跑完采集,服务端的结果最终要传递到浏览器和业务方,常见的有几种形式:
| 响应形态 | 常见标记 | 典型含义 | 变更频率 |
|---|---|---|---|
| Set-Cookie | __shape_a | 短期会话标记,生命周期短 | 每次会话/请求级刷新 |
| Set-Cookie | __shape_b | 长期设备标记,生命周期长 | 相对稳定 |
| Set-Cookie | __shape_shape | 验证状态相关,出现它说明策略介入 | 触发验证/解除验证时变化 |
| 响应头 | shape-render-mode等 | 指示前端以何种方式渲染验证 | 策略变更时变化 |
| 页面注入 | 动态插入的challenge JS | 触发人机验证界面 | 命中策略时出现 |
排障的时候,关键在于分清“它到底只是给你打了标记,还是已经发了验证指令”。前者往往只是常规采集,后者才是用户被卡住的直接原因。如果发现__shape_a和__shape_shape同时出现且策略ID在变化,多半是会话风险分在动态调整。
3. 实际逆向分析流程:抓包、断点、AST解混淆、行为验证
3.1 环境与工具链选型
做这类分析,环境干净比技术本身更重要。我常用的组合是:
- 抓包:Charles或mitmproxy,用于看完整请求链路,重点记录SDK的配置请求、上报请求、Cookie变化顺序。
- 浏览器调试:Chrome DevTools,配合断点看JS运行时的调用栈。
- 代码美化与解混淆:
js-beautify负责格式化,Babel或ast-grep负责AST级别的结构还原。 - 行为对照:Puppeteer/Playwright,用来造不同指纹、不同行为模式的浏览器环境做对比实验。
注意:整个分析过程必须限定在已授权范围内,比如自建测试环境、与防护厂家的合作项目,或者你对美西南这类第三方站点做的是“仅观察不干扰”的被动分析。任何插件注入、流量修改、验证尝试都必须有授权依据,这是基本底线。
3.2 定位关键函数的完整链路
实际动手时,我会按下面的顺序把链路完整走一遍,这也是我觉得最有价值的一套方法论,而不是某个特定版本的特征:
第一步:找到SDK入口,保存脚本。在DevTools里过滤shape或f5,定位到主脚本URL,保存后先跑js-beautify格式化,把压缩成一行的大文件还原成可读结构。
第二步:特征字符串地毯式搜索。打开格式化后的代码,搜canvas、getBattery、mouse、touchstart、sendBeacon、XMLHttpRequest、localStorage、indexedDB这些关键词。新版混淆比较狠,字符串经常是动态拼的,但关键API名总会出现在某个不太起眼的位置。找不到就说明做了字符串数组偏移,得用AST还原。
第三步:在Cookie写入点和网络请求处下断点。打开Sources面板,在Document.cookie的setter、sendBeacon、XMLHttpRequest.send、fetch上打断点。刷新页面,观察SDK在哪个执行阶段触发了网络请求,请求体里带了什么参数。这样能把“采集代码”和“上报时机”对应起来。
第四步:顺着调用栈找到数据封装函数。从断点往上翻调用栈,找出发送前的数据序列化逻辑,那里通常是特征打包的地方。新版会用动态属性名做一层包装,AST解混淆后字段名还是可能不直观,这时候别死磕,用“黑盒观测+关键节点断点”配合,把字段名和实际值对应起来记录一版就够了。
第五步:对照测试验证权重。这一步最关键。做一个最小环境,用不同指纹和行为组合去访问同一个接口,记录每次的决策结果,反推哪些信号权重高。
3.3 解混淆之后看什么:采集维度清单
地址解完、函数理清以后,重点看SDK到底从哪些维度采集数据。我整理了一个常观察的维度表:
| 维度类别 | 具体采集点 | 潜在用途 |
|---|---|---|
| 基础环境 | UA、Accept、Accept-Language、时区、语言 | 建立基础画像 |
| 硬件指纹 | canvas、WebGL、字体、屏幕分辨率、设备内存、CPU核数 | 识别真实设备 |
| 存储痕迹 | localStorage、IndexedDB、Cookie可用性探测 | 判断是否清白历史 |
| 行为轨迹 | 鼠标、触摸、键盘事件间隔、滚动行为 | 判断“人来操作”还是脚本操作 |
| 网络特征 | RTT小包探测、连接类型、Service Worker状态 | 估算网络环境与代理特征 |
| 环境异常 | iframe嵌套、窗口尺寸、WebDriver标记、DevTools检测 | 检测浏览器是否被自动化控制 |
这些维度单个看都弱,“UA+时区+屏幕”就能撞出一大片人,但组合起来、再叠加服务端的动态模型,区分度就上来了。这也是为什么Shape敢把判定全部放云端——因为前端采集的数据本身是“原材料”,模型才是“加工厂”。
3.4 一个最小观测脚本的思路
下面这个脚本,目的是复现“把SDK的运行过程录下来”这个动作,方便对比不同环境下的行为差异。它不伪造任何东西,只是观察:
// 观测性示例,仅用于已授权环境的SDK行为分析 const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({ headless: false, // 用有头模式,尽量接近真实环境 args: ['--window-size=1366,768'] }); const page = await browser.newPage(); // 记录SDK相关的请求和响应细节 page.on('response', async (resp) => { const url = resp.url(); if (url.includes('shape') || url.includes('f5')) { console.log('[SDK request]', resp.status(), url); } }); // 打开目标URL(请替换成你具备分析权限的站点/测试环境) await page.goto('https://your-lab-site.com', { waitUntil: 'networkidle2', timeout: 60000 }); // 输出当前页面上的shape相关cookie const cookies = await page.cookies(); console.log(cookies.filter(c => c.name.startsWith('__shape'))); await browser.close(); })();跑通之后,多准备几组对照环境:
| 对照组 | 环境情况 | 常见结果 |
|---|---|---|
| A组 | 有头Chrome,正常鼠标移动,停留一段时间再操作 | 基本不触发验证 |
| B组 | 无头模式、直接发请求、零交互 | 高概率触发challenge |
| C组 | 有头Chromium但自动化标记明显 | 大概率被标记为可疑 |
对照实验的目的,不是写一个“逃过检测”的脚本(那条路既没意思也没授权),而是搞清楚哪些信号权重大、哪些行为最容易触发误杀——这恰恰是甲方排障时最需要的信息。
4. 美西南和xbk两种场景下的检测信号差异
4.1 站点形态差异对SDK的影响
美西南(Southwest Airlines)这类航旅站点的体量摆在那,子域多、业务链路长,登录、搜索、支付、会员中心各自都有独立页面。实际观察下来,不同子域加载的SDK配置经常不是一套:有的页面只加载基础采集版,有的页面会额外加载增强验证模块。加上流量大,A/B测试和灰度发版几乎全天在线,同一个用户上午下午拿到的SDK都可能是不同版本。
相比之下,xbk是我自己搭的测试站点代号,用的是同一家SDK服务,但流量小、业务路径短、也没有灰度策略,SDK版本相对固定。这带来的一个实际影响是:在xbk上分析出的结论不能完全等价套到美西南场景,版本差异、策略差异、流量模型差异都可能导致行为信号不一致。
4.2 行为阈值会随风险状态动态变化
这是我在两个场景里对比之后印象最深的点:同样是“没有鼠标移动、快速连续请求”的行为模式,在xbk环境可能只是分数上调,在美西南环境很可能直接触发验证。这说明服务端不是用的固定规则,而是接了实时风险变量——IP信誉、会话新鲜度、请求频率、路径异常度都会影响最终阈值。
换句话说,同一套采集数据,在不同时间、不同入口、不同风险上下文中,对应的决策可能完全不同。做逆向分析时如果只拿一次结果下结论,很容易被误导。正确打开方式是拉长观测周期,记录至少一周的决策结果变化,再去找规律。
4.3 容易被忽略的网络侧信号
除了浏览器端的行为数据,Shape服务端还会结合网络侧信息做交叉验证。比较常见的是看到IDC出口IP即使浏览器指纹完全正常,也容易进验证队列;而住宅宽带、移动网络的用户即使指纹有一点点偏差,分也不一定高。这个“网络画像+设备指纹”的交叉逻辑,是很多误杀的来源。
对甲方运维来说,这个特性的直接启示是:排查误杀不能只让用户“换个浏览器试试”,还要看他的出口IP类型和会话建立方式。来自云厂商出口、数据中心出口的办公网用户,被Bot防护误伤的概率天然高一些,这不是前端能解决的问题,只能通过策略白名单或调整阈值来缓解。
5. 搞懂机制之后,真正能落地的事:防御协作与误杀排查
5.1 一套完整的误杀排查链路
如果你是被Shape保护的站点方,最常见的需求不是“分析SDK怎么工作”,而是“现在用户被误杀了,怎么快速定位”。我踩过几次坑之后,总结了这条链路:
- 收集现场信息:拿到目标用户的UA、IP、访问时间、会话Cookie,先确认同一IP段、同一UA模式的用户是否大面积受影响。
- 区分决策类型:看响应是
challenge(弹验证)还是block(直接拒绝),两种处置对应的排查方向完全不同。 - 确认SDK版本:让用户清缓存重新加载页面,看下发的是哪个版本。如果新版本刚灰度不久,误杀概率会显著上升。
- 简单A/B确认:在测试环境临时屏蔽SDK,模拟相同操作路径,看问题是否消失。如果消失了,就基本锁定是Bot层的问题,要反馈给防护团队。
- 提交加白或调参工单:把上面收集的信息整理成证据链,联系F5或厂商侧调整策略、加白特定UA/路径,或者把风险阈值从严格调回中等。
5.2 与风控、研发协作时的日志字段清单
误杀排查最怕部门墙,风控说“是网络问题”,研发说“应用层没报错”,安全说“是防护策略误杀”。要打破这个局面,关键是大家手里有一份统一的排障日志。建议至少要记录这些字段:
| 字段 | 说明 |
|---|---|
| 时间戳 | 精确到秒,保留时区 |
| 会话ID / 用户标识 | 用于关联前后端日志 |
| Shape决策结果 | allow / challenge / block |
| 触发策略ID | 方便定位是哪些规则生效 |
| 客户IP(脱敏) | 判断是住宅、移动还是IDC出口 |
| UA | 判断是正常浏览器还是自动化工具 |
| SDK版本号 | 定位是否灰度新版本引入 |
| 验证页面URL | 用户到底是被哪个页面的验证卡住 |
这份清单的意义在于,三方都能基于同一组数据说话,而不是互相猜。拿到工单的第一时间,先看“决策结果”和“触发策略ID”,比看一百行应用日志都管用。
5.3 几个值得长期观测的健康指标
最后建议被防护方把下面几个指标做成日常监控。它们不是单纯看“有没有被攻击”,而是看“防护系统有没有误伤良民”,两者同等重要:
- challenge率:被弹验证的请求占总请求的比例,正常波动应该在某个范围,突然飙升就是要出事了。
- 验证通过率:弹了验证之后有多少人能真正通过,过低说明验证门槛太高或策略误杀严重。
- 接口成功率:新增或变更防护策略后,这个指标是最直观的“用户体验体检表”。
- SDK加载耗时:Shape SDK不算轻量,对首屏渲染有可感知的影响,建议设一个性能预算。
- 版本号分布:保持对线上SDK版本的感知,灰度出了问题可以在几小时内止损。
这套指标配上前面的排查链路,基本能覆盖日常防护运维里90%的误杀场景。
我个人在实际项目里的体会是:F5 Shape这类云端Bot防护的逆向分析,价值不在于“破解一个版本”,而在于建立一套长期有效的“版本追踪+行为观察+误杀排查”机制。今天分析的结果,三个月后很可能就因为一次灰度更新全部作废,但方法论和分析链路是可以一直复用的。所以我会把每次观察到的版本特征、决策变化、踩坑记录归档成一个持续更新的特征库文档,排障时直接查,效率比临时翻混淆代码高得多。另外说一句,很多讨论会把F5 Shape和F5 BIG-IP、F5 NGINX的漏洞通知混在一起,实际上Shape是收购来的独立产品线,走的是SaaS化Bot防御订阅,和自运维的BIG-IP/Nginx基础设施是两个体系,排障时不要混着看,不然会白白浪费很多时间。