本报告由 GCSA Web3 Labs ZkSec 发布:任意外部调用漏洞——合约将调用目标与数据完全交给调用者填写——是链上授权资金面临的一类核心风险。本文基于归档节点数据,重放 21 亿笔历史交易,扫描出超过一百个存在相关风险的合约,涉及多个代码族。文章系统介绍检测方法(历史交易重放、调用参数溯源与变异验证),分析漏洞构成与以太坊/BSC 链间差异,并以已知攻击与扫描检出案例进行交叉验证,最终给出防御建议。
1.1 任意外部调用漏洞
存在任意外部调用漏洞的合约,提供一个公开入口:调用的目标地址和数据由任何提交者填写,合约以自己的身份执行。正常合约不是这样——要调用谁、传什么数据,写在代码里或限定在白名单内。
这种入口有时是刻意的设计取舍(批量工具、跨链桥为了通用故意开放),有时是设计缺陷(本想限制调用范围,却漏了校验)。无论是刻意开放还是校验遗漏,只要合约持有用户授权或滞留资金,缺少调用者限制的入口就可能被非预期调用者利用。
1.2 一个目标限制不足的案例
BSC 上的 MetaAggregatorSwapContract(0xc17b234fad8e40642662f1d2a6cbd1d609e7b9b5)存在目标限制不足的任意外部调用风险。它的功能是代用户执行兑换:接收代币,调用外部聚合器完成交易。

aggregator 和 swapData 都来自调用者,合约没有白名单校验。非 delegate 模式下,合约通过 CALL 调用 aggregator;delegate 模式下,代码在 SWAP_TARGET 的上下文中执行。目标限制不足,可能扩大合约可调用的范围;合约名下还挂着用户为兑换授权的额度。
aggregator 和 swapData 可控,但这本身不能推出任意 token.transferFrom 攻击。minAmountOut 在源码中要求大于零;非 delegate 模式下,MetaAggregator 通过 CALL 调用 aggregator,代币看到的调用者还要由实际调用链确定。因此,本案例只有在给出完整 trace、spender 和资金路径后,才能判定为授权转移漏洞。
要把该入口判定为授权转移漏洞,还需列出实际 spender、受害者授权和资金流;现有示例只支持滞留余额路径。两条路径应分开列出。
2.1 漏洞扫描系统设计
把历史交易一笔一笔重新执行,拿到完整的调用树——每一层调用、每个参数都有记录。然后在树里找可操控的点:内部调用的某个参数(转账给谁、转多少、调用哪个合约、带什么数据),如果能一路追溯到交易顶层 calldata 里的原始字节,只能说明这个参数可能受外部提交者影响,还要通过变异重放确认。把这些点逐个替换后重放,验证参数是否确实可控,以及操控能否传导成真实的资金流动。
2.2 扫描执行流程
一笔交易在代码里的完整执行流程见图 1。

2.3 调用参数溯源
扫描器先检查内层参数是否能在顶层 calldata 中找到原始字节。找到时,只能说明该参数在当前编码路径上可由提交者影响,仍需通过变异重放确认。
扫描器做字节级匹配:参数的 32 字节直接出现在顶层对齐位置,或整段调用 calldata 作为连续字节串嵌在顶层,即登记为可由外部参数改变的内部参数并记下偏移;匹配不到时标记为“无法建立参数来源”,不据此断定该值由合约内部算出。变异时改顶层偏移,重放验证内层是否跟随。
当前实现不做语义分析,只做 EVM 层的字节匹配,因此开源和闭源合约都能处理。已知缺口是:合约在内存里重组 calldata 时,参数来源可能无法继续追踪。
2.4 实现
整套系统只有一个核心设计:每笔历史交易,都在它发生前一瞬的世界状态上执行两遍(图 2)。

