Ethernaut_WP(1-5)
字数 1887 2025-08-29 22:41:24

Ethernaut 1-5 关卡详细解析与教学文档

第一关:Fallback 合约攻击

合约分析

目标:获得 Fallback 合约的所有权,并将其余额减少到 0。

合约中有两个关键函数涉及 owner 分配:

  1. contribute() - 需要贡献 1000 ether 才能成为 owner(不现实)
  2. receive()fallback() - 只要 msg.value > 0 且已有贡献即可成为 owner

攻击原理

  1. 首先调用 contribute() 贡献少量 ETH(大于 0)
  2. 然后发送一笔交易:
    • 设置 value > 0
    • calldata 为空(触发 receive 或 fallback 函数)
  3. 成为 owner 后调用 withdraw() 清空合约余额

关键技术点

  • receive 函数receive() external payable,无参数,无返回值,必须 external 和 payable
  • fallback 函数fallback() external payable,当没有匹配函数且无 receive 时触发
  • 在 calldata 为空时,receive 和 fallback 功能等价

修复建议

  1. 移除 receive() 逻辑或在其中添加 onlyOwner 限制
  2. 严格限制 owner 更换条件
  3. 在 receive() 函数中加入额外验证

第二关:Fallout 合约攻击

合约分析

  • 合约名:Fallout
  • 构造函数名错误:Fal1out(拼写错误)
  • 在 Solidity 0.4.22 之前,构造函数必须与合约同名

攻击原理

  1. 由于构造函数名错误,合约部署时未初始化
  2. Fal1out() 被视为普通函数,可被多次调用
  3. 调用该函数即可成为 owner

修复建议

  1. 使用 constructor 关键字代替旧式构造函数
  2. 仔细检查代码拼写

第三关:Coin Flip 预测攻击

合约分析

目标:连续猜对 10 次硬币正反面。

随机数生成逻辑:

uint256 blockValue = uint256(blockhash(block.number.sub(1)));
_guess = blockValue.div(FACTOR); // FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968

攻击原理

  1. 区块链上所有数据公开,包括前一个区块的哈希
  2. 攻击合约可以计算与目标合约相同的随机数
  3. 攻击合约预先计算并提交正确结果

攻击代码示例

function attack() public {
    uint256 blockValue = uint256(blockhash(block.number - 1));
    uint256 coinFlip = blockValue / FACTOR;
    bool side = coinFlip == 1;
    target.flip(side);
}

修复建议

  1. 使用更复杂的随机数生成:
    uint256 randomValue = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, block.difficulty)));
    
  2. 使用 Chainlink VRF 提供真正随机数

第四关:Telephone 合约攻击

合约分析

关键验证:

require(tx.origin != msg.sender);

概念区分

  • tx.origin:原始交易发送者(完整调用链的起点)
  • msg.sender:当前调用的直接发送者

攻击原理

  1. 用户通过攻击合约调用目标合约
  2. tx.origin 是用户地址
  3. msg.sender 是攻击合约地址
  4. 满足 tx.origin != msg.sender 条件

攻击合约示例

function attack() public {
    telephone.changeOwner(msg.sender);
}

修复建议

  1. 避免使用 tx.origin,改用 msg.sender
  2. 使用 OpenZeppelin 的 Ownable 修饰符

第五关:Token 合约攻击

合约分析

漏洞代码:

require(balances[msg.sender] - _value >= 0);

攻击原理

  1. Solidity 无符号整数下溢问题
  2. balances[msg.sender] < _value 时:
    • balances[msg.sender] - _value 会下溢变成极大值
    • 仍然满足 >= 0 条件
  3. 攻击者可转移超过自己余额的代币

攻击方法

  1. 初始余额为 20
  2. 转移 21 代币(导致下溢)
  3. 攻击者余额变为极大值

修复建议

  1. 修改 require 语句:
    require(balances[msg.sender] >= _value, "Insufficient balance");
    
  2. 使用 Solidity 0.8.0+(内置溢出检查)
  3. 导入 OpenZeppelin 的 SafeMath 库

总结

