# BNO pool: emergencyWithdraw leaves NFT stake claimable, drained

BNO pool's emergencyWithdraw refunded stake without clearing NFT stake state, letting attacker loop 100x to drain ~$505K via PancakeSwap flash loan.

- Payout: Real-world hack
- Severity: high
- Program: BNO
- Analysis by: DeFiHackLabs
- Disclosed: 2023-07-18
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2023-07/BNO_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20230718-bno
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic vuln:flash-loan

**Target:** BSC staking pool contract (stakeNft/pledge/emergencyWithdraw)
**Root cause:** emergencyWithdraw refunds the pledged stake but does not clear the staked NFT records, so the same NFTs remain claimable via unstakeNft after the stake is cancelled.
**Steps:**
1. Flash-borrow BNO from PancakePair via `swap(0, BNO.balanceOf(PancakePair) - 1, address(this), hex"00")`.
2. In pancakeCall, `BNO.approve(address(Pool), type(uint256).max)`.
3. Loop 100 times: approve NFTs 13 and 14 to Pool, `Pool.stakeNft{value: 0.008 ether}(tokenIds)`, `Pool.pledge{value: 0.008 ether}(BNO.balanceOf(address(this)))`, `Pool.emergencyWithdraw()`, then `Pool.unstakeNft{value: 0.008 ether}(tokenIds)`.
4. Repay flash loan by `BNO.transfer(address(PancakePair), 296_077 * 1e18)`.
5. Keep remaining BNO as profit (~$505K).
**How found:** Likely code review of the pool's emergencyWithdraw vs unstakeNft state handling: noticing emergencyWithdraw refunds pledge without clearing NFT stake records, leaving unstakeNft callable on already-refunded stakes.
**Exploitation:** Attacker flash-borrowed nearly all BNO from a PancakeSwap pair, then repeatedly staked two NFTs and pledged BNO, called emergencyWithdraw to reclaim the pledge while NFTs stayed claimable, and unstaked the NFTs to recover them — repeating 100x to accumulate BNO, then repaid the flash loan and kept the remainder.
**Escalation:** Looping the stake/emergencyWithdraw/unstake cycle 100 times multiplied the per-iteration gain into a ~$505K drain.
**Insight:** When a contract has both an emergency exit and a normal exit, check whether the emergency path clears all state the normal path relies on — leftover NFT/stake records let you double-dip refunds.

