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: 81/131
Findings: 2
Award: $13.18
🌟 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.0606 USDC - $0.06
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarket.sol#L142
market cannot be closed
external function closeMarket
use an onlyController
modifier which is defined in WildcatMarketBase.sol
:
modifier onlyController() { if (msg.sender != controller) revert NotController(); _; }
to ensure that only controller
can invoke this function.
and controller is an immutable variable which is set in wildcatMarketBase constructor:
MarketParameters memory parameters = IWildcatMarketController(msg.sender).getMarketParameters(); sentinel = parameters.sentinel; borrower = parameters.borrower; controller = parameters.controller; <------------------------------get from IWildcatMarketController. feeRecipient = parameters.feeRecipient; protocolFeeBips = parameters.protocolFeeBips; delinquencyFeeBips = parameters.delinquencyFeeBips; delinquencyGracePeriod = parameters.delinquencyGracePeriod; withdrawalBatchDuration = parameters.withdrawalBatchDuration;
let's have a look at getMarketParameters
in WildcatMarketController.sol
function getMarketParameters() external view returns (MarketParameters memory parameters) { parameters.asset = _tmpMarketParameters.asset; parameters.namePrefix = _tmpMarketParameters.namePrefix; parameters.symbolPrefix = _tmpMarketParameters.symbolPrefix; parameters.borrower = borrower; parameters.controller = address(this);<-------------------------controller address. parameters.feeRecipient = _tmpMarketParameters.feeRecipient; parameters.sentinel = sentinel; parameters.maxTotalSupply = _tmpMarketParameters.maxTotalSupply; parameters.protocolFeeBips = _tmpMarketParameters.protocolFeeBips; parameters.annualInterestBips = _tmpMarketParameters.annualInterestBips; parameters.delinquencyFeeBips = _tmpMarketParameters.delinquencyFeeBips; parameters.withdrawalBatchDuration = _tmpMarketParameters.withdrawalBatchDuration; parameters.reserveRatioBips = _tmpMarketParameters.reserveRatioBips; parameters.delinquencyGracePeriod = _tmpMarketParameters.delinquencyGracePeriod; }
thus , the contract address of WildcatMarketController.sol
will be the value of controller
.
However we can't find any function which invoke closeMarket
in WildcatMarketController.sol
.
manury
should use onlyBorrower
instead of onlyController
or add closeMarket
in controller
Access Control
#0 - c4-pre-sort
2023-10-27T07:31:30Z
minhquanym marked the issue as duplicate of #147
#1 - c4-judge
2023-11-07T14:08:33Z
MarioPoneder marked the issue as partial-50
#2 - c4-judge
2023-11-07T14:16:53Z
MarioPoneder changed the severity to 3 (High Risk)
🌟 Selected for report: 0xStalin
Also found by: 0xCiphky, 0xComfyCat, 0xbepresent, 3docSec, Eigenvectors, Fulum, HChang26, Infect3d, QiuhaoLi, SandNallani, SovaSlava, TrungOre, YusSecurity, ZdravkoHr, ast3ros, ayden, bdmcbri, cu5t0mpeo, elprofesor, gizzy, jasonxiale, kodyvim, marqymarq10, max10afternoon, nisedo, nobody2018, radev_sw, rvierdiiev, serial-coder, xeros
13.1205 USDC - $13.12
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L74#L81
Once a lender is flagged or sanctioned by Chainalysis, anyone can trigger the nukeFromOrbit
function to freeze that account. Subsequently, they can create an Escrow contract to reduce the scaledBalance of the blocked account and transfer the assets to the Escrow contract. However, the lender has the option to monitor the mempool and, upon detecting that their account has been blocked and someone intends to execute a nukeFromOrbit
to transfer their tokens to a target contract, they can initiate a transfer transaction and outbid with a higher gas fee to preempt the action.
Assume Alice is added to chainalysis block list . And then before someone invoke nukeFromOrbit
to freeze his account .Alice can transfer all market ERC20 token to one of his accounts even if the account not interact with this protocol before.
1.transfer token to address attacker
2.use attacker account to get withdrawOnly role
3.invoke market queueWithdrawal
add test case to WildcatMarketConfig.t.sol:
function test_nukeFromOrbit_WithBalance_FrontRun() external { address attacker = vm.addr(1001); _deposit(alice, 1e18); assert(market.scaledBalanceOf(alice) == 1e18); //alice is added to block list. address escrow = sanctionsSentinel.getEscrowAddress(alice, borrower, address(market)); sanctionsSentinel.sanction(alice); //front-run to avoid token being transfer to contract. vm.prank(alice); market.transfer(attacker,1e18); //alice is blocked. market.nukeFromOrbit(alice); assert(market.scaledBalanceOf(alice) == 0); //attacker get alice's token. assert(market.scaledBalanceOf(attacker) == 1e18); assertEq( uint(market.getAccountRole(alice)), uint(AuthRole.Blocked), 'account role should be Blocked' ); //get withdraw role. address[] memory markets = new address[](1); markets[0] = address(market); controller.updateLenderAuthorization(attacker,markets); assertEq( uint(market.getAccountRole(attacker)), uint(AuthRole.WithdrawOnly), 'account role should be WithdrawOnly' ); vm.prank(attacker); market.queueWithdrawal(1e18); AccountWithdrawalStatus memory accountStatus = market.getAccountWithdrawalStatus(attacker,86401); assert(uint256(accountStatus.scaledAmount) == 1e18); } ## Tools Used vscode ## Recommended Mitigation Steps It is necessary to check whether a user has been blacklisted when processing user transfers. However, at the moment, only a check for whether the user has been set to a "Blocked" status has been implemented, and changing this status requires invoking "nukeFromOrbit." ## Assessed type Access Control
#0 - c4-pre-sort
2023-10-27T03:21:15Z
minhquanym marked the issue as duplicate of #54
#1 - c4-judge
2023-11-07T14:39:54Z
MarioPoneder marked the issue as satisfactory