赏金踩坑实录:因“非标代币不在白名单”被拒收的白链桥假充值漏洞分析
本文分析了 Whitechain 桥接合约中因忽略 transferFrom 返回值且缺失余额差值校验导致的假充值漏洞。攻击者可利用静默失败的非标准代币零成本伪造跨链存款事件,掏空目标链流动性池。文章同时复盘了该漏洞因非标准代币不在白名单范围内而被赏金平台拒收的经验。
前言(踩坑经验分享)
最近在对白链(Whitechain)的桥接协议(bridge-contracts)进行代码审计时,我发现了一个逻辑非常严密的 Critical 级别漏洞(假充值漏洞)。理论上,攻击者可以利用该漏洞以零成本掏空目标链的流动性池。
我用 Foundry 编写了完美的 PoC 并在本地跑通了全流程,随后提交至 HackenProof 平台。但遗憾的是,这份报告最终被官方以“Out of Scope(超出审计范围)”为由关闭了。
这次“踩坑”经历非常典型。在 Web3 赏金平台,除了比拼技术实力,读懂项目方的“威胁模型(Threat Model)”同样重要。借此机会,我写下这篇复盘文章,一来分享漏洞挖掘的技术细节与 Foundry PoC,二来总结一下在 Bug Bounty 挖掘中,如何让自己的报告更契合平台的规则,希望能给各位提供一些参考经验。
一、 漏洞详情与代码分析
该漏洞存在于目标项目的 Bridge.sol 合约中,具体位于存款(Deposit)的流程内。这个攻击之所以能够成立,是因为合约中同时存在两个致命的设计缺陷,它们组合在一起形成了一条完美的攻击链。
漏洞点一:绕过 SafeERC20 且忽略返回值
在桥接合约的 _executeTokenTransferFrom 函数中,处理代币转账的逻辑存在严重问题。当函数参数 useTransfer 为 true 时,合约显式地绕过了引入的安全库 SafeERC20Upgradeable,而是直接使用了底层的原生调用进行代币转移,伪代码如下:
IERC20Upgradeable(token).transferFrom(from, to, amount);
致命点在于:这个原生调用完全忽略了布尔返回值。在以太坊的 ERC-20 标准中,transferFrom 成功通常返回 true,失败返回 false。由于合约没有使用 require() 去捕获这个返回值(即没有检查是否转账成功),一旦遇到转账失败(返回 false)或只转账了部分金额(Fee-on-transfer 扣税代币)的情况,合约的执行不仅不会回滚(revert),还会继续往下走,仿佛什么都没发生一样。漏洞点二:缺失余额差值校验(盲目信任用户输入)
紧接着,在上层的 bridgeTokens 函数中,用户发起跨链存款请求。逻辑是:先调用刚刚提到的 _executeTokenTransferFrom 尝试把钱转进桥接合约,然后根据用户传入的 amount 参数,发出一个 Deposit 跨链事件,通知目标链的中继器(Relayer)给用户放款。
致命点在于:合约在调用转账之后,没有去计算实际收到多少代币(例如使用 balanceAfter - balanceBefore 来校验)。它直接盲目地信任了用户输入的 amount 参数来记账和发事件。也就是说,只要代码不报错,不管你实际转没转进来钱,合约都以为你转进来了。完整的攻击链路
结合上述两个漏洞点,攻击者可以实施极具破坏性的跨链假充值攻击:攻击者部署或利用一个非标准代币(例如转账永远静默失败返回 false 的代币,或者带有高额转账手续费的代币)。
攻击者调用桥接合约的 bridgeTokens,声称要存入 1,000,000 枚该代币。
桥接合约执行转账,由于代币是静默失败的,桥接合约实际上收到了 0 枚代币。
但因为【漏洞点一】,忽略返回值,代码继续执行;因为【漏洞点二】,合约没有校验余额,直接根据入参发出了 1,000,000 枚代币的跨链存款事件。
目标链的 Relayer 监听并接收到事件后,将释放 1,000,000 枚真实代币给攻击者。攻击者完成零成本套现,导致目标链流动性池被直接抽干。
现实世界中的可利用性
这不是一个仅仅依靠“管理员恶意注册假币”才能触发的边缘场景,它实质上是一个代币兼容性问题,会影响主流的合法代币:
• 静默失败代币:例如老版本的 USDT,在某些极端网络或合约状态下,它失败时是返回 false 而不是直接回滚的。这会导致一笔失败的存款被系统误认为成功。
• 转账扣费代币(Fee-on-Transfer):如果某个代币设置了 10% 的交易税,用户尝试跨链存入 1000 枚,实际上只有 900 枚到达桥接合约。但由于【漏洞点二】缺失了余额校验,桥接合约依然会给用户记上 1000 枚的跨链额度。这意味着跨链池子里的流动性每被操作一次,就会流失 10% 的差额。
二、 PoC 复现(Foundry 实战)
为了验证漏洞的真实性,我使用 Foundry 编写了 PoC,模拟了一个 FalseToken(静默失败代币)以及极简的桥接逻辑。核心合约代码
// 模拟真实世界中有缺陷的代币(静默失败:不报错,只返回 false)
contract FalseToken {
mapping(address => uint256) public balanceOf;// 核心作弊逻辑:转账时永远失败,返回 false,也不扣钱
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
return false;
}
}
// 模拟白链桥漏洞的极简靶机
contract BridgeTarget {
function bridgeTokens(address token, uint256 amount) external {
// 直接调用 transferFrom,并且【不检查返回值】,也不检查余额差值
(bool success, ) = token.call(
abi.encodeWithSignature("transferFrom(address,address,uint256)", msg.sender, address(this), amount)
);
// 致命漏洞:这里没有 require(success),也没有 balanceAfter - balanceBefore 校验!
// 合约以为存款成功,直接发出跨链事件
emit DepositSuccess(msg.sender, token, amount);
}
event DepositSuccess(address indexed user, address indexed token, uint256 amount);
}
2. 攻击测试脚本
import {Test, console} from "forge-std/Test.sol";
import {FalseToken, BridgeTarget} from "src/FakeDeposit.sol";
contract FakeDepositTest is Test {
FalseToken public fakeToken;
BridgeTarget public bridge;
address public attacker;
function setUp() public {
fakeToken = new FalseToken();
bridge = new BridgeTarget();
attacker = makeAddr("attacker");
}
function testFakeDepositAttack() public {
console.log("--- Start Fake Deposit Attack ---");
uint256 targetAmount = 1_000_000 ether;
vm.prank(attacker); // 攻击者假装存入 100 万个代币
bridge.bridgeTokens(address(fakeToken), targetAmount);
console.log("Attacker successfully faked a deposit of:", targetAmount);
console.log(">>> [SUCCESS] Fake Deposit Attack executed! Zero cost! <<<");
}
}
3. 运行结果
执行 forge test -vv,测试完美通过(1 passing)。终端输出了 >>> [SUCCESS] Fake Deposit Attack executed! Zero cost! <<<,证明攻击者以零成本伪造了跨链存款事件。