一笔交易的执行结果,取决于它之前所有交易留下的状态——区块里第 5 笔交易看到的余额,是前 4 笔执行完的余额。重放第 5 笔之前,先把前 4 笔逐笔执行到一个临时状态层(PreState)里,停在第 5 笔开始前。
然后在这份状态上,把这笔交易跑两遍。第一遍原样执行,记录完整过程:调用了哪些合约、转了哪些钱、改了哪些存储。第二遍换掉可操控的参数——发起人、收款人、金额——再执行,看结果有什么不同。两遍都不改这份状态,所以换不同参数可以反复试。
试完之后,把第一遍的结果写进状态层,继续处理下一笔交易。这样区块里的每一笔,都在它当年看到的那个世界上执行。
2.5 排除误报
重放产出的待审核记录以万计,人工逐条看完不可能,绝大多数在复核之前就被流水线筛掉了。图 1 的每个 ✕ 标记一类被筛除的原因,这里展开其中几种。
地址投毒批次 钓鱼者用假币、零值或极小额转账污染地址记录。扫描器会看到多个受害者、官方代币名称和极小额转账等相似形态,因此需要结合代币代码、金额和调用批次排除。

实例:0x184aaf087ca6dd17eb1200dbfbee9f671bd906fc。该家族在同轮扫描中产生数百条记录,包含几十个 helper,未发现官方稳定币的非零余额。
仿冒币名 假币攻击的升级——代币名字里混进西里尔字母或零宽字符,肉眼看是 USDT,字节上不是。一律标记。
USDT → 55 53 44 54 (ASCII)
USDТ → 55 53 44 d0a2 (末位是西里尔字母 Т,U+0422)
实例:0x9257cf49…f4253、0x6b1b80b4…1ca6cc,同一批地址投毒记录中转出的仿冒 USDT/USDC。
EIP-7702 重放限制 在这组 type-4 重放中,原交易由原发起人携带 authorizationList 执行;换成测试地址并去掉该列表后,交易回滚。这个结果只说明该重放依赖原授权状态,不能推广为所有 EIP-7702 交易都必须携带 authorizationList。扫描器保留这类记录,再用 caller_only 和 dual 两种变体区分真实可执行路径与重放条件导致的回滚。
原始交易 type-4,携带 authorizationList(账户本人签名)→ 执行成功
换人重放 同一 calldata,发起人换成测试地址,无授权列表 → 整笔 revert
使用相同字节码的账户组 多个地址运行同一段字节码、使用同一个发起人和归集地址,待审记录来自一组同特征账户。
// 一组账户的共同特征:字节码全部指向同一个委托目标
0xef0100 + 631af7731569e10b22a0430051cfd5c8930a1656 // 多个账户共用
发起人相同 · 归集地址相同
实例:0x001100bd…3ff16、0x043d01a9…669d,两个成员的字节码完全相同。
3.1 判定条件
扫描器筛出的漏洞记录按图 3 依次判定。条件一、二用于确认参数可控性和模拟执行是否成功;条件三只区分资金来源:付款方是授权者时标为授权滥用,资金来自公开流动性时标为公开套利。两类记录的后续复核不同,不能直接等同于真实漏洞。

3.2 AI 审核
扫描器产出的待审核记录由 AI 自动复核(图 4)。AI 读取原始交易的调用和资金流,分别做换发起人和换收款人重放。模拟成功只证明替换参数后交易可执行;资金来源、授权关系或调用链证据不足时,记录标为“证据不足,待人工复核”,不自动判为漏洞。

AI 复核结果回写到面板(图 5)。图 5 展示单条记录的五阶段证据链、标签、备注和模拟编号;它不展示全局标签计数。

4.1 已知攻击交易检测
Seneca Protocol Chamber 用于验证扫描器检出的漏洞:该事件有公开分析,相关调用模式也出现在扫描结果中。
Seneca Protocol 于 2024 年 2 月 28 日遭到攻击,损失达数百万美元。BlockSec、SlowMist 和 Rekt 分别分析了这起事件。受影响合约地址为 0x65c210c5…91f06,其 Chamber.sol 源码已在 Etherscan 验证。图 6、图 7 对应的 PT-weETH 重放交易为 0x6da8f7cb…e820d,调用目标为 clone 0xbc83f271…7c05。0x23fcf9d4…01286 是另一笔 PT-rsETH 交易,不与图混用。
漏洞代码如下,省略无关部分:

