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: 10/131
Findings: 2
Award: $654.32
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: MiloTruck
Also found by: 0xDING99YA, Robert
647.6492 USDC - $647.65
createEscrow() will return the escrow contract address if it was already deployed, or deploy a new one if not. However, attacker can make the createEscrow() always return the escrow address without code deployed, sanctioned funds will be transferred to that address and since there is no code there, there is no way to recover these funds.
createEscrow() will first get the escrow contract address and use code hash to determine if it is a contract.
escrowContract = getEscrowAddress(borrower, account, asset); if (escrowContract.codehash != bytes32(0)) return escrowContract;
If escrowContract.codehash != bytes32(0), it will consider the escrowContract was already deployed and return that address. If escrowContract does not exist or it's empty, 0x0 code hash will be returned. See here: https://eips.ethereum.org/EIPS/eip-1052
However, the issue here is that an attacker can send 1 Wei to the not yet created escrowContract, that will make the escrowContract not defined as empty anymore since it has balance now, and the returned code hash will be c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470, which is not 0. The definition of empty address can be found here: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md
As a result, since the escrowContratc has 1 Wei balance and will return a non-zero code hash, createEscrow() will directly return that address and never create the real escrow. Funds will still be transferred to that escrowContract when an account is sanctioned, but later when the account is not sanctioned and want to retrieve those funds it will not work since the escrow is basically an EOA.
Manual Review
Use external code size instead to determine if an address is contract or not
Other
#0 - c4-pre-sort
2023-10-27T02:59:13Z
minhquanym marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-10-28T14:51:17Z
minhquanym marked the issue as primary issue
#2 - d1ll0n
2023-10-31T09:55:24Z
Well that's embarrassing.
Good catch, thank you :pray:
#3 - c4-sponsor
2023-10-31T09:55:29Z
d1ll0n (sponsor) confirmed
#4 - c4-judge
2023-11-07T10:14:22Z
MarioPoneder marked the issue as duplicate of #491
#5 - c4-judge
2023-11-07T10:16:50Z
MarioPoneder marked the issue as partial-25
#6 - MarioPoneder
2023-11-07T10:17:44Z
found 1/3 instances, see primary
#7 - c4-judge
2023-11-07T10:18:17Z
MarioPoneder marked the issue as satisfactory
#8 - c4-judge
2023-11-07T10:21:51Z
MarioPoneder marked the issue as partial-25
🌟 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/WildcatMarketBase.sol#L157-L187 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketWithdrawals.sol#L164-L170 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatSanctionsSentinel.sol#L108 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatSanctionsEscrow.sol#L16-L19
When creating an escrow for an account, the parameters used should be in "borrower, account, asset" order, however, in _blockAccount() and executeWithdrawals(), these two functions use "account, borrower, asset" order when constructing the escrow. The escrow contract created in this way will mark the sanctioned account as the borrower, and the sanctioned account can simply update the sanctionOverride mapping to get their funds back when it is still under sanctioned.
From WildcatSanctionsSentinel.sol and WildcatSanctionsEscrow.sol we can see that the correct parameters when constructing the escrow should be in order "borrower, account, asset".
tmpEscrowParams = TmpEscrowParams(borrower, account, asset); new WildcatSanctionsEscrow{ salt: keccak256(abi.encode(borrower, account, asset)) }(); constructor() { sentinel = msg.sender; (borrower, account, asset) = WildcatSanctionsSentinel(sentinel).tmpEscrowParams(); }
However, in _blockAccount() and executeWithdrawl() the order is not correct.
address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow( accountAddress, borrower, address(asset) ); if (scaledBalance > 0) { account.scaledBalance = 0; address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow( accountAddress, borrower, address(this) );
We can see it uses "account, borrower, asset" instead of "borrower, account, asset". So the escrow created will mark the real sanctioned account as the borrower, mark the real borrower as the sanctioned address and then transfer the funds. Therefore the real sanctioned address can simply call overrideSanction() and then transfer the funds back (Actually they don't even need to do that because isSanctioned() will return false for the real borrower).
Manual Review
Just make sure when constructing the escrow the parameters used are correct
Other
#0 - c4-pre-sort
2023-10-27T02:53:11Z
minhquanym marked the issue as duplicate of #515
#1 - c4-judge
2023-11-07T12:10:11Z
MarioPoneder marked the issue as satisfactory