call函數(shù)介紹
在Solidity中兔毙,可以使用call柠辞、delegatecall尘颓、callcode三個(gè)函數(shù)實(shí)現(xiàn)跨合約的函數(shù)調(diào)用功能走触。
這里主要介紹call函數(shù)的使用,call函數(shù)的調(diào)用模型:
<address>.call(...) returns (bool)
call函數(shù)可以接受任何長(zhǎng)度疤苹、任何類型的參數(shù)互广,其傳入的參數(shù)會(huì)被填充至 32 字節(jié)最后拼接為一個(gè)字符串序列,由 EVM 解析執(zhí)行卧土。
在call函數(shù)調(diào)用的過程中惫皱,Solidity中的內(nèi)置變量 msg
會(huì)隨著調(diào)用的發(fā)起而改變,msg
保存了調(diào)用方的信息包括:調(diào)用發(fā)起的地址尤莺,交易金額旅敷,被調(diào)用函數(shù)字符序列等。
使用call函數(shù)進(jìn)行跨合約的函數(shù)調(diào)用后颤霎,內(nèi)置變量 msg
的值會(huì)修改為調(diào)用者媳谁,執(zhí)行環(huán)境為被調(diào)用者的運(yùn)行環(huán)境(合約的storage)。
通過下面的例子友酱,演示call函數(shù)的調(diào)用:
pragma solidity ^0.4.0;
contract A {
address public temp1;
uint256 public temp2;
function fcall(address addr) public {
temp1 = msg.sender;
temp2 = 100;
addr.call(bytes4(keccak256("test()")));
}
}
contract B {
address public temp1;
uint256 public temp2;
function test() public {
temp1 = msg.sender;
temp2 = 200;
}
}
在Remix中進(jìn)行部署晴音、調(diào)用測(cè)試:
-
部署合約,A合約地址:
0x100eee74459cb95583212869f9c0304e7ce11eaa
, B合約地址:0xe90f4f8aeba3ade774cac94245792085a451bc8e
-
調(diào)用A合約的fcall函數(shù)缔杉,使用B合約的地址作為參數(shù)
分別查看A合約和B合約的temp1和temp2變量的值:
- A合約的temp1:0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c
- A合約的temp2:100
- B合約的temp1:0x100eeE74459CB95583212869f9c0304e7cE11EAA
-
B合約的temp2:200
通過上面的例子锤躁,可以看出:
在A合約中,msg.sender = address(調(diào)用者)
壮吩;在A合約中調(diào)用B合約的函數(shù)进苍,函數(shù)中加缘, msg.sender = address(A合約地址)
鸭叙。
同時(shí),在A合約中調(diào)用B合約的函數(shù)拣宏,調(diào)用的運(yùn)行環(huán)境是被調(diào)用者的運(yùn)行環(huán)境沈贝,即是B合約的運(yùn)行環(huán)境。
call函數(shù)濫用漏洞說明
利用call函數(shù)濫用漏洞勋乾,可以執(zhí)行 call注入 攻擊宋下。call注入 是一種新的攻擊方式,原因是對(duì)call調(diào)用處理不當(dāng)辑莫,配合一定的應(yīng)用場(chǎng)景的一種攻擊手段学歧。
通常情況下,跨合約間的函數(shù)調(diào)用會(huì)使用call函數(shù)各吨,由于call在相互調(diào)用過程中內(nèi)置變量msg會(huì)隨著調(diào)用方的改變而改變枝笨,這就成為了一個(gè)安全隱患,在特定的應(yīng)用場(chǎng)景下將引發(fā)安全問題,被惡意攻擊者利用横浑,施行 call注入 攻擊剔桨。
call函數(shù)的調(diào)用方式:
<address>.call(function_selector, arg1, arg2, ...)
<address>.call(bytes)
call函數(shù)擁有極大的自由度:
- 對(duì)于一個(gè)指定合約地址的call調(diào)用,可以調(diào)用該合約下的任意函數(shù)
- 如果call調(diào)用的合約地址由用戶指定徙融,那么可以調(diào)用任意合約的任意函數(shù)
同時(shí)洒缀,call函數(shù)調(diào)用,會(huì)自動(dòng)忽略多余的參數(shù)欺冀,如下:
pragma solidity ^0.4.0;
contract A {
uint256 public aa = 0;
function test(uint256 a) public {
aa = a;
}
function callFunc() public {
this.call(bytes4(keccak256("test(uint256)")), 10, 11, 12);
}
}
call函數(shù)調(diào)用中的參數(shù)11树绩,12將會(huì)被自動(dòng)忽略,test函數(shù)中 aa = 10
隐轩。
call注入攻擊模型
下面的例子展示了call注入模型:
contract A {
function info(bytes data) public{
this.call(data);
//this.call(bytes4(keccak256("secret()"))); //利用代碼示意
}
function secret() public{
require(this == msg.sender);
// secret operations
}
}
在合約A中存在 info()
和 secret()
函數(shù)葱峡,其中 secret()
函數(shù)只能由合約自己調(diào)用,在 info()
中有用戶可以控制的call調(diào)用龙助,用戶精心構(gòu)造傳入的數(shù)據(jù)(將注釋轉(zhuǎn)為字節(jié)序列)砰奕,即可繞過 require()
的限制,成功執(zhí)行 secret()
下面的代碼提鸟。
call注入攻擊引起的安全問題
權(quán)限繞過
function callFunc(bytes data) public {
this.call(data);
//this.call(bytes4(keccak256("withdraw(address)")), target); //利用代碼示意
}
function withdraw(address addr) public {
require(isAuth(msg.sender));
addr.transfer(this.balance);
}
function isAuth(address src) internal view returns (bool) {
if (src == address(this)) {
return true;
}
else if (src == owner) {
return true;
}
else {
return false;
}
}
通過精心構(gòu)造 callFunc()
的傳入?yún)?shù)(如:this.call(bytes4(keccak256("withdraw(address)")), target);
)军援,惡意攻擊者可以繞過函數(shù) withdraw()
的權(quán)限驗(yàn)證。
竊取代幣
在代幣合約中称勋,往往會(huì)加入一個(gè)call回調(diào)函數(shù)胸哥,用于通知接收方以完成后續(xù)的操作。但由于call調(diào)用的特性赡鲜,用戶可以向call傳入 transfer()
函數(shù)調(diào)用空厌,即可竊取合約地址下代幣。
function transfer(address _to, uint256 _value) public {
require(_value <= balances[msg.sender]);
balances[msg.sender] -= _value;
balances[_to] += _value;
}
function callFunc(bytes data) public {
this.call(data);
//this.call(bytes4(keccak256("transfer(address,uint256)")), target, value); //利用代碼示意
}
漏洞預(yù)防
預(yù)發(fā)call函數(shù)濫用漏洞的最好方式是盡量減少使用call函數(shù)银酬。
如果合約邏輯無(wú)法避免跨合約的函數(shù)調(diào)用嘲更,可以采用 new
合約,并指定 function_selector
的方式揩瞪,指定調(diào)用的合約及合約方法赋朦,并做好函數(shù)參數(shù)的檢查。
constructor() {
b = new B();
}