ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

WTF-Solidity 链上安全实战:用 Foundry 从零撰写 DFX Finance 重入攻击 PoC

WTF-Solidity 链上安全实战:用 Foundry 从零撰写 DFX Finance 重入攻击 PoC WTF-Solidity 链上安全实战用 Foundry 从零撰写 DFX Finance 重入攻击 PoC【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity本篇技术指南是 WTF-Solidity 仓库《OnChain Transaction Debugging》链上交易调试系列第五讲的完整展开英文版由 gbaleeee 撰写、Spark 翻译。文章以 2022 年 11 月 DFX Finance 的dfx-xidr-v2池被攻击的真实事件为主线带你从 Etherscan 交易概览、Phalcon 调用轨迹、MetaSleuth 资金流逐层拆解攻击过程最终使用 Foundry 测试框架手写一份可复现攻击的 PoC 合约。读完本文你将掌握一条完整的事件分析 → 漏洞定位 → PoC 复现 → 资金流验证方法论并深入理解跨函数重入Cross-Function Reentrancy这一高频攻击模式。前置知识撰写 PoC 之前需要具备什么原文档明确提出了两点前置要求了解常见智能合约漏洞样态可以通过 DeFiVulnLabs 之类的靶场练习入门了解 DeFi 基础模型如何运作以及智能合约与智能合约之间如何交互。在本仓库中恰好有一套配套的学习路径本系列前四讲分别讲解了链上分析工具01_tools、交易与合约交互热身02_warmup、为什么要写 Reproduce PoC 以及价格预言机基础03_write_your_own_poc、MEV Bot 攻击的 PoC 撰写04_write_your_own_poc可以作为本讲的前置热身材料。重入攻击概念与三种类型重入攻击是区块链世界中最广为流传的一种攻击手法。对攻击模式做一个简单概括当一个函数对另一个不受信任的合约进行外部调用时重入攻击就有可能发生。如果被调用的外部合约在回调中再次进入调用方尚未完成状态更新的函数就可能造成状态错乱、资产被重复支取。目前重入攻击主要可以分为三种类型单函数重入Single Function Reentrancy在同一个函数内部完成重入。仓库 S01_ReentrancyAttack 中的经典Bank/Attack例子即是代表——Bank.withdraw()先转账ETH再清零余额攻击合约在receive()回调里反复调用withdraw()直到把银行提空对应源码见 ReentrancyAttack.sol跨函数重入Cross-Function Reentrancy从函数 A 切入重入到同一合约中没有保护的函数 B。DFX Finance 事件正是这一类型后文详述跨合约重入Cross-Contract Reentrancy重入到另一个合约甚至利用只读重入Read-Only Reentrancy攻击依赖受害合约状态做决策的第三方协议。仓库 S17_CrossReentrancy 给出了跨函数、跨合约、跨项目三类进阶案例及其对应的全局重入锁防御思路。值得注意的是一旦函数中存在外部调用重入的触发点并不局限于ETH转账ERC721/ERC1155的safeTransfer()/safeTransferFrom()以及ERC777的tokensReceived回调同样会交出执行权因此重入更多是一个宏观上的设计问题而不只是转账方式问题。事件背景DFX Finance 闪电贷重入事件事件概览2022 年 11 月 11 日安全公司 Peckshield 发布公开告警DFX Finance 的 DEX 池名为 Curve合约代号dfx-xidr-v2因缺乏合理的重入保护被攻击损失约 3000 ETH约合 400 万美元被盗资金随后被转入 Tornado Cash。交易概览在区块链浏览器中查看攻击交易能获得的信息是有限的主要包括交易的发起者sender即攻击者、被调用的合约、代币转移过程中发出的事件等。但有两个细节值得注意这笔交易被打上了MEV Transaction和Flashbots标签说明攻击者刻意绕开公开 mempool避免自己的攻击交易被 front-run 机器人抢跑攻击者的操作并非一笔简单的转账而是一系列合约调用的组合需要借助专业工具才能看清全貌。余额分析Balance Changes使用 BlockSec 的交易分析工具 Phalcon 深入分析后在Balance Changes一栏可以看到这笔交易所带来的资金变化标记为 receiver 的攻击合约收获了大量的USDC、XIDR代币名为dfx-xidr-v2的合约损失了大量USDC、XIDR代币地址以0x27e8开头的地址也收获了一些USDC、XIDR代币经查证该地址是DFX Finance 治理多签钱包。结合经验可以判断受害者是 DFX Finance 的dfx-xidr-v2合约损失资产为USDC与XIDR多签钱包在攻击过程中收到代币通常对应合约交互流程中的手续费收取逻辑。资金流分析Asset Stream进一步使用 BlockSec 的另一款资金流分析工具 MetaSleuth 观察代币转移情况可以看到完整的资金走向攻击者exploiter先从受害合约中借出大量USDC、XIDR代币攻击者随后将USDC、XIDR代币发送回受害合约名为dfx-xidr-v2的曲线代币从零地址被铸造给攻击者DFX Finance 多签钱包收到USDC、XIDR代币即手续费攻击者的dfx-xidr-v2代币被销毁发送回零地址。这五步资金流向可以在接下来的调用轨迹Call Trace分析中得到验证。调用轨迹分析还原攻击的函数执行链将交易在 Phalcon 中展开级别设为 2可以观察到完整的函数调用流程攻击者调用攻击合约中函数选择器哈希为0xb727281f的函数攻击流程由此开始通过staticcall调用dfx-xidr-v2合约中的viewDeposit函数通过call调用dfx-xidr-v2合约中的flash函数。值得注意的是在这个调用内部完成了一次对攻击合约的回调对应函数选择器哈希为0xc3924ed6最后通过call调用dfx-xidr-v2合约中的withdraw函数。这条调用链清晰地呈现出攻击者的操作顺序先读取、再闪电贷、最后提现。其中最关键的是第 3 步内嵌的回调——这正是重入发生的位置。函数级拆解漏洞是如何被一步步利用的viewDeposit为铸造曲线代币做准备攻击者第一步调用viewDeposit函数的意图可以从该函数的代码实现与注释中找到攻击者希望获得铸造 200_000 * 1e18 个dfx-xidr-v2曲线代币所需要的两种底层代币USDC、XIDR的数量。在下一步攻击者调用flash函数的参数中可以看到攻击者把viewDeposit的返回值作为flash输入参数的近似值数值不完全一致的原因在分析手续费机制后就会清楚。flash类似 Uniswap V2 的闪电贷从flash函数的代码实现可以看出这是一个与 Uniswap V2 闪电贷非常相似的功能用户可以通过该函数从合约中借出资产同时合约会向调用者发起一次回调对应代码如下IFlashCallback(msg.sender).flashCallback(fee0, fee1, data);这处外部调用正好对应此前调用轨迹中对攻击合约的回调。对该回调函数签名做 4 Bytes Hash 验证结果正是0xc3924ed6。withdraw销毁曲线代币、取回配对资产最后一步调用的withdraw函数根据代码实现与注释可知其作用是销毁稳定币dfx-xidr-v2曲线代币并取回对应的两种底层代币USDC、XIDR。回调中的 deposit绕过校验的关键此时一个关键疑问浮现攻击者明明只是做了一笔闪电贷凭什么能调用withdraw把合约资产取走答案就藏在闪电贷的执行过程中——攻击者唯一能自由操作的地方就是回调函数。展开回调内部可以看到攻击者在回调中调用了受害合约的deposit函数。deposit的职责是接收池子支持的两种资产USDC、XIDR并铸造对应的曲线代币。结合transferFrom调用可知攻击者正是把USDC、XIDR代币发送给了受害合约。而flash函数对闪电贷是否完成的判定是通过检查回调执行前后合约中对应代币的余额关系来实现的判定代码为require(balance0Before.add(fee0) balance0After, Curve/insufficient-token0-returned); require(balance1Before.add(fee1) balance1After, Curve/insufficient-token1-returned);deposit执行流程中向合约转入USDC、XIDR的操作恰好满足了这一余额校验。这里有一个细节为了满足flash函数对手续费的要求攻击者存入的USDC、XIDR数量略高于从flash中借出的数量——多出的部分会在flash函数后续执行中作为手续费发送给 DFX Finance 多签钱包。也就是说攻击者在发起攻击前预先准备了一些USDC、XIDR作为手续费资金通过deposit发送给受害合约的总数量 闪电贷借出的数量 手续费这样既完成了deposit又通过了flash函数中的余额校验。于是攻击者通过在闪电贷回调中执行deposit达成了两个效果其一满足了闪电贷的还款校验其二在受害合约中留下了存款记录相当于用借来的钱完成了存款使后续withdraw有了合法依据。提现时受害合约按存款记录全额给付最终池子净损失了借出的本金。手把手撰写 PoCPoC 骨架基于以上分析可以先把 PoC 的主要框架写出来contract EXP { uint256 amount; function testExploit() public{ uint[] memory XIDR_USDC new uint[](2); XIDR_USDC[0] 0; XIDR_USDC[1] 0; ( , XIDR_USDC) dfx.viewDeposit(200_000 * 1e18); dfx.flash(address(this), XIDR_USDC[0] * 995 / 1000, XIDR_USDC[1] * 995 / 1000, new bytes(1)); // 5% fee dfx.withdraw(amount, block.timestamp 60); } function flashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external{ /* xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx */ } }其中995 / 1000意味着借出数量约为viewDeposit返回值的 99.5%差额部分即为覆盖闪电贷手续费的资金该部分最终流向 DFX 多签钱包。在回调函数内部完成重入即完成整个攻击闭环。完整 PoC把回调函数补全后得到完整的 PoCcontract EXP { uint256 amount; function testExploit() public{ uint[] memory XIDR_USDC new uint[](2); XIDR_USDC[0] 0; XIDR_USDC[1] 0; ( , XIDR_USDC) dfx.viewDeposit(200_000 * 1e18); dfx.flash(address(this), XIDR_USDC[0] * 995 / 1000, XIDR_USDC[1] * 995 / 1000, new bytes(1)); // 5% fee dfx.withdraw(amount, block.timestamp 60); } function flashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external{ (amount, ) dfx.deposit(200_000 * 1e18, block.timestamp 60); } }更完整、可直接运行的代码库可参考 DeFiHackLabs 仓库中的DFX_exp.sol测试用例。攻击流程总结提前准备一些USDC、XIDR代币调用viewDeposit()获取后续deposit()操作所需的代币数量根据第 2 步的返回值调用受害合约的flash()借出USDC、XIDR在flash()的回调中调用受害合约的deposit()将USDC、XIDR发送回受害合约完成重入由于第 4 步留下了存款记录直接调用受害合约的withdraw()取走代币。在本仓库中运行与验证本仓库根目录的 foundry.toml 已配置好统一的 Foundry 环境默认 profile 使用solc 0.8.34src src、test test并通过 remappings 将forge-std与openzeppelin/contracts指向lib/下的依赖。你可以基于这套配置运行forge test执行测试同时如同本系列 04 讲所介绍的也可以使用cast run txid --quick --rpc-url rpc配合 RPC 节点重放链上交易、打印函数调用轨迹作为对 PoC 行为的对照验证。用链上事件验证资金流写完成 PoC 后还可以回到攻击交易本身用代币事件对之前的资金流图做交叉验证deposit函数执行过程中最后发出的事件验证了dfx-xidr-v2曲线代币从零地址铸造给攻击者flash函数执行过程中的USDC、XIDR转移事件闪电贷手续费收取对应 DFX Finance 多签钱包收到少量USDC、XIDR代币withdraw函数执行过程中最后发出的事件对应dfx-xidr-v2曲线代币发送到零地址销毁。三条事件链与 MetaSleuth 资金流分析完全吻合证明了前述对攻击机制的理解是正确的。总结典型的跨函数重入DFX Finance 重入攻击是一起典型的跨函数重入Cross-Function Reentrancy事件攻击者从flash函数切入在闪电贷回调中调用了同一合约内没有重入保护的deposit函数完成重入留下存款记录后顺利提现。它的本质与仓库 S17 跨函数重入案例一致——漏洞根源是状态变量在外部调用完成前未先行更新同时相关函数缺乏统一的防护。值得强调的是这次攻击的手法恰好对应 CTF 靶场 damnvulnerabledefi 的第四题Side Entrance。如果项目开发者此前认真研究过这道题或许这次攻击就不会发生。而在同年 12 月Defrost 项目也因类似问题被攻击。作为防御启示可以参考仓库 S01_ReentrancyAttack 中的两条基本防线检查-影响-交互模式CEI先更新状态变量再与外部合约交互从根本上消除重入窗口重入锁nonReentrant用状态变量标记函数执行状态重入调用直接 revert。同时结合 S17 进阶案例 的教训简单的工具永远不会是完美防御——跨函数重入要求所有改变状态的函数统一加锁必要时使用全局重入锁跨合约/只读重入则要求把状态更新的顺序与可见性纳入整体设计在构造过程中的每一步都布置多道不同的防御机制。延伸学习仓库内的配套资源链上分析工具选型与使用Phalcon、Ethtx、Tenderly、4byte 签名库等见 01_toolsFoundry 环境搭建与基础交易分析见 02_warmup撰写 Reproduce PoC 的意义与价格预言机基础见 03_write_your_own_poc未开源合约MEV Bot的 PoC 撰写与cast run用法见 04_write_your_own_poc重入攻击的经典概念与 CEI / 重入锁防御代码S01_ReentrancyAttack、ReentrancyAttack.sol跨函数 / 跨合约 / 跨项目重入的进阶案例与全局重入锁S17_CrossReentrancy。此外原文档还推荐了若干外部学习材料包括 Consensys 智能合约最佳实践中关于 Reentrancy 的章节、Reentrancy Attacks on Smart Contracts Distilled、C.R.E.A.M. Finance AMP 漏洞事后复盘、Cross-Contract Reentrancy Attack 分析、Sherlock Yield Strategy 漏洞赏金事后复盘以及 QuillAudits 关于只读重入漏洞的解读可作为深入学习重入攻击的延伸阅读清单。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表