# Team HW6
# [NBLGAME](https://app.blocksec.com/explorer/security-incidents?page=1&pageSize=200&sort=1) Expolit Analysis
# Team Member


## 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)
### 攻擊前置作業

### 調用 vulnerable function

### 將竊取的 token 換成其他資產

## 如何做 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);
}
```