Platform: Code4rena
Start Date: 21/06/2022
Pot Size: $55,000 USDC
Total HM: 29
Participants: 88
Period: 5 days
Judge: gzeon
Total Solo HM: 7
Id: 134
League: ETH
Rank: 20/88
Findings: 6
Award: $771.42
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Lambda
Also found by: 0x29A, Chom, cryptphi, itsmeSTYJ, kenzo, kirk-baird, sashik_eth
https://github.com/code-423n4/2022-06-illuminate/blob/92cbb0724e594ce025d6b6ed050d3548a38c264b/marketplace/ERC5095.sol#L116 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/redeemer/Redeemer.sol#L275-L296
ERC5095 redeem must check allowance with principalAmount instead of underlyingAmount. Allowing any user to redeem token of anybody freely. This is very critical, anyone can lost their money everytime without their acknowledgement.
function redeem(uint256 principalAmount, address receiver, address holder) external override returns (uint256 underlyingAmount){ ... require(_allowance[holder][msg.sender] >= underlyingAmount, 'not enough approvals'); return IRedeemer(redeemer).authRedeem(underlying, maturity, holder, receiver, principalAmount);
underlyingAmount
is a return value which is always 0 on that require statement, so it can be simplify to _allowance[holder][msg.sender] >= 0
which means always allow every redeem request.
By calling redeem
on principal token -> authRedeem
on Redeemer contract. You can redeem money on behalf of anybody you want without any approval requirement.
Manual
Allowance must be checked with principalAmount
which is an input
require(_allowance[holder][msg.sender] >= principalAmount, 'not enough approvals'); return IRedeemer(redeemer).authRedeem(underlying, maturity, holder, receiver, principalAmount);
#0 - KenzoAgada
2022-06-28T06:19:13Z
Duplicate of #173
29.8781 USDC - $29.88
User can send malicious address pool, address aave
to lend
function to mint unlimited principal token.
Consider these malicious AAVE pool and APWineRouter are being sent to the lend function
function deposit(address u, uint256 lent, address a, uint256 x) public {}
Overwrite deposit to do nothing
function swapExactAmountIn(uint256 i, uint256 i1, uint256 lent, uint256 i2, uint256 r, address a) public returns(uint256) { return 1000000000 ether; }
Override swapExactAmountIn to do nothing and return a extremely high number.
// Instantiate market and tokens address principal = IMarketPlace(marketPlace).markets(u, m, p); if (IAPWineToken(principal).getUnderlyingOfIBTAddress() != u) { revert NotEqual('underlying'); }
returned
returned
Principal token is minted to attacker. Noticed that returned
is now extremely high, so it mint too much principal token to the attacker.Manual
You must whitelist valid AAVE pool and APWineRouter. And perform check to assert whether AAVE pool and APWineRouter is whitelisted before proceed to the minting logic.
#0 - sourabhmarathe
2022-06-29T12:43:18Z
Duplicate of #349.
🌟 Selected for report: Picodes
Also found by: Chom, Lambda, auditor0517, cryptphi, csanuragjain, hansfriese, hyh, kenzo, kirk-baird, pashov, unforgiven, zer0dot
82.1689 USDC - $82.17
Transfer the original underlying token back to the user but actually transfer from lender to redeemer. Underlying token may be locked forever and user may not receive their fund after redeeming.
// Transfer the original underlying token back to the user Safe.transferFrom(IERC20(u), lender, address(this), amount);
Comment: Transfer the original underlying token back to the user Code: Obviously transfer the original underlying token to the redeemer
Manual
// Transfer the original underlying token back to the user Safe.transferFrom(IERC20(u), lender, msg.sender, amount);
#0 - sourabhmarathe
2022-06-29T14:06:54Z
Duplicate of #384.
🌟 Selected for report: hyh
Also found by: 0x1f8b, 0x29A, Chom, Soosh, cccz, csanuragjain, hansfriese, itsmeSTYJ, kenzo, pashov, shenwilly, unforgiven
Everyone can burn principal token of any other user. It is very critical that you may lost your principal token anytime without any permission.
// Burn the prinicipal token from Illuminate token.burn(o, amount);
function redeem( uint8 p, address u, uint256 m, address o ) public returns (bool) {
This is a public function without any permission check, anyone can pass arbitrary o
with arbitrary amount
and principal token of o
will get burned by amount
Manual
Should burn from msg.sender. Not o.
token.burn(msg.sender, amount);
#0 - sourabhmarathe
2022-06-28T20:38:13Z
Duplicate of #387.
Transfer the principal token from the lender contract to here but actually transfer underlying token from lender to redeemer.
// Transfer the principal token from the lender contract to here Safe.transferFrom(IERC20(u), lender, address(this), amount);
It is transferring IERC20(u) which is underlying token instead of IERC20(principal)
Manual
// Transfer the principal token from the lender contract to here Safe.transferFrom(IERC20(principal), lender, address(this), amount);
#0 - sourabhmarathe
2022-07-01T21:20:12Z
Duplicate of #268.
🌟 Selected for report: defsec
Also found by: 0x1f8b, 0x29A, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 0xkowloon, 0xmint, Bnke0x0, BowTiedWardens, Chom, ElKu, Funen, GalloDaSballo, GimelSec, IllIllI, JC, Kenshin, Kulk0, Lambda, Limbooo, MadWookie, Metatron, Picodes, Soosh, StErMi, TomJ, WatchPug, Waze, Yiko, _Adam, ak1, asutorufos, aysha, bardamu, catchup, datapunk, delfin454000, dipp, fatherOfBlocks, grGred, hake, hansfriese, hyh, joestakey, kebabsec, kenzo, kirk-baird, oyc_109, pashov, poirots, rfa, robee, saian, sashik_eth, shenwilly, simon135, slywaters, z3s, zeesaw, zer0dot
63.9425 USDC - $63.94
Currently, it is too terrible name, please change it to human readable name. For example
function createMarket( address u, uint256 m, address[8] memory t, string calldata n, string calldata s, uint8 d )
to
function createMarket( address underlying, uint256 maturity, address[8] memory principals, string calldata name, string calldata symbol, uint8 decimals )
That better right?
In each redeem function in Redeemer contract
// Get the principal token that is being redeemed by the user address principal = IMarketPlace(marketPlace).markets(u, m, p);
There aren't any check whether principal token is valid or not. Check can be added
// Get the principal token that is being redeemed by the user address principal = IMarketPlace(marketPlace).markets(u, m, p); if (principal == address(0)) revert Invalid('principal');