---
# System prepended metadata

title: Team HW6

---

# Team HW6
# [NBLGAME](https://app.blocksec.com/explorer/security-incidents?page=1&pageSize=200&sort=1) Expolit Analysis
# Team Member
![image](https://hackmd.io/_uploads/rkgtWOpEyx.png =40%x)![image](https://hackmd.io/_uploads/H13cWO6N1l.png =40%x)
![image](https://hackmd.io/_uploads/By3hWu6Ekx.png =40%x)![image](https://hackmd.io/_uploads/BJmRWOaN1l.png =40%x)


## NBLGAME 協議介紹
### 合約組成
- [NBL ERC20](https://optimistic.etherscan.io/token/0x4b03afc91295ed778320c2824bad5eb5a1d852dd)
- [NBL Box ERC721](https://optimistic.etherscan.io/token/0x534e1a8a89548c44be7aba1c3c27951801940c10)
- [NblNftStake](https://optimistic.etherscan.io/address/0x5499178919c79086fd580d6c5f332a4253244d91)
- [Stakebook](https://optimistic.etherscan.io/address/0x770b279678105b378f90055aad1b22d2a0306baf)

### 協議功能
NBLGAME 協議主要實現 NFT 和 FT 的質押功能，允許用戶將擁有的 NFT 和 FT 存入合約中，參與質押以獲取獎勵。

用戶可以將 `NBL FT` 和 `NBL Box NFT` 存入 `NblNftStake` 合約中，合約會根據質押的 NFT 等級和 FT 數量去計算獎勵，最後調用 `Stakebook` 合約去給予用戶 `NBL FT`
## 駭客如何利用漏洞點攻擊
### 使用到的合約
- [Entry point contract](https://optimistic.etherscan.io/address/0xe4d41bdd6459198b33cc795ff280cee02d91087b)
- [Attack logic contract](https://optimistic.etherscan.io/address/0xfc3b08555b1c328ecf8b8a0ccd85679bf59bba4c)
- [UniswapV3](https://optimistic.etherscan.io/address/0xfaf037caafa9620bfaebc04c298bf4a104963613#code)

### 漏洞利用方式
`NblNftStake`合約中的 ==withdrawNft()== function 裡調用到 NBL Box ERC721 的 ==safeTransferFrom()==，==safeTransferFrom()== 在 receiver 是合約的話會去調用 ==onERC721Received()== 做檢查，駭客在 ==withdrawNft()== 沒做 reentrancy 防護的狀況下寫了一份惡意合約作為 receiver，而在 ==onERC721Received()== 中再次調用 ==withdrawNft()== 進行重入攻擊，由於 StakeInfo 的狀態變數還未改變，因此可以再次通過 require 的檢查，而讓 `NblNftStake` 重複執行了兩次 NBL ERC20 的 transfer，成功移轉合約內的所有資金給惡意合約

## Transaction trace
[Tenderly](https://dashboard.tenderly.co/tx/optimistic/0xf4fc3b638f1a377cf22b729199a9aeb27fc62fe2983a65c4d14b99ee5c5b2328?trace=0.2.1.3.10.1.0.1.3)

### 攻擊前置作業
![hpa-026269](https://hackmd.io/_uploads/Bk1-Yq671g.png)

### 調用 vulnerable function
![upload_d43647389992c4d10cf072b0cf7c595f](https://hackmd.io/_uploads/rJ4Idoa7kg.png)

### 將竊取的 token 換成其他資產
![upload_5ef8ef39f54fad1e83bab0dfe849cd2c](https://hackmd.io/_uploads/S1LlYoTQJl.png)


## 如何做 POC 重現
### [POC GitHub](https://github.com/CT77777/NBLGAME-exploit-analysis)

### 合約調用流程
```mermaid
graph TD
		Z[攻擊合約] -->
    A[UniswapV3Pool - NBL_USDT.flash] -->|1.借出 NBL| B[攻擊合約 - uniswapV3FlashCallback]
    B -->|2.解鎖槽位| C[NblNftStake - NblNftStake.unlockSlot]
    C -->|3.存入 NFT| D[NblNftStake - NblNftStake.depositNft]
    D -->|4.存入 NBL| E[NblNftStake - NblNftStake.depositNbl]
    E -->|5.提取 NFT| F[NblNftStake - NblNftStake.withdrawNft]
    F -->|6.轉移 NFT| G[NFT合約 - nft.safeTransferFrom]
    G -->|7.觸發回調| H[攻擊合約 - onERC721Received]
    H -->|8.轉回 NFT| I[NFT合約 - NBF.transferFrom]
    I -->|9.重入攻擊| J[NblNftStake - NblNftStake.withdrawNft]
    J -->|10.再次存入| K[NblNftStake - NblNftStake.depositNft]
    K -->|11.歸還閃電貸| L[NBL - NBL.transfer]
    L -->|12.完成| M[UniswapV3Pool - Repay Flash Loan]

    %% 顏色標記
    classDef flashloan fill:#f9f,stroke:#333,stroke-width:2px
    classDef attack fill:#f96,stroke:#333,stroke-width:2px
    classDef normal fill:#fff,stroke:#333,stroke-width:2px

    %% 應用樣式
    class A,B,L,M flashloan
    class F,G,H,J attack
    class C,D,E,I,K normal
```

## 優化方法

1. 正確使用 ReentrancyGuard
2. 遵循 CEI（Checks-Effects-Interactions）模式：

```solidity
function withdrawNft(uint256 _index) public nonReentrant {
    // 1. Checks
    StakeInfo[] storage stakes = userStakeInfo[msg.sender];
    require(_index < stakes.length, "invalid stake index");

    uint tokenid = stakes[_index].nftTokenId;
    require(tokenid > 0, "no stake available");

    uint amount = stakes[_index].nblStakeAmount;
    uint power = getSlotPower(msg.sender, _index);
    uint beginTimestamp = stakes[_index].begin;
    uint inscriptionId = stakes[_index].inscriptionId
    
    // 2. Effects (先更新狀態)
    stakes[_index].nftTokenId = 0;
    stakes[_index].inscriptionId = 0;
    stakes[_index].nblStakeAmount = 0;
    stakes[_index].begin = 0;
    
    // 3. Interactions (最後進行外部調用)
    nft.safeTransferFrom(address(this), msg.sender, tokenid);
    if (inscriptionId > 0) {
        inscription.safeTransferFrom(
            address(this),
            msg.sender,
            inscriptionId
        );
    }
    
    uint discount = calcDiscount(beginTimestamp, amount);
    nbl.safeTransfer(community, discount);
    nbl.safeTransfer(msg.sender, SafeMath.sub(amount, discount));

    uint multiply = slotPowerMultiplies[stakes.length - 1];
    power = SafeMath.mul(power, multiply) / 100;
    stakebook.withdraw(msg.sender, power);
    
    emit WithdrawNft(msg.sender, tokenid);
}
```
