<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>SEPA on 逍遥笔记</title><link>https://blog.7qiang.com/tags/sepa/</link><description>Recent content in SEPA on 逍遥笔记</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>逍遥笔记</copyright><lastBuildDate>Sun, 26 Jul 2026 04:49:00 +0800</lastBuildDate><atom:link href="https://blog.7qiang.com/tags/sepa/index.xml" rel="self" type="application/rss+xml"/><item><title>焚决 Claude — 一个油猴脚本如何撬开 Anthropic 的支付大门</title><link>https://blog.7qiang.com/posts/claude-cassia-mock-payment-bypass-analysis/</link><pubDate>Sun, 26 Jul 2026 04:49:00 +0800</pubDate><guid>https://blog.7qiang.com/posts/claude-cassia-mock-payment-bypass-analysis/</guid><description>&lt;img src="https://blog.7qiang.com/blog-cover-cyber-mask.gif" alt="Featured image of post 焚决 Claude — 一个油猴脚本如何撬开 Anthropic 的支付大门" /&gt;&lt;section class="xiaoyao-contact-card" aria-label="联系逍遥"&gt;
 &lt;img class="xiaoyao-contact-card__qr" src="https://blog.7qiang.com/contact-qr.png" width="690" height="690" loading="lazy" decoding="async" alt="逍遥微信二维码"&gt;
 &lt;div class="xiaoyao-contact-card__content"&gt;
 &lt;h3 class="xiaoyao-contact-card__title"&gt;联系逍遥&lt;/h3&gt;
 &lt;p class="xiaoyao-contact-card__description"&gt;扫码添加微信，或点击下方按钮复制联系方式。&lt;/p&gt;
 &lt;div class="xiaoyao-contact-buttons" data-contact-buttons&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--wechat" type="button" data-copy-contact="plus6566" data-copy-label="微信号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;微信&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;plus6566&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--qq" type="button" data-copy-contact="826215906" data-copy-label="QQ 号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;QQ&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;826215906&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;span class="xiaoyao-contact-feedback" role="status" aria-live="polite"&gt;&lt;/span&gt;
&lt;/div&gt;

 &lt;/div&gt;
&lt;/section&gt;

