Platform: Code4rena
Start Date: 05/04/2022
Pot Size: $30,000 USDC
Total HM: 10
Participants: 47
Period: 3 days
Judge: gzeon
Total Solo HM: 4
Id: 106
League: ETH
Rank: 1/47
Findings: 4
Award: $7,063.20
๐ Selected for report: 2
๐ Solo Findings: 1
๐ Selected for report: rayn
6144.5095 USDC - $6,144.51
https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L205-L208 https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L215-L218
If an attacker already calls lend()
to lend to a loan, the attacker can manipulate loanInfo
by reentrancy attack when any lenders try to buyout. The attacker can set bad values of lendInfo
(e.g. very long duration, and 0 interest rate) that the lender who wants to buyout don't expect.
An attacker lends a loan, and loanAssetContractAddress
in loanInfo
is ERC777 which is suffering from reentrancy attack. When a lender (victim) try to buyout the loan of the attacker:
lend()
.lend()
, it always call ERC20(loanAssetContractAddress).safeTransfer
to send accumulatedInterest + previousLoanAmount
to currentLoanOwner
(attacker).transfer
of loanAssetContractAddress
ERC777 will call _callTokensReceived
so that the attacker can call lend()
again in reentrancy with parameters:
currentLoanOwner
)loanInfo
are changed to bad value, and the victim will get the lend ticket but the loan term is manipulated, and can not set it back (because it requires a better term).vim
Use nonReentrant
modifier on lend()
to prevent reentrancy attack:
#0 - wilsoncusack
2022-04-07T17:08:23Z
we should mitigate, but will think on this
#1 - wilsoncusack
2022-04-08T00:49:54Z
not sure whether this should be medium or high risk
#2 - wilsoncusack
2022-04-08T10:37:41Z
thinking more, again we should definitely mitigate, but I think this is less severe because I do not think ERC777 tokens will work with our contract? The on received call will revert? So this would need to be a malicious ERC20 designed just for this
#3 - wilsoncusack
2022-04-08T10:50:42Z
er erc777 does work because reception ack is not needed in the normal case
#4 - wilsoncusack
2022-04-13T20:54:24Z
#5 - gzeoneth
2022-04-15T11:40:51Z
Sponsor confirmed with fix
829.5088 USDC - $829.51
https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L205-L208 https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L215-L218
If an attacker (lender) lends to a loan, the attacker can always revert transactions when any lenders try to buyout, making anyone can not buyout the loan of the attacker.
lend()
, trying to buyout the loan of the attacker.lend()
, it always call ERC20(loanAssetContractAddress).safeTransfer
to send accumulatedInterest + previousLoanAmount
to currentLoanOwner
(attacker).transfer
of loanAssetContractAddress
is ERC777, it will call _callTokensReceived
that the attacker can manipulate and always revert it.NFTLoanFacilitator
uses safeTransfer
and safeTransferFrom
to check return value, the transaction of the victim will also be reverted. It makes anyone can not buyout the loan of the attacker.In _callTokensReceived
, the attacker just wants to revert the buyout transaction, but keep repayAndCloseLoan
successful. The attacker can call loanInfoStruct(uint256 loanId)
in _callTokensReceived
to check if the value of loanInfo
is changed or not to decide to revert it.
vim
Don't transfer ERC20(loanAssetContractAddress)
to currentLoanOwner
in lend()
, use a global mapping to record redemption of lenders and add an external function redeem
for lenders to transfer ERC20(loanAssetContractAddress)
.
#0 - wilsoncusack
2022-04-07T17:04:39Z
I think this is just part of perils of working with certain assets and I am not sure we will mitigate
#1 - wilsoncusack
2022-04-13T21:06:16Z
Sorry I lost track of this one/labeled incorrectly. This is indeed an issue we intend to address: we will block erc777 tokens.
The worse implication here is that a lender could prevent a borrower from repaying and could seize the NFT.
#2 - wilsoncusack
2022-04-13T21:08:56Z
Still not sure if this should be high or medium. But there are legit ERC777 tokens that a borrower might selecting unknowingly, so probably is high?
#3 - gzeoneth
2022-04-15T11:44:23Z
I suggest this as Med Risk as no fund is loss by preventing buyout.
#4 - wilsoncusack
2022-04-16T01:21:57Z
I suggest this as Med Risk as no fund is loss by preventing buyout.
But as I said above the bigger issue is they could block repayment, guaranteeing default and seizure of collateral?
#5 - gzeoneth
2022-04-16T04:10:26Z
I suggest this as Med Risk as no fund is loss by preventing buyout.
But as I said above the bigger issue is they could block repayment, guaranteeing default and seizure of collateral?
I think you are correct as there is a similar call in L241. However both warden failed to describe such attack path and I am inclined to keep this as Med Risk.
#6 - wilsoncusack
2022-04-21T20:15:35Z
resolved with https://github.com/with-backed/backed-protocol/pull/69
๐ Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xkatana, BouSalman, CertoraInc, Dravee, FSchmoede, Hawkeye, Kenshin, Meta0xNull, PPrieditis, Ruhum, TerrierLover, VAD37, WatchPug, berndartmueller, csanuragjain, hake, horsefacts, hubble, m9800, rayn, reassor, robee, samruna, securerodd, shenwilly, sorrynotsorry, t11s, teryanarmen, tintin, z3s
54.2825 USDC - $54.28
We list 2 non-critical findings here:
In conclusion, it's better to check the validity of the mappingโs key. And using a safe version of ERC721 functions is usually a better choice.
In NFTLoanFacilitator.sol/loanEndSeconds()
, it doesnโt check whether loanId is valid
https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L363
Add check for loanId
In NFTLoanFacilitator.sol
, it uses IERC721Mintable(borrowTicketContract).mint
which is not a good practice.
https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L102
https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L161
Use safeMint
instead of mint
#0 - wilsoncusack
2022-04-15T17:37:34Z
fixed loan end seconds https://github.com/wilsoncusack/backed-protocol/pull/73
๐ Selected for report: Dravee
Also found by: 0v3rf10w, 0x1f8b, 0xkatana, CertoraInc, FSchmoede, Funen, IllIllI, Kenshin, Meta0xNull, TerrierLover, Tomio, csanuragjain, joshie, obront, rayn, rfa, robee, saian, securerodd, sorrynotsorry, t11s, z3s
34.8992 USDC - $34.90
Those can be declared as private for gas savings, only creating view functions for variables that should be read externally.
https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L17-L44
Declare these variables to private.
If storage is accessed more than once, it should be fetch into memory to save gas. loan.lastAccumulatedTimestamp
is accessed twice in totalOwed
and interestOwed
https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L339
https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L343
https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L352
https://github.com/code-423n4/2022-04-backed/blob/main/contracts/NFTLoanFacilitator.sol#L356
Fetch loan.lastAccumulatedTimestamp into memory
uint40 lastAccumulatedTimestamp = loan.lastAccumulatedTimestamp;
#0 - wilsoncusack
2022-04-15T14:49:34Z