作者:LoRexxar'@知道創宇404區塊鏈安全研究團隊
時間:2018年11月1日

系列文章:

一、簡介

在知道創宇404區塊鏈安全研究團隊整理輸出的《知道創宇以太坊合約審計CheckList》中,我們把超過10個問題點歸結為開發者容易忽略的問題隱患,其中包括“語法特性”、“數據私密性”、“數據可靠性”、“gas消耗優化”、“合約用戶”、“日志記錄”、“回調函數”、“Owner權限”、“用戶鑒權”、 “條件競爭”等,統一歸類為“以太坊智能合約編碼隱患”。

“昊天塔(HaoTian)”是知道創宇404區塊鏈安全研究團隊獨立開發的用于監控、掃描、分析、審計區塊鏈智能合約安全自動化平臺,目前已經集成了部分基于opcode的審計功能。我們利用該平臺針對上述提到的《知道創宇以太坊合約審計CheckList》中“以太坊智能合約編碼隱患”類問題在全網公開的智能合約代碼做了掃描分析。詳見下文:

二、漏洞詳情

以太坊智能合約是以太坊概念中非常重要的一個概念,以太坊實現了基于solidity語言的以太坊虛擬機(Ethereum Virtual Machine),它允許用戶在鏈上部署智能合約代碼,通過智能合約可以完成人們想要的合約。

這次我們提到的問題多數屬于智能合約獨有問題,與我們常見的各類代碼不同,在編寫智能合約代碼時還需要考慮多種問題。

1、語法特性

在智能合約中小心整數除法的向下取整問題

在智能合約中,所有的整數除法都會向下取整到最接近的整數,當我們需要更高的精度時,我們需要使用乘數來加大這個數字。

該問題如果在代碼中顯式出現,編譯器會提出問題警告,無法繼續編譯,但如果隱式出現,將會采取向下取整的處理方式。

錯誤樣例

uint x = 5 / 2; // 2
正確代碼

uint multiplier = 10;
uint x = (5 * multiplier) / 2;

2、數據私密性

在合約中,所有的數據都是公開的。包括私有變量等,不得將任何帶有私密性的數據儲存在鏈上。

3、數據可靠性

在合約中,許多開發者習慣用時間戳來做判斷條件,例如

uint someVariable = now + 1;

if (now % 2 == 0) { // now可能被礦工控制

}

now、block_timestamp會被礦工所控制,并不可靠。

4、gas消耗優化

