实战Ethernaut CTF:智能合约漏洞攻防解析

本文详细解析了Ethernaut CTF中六个智能合约挑战的攻防技术,涵盖回退函数滥用、构造函数误用、整数下溢、委托调用风险、强制转账和重入攻击等核心漏洞,使用Manticore和Slither等工具进行自动化分析。

1. Fallback

挑战描述

合约包含一个构造函数和四个函数。目标是成为合约所有者并提取所有资金。

构造函数在部署时初始化所有者的贡献值:

1
2
3
function Fallback() {
  contributions[msg.sender] = 1000 * (1 ether);
}

contribute()函数记录调用者的以太坊贡献,若贡献超过当前所有者则转移所有权:

1
2
3
4
5
6
7
function contribute() public payable {
  require(msg.value < 0.001 ether);
  contributions[msg.sender] += msg.value;
  if(contributions[msg.sender] > contributions[owner]) {
    owner = msg.sender;
  }
}

withdraw()函数仅允许所有者提取资金:

1
2
3
function withdraw() onlyOwner {
  owner.transfer(this.balance);
}

回退函数在满足条件时直接转移所有权:

1
2
3
4
function() payable {
  require(msg.value > 0 && contributions[msg.sender] > 0);
  owner = msg.sender;
}

回退函数机制

以太坊函数调用通过4字节函数选择器识别,未匹配时触发回退函数。函数选择器由keccak256哈希的前4字节生成,例如transfer(address,uint256)的选择器为0xa9059cbb。

解决方案

  1. 调用contribute()存入少量ETH
  2. 直接向合约地址转账触发回退函数
  3. 调用withdraw()提取资金

2. Fallout

挑战描述

构造函数名称Fal1out与合约名Fallout拼写差异,导致其成为普通公共函数:

1
2
3
4
function Fal1out() payable {
  owner = msg.sender;
  allocations[owner] = msg.value;
}

解决方案

直接调用contract.Fal1out()即可获得所有权。此类错误在真实合约(如ZiberCrowdsale)中确实存在。

3. Token

挑战描述

transfer函数存在整数下溢漏洞:

1
2
3
4
5
6
function transfer(address _to, uint _value) public returns (bool) {
  require(balances[msg.sender] - _value >= 0);  // 无符号整数永真
  balances[msg.sender] -= _value;              // 可触发下溢
  balances[_to] += _value;
  return true;
}

解决方案

调用contract.transfer(0x0, 21)使余额变为2^256-1

4. Delegation

挑战描述

Delegation合约通过delegatecall在回退函数中调用Delegate合约:

1
2
3
4
5
function() {
  if(delegate.delegatecall(msg.data)) {
    this;
  }
}

Delegate合约包含所有权修改函数:

1
2
3
function pwn() {
  owner = msg.sender;
}

解决方案

向合约发送包含pwn()函数选择器0xdd365b8b的交易数据,通过delegatecall在Delegation上下文中执行所有权转移。

5. Force

挑战描述

目标向无payable函数的合约强制转账。解决方案:

  1. 使用selfdestruct(address)
  2. 设置挖矿奖励地址
  3. 合约创建前向地址转账

解决方案

创建自毁合约:

1
2
3
4
5
6
contract Selfdestruct{
  function Selfdestruct() payable{}
  function attack(){
    selfdestruct(0x..);
  }
}

6. Re-entrancy

挑战描述

withdraw函数存在重入漏洞:

1
2
3
4
5
6
7
8
function withdraw(uint _amount) public {
  if(balances[msg.sender] >= _amount) {
    if(msg.sender.call.value(_amount)()) {  // 外部调用优先执行
      _amount;
    }
    balances[msg.sender] -= _amount;        // 余额更新滞后
  }
}

解决方案

创建代理合约:

  1. 实现调用withdraw的回退函数
  2. 调用donate()存款
  3. 调用withdraw()触发重入攻击

结论

本CTF通过实践方式深入讲解了智能合约安全漏洞,展示了Manticore符号执行和Slither静态分析工具的实际应用。所有挑战均可通过Remix浏览器在ropsten测试网部署验证。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计