资讯动态

Python补环境破解Cloudflare 5秒盾:13次请求全流程拆解

发布时间:2026/10/3 10:16:35 来源:尧图企业网站定制
做数据采集和安全测试的同学十有八九都遇到过 Cloudflare 5秒盾这道门。访问目标站时页面先弹一个“正在验证您的浏览器...”的遮罩转几秒才放你进去。它不是简单的等待而是那段藏在页面里的 JS 在真实浏览器环境里跑完了一整套计算最后给你发一张“通行证”。标题里说的“13次请求”是我在一台授权测试站点上抓下来的一次完整访问链路恰好13个HTTP请求从第一次碰主页到拿到业务数据一条线走完。这篇文章不是给你一个“跑一遍就出结果”的现成脚本而是把背后那套 Python 补环境框架的搭建思路完整拆开为什么要补环境、补哪些东西、13次请求怎么串联、踩过的坑长什么样。适合已经写过一点 Python、接触过 JS 逆向但还没系统试过补环境的人看完能自己上手搭建一套可复现的反5秒盾模拟流程。提前说清楚这篇文章的所有方法只用于自己持有授权或搭建的测试环境上线前务必确认合规边界。1. 5秒盾为什么难绕它到底在校验什么1.1 5秒盾的本质一个JS谜题加一套指纹取证5秒盾在防护体系里叫 Managed Challenge也就是“托管质询”。服务端在返回页面时除了正常HTML还会塞进一段挑战脚本。浏览器拿到这段脚本后需要在极短时间内执行一个由多种加密算法拼起来的任务得到类似cf_clearance的Cookie或token然后带着这个凭证重新请求目标地址。服务端校验通过才返回真实业务内容。难点不在那个加密算法本身。Cloudflare 在设计时就把“人”和“机器”的差异也编进了算法输入——脚本会读取浏览器的navigator、screen、location、时间、Canvas、WebGL、AudioContext 等一大堆运行环境数据把它们序列化后混入计算载荷。你在纯命令行环境里用Python发请求这些环境一个都不存在等于考生进考场连笔都没带。所以“逆向”5秒盾很大一部分工作不是把算法还原成数学公式而是先把丢失的“浏览器环境”补回来让脚本在模拟环境里能完整跑完拿到的凭证才可能通过服务端校验。1.2 三套主流方案怎么选还原算法、浏览器自动化、补环境我在项目初期试过两条路后来才走到补环境。先讲清楚三条路线的取舍你就不容易走弯。第一条纯算法还原。把挑战脚本拿下来格式化、去混淆、控制流平坦化一行行读懂再自己用Python重写整个计算逻辑。这条路的优点是一旦还原成功执行速度极快、稳定性极高。但缺点很明显工作量巨大而且Cloudflare的混淆版本更新频繁今天还原的算法明天可能就换了入口函数。对绝大多数项目来说投入产出比太差。第二条浏览器自动化比如 Selenium、Playwright。优点是“环境天然真实”不用补什么脚本自己会跑缺点是性能和稳定性都吃亏每处理一次质询都要拉起一个完整浏览器内存开销高启动慢而且自动化痕迹容易被检测到头部、WebDriver标记、鼠标轨迹都可能是判定特征。第三条就是本文用的补环境模拟。核心思路是把挑战JS直接丢进一个高性能 JS 引擎比如 V8然后用Python写一套“伪浏览器环境”注入进去脚本读window我给window读navigator我给navigator缺什么补什么直到脚本能正常跑完并生成凭证。它保留了真实JS逻辑又甩掉了浏览器重量级的渲染开销。这是目前对抗5秒盾性价比最高的一条路也是接下来要重点展开的主体。1.3 13次请求的流水线设计逻辑很多人以为逆向5秒盾就是“打开浏览器开发者工具复制一段JSexecjs跑一下”。真实项目远不是这样它是一次完整的工程调度。一次成功的访问从输入URL到拿到目标数据中间隔着大量页面资源请求、挑战资源请求、业务接口请求我在测试站上记录的这次正好是13个HTTP请求。序号请求作用1GET /获取challenge HTML页面2GET /cdn-cgi/challenge-platform/...拉取挑战JS主脚本3GET /cdn-cgi/challenge-platform/.../asset.js拉取挑战辅助脚本4GET /favicon.ico浏览器自动产生的静态请求5POST /cdn-cgi/challenge-platform/.../execute提交JS执行结果、换取放行凭证6GET / 带上cf_clearance重新访问主页验证凭证7GET /assets/vendor.js加载页面主资源8GET /assets/main.css加载样式资源9GET /api/session获取会话信息10POST /api/login登录业务系统11GET /api/list?page1目标业务请求112GET /api/detail?id123目标业务请求213GET /api/list?page2目标业务请求3不同网站的请求数量和顺序会有差异但骨架逻辑是共通的先拿质询页再拉挑战JS执行JS取得凭证重新访问验证然后进入正常业务请求。后面实操章节我会把这13步一条龙走一遍。2. 补环境框架的搭建思路2.1 补哪些环境从第一次报错到能跑通的逐步反推补环境的第一个原则是“不要一次补完”。一开始就试图把完整的浏览器对象模型全塞进去不但工作量大还容易补出一套自相矛盾的“环境指纹”反而被服务端一眼识破。正确做法是跑一步看一个报错缺谁补谁。我习惯按梯队排优先级。第一梯队是几乎所有挑战JS都会读取的基础对象window、document、navigator、location、screen。这些不补脚本连启动都会挂。第二梯队是跨域存储和渲染相关localStorage、sessionStorage、cookie、CanvasRenderingContext2D、WebGLRenderingContext、AudioContext。第三梯队才是行为感知类的MutationObserver、IntersectionObserver、requestAnimationFrame、XMLHttpRequest、fetch。每一梯队内部也不是随便补。比如navigator里面至少有几十个字段实际挑战脚本可能只读其中几个。通过报错定位最有效——脚本抛Cannot read properties of undefined (reading webdriver)你就知道它要读navigator.webdriver它不报错你就不用补。2.2 运行时选型PyMiniRacer、execjs还是js2py补环境框架的执行引擎我前后试过三个做个对照运行时底层引擎优点缺点PyMiniRacerV8速度快、无Node依赖、API简洁Windows下安装依赖编译工具稍折腾execjsNode.js代码直观、社区资料多、JS兼容性强每次调用有进程开销需预装Nodejs2py纯Python免安装执行慢对ES6语法支持差挑战JS易挂最终我选的是 PyMiniRacer。原因很直接5秒盾挑战JS基本是ES2015语法V8的兼容性最好且PyMiniRacer能直接在Python里创建上下文、注入代码、调用JS函数整个流程非常流畅。它的缺点是安装时对编译环境有一定要求如果卡住了可以退而求其次用execjs牺牲点性能换来省心。还有个参数选择的问题。V8默认内存和时间没有硬上限但挑战脚本有时会埋一些行为检测的循环、定时器你不希望Python进程被拖死。PyMiniRacer没有现成的超时参数可以在Python侧用concurrent.futures包一层超时控制或者定期用ctx.eval探活。实际执行一次挑战脚本通常在0.1秒到1秒之间和网速相比可以忽略。2.3 原型链补环境补的是宿主对象不是内置对象补环境这个圈子里“原型链补环境”是个高频词。简单说挑战脚本不一定直接读window.xxx它可能通过原型链一路往上找方法。比如常见的[].constructor.constructor(return this)()这种写法本质是通过数组的构造函数拿到 Function 构造函数再借 Function 构造器直接取到全局对象。如果你只顾着伪造window没注意Function.prototype、Object.prototype这些内置对象不能乱动就容易被这种技巧“抄了后路”。所以补环境有一条非常清晰的边界补的是宿主对象不是内置对象。宿主对象是浏览器提供的那套API内置对象是V8引擎自带的Object、Function、Array、Promise这些。前者可以伪造、覆盖后者尽量保持原样否则引擎内部逻辑会乱。比如你为了补某个属性把Object.defineProperty整个覆写掉很可能导致后面所有对象的属性描述调用失败。另外热词里还有个“iv8补环境”那是安卓App逆向里常用的一类思路原理和这里一样——通过V8或类似JS引擎去模拟目标App缺失的Java/Web环境。遇到5秒盾这类Web挑战时很多手法是互通的。3. 核心环节实现13次请求逐步跑通3.1 抓请求瀑布、识别挑战资源正式动手前先把测试站点的请求瀑布抓下来。我用的是 Chrome 开发者工具开一个无痕窗口勾选 Preserve log清空缓存后访问目标地址。让页面完整经过一次5秒盾等它放行并加载出业务页面不要急着操作直接看 Network 面板里的完整请求列表。抓包时重点找两类东西一类是/cdn-cgi/challenge-platform/路径下的脚本通常就是挑战JS主体和相关辅助资源另一类是第一个HTML响应体里的内联脚本。很多时候挑战入口就藏在那段内联JS里它会动态加载后续脚本。把这些请求按出现顺序记录下来就是后面要编排的流水线骨架。我在测试站上抓到的完整序列刚好13个前面表格就是那次的实际记录。这个阶段需要留意的是Risk请求顺序和Cookie变化同步看。比如第1个请求响应里Set-Cookie了一个__cf_bm第2个请求拉JS时又带上了别的Cookie这些都要记录。后面的框架代码要靠这些信息去设置header和cookie。3.2 用Python执行挑战JS生成cf_clearance拿到挑战JS后搭建一个最小可用的补环境框架。下面这段代码是我在测试环境里能跑通的演示框架只保留核心结构真实网站上你需要根据抓包结果替换JS路径和入口函数。# -*- coding: utf-8 -*- Cloudflare 5秒盾补环境演示框架 仅用于授权测试与安全研究 from py_mini_racer import MiniRacer CHALLENGE_JS_PATH ./challenge.js # 最小浏览器环境脚本按报错逐步追加 ENV_PREFIX // ---------- 基础全局对象 ---------- var window this; var globalThis window; var self window; var top window; var parent window; // ---------- Navigator ---------- var navigator { userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36, appVersion: 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36, platform: Win32, vendor: Google Inc., language: zh-CN, languages: [zh-CN, zh, en], cookieEnabled: true, onLine: true, hardwareConcurrency: 8, deviceMemory: 8, maxTouchPoints: 0, plugins: [ { name: Chrome PDF Plugin, filename: internal-pdf-viewer }, { name: Chrome PDF Viewer, filename: mhjfbmdgcfjbbpaeojofohoefgiehjai }, { name: Native Client, filename: internal-nacl-plugin } ], webdriver: false, javaEnabled: function() { return false; }, sendBeacon: function() { return true; } }; // ---------- Screen ---------- var screen { width: 1920, height: 1080, availWidth: 1920, availHeight: 1040, colorDepth: 24, pixelDepth: 24, orientation: { type: landscape-primary, angle: 0 } }; // ---------- Location ---------- var location { href: https://target.example.com/, protocol: https:, host: target.example.com, hostname: target.example.com, pathname: /, search: , hash: , reload: function() {} }; // ---------- Date / 时区 ---------- // 这里示例固定为东八区偏移实际抓包时按目标站点的访问者时区改 var RealTimezoneOffset -480; Date.prototype.getTimezoneOffset function() { return RealTimezoneOffset; }; def load_env(ctx: MiniRacer): ctx.eval(ENV_PREFIX) def load_challenge_js(ctx: MiniRacer): with open(CHALLENGE_JS_PATH, r, encodingutf-8) as fp: code fp.read() ctx.eval(code) def main(): ctx MiniRacer() load_env(ctx) load_challenge_js(ctx) # 挑战脚本执行完后把生成的cookie从JS侧取回来 # 不同站点的入口函数名和cookie名不同以抓包结果为准 cookie ctx.eval(window.cf_clearance || ) if cookie: print(cf_clearance -, cookie[:80]) else: # 如果脚本通过回调函数输出结果改用 ctx.call(入口函数名, ...) print(未在window上取到cf_clearance检查挑战脚本入口) if __name__ __main__: main()这段框架的调试过程是这样的先跑一次看第一个报错比如ReferenceError: document is not defined我就去ENV_PREFIX里补var document { ... }。再跑报Cannot read properties of undefined (reading createElement)就给document补createElement方法。如此循环直到load_challenge_js不报错并且能从window上取到目标cookie。取到cookie只是第一步。挑战脚本的执行结果往往不会直接放在window上而是构造一个POST请求体发到/cdn-cgi/challenge-platform/.../execute这个接口。也就是说你需要在JS侧捕获“发送请求”的动作把计算出的载荷通过回调吐给Python再由Python来发那个execute请求。这一步是框架的核心环节也是最花费时间调试的地方。3.3 串联13次请求用Session保持Cookie和Header框架能生成凭证后下一步是把13次请求编排成一条流水线。我用requests.Session维护Cookie和Header的一致性这是最基础也是最重要的操作。import requests session requests.Session() session.headers.update({ User-Agent: UA, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, }) # 第1步拿质询页 resp session.get(BASE_URL) # 解析出 challenge script 地址 所需的cookie示例略 # 第2~4步拉取挑战JS相关资源 # 第5步执行JS并提交execute请求 # 这里调用上一小节的补环境框架得到载荷再POST到execute接口 # 第6步重新访问主页此时应带cf_clearance并返回真实页面 resp2 session.get(BASE_URL) # 检查resp2.text里是否还有challenge特征有则说明凭证未被接受 # 第7~13步正常业务请求 resp_list session.get(API_LIST, params{page: 1})这里有个细节值得展开cf_clearance和请求时用的IP、User-Agent是绑定的换IP或换UA都会导致它失效。所以整个13步流程中Session的headers一旦设置好就不要变尤其是User-Agent。另外Cookie的Domain和Path也要保持一致不能把Challenge域的Cookie直接塞到业务域名上那样requests虽然会带上Cookie但服务端识别不了。cf_clearance的有效期没有固定值取决于站点配置短则几十分钟长则几天。实际使用中不要在一条流水线里复用太久尤其是业务请求频率高时更建议定时重新拉一次质询页面刷新凭证。4. 常见问题与排查技巧实录4.1 快速定位JS报错靠日志和探针补环境框架调试中最耗时间的就是JS报错。报错信息经常是undefined is not a function你根本不知道是哪个对象、哪一行出的问题。我的经验是在注入环境时加“探针”——把关键对象的访问打上日志。比如给window和document的取值、调用函数都包一层日志一旦脚本访问到某个未定义的属性日志会明确告诉你它在找什么。// 注入探针示例 var _origDocument document; document new Proxy(_origDocument, { get: function(target, prop) { console.log([document get], prop); if (!(prop in target)) { throw new Error(Missing document. prop); } return target[prop]; } });4.2 常见报错速查表报错信息原因处理思路ReferenceError: window is not defined环境前缀没注入成功确认ENV_PREFIX已执行且var window this在脚本加载前完成Cannot read properties of undefined (reading createElement)document对象缺方法给document补对应方法返回一个最小Element对象TextEncoder is not defined缺Web API编码类注入TextEncoder/TextDecoder的最小实现process is not defined脚本疑似检测Node环境补var process { versions: { node: 20.0.0 }, env: {} }Execution timed out脚本陷入循环或定时器用超时控制定位卡点检查是否缺少定时器清空逻辑403 Access Denied凭证未被服务端接受优先检查IP、UA、时区是否和生成cookie时一致429 Too Many Requests请求频率过高降低并发加随机延时4.3 指纹检测不通过怎么办有时候JS不报错cookie也拿到了但重访时服务端依然拒绝。大概率是环境指纹“太假”或者“不一致”。比如前面给screen填了1920x1080但navigator.userAgent写的是小屏手机UA或者JS执行时读到的当前时区与UA语言矛盾。面对这种情况要回头把环境变量整体看一遍确保风格统一而不是单个字段越真实越好。有些检测会读取 Canvas 指纹、WebGL 渲染器信息这些在补环境里很难做到100%还原。我的经验是返回“稳定且普遍”的默认值比如Canvas的toDataURL接口返回一个固定的base64字符串WebGL的getParameter返回常见显卡厂商的字符串。关键是同一套环境每次执行的结果必须一致一旦前后不一致后端比对立刻现形。4.4 请求编排中的频率控制与合规边界最后聊点工程化的东西。就算补环境框架已经稳定跑通也要控制请求节奏。5秒盾本身是防流量洪峰和恶意流量设计的如果你在它上面跑几百上千个并发即便技术层面能过业务频控也会触发甚至可能拖累源站。实际项目中我会在请求之间加随机延时时间范围根据业务接口的承载能力调整而不是追求极致的“快”。我在实际项目中还有个小技巧把补环境框架的产物cookie/token做成一个定时刷新的服务单独部署一台机器业务侧只通过接口获取最新凭证避免每次业务请求都重新做一遍完整的13步流程。这样既控制了对目标站的请求量也让业务代码和反爬逻辑解耦后续框架升级也方便。回过头来说补环境这条路看着是“逆向”其实考验的是你对浏览器环境的熟悉程度以及在报错面前能不能沉住气一步步补全。我这个项目从抓包到最终跑通中途大概重写了三版环境前缀每次都是因为检测逻辑升级。不要一开始就追求一次补到位先让脚本“能跑”再让它“跑得真”这个节奏才是补环境框架落地的正路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