TheDAO攻击代码简介

  • A+
所属分类:区块链技术

TheDAO 被攻击,攻击利用两个代码漏洞创建子合约提取了360万个以太币。
因为攻击者是通过splitDAO开始攻击。我们从splitDAO代码开始分析

  function splitDAO(
  uint _proposalID,
  address _newCurator
 ) noEther onlyTokenholders returns (bool _success) 

函数调用者只能是TokenHolder。不能是ether账户。这里没什么问题,符合预期
splitDAO的代码有点长,我们看到如下的代码

 uint fundsToBeMoved =
  (balances[msg.sender] * p.splitData[0].splitBalance) /
   p.splitData[0].totalSupply;
 if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false)
   throw;

在这里是计算需要支付的金额,然后调用createTokenProxy函数。我们需要记住这点。
再看如下代码块

  // Burn DAO Tokens
 Transfer(msg.sender, 0, balances[msg.sender]);
  withdrawRewardFor(msg.sender); // be nice, and get his rewards
  totalSupply -= balances[msg.sender];
  balances[msg.sender] = 0;
  paidOut[msg.sender] = 0;
  return true;

这段代码就有问题,他先调用了withdrawRewardFor,然后再对变量totalSupply,balances,paidOut赋值。这种方式可能会有RACE TO EMPTY攻击。
归纳来说,是当withdrawRewardFor调用时,balances,如果这个时候再去获取balances、paidOut的值是取了未更新前的值。
我们再往下看,withdrawRewardFor是非常不安全的。

  function withdrawRewardFor(address _account) noEther internal returns (bool _success) {
  if ((balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply < paidOut[_account])
   throw;
  uint reward =
   (balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply - paidOut[_account];
  if (!rewardAccount.payOut(_account, reward))
   throw;
  paidOut[_account] += reward;
  return true;
 }

代码很短,一个判断再上一个payOut的调用。但是paidOut的值是在payOut调用后置的。这同样是一个很不好的习惯。
再往下。rewardAccount是ManagedAccount的合约,我们找到payOut的函数

 function payOut(address _recipient, uint _amount) returns (bool) {
  if (msg.sender != owner || msg.value > 0 || (payOwnerOnly && _recipient != owner))
   throw;
  if (_recipient.call.value(_amount)()) {
   PayOut(_recipient, _amount);
   return true;
  } else {
   return false;
  }
 }   

_recipient.call.value()() 这个函数调用时,没有gas数量,这样更容易造成攻击。
我们现在可以建立一个攻击合约。
1. 创建一个钱包,调用splitDAO多次,直到到达合约的gas limit,或者stack limit。
1. 创建一个分割提案到一个新的钱包地址。
1. 等待7天。
1. 调用splitDAO
调用堆栈如下 (假设只调用了两次)

 splitDao
   withdrawRewardFor
   payOut
   recipient.call.value()()
   splitDao
  withdrawRewardFor
  payOut
  recipient.call.value()()
 ```
 调用了两次splitDAO,withdrawRewardFor也调用了两次,并且两次都是成功。成功窃取ether。
 所以一定要先重置变量,再调用发送函数。
关于RACE To Empty。可参考http://vessenes.com/more-ethereum-attacks-race-to-empty-is-the-real-deal/
TheDAO合约源码地址为http://etherscan.io/address/0xbb9bc244d798123fde783fcc1c72d3bb8c189413#code
攻击者合约地址:http://etherscan.io/address/0x304a554a310c7e546dfe434669c62820b7d83490#internaltx
详细内容请参考:http://vessenes.com/deconstructing-thedao-attack-a-brief-code-tour/
weinxin
共识社
用手机扫一扫,加入组织,时刻关注组织动态。
daodaoliang

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: