AbraNFT contest - Ruhum's results

A peer to peer lending platform, using NFTs as collateral.

General Information

Platform: Code4rena

Start Date: 27/04/2022

Pot Size: $50,000 MIM

Total HM: 6

Participants: 59

Period: 5 days

Judge: 0xean

Id: 113

League: ETH

Abracadabra Money

Findings Distribution

Researcher Performance

Rank: 7/59

Findings: 3

Award: $1,651.93

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: cccz

Also found by: IllIllI, Ruhum, WatchPug, antonttc, berndartmueller, catchup, hyh, plotchy

Labels

bug
duplicate
3 (High Risk)

Awards

533.6961 MIM - $533.70

External Links

Lines of code

https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L287-L288 https://github.com/code-423n4/2022-04-abranft/blob/5cd4edc3298c05748e952f8a8c93e42f930a78c2/contracts/interfaces/INFTOracle.sol#L5-L10

Vulnerability details

Impact

The INFTOracle.get() function returns two values. A boolean specifying whether a valid rate is available and a uint specifying the current price: https://github.com/code-423n4/2022-04-abranft/blob/5cd4edc3298c05748e952f8a8c93e42f930a78c2/contracts/interfaces/INFTOracle.sol#L5-L10

The NFTPairWithOracle contract only checks the second value and ignores the first one. If the oracle doesn't have a valid price it will return (false, 0) since the zero-value of the uint will probably be used in a such a case.

If that happens, the lender is able to seize the borrower's collateral. Just because the oracle doesn't have a recent value doesn't mean that the collateral's value dropped. In such a case, the lender shouldn't be able to seize it. Instead, it should simply be ignored until the oracle is able to provide a valid value.

Proof of Concept

https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L287-L288

uint256 amount = loanParams.valuation + interest;
(, uint256 rate) = loanParams.oracle.get(address(this), tokenId);
require(rate.mul(loanParams.ltvBPS) / BPS < amount, "NFT is still valued");

If the oracle returns (false, 0) the require statement will always be true since the left side of the inequality will always be 0 while the right side will always be larger than 0.

Tools Used

none

Change the linked lines to:

uint256 amount = loanParams.valuation + interest;
(bool success, uint256 rate) = loanParams.oracle.get(address(this), tokenId);
require(success, "oracle doesn't return valid value");
require(rate.mul(loanParams.ltvBPS) / BPS < amount, "NFT is still valued");

#0 - 0xm3rlin

2022-05-04T14:06:22Z

Findings Information

🌟 Selected for report: Ruhum

Also found by: BowTiedWardens, IllIllI, WatchPug, gzeon, plotchy, scaraven

Labels

bug
3 (High Risk)
sponsor confirmed

Awards

1045.8477 MIM - $1,045.85

External Links

Lines of code

https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L198-L223 https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L200-L212 https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L288

Vulnerability details

Impact

The lender should only be able to seize the collateral if:

  • the borrower didn't repay in time
  • the collateral loses too much of its value

But, the lender is able to seize the collateral at any time by modifying the loan parameters.

Proof of Concept

The updateLoanParams() allows the lender to modify the parameters of an active loan in favor of the borrower. But, by setting the ltvBPS value to 0 they are able to seize the collateral.

If ltvBPS is 0 the following require statement in removeCollateral() will always be true:

https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L288

rate * 0 / BPS < amount is always true.

That allows the lender to seize the collateral although its value didn't decrease nor did the time to repay the loan come.

So the required steps are:

  1. lend the funds to the borrower
  2. call updateLoanParams() to set the ltvBPS value to 0
  3. call removeCollateral() to steal the collateral from the contract

Tools Used

none

Don't allow updateLoanParams() to change the ltvBPS value.

#0 - cryptolyndon

2022-05-05T03:32:42Z

Confirmed, and the first to report this particular exploit.

Awards

72.3807 MIM - $72.38

Labels

bug
QA (Quality Assurance)
sponsor disputed

External Links

Report

Pair can be initialized without valid asset

In NFTPair.init() you verify that the collateral is not a zero-address. But, you don't do the same thing for the asset. If either of them is not a valid address the NFTPair is unusable. So if you do it for the collateral you should also do it for the asset.

Relevant code:

Add:

require(address(asset) != address(0), "NFTPair: invalid asset");

Use ERC721 safe methods when transferring to an arbitrary address

The ERC721 safe methods check whether the receiving address is able to handle ERC721 tokens. In removeCollateral() you send the collateral to a user defined address. If the user makes a mistake and uses a contract that can't handle the collateral the token will be locked up.

You might argue that it's the user's fault and not the contract's responsibility. But switching to the safe method wouldn't introduce any reentrancy risk. The collateral is already transferred at the very end of the function. After all the relevant state variables are updated.

It provides a little more protection for the user with a small increase in gas costs.

Relevant code:

#0 - cryptolyndon

2022-05-12T04:25:34Z

  • That check is to ensure init() is only called once. Having one or both be zero or invalid is otherwise harmless, if wasteful.
  • See #20 for discussion on safeTransferFrom()
AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter