天天看點

Solidity智能合約程式設計漏洞及對策上溢(Overflow)和下溢(Underflow)Solidity可見性修飾符之差别重入/Reentrancy問題(THEDAO Hack)以太坊智能合約 —— 最佳安全開發指南

上溢(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

繼續閱讀