&lt;p&gt;进微信群请联系博主，各位觉得文章对你有帮助的话可否打赏一些呀~&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;⚠️ 时效性提醒&lt;/strong&gt;：本文发布于 2026 年 7 月，文中涉及的 API 端点、支付流参数均为发布时有效值。Anthropic 随时可能修改 &lt;code&gt;checkout_capabilities&lt;/code&gt; 响应结构或在后端追加校验逻辑，届时本方法将失效。本文保留仅供安全研究与客户端安全防御参考。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="前言"&gt;前言
&lt;/h2&gt;&lt;p&gt;最近圈子里流传着一个叫**「焚决」**的东西，名字取得很玄，搞得好像什么了不得的秘术。拿到手一看——是一段不到 200 行的 Tampermonkey 油猴脚本。&lt;/p&gt;
&lt;p&gt;但别小看这 200 行代码。它干了一件非常漂亮的事情：在你打开 Claude 网页版准备付费订阅的瞬间，在浏览器内部悄无声息地篡改了一个 API 响应，把你看到的支付页面从信用卡表单换成了 SEPA 银行转账。然后你去 &lt;code&gt;randomiban.com&lt;/code&gt; 随便生成一个德国银行账号，填进去，提交——&lt;strong&gt;Claude Max 到手，扣款金额 0 元。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这玩意在外面传得沸沸扬扬，各路卡网卖的廉价 Claude 会员，相当一部分就是靠这个批量生产的。&lt;/p&gt;
&lt;p&gt;今天咱们把它彻底拆开。不光讲它怎么工作，更要讲&lt;strong&gt;为什么能成&lt;/strong&gt;、&lt;strong&gt;它利用了支付系统的哪个结构性缺陷&lt;/strong&gt;、以及&lt;strong&gt;这种攻击模式怎么迁移到其他平台&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一攻击全景一个-api-字段改写引发的连锁反应"&gt;一、攻击全景：一个 API 字段改写引发的连锁反应
&lt;/h2&gt;&lt;h3 id="正常的-claude-订阅流程"&gt;正常的 Claude 订阅流程
&lt;/h3&gt;&lt;p&gt;打开 &lt;code&gt;claude.ai&lt;/code&gt;，点击升级订阅，前端第一件事不是弹出支付表单——它先问后端一个问题：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GET /api/organizations/{org_id}/subscription/checkout_capabilities
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个接口的职责很简单：&lt;strong&gt;告诉前端，当前这个用户应该走哪条支付通道。&lt;/strong&gt; 后端会综合 IP 地理位置、账号注册区域、浏览器 Accept-Language 等信号，返回一个支付流标识：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;checkout_flow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;stripe&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;前端拿到 &lt;code&gt;stripe&lt;/code&gt;，渲染信用卡表单——卡号、有效期、CVV、3DS 二次验证，一套标准流程。信用卡支付是同步授权的，银行实时校验，过不了就是过不了。&lt;/p&gt;
&lt;h3 id="劫持后的流程"&gt;劫持后的流程
&lt;/h3&gt;&lt;p&gt;脚本做的事情只有一件：&lt;strong&gt;把所有命中 &lt;code&gt;checkout_capabilities&lt;/code&gt; 的响应体替换为&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;checkout_flow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cassia&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;cassia&lt;/code&gt; 是 Anthropic 内部用于欧洲 SEPA 银行转账的支付流标识。前端一看返回值变了，渲染逻辑直接切换——不再是信用卡表单，而是一个 IBAN 输入框。&lt;/p&gt;
&lt;p&gt;这时候去 randomiban.com 生成一个德国 IBAN（格式：&lt;code&gt;DE&lt;/code&gt; + 2 位校验码 + 8 位银行代码 + 10 位账号，共 22 位），填进去，点提交。&lt;/p&gt;
&lt;p&gt;前端校验？只检查 IBAN 格式是否合法（MOD 97-10 校验通过即可）。&lt;/p&gt;
&lt;p&gt;后端校验？SEPA 体系下，&lt;strong&gt;提交时不做实时账户验证&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结果：Claude 立刻给你开通了订阅。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二sepa-直接借记为什么填个假账号也能过"&gt;二、SEPA 直接借记——为什么填个假账号也能过
&lt;/h2&gt;&lt;p&gt;这是整个攻击的核心利用点，不理解 SEPA 的清算机制，就不可能理解这个漏洞为什么成立。&lt;/p&gt;
&lt;h3 id="先搞清楚-sepa-是什么"&gt;先搞清楚 SEPA 是什么
&lt;/h3&gt;&lt;p&gt;SEPA（Single Euro Payments Area，单一欧元支付区）覆盖 36 个欧洲国家和地区，是欧盟推动的统一支付基础设施。其中的 &lt;strong&gt;SEPA Direct Debit&lt;/strong&gt;（直接借记，德语叫 Lastschriftverfahren）允许商家向消费者的银行账户&amp;quot;拉钱&amp;quot;——不是你给商家转账，而是商家拿着你的授权凭证去你的银行把钱取走。&lt;/p&gt;
&lt;p&gt;听起来很方便对吧？问题就出在这个&amp;quot;拉钱&amp;quot;的流程设计上。&lt;/p&gt;
&lt;h3 id="信用卡-vs-sepa-直接借记两套完全不同的信任模型"&gt;信用卡 vs SEPA 直接借记：两套完全不同的信任模型
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;信用卡（Stripe 标准流）&lt;/th&gt;
 &lt;th&gt;SEPA 直接借记（cassia 流）&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;授权模式&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;同步在线授权&lt;/td&gt;
 &lt;td&gt;异步离线扣款&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;交易发起时校验&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;卡号 Luhn 校验 + CVV 验证 + 3DS 二次认证 + 银行实时授权&lt;/td&gt;
 &lt;td&gt;IBAN MOD 97-10 格式校验，&lt;strong&gt;仅此而已&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;银行参与时机&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;交易发起的瞬间&lt;/td&gt;
 &lt;td&gt;交易发起后的 1-3 个工作日&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;资金确认&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;毫秒级返回授权结果&lt;/td&gt;
 &lt;td&gt;银行不返回任何确认，排队等清算&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;消费者保护&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Chargeback 争议流程&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;8 周无条件撤回权&lt;/strong&gt;（SEPA CORE 规则）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;看到关键区别了吗？信用卡在你点击支付的&lt;strong&gt;那一瞬间&lt;/strong&gt;就跟银行确认了你有没有钱、卡号对不对、你本人是不是同意的。而 SEPA 直接借记&lt;strong&gt;完全不做这些&lt;/strong&gt;——它只检查 IBAN 的格式，然后把扣款请求扔进银行间的批量清算队列里，等着慢慢处理。&lt;/p&gt;