contract EUXLinkToken is ERC20 {
    using SafeMath for uint256;
    address owner = msg.sender;
    mapping (address => uint256) balances;
    mapping (address => mapping (address => uint256)) allowed;
    mapping (address => bool) public blacklist;
    string public constant name = "xx";
    string public constant symbol = "xxx";
    uint public constant decimals = 8;
    uint256 public totalSupply = 1000000000e8;
    uint256 public totalDistributed = 200000000e8;
    uint256 public totalPurchase = 200000000e8;
    uint256 public totalRemaining = totalSupply.sub(totalDistributed).sub(totalPurchase);
    uint256 public value = 5000e8;
    uint256 public purchaseCardinal = 5000000e8;
    uint256 public minPurchase = 0.001e18;
    uint256 public maxPurchase = 10e18;

在合約中,涉及到狀態變化的代碼會消耗更多的,為了經可能優化gas消耗,對于不涉及狀態變化的變量應該加constant來限制

5、合約用戶

合約中,交易目標可能為合約,因此可能會產生的各種惡意利用。

contract Auction{
    address public currentLeader;
    uint256 public hidghestBid;
    function bid() public payable {
        require(msg.value > highestBid);
        require(currentLeader.send(highestBid));
        currentLeader = msg.sender;
        highestBid = currentLeader;
    }
}

上述合約就是一個典型的沒有考慮合約為用戶時的情況,這是一個簡單的競拍爭奪王位的代碼。當交易ether大于合約內的highestBid,當前用戶就會成為合約當前的"王",他的交易額也會成為新的highestBid。

contract Attack {
    function () { revert(); }
    function Attack(address _target) payable {
        _target.call.value(msg.value)(bytes4(keccak256("bid()")));
    }
}

但當新的用戶試圖成為新的“王”時,當代碼執行到require(currentLeader.send(highestBid));時,合約中的fallback函數會觸發,如果攻擊者在fallback函數中加入revert()函數,那么交易就會返回false,即永遠無法完成交易,那么當前合約就會一直成為合約當前的"王"。

6、日志記錄

當合約跑在鏈上之后,鏈上的一切數據都難以監控,對于一個健康的智能合約來說,記錄合理的event,為了便于運維監控,除了轉賬,授權等函數以外,其他操作也需要加入詳細的事件記錄,如轉移管理員權限、其他特殊的主功能。

fonction transferOwnership(address newOwner) onlyOwner public {
    ownner = newOwner;
    emit OwnershipTransferred(owner, newowner);
    }

7、回調函數

fallback機制是基于智能合約的特殊性而存在的。對于智能合約來說,任何函數的執行都是通過交易來完成的,但函數的執行過程中可能會遇到各種各樣的問題,在交易失敗或者交易結束后,就會執行fallback來最后處理結果和返回。

而在合約交易中,執行的每一個操作都會花費巨大的gas,如果gas不足,那么fallback函數也會執行失敗。在evm中規定,交易失敗時,只有2300gas用于執行fallback函數,而2300gas只允許執行一組字節碼指令。一旦遇到極端情況,可能會因為gas不夠用導致某種情況發生,導致未知的不可挽回的后果。

例如

function() payable { LogDepositReceived(msg.sender); }
function() public payable{ revert();};

8、Owner權限

避免owner權限過大

部分合約owner權限過大,owner可以隨意操作合約內各種數據,包括修改規則,任意轉賬,任意鑄幣燒幣,一旦發生安全問題,可能會導致嚴重的結果。

  function destroy() onlyOwner public onlyOwner{
    selfdestruct(owner);
  }

9、用戶鑒權問題

合約中不要使用tx.origin做鑒權

tx.origin代表最初始的地址,如果用戶a通過合約b調用了合約c,對于合約c來說,tx.origin就是用戶a,而msg.sender才是合約b,對于鑒權來說,這是十分危險的,這代表著可能導致的釣魚攻擊。

下面是一個范例:

pragma solidity >0.4.24;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet {
    address owner;
    constructor() public {
        owner = msg.sender;
    }
    function transferTo(address dest, uint amount) public {
        require(tx.origin == owner);
        dest.transfer(amount);
    }
}

我們可以構造攻擊合約

pragma solidity >0.4.24;
interface TxUserWallet {
    function transferTo(address dest, uint amount) external;
}
contract TxAttackWallet {
    address owner;
    constructor() public {
        owner = msg.sender;
    }
    function() external {
        TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
    }
}

當用戶被欺騙調用攻擊合約,則會直接繞過鑒權而轉賬成功,這里應使用msg.sender來做權限判斷。

https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin

10、條件競爭

在智能合約中,經常容易出現對交易順序的依賴,如占山為王規則、或最后一個贏家規則。都是對交易順序有比較強的依賴的設計規則,但以太坊本身的底層規則是基于礦工利益最大法則,在一定程度的極限情況下,只要攻擊者付出足夠的代價,他就可以一定程度控制交易的順序。開發者應避免這個問題。

真實世界事件

智能合約游戲之殤——類 Fomo3D 攻擊分析

三、漏洞影響范圍

使用Haotian平臺智能合約審計功能可以準確掃描到該類型問題。

基于Haotian平臺智能合約掃描功能規則,我們對全網的公開的共47305個合約代碼進行了掃描。

其中存在數據可靠問題的合約共2732個,
存在int型變量gas優化問題的合約共18285個,
存在string型變量gas優化問題的合約共194個,
存在Owner權限過大或合約后門的合約共1194個,
存在tx.origin 鑒權問題問題的合約共52個。

1、數據可靠性

截止2018年10月31日,我們發現了2732個存在數據可靠問題的合約代碼,存在潛在的安全隱患。其中交易量最高的10個合約情況如下:

2、gas消耗優化

截止2018年10月31日,我們發現了18285個存在int型變量gas優化問題的合約代碼,存在潛在的安全隱患。其中交易量最高的10個合約情況如下:

截止2018年10月31日,我們發現了194個存在string型變量gas優化問題的合約代碼,存在潛在的安全隱患。其中交易量最高的10個合約情況如下:

3、回調函數

截止2018年10月31日,我們發現了8321個存在復雜回調的合約代碼,存在潛在的安全隱患。其中交易量最高的10個合約情況如下:

4、Owner權限

截止2018年10月31日,我們發現了1194個存在Owner權限過大或合約后門,其中交易量最高的10個合約情況如下:

5、tx.origin 鑒權問題

截止2018年10月31日,我們發現了52個存在tx.origin 鑒權問題,其中交易量最高的10個合約情況如下:

四、修復方式

1、語法特性

在智能合約中小心整數除法的向下取整問題,可以通過先乘積為整數再做處理。

uint multiplier = 10;
uint x = (5 * multiplier) / 2;

2、數據私密問題

在處理一些隱私數據是盡量保留在服務端,可以通過hash-commit的方式來check變量值。

3、數據可靠性

盡量使合約內容不依賴時間順序,如果需要外部變量影響,那盡量采用block.height和block.hash等這類難以控制的變量。

4、gas消耗優化

對于某些不涉及狀態變化的函數和變量可以加constant來避免gas的消耗

5、合約用戶

合約中,應盡量考慮交易目標為合約時的情況,避免因此產生的各種惡意利用。

6、日志記錄

關鍵事件應有Event記錄,為了便于運維監控,除了轉賬,授權等函數以外,其他操作也需要加入詳細的事件記錄,如轉移管理員權限、其他特殊的主功能。

fonction transferOwnership(address newOwner) onlyOwner public {
    ownner = newOwner;
    emit OwnershipTransferred(owner, newowner);
    }

7、回調函數

合約中定義Fallback函數,并使Fallback函數盡可能的簡單。盡量避免在回調函數中調用transfer、call等涉及狀態變化的操作,避免gas不夠用直接導致未知情況發生。

8、Owner權限問題

部分合約owner權限過大,owner可以隨意操作合約內各種數據,包括修改規則,任意轉賬,任意鑄幣燒幣,一旦發生安全問題,可能會導致嚴重的結果。

關于owner權限問題,應該遵循幾個要求:

  1. 合約創造后,任何人不能改變合約規則,包括規則參數大小等
  2. 只允許owner在合約銷毀前,從合約中提取余額
  3. owner不能在未限制的情況下操作其他用戶的余額等

9、用戶鑒權

在需要用戶鑒權的時刻,盡量使用msg.sender作為目標方。 https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin

10、條件競爭

在智能合約的設計中,避免對交易順序的依賴,或者想辦法強制要求交易順序。

五、一些思考

在這一次整理合約編碼隱患的過程中,對智能合約本身的特殊性進行了深入了解。和每個語言一樣,智能合約有基于區塊鏈這個大前提在,許多代碼都出現了新的問題,如果開發者沒有注意到這些隱患,一旦出現問題,這些隱患就可能導致更大的問題發生。

截止2018年10月31日,以太坊合約審計Checklist的所以問題完成了第一輪掃描,第一輪掃描針對以太坊公開的所有合約,其中超過80%的智能合約存在1個以上的安全隱患問題。在接下來的掃描報告中,我們會公開《以太坊合約審計Checklist》并使用HaoTian對以太坊公鏈上的所有智能合約進行基于opcode的掃描分析。


智能合約審計服務

針對目前主流的以太坊應用,知道創宇提供專業權威的智能合約審計服務,規避因合約安全問題導致的財產損失,為各類以太坊應用安全保駕護航。

知道創宇404智能合約安全審計團隊: https://www.scanv.com/lca/index.html
聯系電話:(086) 136 8133 5016(沈經理,工作日:10:00-18:00)

歡迎掃碼咨詢:

區塊鏈行業安全解決方案

黑客通過DDoS攻擊、CC攻擊、系統漏洞、代碼漏洞、業務流程漏洞、API-Key漏洞等進行攻擊和入侵,給區塊鏈項目的管理運營團隊及用戶造成巨大的經濟損失。知道創宇十余年安全經驗,憑借多重防護+云端大數據技術,為區塊鏈應用提供專屬安全解決方案。

歡迎掃碼咨詢:



Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/732/