Ethernaut_WP(1-5)
字数 1887 2025-08-29 22:41:24
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 次硬币正反面。
随机数生成逻辑:
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
_guess = blockValue.div(FACTOR); // FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968
攻击原理
- 区块链上所有数据公开,包括前一个区块的哈希
- 攻击合约可以计算与目标合约相同的随机数
- 攻击合约预先计算并提交正确结果
攻击代码示例
function attack() public {
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1;
target.flip(side);
}
修复建议
- 使用更复杂的随机数生成:
uint256 randomValue = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, block.difficulty))); - 使用 Chainlink VRF 提供真正随机数
第四关:Telephone 合约攻击
合约分析
关键验证:
require(tx.origin != msg.sender);
概念区分
tx.origin:原始交易发送者(完整调用链的起点)msg.sender:当前调用的直接发送者
攻击原理
- 用户通过攻击合约调用目标合约
tx.origin是用户地址msg.sender是攻击合约地址- 满足
tx.origin != msg.sender条件
攻击合约示例
function attack() public {
telephone.changeOwner(msg.sender);
}
修复建议
- 避免使用
tx.origin,改用msg.sender - 使用 OpenZeppelin 的 Ownable 修饰符
第五关:Token 合约攻击
合约分析
漏洞代码:
require(balances[msg.sender] - _value >= 0);
攻击原理
- Solidity 无符号整数下溢问题
- 当
balances[msg.sender] < _value时:balances[msg.sender] - _value会下溢变成极大值- 仍然满足
>= 0条件
- 攻击者可转移超过自己余额的代币
攻击方法
- 初始余额为 20
- 转移 21 代币(导致下溢)
- 攻击者余额变为极大值
修复建议
- 修改 require 语句:
require(balances[msg.sender] >= _value, "Insufficient balance"); - 使用 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 |
这些关卡涵盖了智能合约开发中常见的安全问题,开发者应深入理解这些漏洞原理并在实际开发中避免类似错误。