请找出下列合约漏洞,并说明如何盗取ContractB 中的数字资产,并修复合约。中说明:ContractB 的contract_a接口为ContractA 地址
pragma solidity ^0.8.21;
interface ContractA {
function get_price() external view returns (uint256);
}
interface ERC20 {
function balanceOf(address) external view returns (uint256);
function transfer(address, uint256) external returns (bool);
}
interface UniswapV2Pair {
function transfer(address, uint) external returns (bool);
function mint(address to) external returns (uint ) ;
function burn(address to) external returns (uint , uint );
}
contract ContractB {
ContractA contract_a;
UniswapV2Pair _uniswapV2Pair;
ERC20 token0;
ERC20 token1;
uint256 liquidity;
address public _owner;
mapping (address => uint256) private _balances;
bool check=true;
modifier noreentrancy(){
require(check);
check=false;
_;
check=true;
}
constructor(address owner){
_owner=owner;
}
function setcontracta(address addr,address uniswapV2Pair,address _token0,address _token1) public {
require(msg.sender==_owner);
contract_a = ContractA(addr);
_uniswapV2Pair = UniswapV2Pair(uniswapV2Pair);
token0=ERC20(_token0);
token1=ERC20(_token1);
}
function depositFunds() public payable noreentrancy(){
uint256 mintamount=msg.value*contract_a.get_price()/10e8;
_balances[msg.sender]+=mintamount;
}
function withdrawFunds(uint256 burnamount) public payable noreentrancy(){
_balances[msg.sender]-=burnamount;
uint256 amount=burnamount*10e8/contract_a.get_price();
msg.sender.call{value:amount}("");
}
function balanceof(address acount)public view returns (uint256){
return _balances[acount];
}
}
pragma solidity ^0.8.21;
contract ContractA {
uint256 private _totalSupply;
uint256 private _allstake;
mapping (address => uint256) public _balances;
bool check=true;
modifier noreentrancy(){
require(check);
check=false;
_;
check=true;
}
constructor(){
}
function get_price() public view virtual returns (uint256) {
if(_totalSupply==0||_allstake==0) return 10e8;
return _totalSupply*10e8/_allstake;
}
function deposit() public payable noreentrancy(){
uint256 mintamount=msg.value*get_price()/10e8;
_allstake+=msg.value;
_balances[msg.sender]+=mintamount;
_totalSupply+=mintamount;
}
function withdraw(uint256 burnamount) public noreentrancy(){
uint256 sendamount=burnamount*10e8/get_price();
_allstake-=sendamount;
payable(msg.sender).call{value:sendamount}("");
_balances[msg.sender]-=burnamount;
_totalSupply-=burnamount;
}
}
这段 Solidity 代码中存在一个关键的漏洞,允许攻击者在 ContractB 中盗取数字资产。攻击者可以利用 withdrawFunds
函数中的漏洞来实现攻击,让我们一步步分析和修复这个问题。
漏洞分析:
-
在
withdrawFunds
函数中,合约从_balances[msg.sender]
减去了burnamount
,然后使用msg.sender.call{value:amount}("");
从合约向调用者地址发送以太币。 -
这里的漏洞是没有重入保护机制的。攻击者可以通过多次调用
withdrawFunds
函数来进行重入攻击,每次都可以取回以太币并重复扣除_balances[msg.sender]
。
攻击步骤:
-
攻击者在合约中调用
depositFunds
函数来存入以太币,生成相应数量的代币。 -
攻击者多次调用
withdrawFunds
函数,重入攻击使_balances[msg.sender]
不断减少,而每次调用都会从合约中取回相同数量的以太币。 -
因此,攻击者可以重复取回代币,并获得更多的以太币。
修复漏洞:
为了修复这个漏洞,需要在 withdrawFunds
函数中添加重入保护。以下是修复后的代码:
solidity
bool private checkReentrancy = true;
modifier noreentrancy() {
require(checkReentrancy, "Reentrant call detected");
checkReentrancy = false;
_;
checkReentrancy = true;
}
function withdrawFunds(uint256 burnamount) public payable noreentrancy() {
require(_balances[msg.sender] >= burnamount, "Insufficient balance");
_balances[msg.sender] -= burnamount;
uint256 amount = burnamount * 10e8 / contract_a.get_price();
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
在修复的代码中,我们在 withdrawFunds
函数内添加了一个 checkReentrancy
布尔变量,用于保护函数免受重入攻击。在函数调用之前,我们要求 checkReentrancy
为 true
,并在函数开始时将其设置为 false
,以防止多次进入。
注意:
- 重入攻击是一种常见的智能合约漏洞,请务必在编写合约时采取适当的重入保护措施。
- 此处提供的修复只是一个示例,根据实际情况和需求可能需要进行进一步的安全性和逻辑审查。
- 在部署和使用智能合约时,务必注意审查合约代码,测试合约的各种情况,以确保安全性和可靠性。