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

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章