prePO contest - csanuragjain's results

Decentralized Exchange for Pre-IPO Stocks & Pre-IDO Tokens.

General Information

Platform: Code4rena

Start Date: 09/12/2022

Pot Size: $36,500 USDC

Total HM: 9

Participants: 69

Period: 3 days

Judge: Picodes

Total Solo HM: 2

Id: 190

League: ETH

prePO

Findings Distribution

Researcher Performance

Rank: 1/69

Findings: 3

Award: $3,864.68

QA:
grade-b

🌟 Selected for report: 1

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: csanuragjain

Labels

bug
2 (Med Risk)
satisfactory
selected for report
sponsor confirmed
M-01

Awards

3783.7224 USDC - $3,783.72

External Links

Lines of code

https://github.com/prepo-io/prepo-monorepo/blob/feat/2022-12-prepo/apps/smart-contracts/core/contracts/WithdrawHook.sol#L70

Vulnerability details

Impact

User can bypass the userWithdrawLimitPerPeriod check by transferring balance to another account

Proof of Concept

  1. Assume userWithdrawLimitPerPeriod is set to 1000
  2. User A has current deposit of amount 2000 and wants to withdraw everything instantly
  3. User A calls the withdraw function and takes out the 1000 amount
function withdraw(uint256 _amount) external override nonReentrant { uint256 _baseTokenAmount = (_amount * baseTokenDenominator) / 1e18; uint256 _fee = (_baseTokenAmount * withdrawFee) / FEE_DENOMINATOR; if (withdrawFee > 0) { require(_fee > 0, "fee = 0"); } else { require(_baseTokenAmount > 0, "amount = 0"); } _burn(msg.sender, _amount); uint256 _baseTokenAmountAfterFee = _baseTokenAmount - _fee; if (address(withdrawHook) != address(0)) { baseToken.approve(address(withdrawHook), _fee); withdrawHook.hook(msg.sender, _baseTokenAmount, _baseTokenAmountAfterFee); baseToken.approve(address(withdrawHook), 0); } baseToken.transfer(msg.sender, _baseTokenAmountAfterFee); emit Withdraw(msg.sender, _baseTokenAmountAfterFee, _fee); }
  1. Remaining 1000 amount cannot be withdrawn since userWithdrawLimitPerPeriod is reached
function hook( address _sender, uint256 _amountBeforeFee, uint256 _amountAfterFee ) external override onlyCollateral { ... require(userToAmountWithdrawnThisPeriod[_sender] + _amountBeforeFee <= userWithdrawLimitPerPeriod, "user withdraw limit exceeded"); ... }
  1. User simply transfer his balance to his another account and withdraw from that account

  2. Since withdraw limit is tied to account so this new account will be allowed to make withdrawal thus bypassing userWithdrawLimitPerPeriod

User should only be allowed to transfer leftover limit. For example if User already utilized limit X then he should only be able to transfer userWithdrawLimitPerPeriod-X

#0 - c4-sponsor

2022-12-20T02:21:31Z

ramenforbreakfast marked the issue as sponsor confirmed

#1 - c4-judge

2023-01-07T11:10:36Z

Picodes marked the issue as satisfactory

Findings Information

🌟 Selected for report: obront

Also found by: 8olidity, HE1M, Madalad, Trust, cccz, csanuragjain, deliriusz, hansfriese, hihen, joestakey, rvierdiiev, wait, zaskoh

Labels

bug
2 (Med Risk)
satisfactory
duplicate-254

Awards

52.8446 USDC - $52.84

External Links

Lines of code

https://github.com/prepo-io/prepo-monorepo/blob/feat/2022-12-prepo/apps/smart-contracts/core/contracts/Collateral.sol#L80

Vulnerability details

Impact

It seems MANAGER_WITHDRAW_ROLE can steal entire user balance if SET_MIN_RESERVE_PERCENTAGE_ROLE has set setMinReservePercentage to 0%

Proof of Concept

  1. The SET_MIN_RESERVE_PERCENTAGE_ROLE role sets minReservePercentage to 0%
function setMinReservePercentage(uint256 _newMinReservePercentage) external override onlyRole(SET_MIN_RESERVE_PERCENTAGE_ROLE) { require(_newMinReservePercentage <= PERCENT_DENOMINATOR, ">100%"); minReservePercentage = _newMinReservePercentage; emit MinReservePercentageChange(_newMinReservePercentage); }
  1. MANAGER_WITHDRAW_ROLE role simply calls the managerWithdraw with contract balance as _amount
function managerWithdraw(uint256 _amount) external override onlyRole(MANAGER_WITHDRAW_ROLE) nonReentrant { if (address(managerWithdrawHook) != address(0)) managerWithdrawHook.hook(msg.sender, _amount, _amount); baseToken.transfer(manager, _amount); }
  1. This calls hook which allow to withdraw everything above reserve
function hook( address, uint256, uint256 _amountAfterFee ) external view override { require( collateral.getReserve() - _amountAfterFee >= getMinReserve(), "reserve would fall below minimum" ); }
  1. getMinReserve is implemented as below. Since minReservePercentage is 0 so getMinReserve becomes 0
function getMinReserve() public view override returns (uint256) { return (depositRecord.getGlobalNetDepositAmount() * minReservePercentage) / PERCENT_DENOMINATOR; }
  1. This means user will be able to withdraw full collateral.getReserve() - _amountAfterFee which is approx entire balance (without fees)
function getReserve() external view override returns (uint256) { return baseToken.balanceOf(address(this)); }

Do not allow SET_MIN_RESERVE_PERCENTAGE_ROLE to set setMinReservePercentage to 0

#0 - hansfriese

2022-12-14T18:22:42Z

duplicate of #254

#1 - c4-judge

2022-12-17T10:08:00Z

Picodes marked the issue as duplicate of #254

#2 - c4-judge

2023-01-01T17:44:21Z

Picodes marked the issue as satisfactory

Awards

28.124 USDC - $28.12

Labels

bug
grade-b
QA (Quality Assurance)
sponsor disputed
Q-06

External Links

Use safeTransferFrom instead of transferFrom

Contract https://github.com/prepo-io/prepo-monorepo/blob/feat/2022-12-prepo/apps/smart-contracts/core/contracts/Collateral.sol#L49

Issue: While depositing contract is making use of transferFrom function which is insecure and the function fails to check the outcome of the transferFrom function (whether it was success or not)

function deposit(address _recipient, uint256 _amount) external override nonReentrant returns (uint256) { uint256 _fee = (_amount * depositFee) / FEE_DENOMINATOR; if (depositFee > 0) { require(_fee > 0, "fee = 0"); } else { require(_amount > 0, "amount = 0"); } baseToken.transferFrom(msg.sender, address(this), _amount); ... }

Recommendation: Use safeTransferFrom which checks the outcome of the transfer and fails if transfer was not success

#0 - c4-judge

2022-12-19T14:31:24Z

Picodes marked the issue as grade-b

#1 - ramenforbreakfast

2022-12-21T23:05:23Z

This entry should be disregarded due quality of submission (just a mention of using safe transfer)

#2 - c4-sponsor

2022-12-21T23:39:26Z

ramenforbreakfast marked the issue as sponsor disputed

#3 - Picodes

2023-01-07T18:20:24Z

@ramenforbreakfast note that the grade also takes into account duplicate QA reports, that's why I gave grade-b

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