OceanAltOceanAlt
latest2026-07-167 分钟读完

攻防实验室 #2:我们在自己的防火墙上打穿了一个洞——一千笔小额

单笔额度挡得住贪婪,挡不住耐心。我们发现自己的参考实现没满足自己写的标准(RAP 支柱2),补上了第六道闸;顺带抓到一个更难看的浮点 bug——它会误杀合法支付。全部是真实运行数据。

OOceanAlt 编辑部
分享:XLinkedInFacebookTelegramWhatsApp微博

攻防实验室 · 第 2 期。这一期我们攻击的不是别人,是自己——并且真的打穿了。

第 1 期我们对自己的合规网关发起六种攻击,全被挡住。那篇文章的结论是"防线成立"。这一期,我们想找一个它挡不住的。

我们找到了。

洞:一千笔小额

我们的防火墙有一道"单笔额度"闸——超过上限的支付,当场拦死。所以想一次掏空钱包,不可能。

但一个被劫持的 agent 不必贪。它可以每次只花 0.01,然后花一千次。

每一笔都在单笔上限内。每一笔都完全"合规"。而钱包空了。

我们当时的授权信封(mandate)是这样的:

单笔上限:0.1 USDC   ✅ 有
单日累计:—           ❌ 没有

RAP 框架支柱 2 白纸黑字写着"单笔 / 单日 / 累计限额"。我们只实现了第一个。 我们自己的参考实现,没有满足我们自己写的标准。这话说出来不好听,但它是真的。

补:第六道闸

我们加了一道"单日累计"闸:每个 agent 每天花掉的总额被记账,超过单日上限就拦死——哪怕每一笔都在单笔上限内。

补完之后,我们对线上网关跑了真实测试(单笔上限 0.1、单日上限 0.3):

| 尝试 | 金额 | 结果 | |---|---|---| | 第 1 笔 | 0.1 USDC | ✅ 200 放行(累计 0.1) | | 第 2 笔 | 0.1 USDC | ✅ 200 放行(累计 0.2) | | 第 3 笔 | 0.1 USDC | ✅ 200 放行(累计 0.3,正好到顶) | | 第 4 笔 | 0.1 USDC | 🛑 403 拦于单日累计闸 |

每一笔都在单笔上限内。第 4 笔依然死了。"一千笔小额"这条路,现在关上了。

附赠:一个更难看的 bug

补完闸之后第一次测试,第 3 笔就被拦了——但它本该放行(累计正好等于上限)。

原因是这一行:

if (spent + amt > dailyCap)   // spent=0.2, amt=0.1, cap=0.3

在 IEEE 754 浮点里,0.2 + 0.1 = 0.30000000000000004,大于 0.3。于是一笔完全合法的支付被误杀。

钱永远不能用浮点比较。 我们改成整数微单位(USDC 的 6 位小数):

const micro = (x) => Math.round(x * 1e6);
if (micro(spent) + micro(amt) > micro(dailyCap))

再测,第 3 笔正确放行,第 4 笔正确拦下。

一道拦不住坏支付的闸很危险;一道会误杀好支付的闸同样危险——它会让人把防火墙关掉

这一期学到的三件事

1. 防线要按"攻击面"补,不是按"直觉"补。 单笔额度挡的是贪婪,挡不住耐心。速度/累计限额挡的才是耐心。真实的攻击者两样都会用。

2. 自己的实现要拿自己的标准去量。 我们写了 RAP 支柱 2 要求"单笔/单日/累计",却只做了单笔。标准不是拿来发布的,是拿来被自己遵守的——所以我们把这次的差距公开写出来,而不是悄悄补上。

3. 金融代码里,浮点是敌人。 这个 bug 没有攻击者也会伤人。

下一期

我们会去打"归因"这一层:一个 agent 能不能冒充另一个已归因的 agent?欢迎带着你的攻击思路来。

亲手打一次:oceanalt.com/firewall · 用你自己的 agent 测:集成指南

(OceanAlt 攻防实验室 · 研究笔记)

分享:XLinkedInFacebookTelegramWhatsApp微博

这篇内容的出处与状态

署名
OceanAlt 编辑部
首次发布
2026-07-16
最后更新
2026-08-01
原始资料
未标注来源

引用这篇内容

OceanAlt 编辑部(2026)。《攻防实验室 #2:我们在自己的防火墙上打穿了一个洞——一千笔小额》。OceanAlt。https://oceanalt.com/zh/articles/attack-lab-2-thousand-small-payments(访问日期:2026-08-03)

本文遵循 编辑准则与事实核查标准。发现错误请告诉我们,核实后会在此处公开更正。