关卡 漏洞类型 关键技术点 修复方案
1 不当权限控制 receive/fallback 函数权限问题 添加权限验证
2 构造函数错误 旧版 Solidity 构造函数命名规则 使用 constructor 关键字
3 伪随机数预测 区块链数据公开性 使用更安全的随机数生成
4 tx.origin 误用 tx.origin 与 msg.sender 区别 改用 msg.sender
5 整数下溢 无符号整数运算特性 使用 SafeMath 或新版 Solidity

这些关卡涵盖了智能合约开发中常见的安全问题,开发者应深入理解这些漏洞原理并在实际开发中避免类似错误。

Ethernaut 1-5 关卡详细解析与教学文档 第一关:Fallback 合约攻击 合约分析 目标:获得 Fallback 合约的所有权,并将其余额减少到 0。 合约中有两个关键函数涉及 owner 分配: contribute() - 需要贡献 1000 ether 才能成为 owner(不现实) receive() 或 fallback() - 只要 msg.value > 0 且已有贡献即可成为 owner 攻击原理 首先调用 contribute() 贡献少量 ETH(大于 0) 然后发送一笔交易: 设置 value > 0 calldata 为空(触发 receive 或 fallback 函数) 成为 owner 后调用 withdraw() 清空合约余额 关键技术点 receive 函数 : receive() external payable ,无参数,无返回值,必须 external 和 payable fallback 函数 : fallback() external payable ,当没有匹配函数且无 receive 时触发 在 calldata 为空时,receive 和 fallback 功能等价 修复建议 移除 receive() 逻辑或在其中添加 onlyOwner 限制 严格限制 owner 更换条件 在 receive() 函数中加入额外验证 第二关:Fallout 合约攻击 合约分析 合约名:Fallout 构造函数名错误:Fal1out(拼写错误) 在 Solidity 0.4.22 之前,构造函数必须与合约同名 攻击原理 由于构造函数名错误,合约部署时未初始化 Fal1out() 被视为普通函数,可被多次调用 调用该函数即可成为 owner 修复建议 使用 constructor 关键字代替旧式构造函数 仔细检查代码拼写 第三关:Coin Flip 预测攻击 合约分析 目标:连续猜对 10 次硬币正反面。 随机数生成逻辑: 攻击原理 区块链上所有数据公开,包括前一个区块的哈希 攻击合约可以计算与目标合约相同的随机数 攻击合约预先计算并提交正确结果 攻击代码示例 修复建议 使用更复杂的随机数生成: 使用 Chainlink VRF 提供真正随机数 第四关:Telephone 合约攻击 合约分析 关键验证: 概念区分 tx.origin :原始交易发送者(完整调用链的起点) msg.sender :当前调用的直接发送者 攻击原理 用户通过攻击合约调用目标合约 tx.origin 是用户地址 msg.sender 是攻击合约地址 满足 tx.origin != msg.sender 条件 攻击合约示例 修复建议 避免使用 tx.origin ,改用 msg.sender 使用 OpenZeppelin 的 Ownable 修饰符 第五关:Token 合约攻击 合约分析 漏洞代码: 攻击原理 Solidity 无符号整数下溢问题 当 balances[msg.sender] < _value 时: balances[msg.sender] - _value 会下溢变成极大值 仍然满足 >= 0 条件 攻击者可转移超过自己余额的代币 攻击方法 初始余额为 20 转移 21 代币(导致下溢) 攻击者余额变为极大值 修复建议 修改 require 语句: 使用 Solidity 0.8.0+(内置溢出检查) 导入 OpenZeppelin 的 SafeMath 库 总结 | 关卡 | 漏洞类型 | 关键技术点 | 修复方案 | |------|---------|-----------|---------| | 1 | 不当权限控制 | receive/fallback 函数权限问题 | 添加权限验证 | | 2 | 构造函数错误 | 旧版 Solidity 构造函数命名规则 | 使用 constructor 关键字 | | 3 | 伪随机数预测 | 区块链数据公开性 | 使用更安全的随机数生成 | | 4 | tx.origin 误用 | tx.origin 与 msg.sender 区别 | 改用 msg.sender | | 5 | 整数下溢 | 无符号整数运算特性 | 使用 SafeMath 或新版 Solidity | 这些关卡涵盖了智能合约开发中常见的安全问题,开发者应深入理解这些漏洞原理并在实际开发中避免类似错误。