作者:隱形人真忙
公眾號:https://mp.weixin.qq.com/s/l3QBZwacLjIzu6KlpUvuWw
TL;DR
這是我在先知安全大會上分享議題中的一部分內容。主要介紹了利用對call調用處理不當,配合一定的應用場景的一種攻擊手段。
0x00 基礎知識
以太坊中跨合約調用是指的合約調用另外一個合約方法的方式。為了好理解整個調用的過程,我們可以簡單將調用發起方合約當做傳統web世界的瀏覽器,被調用的合約看作webserver,而調用的msg則是http數據,EVM底層通過ABI規范來解碼參數,獲取方法選擇器,然后執行對應的合約代碼。

當然,實際上智能合約的執行一般在打包交易或者驗證交易的時候發生,上面的比喻只是方便理解。
在solidity語言中,我們可以通過call方法來實現對某個合約或者本地合約的某個方法進行調用。
調用的方式大致如下:
<address>.call(方法選擇器, arg1, arg2, …)
<address>.call(bytes)
如上所述,可以通過傳遞參數的方式,將方法選擇器、參數進行傳遞,也可以直接傳入一個字節數組,當然要自己去構造msg.data的結構。
Solidity編程中,一般跨合約調用執行方都會使用msg.sender全局變量來獲取調用方的以太坊地址,從而進行一些邏輯判斷等。
比如在ERC20標準中的transfer方法的實現中,就是使用msg.sender來作為扣款方:
function transfer(address _to, uint256_value) returns (bool success) {
….
balances[msg.sender]-= _value;
balances[_to] += _value;
….
}
0x01 攻擊模型
Call方法注入漏洞,顧名思義就是外界可以直接控制合約中的call方法調用的參數,按照注入位置可以分為以下三個場景:
1. 參數列表可控
<address>.call(bytes4 selection, arg1, arg2, ...)
2. 方法選擇器可控
<address>.call(bytes4selection, arg1, arg2, ...)
3. Bytes可控
<address>.call(bytesdata)
<address>.call(msg.data)
簡單舉個例子,比如存在一個合約B,代碼如下:
contract B{
function info(bytes data){
this.call(data) ;
}
function secret() public{
require(this ==msg.sender);
// secret operations
}
}
其中有info和secret方法,secret方法中判斷必須是合約自身調用才能執行。然而這里的info方法中有個call的調用,并且外界可以直接控制call調用的字節數組,因此如果外界精心構造一個data,這個data的方法選擇器指定為secret方法,那么外部用戶就可以以合約身份調用到這個secret方法,這樣就會造成一定的風險。
0x02 具體場景
這里舉兩種實際的攻擊場景:
(1) bytes注入

在合約代碼中,有個approveAndCallcode方法,這個方法中允許調用_spender合約的某些方法或者傳遞一些數據,通過引入了_spender.call來完成這個功能。
如果外界調用中指定_spender為合約自身的地址,就可以以合約的身份去調用合約中的某些方法。比如如果我們使用合約的身份去調用transfer方法:

只需要自己去構造bytes即可,比如把transfer的_to參數指定為我們自己的賬戶地址。這樣其實就可以直接把合約賬戶中的代幣全部轉到自己的賬戶中,因為通過call注入,在transfer方法看來,msg.sender其實就是合約自己的地址。
(2) 方法選擇器注入
比如這里有個logAndCall方法:
function logAndCall(address _to, uint _value, bytes data, string_fallback){
…..
assert(_to.call(bytes4(keccak256(_fallback)),msg.sender, _value, _data)) ;
……
}
這里我們對_fallback參數可控,也就是說我們可以指定調用_to地址的任何方法,但是后面跟了三個參數,分別是msg.sender,_value, _data,類型分別為address,uint256以及bytes。那么我們是不是只能調用參數類型必須為這三個的方法呢?當然不是。這里涉及到EVM在處理calldata的一個特性。

比如Sample1合約中有個test方法,這個方法中有三個參數,都是uint256類型的。而Sample2通過call調用了Sample1的test方法,這里傳入了5個參數,同樣是可以調用成功的。這是因為EVM在獲取參數的時候沒有參數個數校驗的過程,因此取到前三個參數1,2,3之后,就把4,5給截斷掉了,在編譯和運行階段都不會報錯。
利用這個特性,我們其實有很多攻擊面,比如我們可以通過logAndCall中的call注入來調用approve方法:

這里的approve方法有兩個參數,而且類型為address和uint256,所以我們是可以調用成功的。這樣就可以將合約賬戶中的代幣授權給我們自己的賬戶了。
0x03 深遠的問題
ERC223標準是為了解決ERC20中對智能合約賬戶進行轉幣場景缺失的問題,可以看作是ERC20標準的升級版。但是在很多ERC223標準的實現代碼中就帶入了call注入的問題:

此外,很多合約在判斷權限的時候會將合約自身的地址也納入到白名單中:

0x04 防護手段
針對本文提到的這個風險,作為開發者來說,需要對ERC223的實現進行排查,不要引入call注入問題,如果非要執行回調,則可以指定方法選擇器字符串,避免使用直接注入bytes的形式來進行call調用。對于一些敏感操作或者權限判斷函數,則不要輕易將合約自身的賬戶地址作為可信的地址。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/624/
暫無評論