Appearance
深入剖析Ethernaut DoubleEntryPoint:巧用Forta守护加密金库
在Web3世界的安全挑战中,OpenZeppelin的Ethernaut系列题目一直是智能合约学习者和安全研究员的试金石。今天,我们将深入探讨其中一个巧妙的关卡——DoubleEntryPoint。这个挑战不仅考验你对Solidity合约交互的理解,更引入了去中心化监控网络Forta的概念,教你如何利用它来保护你的数字资产金库。
挑战背景:CryptoVault与它的代币
想象你有一个安全的CryptoVault(加密金库)合约,它持有两种不同的代币:
DoubleEntryPointToken(DET):这是金库的核心“底层代币”,对金库的运作至关重要,绝不能被扫走。LegacyToken(LGT):这是另一种代币,用于其他用途。金库有一个sweepToken函数,旨在清理合约中意外滞留的任何非底层代币,并将它们安全地发送到指定地址。
规则很明确:你可以通过sweepToken函数清理LGT,但绝不能触碰DET。金库最初持有100单位的DET和100单位的LGT。我们的任务是找出CryptoVault中的潜在漏洞,并利用Forta网络防止金库中的代币被盗取。
“双重入口”的秘密:LegacyToken的委托机制
问题的关键在于LegacyToken的特殊设计。LGT合约自身继承了ERC20标准,但它有一个巧妙的后门:delegateToNewContract函数。这个函数允许LGT将其transfer(转账)功能的执行权委托给另一个合约。
在本例中,LGT的transfer可以被设置为委托给DoubleEntryPointToken(DET)合约的delegateTransfer函数。这就像LGT成为了一个入口,但实际的转账操作却由DET来完成,故称之为“双重入口”。
DET合约的delegateTransfer函数具有两个重要修饰符:
onlyDelegateFrom:确保只有LegacyToken合约才能调用此函数。fortaNotify:这是我们防御的关键。
Forta网络:链上警报系统
DoubleEntryPoint挑战引入了Forta网络。Forta是一个去中心化的、社区驱动的监控网络,用于检测Web3系统中的威胁和异常。DoubleEntryPointToken合约通过fortaNotify修饰符集成了Forta的通知机制:
当DET的delegateTransfer函数被调用时,fortaNotify修饰符会首先执行以下操作:
- 通知Forta:它会调用
Forta合约的notify函数,进而触发我们注册的DetectionBot合约的handleTransaction函数。 - 检查警报:在完成原始交易逻辑(即实际的
_transfer操作)之后,fortaNotify会检查Forta网络中是否有我们的机器人发出警报(raiseAlert)。 - 回滚交易:如果我们的机器人发出了警报,
DET合约将立即回滚整个交易,并抛出"Alert has been triggered, reverting"的错误。
潜在的漏洞与我们的防御策略
这里的漏洞在于:CryptoVault的sweepToken函数在执行时,仅仅检查了传入的token参数是否不等于underlying(即DET)。由于LGT合约的地址与DET合约的地址不同,sweepToken(LGT)的检查将顺利通过。金库会自信地调用LGT.transfer(),期望清理掉LGT。
然而,如果此时LGT的转账已经被恶意地或被事先设置为委托给了DET,那么这次“清理”LGT的操作,实际上会触发DET的delegateTransfer函数及其内含的fortaNotify安全网。如果无人监控,即使攻击者无法直接扫走DET,他们也能通过此路径导致一些意想不到的行为或绕过其他保护机制。
我们的任务就是利用这个强大的Forta安全网,防止金库被意外清空。
我们的解决方案:Forta检测机器人
我们的任务就是编写一个智能的DetectionBot,并将其注册到Forta网络中。这个机器人需要做的非常简单,但却极其有效:一旦handleTransaction函数被调用(这意味着有交易经过DET的delegateTransfer并触发了fortaNotify),它就立即向Forta报告一个警报(raiseAlert)。
以下是我们的Hack.sol机器人的代码:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./DoubleEntryPoint.sol"; // 导入Forta接口
contract Hack is IDetectionBot {
IForta private immutable target;
// 构造函数:记录Forta合约的地址
constructor(address _target) {
target = IForta(_target);
}
// handleTransaction 函数:当Forta通知时被调用
function handleTransaction(address user, bytes calldata msgData) external {
// 确保是Forta合约调用,增加安全性
require(msg.sender == address(target), "Unauthorized");
// 立即向Forta网络发出警报!
target.raiseAlert(user);
// msgData 可以用于分析交易数据,本例中不作具体分析
msgData;
}
}
当我们的机器人成功注册并被DET的fortaNotify修饰符触发时,它会立刻发出警报。由于DET合约在fortaNotify修饰符中加入了if (forta.botRaisedAlerts(detectionBot) > previousValue) revert("Alert has been triggered, reverting");的逻辑,任何试图通过LGT委托转账来触发DET的交易,都将被强制回滚。
这样一来,即使CryptoVault尝试sweepToken(LGT),在LGT转账被委托给DET的情况下,交易也会因为我们的Forta机器人发出警报而被回滚,从而保护了金库,使得任何形式的意外扫荡都无法成功。
演示与结果
在提供的测试用例98_test_double_entry_point.ts中,我们可以清晰地看到整个防御过程:
- 我们首先部署了
Hack合约,获得了我们的检测机器人。 - 然后,我们将这个机器人的地址注册到Forta合约中(
fortaContract.setDetectionBot(HACK_ADDRESS))。 - 最后,当测试尝试调用
cryptoVaultContract.sweepToken(legacyTokenAddr)时,它会如预期般地失败,并抛出"Alert has been triggered, reverting"的错误。 - 验证金库中
DoubleEntryPointToken的余额仍然大于零,表明核心资产被成功保护。
总结
DoubleEntryPoint挑战精妙地展示了在复杂智能合约交互中潜在的安全风险,以及如何利用去中心化监控网络Forta作为一道额外的防线。这不仅仅是一个简单的漏洞修复,更是一种对合约设计模式、委托调用机制以及链上监控工具的深刻理解。它提醒我们,在设计和部署智能合约时,必须考虑所有可能的交互路径,并为它们构建多层防御,确保数字资产的安全。