# Exploitation of the receive Function to Steal Funds

WiseLending's receive() resets the sendingProgress reentrancy guard, letting an attacker reenter withdrawExactAmountETH, pay off their own now-bad debt, and pass the health check to steal funds.

- Payout: Contest reward
- Severity: high
- Program: Wise Lending
- Credited: 0xCiphky
- Disclosed: 2024-05-03
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2024-02-wise-lending-findings/issues/228
- Library page: https://bugbountylibrary.com/r/c4%3A2024-02-wise-lending%3AH-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:business-logic vuln:reentrancy

**Target:** WiseLending/FeeManager Solidity contracts (ETH withdrawal + bad debt payback)
**Root cause:** _sendValue sets sendingProgress=true then false around an external call, and the receive() function forwards ETH via _sendValue, so a direct ETH transfer during a guarded external call resets the reentrancy flag to false.
**Steps:**
1. Deposit 1 ETH via depositExactAmountETH and borrow 0.5 ETH against it.
2. Call withdrawExactAmountETH to withdraw the full 1 ETH; the payout external call triggers attacker fallback.
3. In fallback, send 0.01 ETH directly to WiseLending to invoke receive() -> _sendValue, resetting sendingProgress to false.
4. Call FeeManager.paybackBadDebtForToken to settle the attacker's own 0.5 ETH loan now classified as bad debt, earning the 5% incentive.
5. Return to withdrawExactAmountETH; healthStateCheck now passes since debt is cleared, attacker keeps 1 ETH plus 0.5 ETH and incentive.
**How found:** Code reading of the syncPool reentrancy guard and _sendValue: noticed sendingProgress is reset to false after the external call and that receive() routes through _sendValue, allowing the guard to be cleared mid-execution; then observed withdrawExactAmountETH performs its healthStateCheck after the external payout.
**Exploitation:** Attacker contract reenters during the ETH payout of withdrawExactAmountETH, resets the guard via a direct ETH transfer to receive(), then calls paybackBadDebtForToken on its own position to erase the debt before the post-call health check, extracting the full deposit plus borrowed amount and incentive.
**Escalation:** Same primitive could be applied via the liquidation process instead of bad-debt payback.
**Insight:** When a reentrancy guard is a plain bool toggled around an external call, look for any other path (receive/fallback, helper) that also writes that flag; and check whether solvency/health checks run after the external call rather than before.
