Solidity智能合约编程漏洞及对策

上溢(Overflow)和下溢(Underflow)

Solidity能处理256位的整数。所以 2²⁵⁶-1 加1就会为0.这个就是Overflow

0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ 0x000000000000000000000000000000000001
----------------------------------------
= 0x000000000000000000000000000000000000

在Solidity中如果使用无符号整数,那么0减1就会得到最大的整数

0x000000000000000000000000000000000000
- 0x000000000000000000000000000000000001
----------------------------------------
= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

对策是使用SafeMath库来做数学运算

Solidity可见性修饰符之差别

Public函数可以被任意调用(本合约的方法,被继承的合约方法,以及其他合约方法)

External函数不能被本合约方法调用

Private函数只能在本合约中被调用

Internal函数,稍微宽松一点,可以被本合约和继承合约的函数调用

Delegate Call是一个消息调用,需注意的是目标地址上的代码是运行在调用合约上下文当中的。这意味着调用的合约,可以在运行时动态的引入其他地址上的代码(模块化设计,对吧?),但是运行在调用合约的上下文中,以为着Storage, Balance, 和Current Address都是用的当前合约的

在下例中,攻击者可以通过在Delegation的上下文中调用Delegate合约中Public的PWN函数,从而获得合约的控制权

pragma solidity ^0.4.11;

// Credits to OpenZeppelin for this contract taken from the Ethernaut CTF
// https://ethernaut.zeppelin.solutions/level/0x68756ad5e1039e4f3b895cfaa16a3a79a5a73c59
contract Delegate {

  address public owner;

  function Delegate(address _owner) {
    owner = _owner;
  }

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

contract Delegation {

  address public owner;
  Delegate delegate;

  function Delegation(address _delegateAddress) {
    delegate = Delegate(_delegateAddress);
    owner = msg.sender;
  }
  
  // an attacker can call Delegate.pwn() in the context of Delegation
  // this means that pwn() will modify the state of **Delegation** and not Delegate
  // the result is that the attacker takes unauthorized ownership of the contract
  function() {
    if(delegate.delegatecall(msg.data)) {
      this;
    }
  }
}

Parity Hack同时错用了修饰符和Delegate Call。一个可以修改合约控制权的函数被设为Public。这就给黑客开了后门:定制虚假的msg.data来获得合约控制权。

msg.data里是要调用函数的签名=sha3 (alias for keccak256)的头8个字节。

web3.sha3("pwn()").slice(0, 10) --> 0xdd365b8b

如果函数有一个参数, pwn(uint256 x):
web3.sha3("pwn(uint256)").slice(0,10) --> 0x35f4581b

重入/Reentrancy问题(THEDAO Hack)

在以下的代码中,Call函数会等待直到所有的Gas都是用掉了。但是Sender和真正扣钱是有时间差的。

这就好比,你走进银行,找柜员取现金1000RMB,你的账户正好有1000RMB. 所以第一次,柜员会给你1000RMB。在柜员还没有来的及操作从你的账户中扣1000RMB,你再问柜员要1000RMB,柜员一查,发现你的账户上有1000RMB(因为还没有来得及扣掉),柜员会又给你1000RMB

function withdraw(uint _amount) public { 
    if(balances[msg.sender] >= _amount) { 
        if(msg.sender.call.value(_amount)()) 
          { _amount; } 
        balances[msg.sender] -= _amount; 
    } 
}

对策是:先扣钱,再送钱。另外一种方法是使用Mutex。

现在最好的方法是msg.sender.transfer(_value) . 如果确实要使用send ,用require(msg.sender.send(_value));

   参考: 

        文章1

        文章2

猜你喜欢

转载自my.oschina.net/gavinzheng731/blog/1819746