这与第 1.2 节的 MetaAggregatorSwapContract 属于同一类外部调用风险:调用者把 callee 和 callData 编入顶层 calldata,Chamber 解码后直接使用。在该历史状态中,Chamber 名下保留着用户为管理仓位授予的 ERC-20 授权,因此需要进一步核对实际 spender、受害者授权对象和资金路径,才能判定为授权转移漏洞。
扫描器重放这笔交易,用 call_tracer 记录完整调用树。在 Chamber 的内部调用帧中,它定位到 callee.call(callData),并确认 callee 和 callData 都能追溯到顶层 datas 参数。系统将它们登记为可操控点,再把顶层 calldata 中对应的收款人地址替换为测试地址,在同一历史状态上重放。交易执行成功,测试地址收到 PT-weETH;资金来源和调用链证据另行记录。
图 6 展示这笔 PT-weETH 重放交易的 Invocation Flow Diff,图 7 展示对应的 Fund Flow Diff;两图共同呈现替换收款人前后的调用和资金差异。


thirdweb Pay UniversalGatewayV1(2025 年,以太坊)——公开事件交叉验证
thirdweb 的支付网关 0x81d57dd01a15bc1c19563693902df621500d4d2a 也出现在扫描结果中。thirdweb 的公开报告确认该旧合约存在授权余额风险并已停用。
该历史实现没有可直接核对的验证源码。签名绑定、假代币地址和 callData 的具体关系,需要与对应区块的代理实现和交易 trace 对齐;当前参考源码不能替代历史实现作为结论依据。
该案例的历史调用包含代币地址、转发数据和授权余额之间的组合关系。当前材料不足以把“假代币地址绕过扣款”确定为该历史实现的根因;在补充对应区块的实现代码和完整 trace 前,不将其作为已确认机制。
5.1 扫描器检出的风险路径
以下案例的证据类型不同:部分来自历史交易重放,部分还有源码、审计或事故报告支持。每节只对已列出的证据作判断,并区分扫描结果和外部资料。
案例状态以所引交易的区块状态或明确的查询时间为准;余额、授权和合约是否仍可调用,不从历史交易直接推导当前状态。
一、Maker Multicall2:没有调用者白名单的批量执行器
0x5ba1e12693dc8f9c48aad8770482f4739beed696

target 和 callData 全部来自调用者,合约没有调用者白名单校验,但仍检查底层调用是否成功。它是通用批量执行器,风险取决于用户是否把代币授权给了这个执行器;本文样本中的授权和资金路径应按具体交易逐笔核对。
二、THORSwap TSAggregatorGeneric:目标限制不完整
0x0ccd5dd5bcf1af77dc358d1e2f06ee880ef63c3c

当前源码只排除了 TTP,没有把 swapRouter 限制到白名单,因此代币合约仍可作为目标。历史状态重放显示,调用者可以把 swapRouter 和 data 指向代币转账路径;是否构成授权滥用,还需同时核对 spender、授权人和资金来源。
三、Symbiosis MetaRouter:跨链路由的任意 calldata
0xf621fb08bbe51af70e7e0f4ea63496894166ff7f
metaRoute 从网关提取用户授权的代币,并检查 relayRecipient 不是网关且目标地址有代码。随后,源码直接使用调用者提供的 otherSideCalldata 调用 relayRecipient;这里没有按函数选择器建立白名单。
otherSideCalldata 的字节内容没有按函数选择器做白名单校验,目标地址只受网关地址和代码存在性检查约束。扫描器检出的交易中,该参数包含 USDT.transferFrom;如要认定为授权滥用,还需把交易中的 spender、授权人和资金接收方逐项列出。
四、LayerZero LZMultiCall:授权错位与无签名执行入口
0x5528cf58feb8fbfce94f43b33240fffb1312bde3

原始复核交易走的是两参数的无签名入口,不是带 _signature 的重载。该入口把 msg.sender 作为 signer;只有目标是 TransferDelegate 时,才检查 transfer 的 from 是否等于 signer。若调用列表直接把代币合约作为 target 并放入 token.transferFrom(受害者, 自己, 金额),代码会走通用的 _handleCall,由 LZMultiCall 作为 spender 发起转账。
LayerZero 的正常流程应把授权交给 TransferDelegate;如果用户把授权直接给了 LZMultiCall,公开的无签名 execute 就可能消耗这份额度。这里的风险来自授权对象与通用转发路径的错位,不涉及带签名入口。
五、thirdweb Pay UniversalGatewayV1:证据范围
0x81d57dd01a15bc1c19563693902df621500d4d2a
该案例已在 4.1 作为公开事件交叉验证。公开报告确认旧合约存在授权余额风险并已停用;历史实现的签名绑定、代币地址和 callData 的具体关系,需要与对应区块的代理实现和交易 trace 对齐。本文不使用当前 UniversalBridgeV1 参考源码替代历史实现下定论。
六、BSC BnbSettler:BASIC 任意调用消耗误授权
0x4c6f446dd88fd1be8b80d2940806002777dc12a2
这是 0x Protocol Settler 的 BSC 部署。风险入口由公开的 execute 和 BASIC action 组成,转账路径通过 action 进入代币合约:

