blockwell.ai KYC Casper Token “牛皮癬廣告” 事件分析
一、背景
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區塊鏈安全研究團隊,轉載請註明來自FreeBuf.COM