三、 经验总结:官方拒收理由与规则解读

虽然 PoC 完美跑通,但最终 HackenProof 审核员(@HP-Triage0x00)将报告关闭。以下是官方的拒收原话:
“所描述的攻击路径——利用用户提供的代币 amount 与实际转移的代币之间的差异——需要使用非标准的 ERC-20 代币(例如,转账手续费代币或 false 失败的旧版代币)。非标准 ERC-20 代币和转账手续费代币已被明确列为本计划的审查范围之外。
该桥接器的记账模型是基于已注册代币映射的标准 ERC-20 行为设计的,并且映射器管理员控制哪些代币地址可以被接受。由于没有针对标准 ERC-20 代币的已验证攻击路径,因此该发现不符合本计划的审查范围资格标准。因此,此问题因超出审查范围而关闭。”
吸收经验与复盘思考:
项目方的防御边界是“白名单”:平台明确说明了,桥接的记账模型依赖管理员映射注册。在他们的设计理念中,“非标准代币”进不来,因此不需要防范此类假充值。
赏金规则的优先级:在 Bug Bounty 中,明确写在 Scope(范围)里的排除项(如非标准 ERC-20、Fee-on-transfer)就是铁律。无论代码层面是否存在逻辑缺陷,只要触发条件属于被排除的范围,平台就有权拒收。
关于代币兼容性的探讨:从技术角度来说,如果未来白名单里接入了可升级代币(Proxy),或者代币逻辑发生改变,这种缺失 balanceAfter - balanceBefore 校验的写法依然是一个隐患。但就本次提交而言,不符合当前的赏金规则。
四、 给同行的避坑建议先看 Scope,再挖洞:下次遇到跨链桥或 DeFi 项目,在撰写“假充值/非标准代币”漏洞报告前,务必先全文检索文档中的 standard ERC-20、fee-on-transfer、out of scope 等关键词。如果明确排除了,直接放弃,避免做无用功。
聚焦核心逻辑漏洞:把精力放在协议自身的核心逻辑上(如权限校验、重入、签名重放、跨链重放等),这些是项目方无法用“白名单”或“外部条件”轻易搪塞过去的。