Due to the incorrect order of arguments in createEscrow function in WildcatSanctionsSentinel contract, lender's tokens/assets is sent directly to the borrower. The variable account is assigned the borrower's address, while the variable borrower is assigned the creditor's address.
Example:
The lender gets added to the chainalysis sanctions list, and someone calls the nukeFromOrbit() function. This function calls internal function _blockAccount(state, lendersAddress).
function _blockAccount(MarketState memory state, address accountAddress) internal {
...
if (scaledBalance > 0) {
account.scaledBalance = 0;
// @audit-issue Wrong arguments order.
address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(
accountAddress, // <---- correct variable should be borrower
borrower, // <---- correct variable should be accountAddress(lender)
address(this)
);
emit Transfer(accountAddress, escrow, state.normalizeAmount(scaledBalance));
_accounts[escrow].scaledBalance += scaledBalance;
...
}
}
// createEscrow function
function createEscrow(
address borrower, <--- account's address was passed instead borrower's
address account, <--- borrower's address was passed instead account's
address asset
) public override returns (address escrowContract) {
....
tmpEscrowParams = TmpEscrowParams(borrower, account, asset);
new WildcatSanctionsEscrow{ salt: keccak256(abi.encode(borrower, account, asset)) }();
// constructor of WildcatSanctionsEscrow contract
constructor() {
sentinel = msg.sender;
(borrower, account, asset) = WildcatSanctionsSentinel(sentinel).tmpEscrowParams();
}
After the escrow contract is created, anyone can call the WildcatSanctionsEscrow.releaseEscrow() function.
This function call internal function canReleaseEscrow() and check, that the return value of this function is always true. Function canReleaseEscrow() sends borrower and account addresses to isSanctioned function. The problem is that the values in these variables are swapped with each other.
function canReleaseEscrow() public view override returns (bool) {
// send lender's address as first argument!
// send borrower's address as second argument!
return !WildcatSanctionsSentinel(sentinel).isSanctioned(borrower, account);
}
WildcatSanctionsSentinel.sol
function isSanctioned(address borrower, address account) public view override returns (bool) {
return
!sanctionOverrides[borrower][account] &&
IChainalysisSanctionsList(chainalysisSanctionsList).isSanctioned(account);
}
This function will return false, because in reality, the check will be performed with incorrect addresses (because they are swapped with each other).
Lines of code
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatSanctionsSentinel.sol#L95-L99 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketWithdrawals.sol#L166-L170 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketBase.sol#L172-L176
Vulnerability details
Impact
Due to the incorrect order of arguments in createEscrow function in WildcatSanctionsSentinel contract, lender's tokens/assets is sent directly to the borrower. The variable
account
is assigned the borrower's address, while the variableborrower
is assigned the creditor's address.Example: The lender gets added to the chainalysis sanctions list, and someone calls the nukeFromOrbit() function. This function calls internal function
_blockAccount(state, lendersAddress)
.After the escrow contract is created, anyone can call the WildcatSanctionsEscrow.releaseEscrow() function. This function call internal function canReleaseEscrow() and check, that the return value of this function is always true. Function
canReleaseEscrow()
sendsborrower
andaccount
addresses to isSanctioned function. The problem is that the values in these variables are swapped with each other.WildcatSanctionsSentinel.sol
This function will return false, because in reality, the check will be performed with incorrect addresses (because they are swapped with each other).
Look at releaseEscrow function
So tokens will be transfered to borrower.
Proof of Concept
Create file transferFromEscrowToWrongAccount.sol in test/market/
Tools Used
Manual review
Recommended Mitigation Steps
Change order or function arguments in WildcatSanctionsSentinel.createEscrow()
Assessed type
Other