&lt;h3 id="sepa-core-清算流程"&gt;SEPA CORE 清算流程
&lt;/h3&gt;&lt;p&gt;实际的清算过程是这样的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;你提交 IBAN
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;支付网关做 MOD 97-10 校验（纯数学公式，检查 IBAN 格式是否合法）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;校验通过 → 支付网关返回&amp;#34;交易成功&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Anthropic 收到成功信号 → 开通你的 Claude Max 订阅
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;... T+1 到 T+3 工作日 ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;扣款请求进入 SEPA CORE 清算系统
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;商家银行 → 消费者银行：请从账号 DEXX XXXX XXXX XXXX XXXX XX 扣 $20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;消费者银行查询账号 → 账号不存在 / 余额不足 / 未授权
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;返回 R 代码拒绝（R02=无效账号, R04=账户已关, R05=被授权人撤销...）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Anthropic 收到扣款失败通知 → 关闭你的订阅
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;重点来了：从你提交 IBAN 到银行清算出结果，中间有一个 1-3 个工作日的真空期。&lt;/strong&gt; 在这段时间里，你的 Claude Max 订阅是完全生效的——能用 Opus、能用所有高级功能、流量不限。&lt;/p&gt;
&lt;h3 id="mod-97-10格式校验的局限性"&gt;MOD 97-10：格式校验的局限性
&lt;/h3&gt;&lt;p&gt;IBAN 的校验算法叫 ISO 7064 MOD 97-10，原理其实很简单：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把 IBAN 的国家代码和校验码移到末尾&lt;/li&gt;
&lt;li&gt;把字母转换为数字（A=10, B=11, …, Z=35）&lt;/li&gt;
&lt;li&gt;对整个大整数做 MOD 97 运算&lt;/li&gt;
&lt;li&gt;结果等于 1 就合法&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个算法能检出什么？&lt;strong&gt;格式错误&lt;/strong&gt;——打错了某一位、漏了一位、国家代码不对。&lt;/p&gt;
&lt;p&gt;这个算法检不出什么？&lt;strong&gt;一切跟真实世界有关的东西&lt;/strong&gt;——账户是否存在、余额是否充足、持有人是不是你。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;randomiban.com&lt;/code&gt; 生成的 IBAN 就是利用了这一点：它按照 MOD 97-10 算法逆向构造，生成的 IBAN &lt;strong&gt;格式 100% 合法&lt;/strong&gt;，能通过任何基于此算法的前端和后端校验。至于这个账号在不在现实中存在——那是 3 天后银行清算时才会发现的事情。&lt;/p&gt;
&lt;h3 id="为什么偏偏是德国-iban"&gt;为什么偏偏是德国 IBAN？
&lt;/h3&gt;&lt;p&gt;SEPA 覆盖 36 个国家，为什么脚本选了德国（DE）？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;格式最简单&lt;/strong&gt;：DE 开头的 IBAN 固定 22 位，结构是 &lt;code&gt;DE + 2位校验码 + 8位银行代码(BLZ) + 10位账号&lt;/code&gt;，没有额外的国家级校验叠加&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通过率最高&lt;/strong&gt;：德国 IBAN 在 Stripe 等主流支付网关的接受度最好，不会触发额外的地区风控&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;银行代码空间大&lt;/strong&gt;：8 位 BLZ 有大量有效的银行代码段，随机生成碰上有效 BLZ 前缀的概率不低&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对比一下法国 IBAN（27 位，还有额外的 RIB 密钥校验）或者西班牙 IBAN（24 位，有 DC 校验位），德国确实是阻力最小的选择。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三脚本逐行技术拆解"&gt;三、脚本逐行技术拆解
&lt;/h2&gt;&lt;p&gt;理解了 SEPA 的机制，再来看脚本本身就清晰多了。每一个设计决策都有明确的技术理由。&lt;/p&gt;
&lt;h3 id="31-元数据抢在一切之前"&gt;3.1 元数据——抢在一切之前
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @name TestExample Cassia Response Mock
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @match *://claude.ai/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @match *://*.claude.ai/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @run-at document-start
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @grant none
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @sandbox raw
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;三个关键设定：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@run-at document-start&lt;/code&gt;&lt;/strong&gt;：页面 DOM 还没开始构建，脚本已经注入完毕。这不是可选项——如果等到 &lt;code&gt;document-idle&lt;/code&gt;，&lt;code&gt;checkout_capabilities&lt;/code&gt; 的请求可能已经发出去了，Hook 就晚了。整个攻击成立的前提就是&lt;strong&gt;比目标请求更早完成注入&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@sandbox raw&lt;/code&gt;&lt;/strong&gt;：绕过 Tampermonkey 的安全沙箱。默认沙箱会隔离脚本的执行上下文，导致你覆盖的 &lt;code&gt;window.fetch&lt;/code&gt; 和页面实际用的 &lt;code&gt;window.fetch&lt;/code&gt; 不是同一个。&lt;code&gt;raw&lt;/code&gt; 模式下脚本直接在页面上下文中执行，Hook 才能真正生效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@grant none&lt;/code&gt;&lt;/strong&gt;：不申请 &lt;code&gt;GM_*&lt;/code&gt; 系列 API 权限。一方面减少 Tampermonkey 的安全提示弹窗，另一方面 &lt;code&gt;@sandbox raw&lt;/code&gt; 本身就不兼容 &lt;code&gt;GM_*&lt;/code&gt; API。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="32-精确制导只改一个接口"&gt;3.2 精确制导——只改一个接口
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TARGET_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sr"&gt;/^\/api\/organizations\/[^/]+\/subscription\/checkout_capabilities\/?$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;正则匹配精确到路径段，连末尾有没有斜杠都考虑了。&lt;strong&gt;只拦截这一个接口，其他所有请求原样放行。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为什么是这个接口？因为它是 Claude 前端支付流的&lt;strong&gt;单一决策点&lt;/strong&gt;——前端根据它的返回值决定渲染 Stripe 信用卡表单还是 SEPA IBAN 输入框。控制了这个响应，就控制了用户看到的整个支付界面。&lt;/p&gt;
&lt;p&gt;这种精准度也是为了隐蔽性——如果你 Hook 了所有请求，前端的行为会出各种异常，容易暴露。只改一个接口，前端其他部分的运行完全正常。&lt;/p&gt;
&lt;h3 id="33-fetch-拦截先发后改"&gt;3.3 Fetch 拦截——先发后改
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nativeFetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getTargetUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;nativeFetch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;targetUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;originalResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;createMockResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意调用顺序：&lt;strong&gt;先用原生 &lt;code&gt;fetch&lt;/code&gt; 把真实请求正常发出去，等拿到响应后再替换返回值。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是这个脚本最巧妙的地方。&lt;/p&gt;
&lt;p&gt;如果直接拦截请求不发出去，构造一个假响应返回呢？功能上没问题。但从服务端的角度看——你打开了支付页面，前端应该调 &lt;code&gt;checkout_capabilities&lt;/code&gt;，结果后端日志里根本没看到这个请求。这就是一个异常信号，可以被 Anthropic 的安全团队用于检测。&lt;/p&gt;
&lt;p&gt;先发后改的策略完美回避了这个问题：服务端日志里看到了正常的 GET 请求、正常的 200 响应，一切如常。篡改发生在浏览器内部，从 HTTPS 加密通道往外看什么都没变。&lt;/p&gt;
&lt;h3 id="34-response-重构细节决定成败"&gt;3.4 Response 重构——细节决定成败
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createMockResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content-length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content-encoding&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;etag&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content-md5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content-type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;application/json; charset=utf-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content-length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MOCK_LENGTH&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;cache-control&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;no-store&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MOCK_BODY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;statusText&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;OK&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;逐个分析删掉的响应头：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;content-encoding&lt;/code&gt;&lt;/strong&gt;：原始响应大概率是 gzip 或 br 压缩的。我们构造的 JSON 是明文，如果不删这个头，浏览器会拿着明文 JSON 去做 gzip 解压——直接炸。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;content-length&lt;/code&gt;&lt;/strong&gt;：原始响应体大小和我们构造的不一样，不改的话浏览器会截断或报错。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;etag&lt;/code&gt; / &lt;code&gt;content-md5&lt;/code&gt;&lt;/strong&gt;：完整性校验头。原始响应的哈希和我们篡改后的内容对不上。虽然浏览器通常不强制校验这些头，但删掉更保险——万一前端代码拿这些值做了缓存校验呢。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cache-control: no-store&lt;/code&gt;&lt;/strong&gt;：强制不缓存。如果浏览器缓存了我们的假响应，下次刷新页面不走 Hook 了就拿到缓存的假数据——反而弄巧成拙。反过来，如果缓存了之前的真响应，前端可能直接用缓存不发请求，Hook 就没机会触发。&lt;code&gt;no-store&lt;/code&gt; 确保每次都走网络请求，每次都经过 Hook。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="35-xhr-双通道保险"&gt;3.5 XHR 双通道保险
&lt;/h3&gt;&lt;p&gt;脚本不光 Hook 了 &lt;code&gt;fetch&lt;/code&gt;，还对 &lt;code&gt;XMLHttpRequest&lt;/code&gt; 做了一套完整的拦截——覆盖了 &lt;code&gt;responseText&lt;/code&gt;、&lt;code&gt;response&lt;/code&gt;、&lt;code&gt;status&lt;/code&gt;、&lt;code&gt;statusText&lt;/code&gt;、&lt;code&gt;getResponseHeader&lt;/code&gt;、&lt;code&gt;getAllResponseHeaders&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;为什么要两套？现代 Web 应用大多用 &lt;code&gt;fetch&lt;/code&gt;，但你无法保证 Anthropic 的前端代码——或者它依赖的第三方库——不会在某些场景下 fallback 到 XHR。少 Hook 一个通道就是留了一条漏网之鱼。&lt;/p&gt;
&lt;p&gt;XHR 的 Hook 方式也值得一看：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;replaceXhrGetter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;propertyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;descriptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOwnPropertyDescriptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;XhrPrototype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;propertyName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;descriptor&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;function&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configurable&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`[Cassia Mock] 无法接管 XHR.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;propertyName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nativeGetter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;XhrPrototype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;propertyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;getMatchedXhr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;nativeGetter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nativeGetter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;用 &lt;code&gt;Object.getOwnPropertyDescriptor&lt;/code&gt; 先取原始的 property descriptor，再通过 &lt;code&gt;Object.defineProperty&lt;/code&gt; 精确替换 getter。比直接赋值靠谱得多——它保留了原始 descriptor 的所有属性（&lt;code&gt;configurable&lt;/code&gt;、&lt;code&gt;enumerable&lt;/code&gt;），而且会先检查 &lt;code&gt;configurable&lt;/code&gt; 是否为 &lt;code&gt;false&lt;/code&gt;，如果属性被冻结就优雅降级而不是静默失败。&lt;/p&gt;
&lt;h3 id="36-状态指示器"&gt;3.6 状态指示器
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;badge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;div&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cassia-mock-badge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Cassia Mock ON&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;页面右下角一个绿色小角标，确认脚本已激活。实际攻击中这是个安慰剂——真正的确认是看支付页面有没有从信用卡变成 IBAN 输入框。但对于批量操作的卡商来说，这个视觉反馈能提高操作效率。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四横向迁移这不是一个漏洞是一类漏洞"&gt;四、横向迁移：这不是一个漏洞，是一类漏洞
&lt;/h2&gt;&lt;p&gt;分析完焚决，我们跳出来看一个更有价值的问题：&lt;strong&gt;这类攻击是可复用的。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="漏洞本质"&gt;漏洞本质
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;客户端信任边界缺失&lt;/strong&gt;：服务端将支付流的决策权——“这个用户该走哪条支付通道”——交给了一个客户端可以任意篡改的 API 响应。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这不是孤例。回顾我们之前分析过的一系列支付漏洞，它们共享同一个底层模式：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;漏洞&lt;/th&gt;
 &lt;th&gt;被篡改的对象&lt;/th&gt;
 &lt;th&gt;信任边界裂缝&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;焚决（本例）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;checkout_capabilities&lt;/code&gt; 响应&lt;/td&gt;
 &lt;td&gt;前端信任 API 返回值决定支付通道&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;GPT Plus offerToken 注入&lt;/td&gt;
 &lt;td&gt;Google Play Billing 内存中的 offerToken&lt;/td&gt;
 &lt;td&gt;服务端不校验 token 与账号资格的绑定关系&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;GPT iOS 收据复用&lt;/td&gt;
 &lt;td&gt;Base64 App Store 收据&lt;/td&gt;
 &lt;td&gt;服务端不验证收据与提交者账号的对应关系&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cloudflare Pro 竞态&lt;/td&gt;
 &lt;td&gt;并发请求的状态窗口&lt;/td&gt;
 &lt;td&gt;权限发放与支付确认之间存在可利用的时序差&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RevenueCat 回调劫持&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;fetch_token&lt;/code&gt; 跨账号提交&lt;/td&gt;
 &lt;td&gt;第三方回调接口缺乏调用方鉴权&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="通用攻击模型"&gt;通用攻击模型
&lt;/h3&gt;&lt;p&gt;把这些漏洞抽象一下，可以提取出一个四步攻击框架：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. 定位决策接口
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 找到决定支付流 / 功能开关 / 定价计算的 API 端点
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 信号关键词：checkout_capabilities, payment_methods, available_plans,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; eligible_promotions, feature_flags, entitlements
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 注入篡改数据
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 通过油猴脚本（Web）、MITM（移动端）、Frida Hook（Native）等
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 方式替换响应内容
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 选择确认延迟最长的路径
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SEPA 直接借记 &amp;gt; 银行转账 &amp;gt; PayPal &amp;gt; 信用卡
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 延迟越长，时间窗口越大
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. 利用&amp;#34;前端校验 + 后端真空&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 前端校验通过后，后端在异步确认完成前不暂停服务发放
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;这个框架可以复用到任何订阅制平台&lt;/strong&gt;，只要目标满足：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支付方式的选择依赖客户端可控状态&lt;/li&gt;
&lt;li&gt;存在至少一种异步确认的支付方式&lt;/li&gt;
&lt;li&gt;“服务开通&amp;quot;发生在&amp;quot;支付确认&amp;quot;之前&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在漏洞挖掘实战中，打开 DevTools Network 面板，搜索 &lt;code&gt;checkout&lt;/code&gt;、&lt;code&gt;payment&lt;/code&gt;、&lt;code&gt;billing&lt;/code&gt;、&lt;code&gt;plan&lt;/code&gt;、&lt;code&gt;subscription&lt;/code&gt; 这些关键词，找到所有影响支付逻辑的端点，逐个测试响应注入——这就是最直接的攻击面枚举方式。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五防御视角"&gt;五、防御视角
&lt;/h2&gt;&lt;h3 id="给服务端开发者"&gt;给服务端开发者
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;支付流决策必须服务端闭环&lt;/strong&gt;。&lt;code&gt;checkout_flow&lt;/code&gt; 的值应该在后端根据用户地区、账号状态、风控信号独立计算，前端只是一个渲染器——它不应该有能力选择自己走哪条支付通道。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异步支付必须延迟开通&lt;/strong&gt;。SEPA、银行转账这类异步支付方式，在银行返回扣款成功确认之前，不应该开通任何付费功能。可以给用户一个&amp;quot;支付处理中&amp;quot;的状态，等清算完成后再激活。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API 响应签名&lt;/strong&gt;。对关键 API 响应做 HMAC 签名（密钥存服务端），前端消费前验签。虽然不能根治（攻击者可以 Hook 验签逻辑），但大幅提高了攻击门槛——从改一个 JSON 字段变成了还得逆向验签实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IBAN 实时银行验证&lt;/strong&gt;。接入 SWIFT gpi 或各国央行的 IBAN 验证服务（如德国 Bundesbank 的 IBAN 验证接口），在提交时做实时的账户存在性校验，而不是只靠本地 MOD 97。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="给安全研究者"&gt;给安全研究者
&lt;/h3&gt;&lt;p&gt;这类漏洞的攻击面很清晰——任何一个返回了影响支付逻辑数据的 API 端点，且这个数据可以被前端篡改，都是潜在的攻击入口。&lt;/p&gt;
&lt;p&gt;一些高价值的&amp;quot;信号接口”：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;checkout_capabilities&lt;/code&gt; / &lt;code&gt;payment_methods&lt;/code&gt; / &lt;code&gt;available_plans&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;region&lt;/code&gt; / &lt;code&gt;locale&lt;/code&gt; / &lt;code&gt;country_detection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eligible_promotions&lt;/code&gt; / &lt;code&gt;offers&lt;/code&gt; / &lt;code&gt;discounts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;feature_flags&lt;/code&gt; / &lt;code&gt;entitlements&lt;/code&gt; / &lt;code&gt;subscription_tiers&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="六实战翻车现场验证失败并清除旧会话"&gt;六、实战翻车现场：“验证失败并清除旧会话”
&lt;/h2&gt;&lt;p&gt;讲完了攻击原理和防御思路，再来看一个真实场景——&lt;strong&gt;焚决并不是每次都能成功的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在实际操作中（不管是焚决的 SEPA 路径还是正常的 Stripe 信用卡路径），你大概率会遇到这样一幕：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Claude支付失败截图" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;p&gt;两条红色报错同时弹出来：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;“未提交付款：发生了意外错误。请检查表单后重试。”&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“验证失败并清除旧会话”&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第一次看到这个，很多人会以为是卡的问题、网络的问题、脚本的问题。其实都不是——&lt;strong&gt;这是会话状态失效的问题&lt;/strong&gt;，而且它能帮我们理解 Anthropic 的支付架构到底是怎么做验证的。&lt;/p&gt;
&lt;h3 id="为什么会出现这个错误"&gt;为什么会出现这个错误？
&lt;/h3&gt;&lt;p&gt;两条错误同时出现，意味着&lt;strong&gt;会话死在了支付提交的瞬间&lt;/strong&gt;。理解这一点需要知道 Claude 支付提交时后端做了什么：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;用户点击&amp;#34;提交支付&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;前端将 Stripe token / IBAN 提交到 Anthropic 后端
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;后端第一件事：校验当前会话（session）是否有效
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;会话失效 → 返回&amp;#34;验证失败&amp;#34; → 前端清除旧 session → 支付自然失败
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键点在于：Anthropic 在处理支付请求之前，会先验证你的登录会话。&lt;/strong&gt; 如果会话已经过期或被吊销，支付请求根本不会到达 Stripe——它在 session 校验这一步就被打回来了。&lt;/p&gt;
&lt;h3 id="五种常见的会话失效原因"&gt;五种常见的会话失效原因
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;① SK Cookie 过期（最常见）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude.ai 使用 &lt;code&gt;__Secure-next-auth.session-token&lt;/code&gt;（业内简称 SK Cookie）维持登录态。这个 cookie 有 TTL，通常几个小时后过期。&lt;/p&gt;
&lt;p&gt;如果你在打开支付页面之前获取的 cookie，到填完表单点提交的时候刚好过期——恭喜，踩中了时间窗口。自动化场景下尤其常见：任务在队列里排了一段时间，等轮到你的时候 cookie 已经不新鲜了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② Session 被踢（多端互踢）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同一个账号如果在多个地方同时登录（比如你开了好几个 Worker 共用一个 cookie），后登录的会话会使前一个失效。你在 Worker A 上正准备提交支付，Worker B 刚好用同一个 cookie 做了一次请求刷新了 session——A 的 session 就死了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 代理 IP 切换（Session-IP 绑定）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Anthropic 很可能在 session 上绑定了创建时的 IP 或 IP 段。如果你的代理在支付流程中间断线重连，换了一个出口 IP，后端一比对——这个 session 是从 &lt;code&gt;45.67.xx.xx&lt;/code&gt; 创建的，现在请求来自 &lt;code&gt;89.12.xx.xx&lt;/code&gt;？验证失败。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;④ Stripe PaymentIntent 过期&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;即使 session 没问题，Stripe 侧也有自己的超时机制。&lt;code&gt;PaymentIntent&lt;/code&gt; 和对应的 &lt;code&gt;client_secret&lt;/code&gt; 有有效期（通常数小时，但在高风控场景下可能更短）。如果从打开支付页面到实际提交间隔太长，Intent 过期了，Stripe 返回错误，前端展示为&amp;quot;发生了意外错误&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑤ Checkout Session 并发冲突&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同一个 Organization 如果同时发起了多个订阅操作（比如一个在升级 Pro，另一个在改支付方式），后一个操作创建的 &lt;code&gt;checkout session&lt;/code&gt; 会使前一个失效。前一个提交的时候引用的 session ID 已经不存在了。&lt;/p&gt;
&lt;h3 id="应对策略"&gt;应对策略
&lt;/h3&gt;&lt;p&gt;针对这类会话失效问题，无论是做自动化还是做安全防护，都有几条通用原则：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;从攻击者/自动化角度看：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;策略&lt;/th&gt;
 &lt;th&gt;做法&lt;/th&gt;
 &lt;th&gt;解决的问题&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Session 预检&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;进支付页之前先调 &lt;code&gt;/api/auth/session&lt;/code&gt; 确认 cookie 存活&lt;/td&gt;
 &lt;td&gt;Cookie 过期&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;快进快出&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;打开支付页后立刻完成填写和提交，压缩停留时间&lt;/td&gt;
 &lt;td&gt;Intent 超时&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;代理钉住&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;支付全流程内锁定同一个代理出口，不允许中途切换&lt;/td&gt;
 &lt;td&gt;IP 绑定&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Cookie 生命周期管理&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;记录每个 cookie 的获取时间，临近 TTL 时主动刷新&lt;/td&gt;
 &lt;td&gt;Cookie 过期&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;错误分类重试&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;检测到&amp;quot;清除旧会话&amp;quot;后标记为 session 失效，重新获取 cookie 再试&lt;/td&gt;
 &lt;td&gt;所有会话类问题&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;单账号串行化&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;同一个 org 同时只跑一个支付操作&lt;/td&gt;
 &lt;td&gt;并发冲突&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;从防御者角度看：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个报错机制本身就是 Anthropic 的一层防线——在处理支付之前先验会话，避免在认证不确定的情况下创建订阅。这个设计是对的。&lt;/p&gt;
&lt;p&gt;但如果要更进一步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Session 绑定 + 支付确认二次绑定&lt;/strong&gt;：不光 session 绑 IP，&lt;code&gt;PaymentIntent&lt;/code&gt; 也应该绑定创建时的 session ID。Session 变了，Intent 就失效——即使攻击者拿到了一个新的有效 session，也得重新走一遍支付页面，不能复用之前的 Intent。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支付提交时的实时 Session 刷新&lt;/strong&gt;：在提交支付的瞬间，后端不仅校验 session 是否有效，还可以强制刷新一次 token，确保提交时的 session 是&amp;quot;活跃&amp;quot;的而不仅仅是&amp;quot;未过期&amp;quot;的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异常模式检测&lt;/strong&gt;：如果一个 org 短时间内反复出现&amp;quot;session 校验通过 → 进入支付页 → 提交时 session 失效&amp;quot;的模式，说明有人在用自动化工具——标记为可疑行为。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="小结"&gt;小结
&lt;/h3&gt;&lt;p&gt;这个&amp;quot;验证失败&amp;quot;的报错看着烦人，但它揭示了一个重要的架构细节：&lt;strong&gt;Anthropic 的支付流不是单纯的前端-Stripe 双边交互，中间有一层 session 校验网关。&lt;/strong&gt; 这层网关既是防线（挡住了过期/伪造的会话），也是自动化场景下最常踩的坑。&lt;/p&gt;
&lt;p&gt;理解了这个机制，不管你是想加固自己平台的支付安全，还是想搞清楚为什么自己的自动化流程老是翻车——这个报错里都有答案。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七写在最后"&gt;七、写在最后
&lt;/h2&gt;&lt;p&gt;焚决这个东西，技术上说穿了就是一个&lt;strong&gt;客户端状态注入&lt;/strong&gt;工具。它不攻击 Claude 的认证系统，不碰 Anthropic 的服务器，攻击目标是&lt;strong&gt;前端与后端之间那条看不见的信任边界&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它的精妙不在于代码多复杂——说真的 200 行 JS 连个正经项目的单元测试都撑不满——而在于它精准地捏住了两个系统设计缺陷的交叉点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Anthropic 的前端支付流由一个可篡改的 API 响应驱动&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SEPA 直接借记在清算完成前不验证账户真实性&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两个缺陷单独拿出来，各自都不算致命。但组合在一起，就变成了一条从&amp;quot;修改一个 JSON 字段&amp;quot;到&amp;quot;零元订阅 Claude Max&amp;quot;的完整攻击链。&lt;/p&gt;
&lt;p&gt;而这类&amp;quot;信任边界错位 + 异步确认间隙&amp;quot;的组合漏洞，绝不仅限于 Claude。任何一个同时提供信用卡和 SEPA 支付的 SaaS 平台，如果支付通道的选择权交给了前端，都可能面临同样的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工具可以被修复，模式值得被记住。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="相关阅读"&gt;相关阅读
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.7qiang.com/posts/gpt-plus-exploit-revenuecat-vulnerability/" target="_blank" rel="noopener"
 &gt;GPT Plus 订阅漏洞深度分析 — Google Play Billing 鉴权缺失导致 0 元订阅&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.7qiang.com/posts/gpt-plus-receipt-vulnerability-2026/" target="_blank" rel="noopener"
 &gt;GPT Plus 收据复用漏洞 — iOS 收据验证缺陷&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.7qiang.com/posts/manual-sec-20260519-cloudflare-billing-race-condition/" target="_blank" rel="noopener"
 &gt;Cloudflare 计费逻辑缺陷 — 请求重放绕过订阅支付&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;标签&lt;/strong&gt;: #Claude #支付安全 #API劫持 #客户端安全 #SEPA #Tampermonkey #漏洞分析 #焚决&lt;/p&gt;</description></item></channel></rss>