Astaria contest - csanuragjain's results

On a mission is to build a highly liquid NFT lending market.

General Information

Platform: Code4rena

Start Date: 05/01/2023

Pot Size: $90,500 USDC

Total HM: 55

Participants: 103

Period: 14 days

Judge: Picodes

Total Solo HM: 18

Id: 202

League: ETH

Astaria

Findings Distribution

Researcher Performance

Rank: 25/103

Findings: 3

Award: $557.15

🌟 Selected for report: 2

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: csanuragjain

Also found by: 7siech, KIntern_NA, Koolex, bin2chen, cergyk, evan, obront, unforgiven

Labels

bug
3 (High Risk)
primary issue
satisfactory
selected for report
H-21

Awards

135.5273 USDC - $135.53

External Links

Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/main/src/VaultImplementation.sol#L239

Vulnerability details

Impact

An unapproved, non-owner of collateral can still take loan for the owner/operator of collateral even when owner did not needed any loan. This is happening due to incorrect checks as shown in POC. This leads to unintended loan and associated fees for users

Proof of Concept

  1. A new loan is originated via commitToLien function by User X. Params used by User X are as below:
collateralId = params.tokenContract.computeId(params.tokenId) = 1 CT.ownerOf(1) = User Y CT.getApproved(1) = User Z CT.isApprovedForAll(User Y, User X) = false receiver = User Y
  1. This internally make call to _requestLienAndIssuePayout which then calls _validateCommitment function for signature verification
  2. Lets see the signature verification part in _validateCommitment function
function _validateCommitment( IAstariaRouter.Commitment calldata params, address receiver ) internal view { uint256 collateralId = params.tokenContract.computeId(params.tokenId); ERC721 CT = ERC721(address(COLLATERAL_TOKEN())); address holder = CT.ownerOf(collateralId); address operator = CT.getApproved(collateralId); if ( msg.sender != holder && receiver != holder && receiver != operator && !CT.isApprovedForAll(holder, msg.sender) ) { revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY); } VIData storage s = _loadVISlot(); address recovered = ecrecover( keccak256( _encodeStrategyData( s, params.lienRequest.strategy, params.lienRequest.merkle.root ) ), params.lienRequest.v, params.lienRequest.r, params.lienRequest.s ); if ( (recovered != owner() && recovered != s.delegate) || recovered == address(0) ) { revert IVaultImplementation.InvalidRequest( InvalidRequestReason.INVALID_SIGNATURE ); } }
  1. Ideally the verification should fail since :

a. User X is not owner of passed collateral b. User X is not approved for this collateral c. User X is not approved for all of User Y token

  1. But observe the below if condition doing the required check:
uint256 collateralId = params.tokenContract.computeId(params.tokenId); ERC721 CT = ERC721(address(COLLATERAL_TOKEN())); address holder = CT.ownerOf(collateralId); address operator = CT.getApproved(collateralId); if ( msg.sender != holder && receiver != holder && receiver != operator && !CT.isApprovedForAll(holder, msg.sender) ) { revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY); }
  1. In our case this if condition does not execute since receiver = holder
if ( msg.sender != holder && // true since User X is not the owner receiver != holder && // false since attacker passed receiver as User Y which is owner of collateral, thus failing this if condition receiver != operator && !CT.isApprovedForAll(holder, msg.sender) ) { revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY); }
  1. This means the signature verification passes and loan is issued for collateral owner without his wish

Revise the condition as shown below:

if ( msg.sender != holder && msg.sender != operator && !CT.isApprovedForAll(holder, msg.sender) ) { revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY); } if ( receiver != holder && receiver != operator ) { revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY); }

#0 - c4-judge

2023-01-24T22:48:00Z

Picodes marked the issue as duplicate of #565

#1 - c4-judge

2023-02-15T07:07:39Z

Picodes marked the issue as satisfactory

#2 - c4-judge

2023-02-15T07:08:23Z

Picodes marked the issue as selected for report

#3 - c4-judge

2023-02-15T07:18:02Z