用户若直接把 ERC-20 授权给 Settler,调用者就可以把 pool 填成代币合约,把 data 编成 token.transferFrom(受害者, 自己, 金额)。Settler 以自身身份发起调用,代币会按这份授权执行。历史重放还显示该合约存在可改变收款人的滞留余额;这两条路径应分开计。
表中按主要利用前提归类;“扫描检出的漏洞”“历史重放确认”和“公开事件交叉验证”不是同一个证据等级:

这些案例的证据类型不同:部分有历史交易重放,部分只有源码或公开报告。扫描器能直接验证的是:在对应历史状态下,改变顶层可控参数是否仍能把资金导向测试地址;其余结论需要单独标注来源和证据范围。
5.2 漏洞记录汇总
扫描器在以太坊和 BSC 上汇总了超过四百条漏洞记录,涉及三百多个地址。同一地址可能对应多条检测记录,地址中也包括 EIP-7702 委托账户。
以太坊的记录主要集中在 Maker Multicall2;BSC 的记录分布在更多地址,单个地址对应的记录较少。其他记录还涉及 CallDataExecutor、UniswapInterfaceMulticall、SquidMulticall 和 0x Settler 等合约。完整地址清单见附录 A、B。

5.3 Lynx:交易分析与模拟平台
Lynx 是我们自研的交易分析与模拟平台,支持以太坊和 BSC,接口与标准 JSON-RPC 兼容。本文的重放验证和资金流对比通过 Lynx 完成。平台支持基于归档节点历史状态的 eth_call 模拟,可替换发起人、修改 calldata 或覆盖账户余额。
Lynx 通过 debug_traceCall 及相应的 tracer 获取调用树,记录调用目标、calldata 和返回值,供扫描器追踪参数来源;界面同时展示存储变化。平台还分别生成原始交易和模拟交易的资金流图,对比资产的来源、去向和金额。第 4.1 节图 6、图 7 分别展示调用流程和资金流的差异,图 8 展示调用树界面。
每次模拟结果按唯一编号保存,可按编号复查。

结语
本文在 21 亿笔历史交易中扫描出超过一百个漏洞合约,涉及基础设施、跨链路由器、支付网关和工具合约等形态。其中一部分漏洞经历史状态重放确认;Seneca 和 thirdweb 提供了独立的公开事件作为交叉验证。
在已复现的案例中,授权额度在余额变化后仍可能存在,因此监控授权存量可以补充余额事件监控。这个结论不外推到所有案例。
防御建议:
用户:用完就撤销授权,或改用支持过期签名的授权方案(如 Permit2)。授权前确认 spender 对应的是协议预期的执行入口;如果协议通过代理或 Permit2 转发,还要核对实际 spender、目标合约和额度。
协议开发者:任意外部调用入口应限制目标和 selector/data;签名转发入口应把签名内容绑定到调用者和接收者。打补丁时审计所有调用路径;THORSwap 的历史补丁只排除了一个目标,其他目标仍需检查。
行业:可将授权存量纳入持续监控,记录 spender、授权人、区块状态和余额变化。本文展示的是历史重放;持续追踪不在本文范围。
附录 A:以太坊漏洞记录对应地址

附录 B:BSC 漏洞记录对应地址(部分)
https://upload.ruyigansu.com/2026/1026/1792987483467.png
版权声明:本报告及相关技术分析由GCSA全球网络安全联盟独家发布。如需转载请完整保留GCSA官方出处及原始链接,并不得对报告核心观点进行恶意篡改。
来源:https://media.gcsa.com/news-3730122
原创文章,作者:新智派,如若转载,请注明出处:https://knewsmart.com/archives/350636
微信扫一扫