Picodes changed the severity to QA (Quality Assurance)

#4 - c4-judge

2023-02-15T07:22:02Z

This previously downgraded issue has been upgraded by Picodes

Findings Information

🌟 Selected for report: rbserver

Also found by: 0Kage, 0xcm, bin2chen, caventa, csanuragjain, synackrst, unforgiven

Labels

bug
2 (Med Risk)
satisfactory
duplicate-486

Awards

39.0944 USDC - $39.09

External Links

Lines of code

https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626-Cloned.sol#L27

Vulnerability details

Impact

The condition verifying that deposited amount is greater than minDepositAmount() is incorrect. Instead of checking assets > minDepositAmount(), the function incorrectly checks shares > minDepositAmount(). This could either allow :

  1. User to deposit amount lesser than minDepositAmount - if share allotted per asset is large
  2. Genuine deposit to fail - if share allotted per asset is low

Proof of Concept

  1. Assume minDepositAmount() is 100
  2. User A calls the deposit function with amount 1000
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) { // Check for rounding error since we round down in previewDeposit. require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES"); require(shares > minDepositAmount(), "VALUE_TOO_SMALL"); // Need to transfer before minting or ERC777s could reenter. ERC20(asset()).safeTransferFrom(msg.sender, address(this), assets); _mint(receiver, shares); emit Deposit(msg.sender, receiver, assets, shares); afterDeposit(assets, shares); }
  1. lets say this resulted in 5 shares from previewDeposit(assets)

  2. The deposit fails since 5<minDepositAmount()=100

  3. But this is incorrect since user deposit amount was 1000 which is 10x more than min amount

Revise the deposit function as below (this is done correctly in mint function)

function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) { ... require(assets > minDepositAmount(), "VALUE_TOO_SMALL"); ... }

#0 - c4-judge

2023-01-26T20:08:23Z

Picodes marked the issue as duplicate of #486

#1 - c4-judge

2023-02-21T22:15:00Z

Picodes marked the issue as satisfactory

Findings Information

🌟 Selected for report: csanuragjain

Also found by: 0xbepresent

Labels

bug
2 (Med Risk)
primary issue
satisfactory
selected for report
sponsor confirmed
M-34

Awards

382.5279 USDC - $382.53

External Links

Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/main/src/Vault.sol#L65

Vulnerability details

Impact

It is possible to make a deposit even when _loadVISlot().isShutdown is true. This check is done in whenNotPaused modifier and is already done for Public Vault but is missing for Private Vault, allowing unexpected deposits

Proof of Concept

  1. Observe the deposit function at https://github.com/code-423n4/2023-01-astaria/blob/main/src/Vault.sol#L59
function deposit(uint256 amount, address receiver) public virtual returns (uint256) { VIData storage s = _loadVISlot(); require(s.allowList[msg.sender] && receiver == owner()); ERC20(asset()).safeTransferFrom(msg.sender, address(this), amount); return amount; }
  1. Observe there is no whenNotPaused modifier in deposit which confirm that shutdown has not been called yet
modifier whenNotPaused() { if (ROUTER().paused()) { revert InvalidRequest(InvalidRequestReason.PAUSED); } if (_loadVISlot().isShutdown) { revert InvalidRequest(InvalidRequestReason.SHUTDOWN); } _; }

Revise the deposit function as below:

function deposit(uint256 amount, address receiver) public virtual whenNotPaused returns (uint256) { VIData storage s = _loadVISlot(); require(s.allowList[msg.sender] && receiver == owner()); ERC20(asset()).safeTransferFrom(msg.sender, address(this), amount); return amount; }

#0 - Picodes

2023-01-26T20:07:35Z

It makes sense though considering it's a private vault so only the owner can deposit

#1 - c4-sponsor

2023-02-01T23:16:29Z

SantiagoGregory marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-23T08:24:52Z

Picodes marked the issue as satisfactory

#3 - c4-judge

2023-02-24T09:32:24Z

Picodes marked the issue as selected for report

#4 - c4-judge

2023-02-24T09:32:30Z

Picodes marked the issue as primary issue

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