All incidents

Grizzifi Sybil Milestone Drain

Share
Aug 13, 2025 17:39 UTCAttackLoss: 48,900 USDTPending manual check5 exploit txWindow: 57m 12s
Estimated Impact
48,900 USDT
Label
Attack
Exploit Tx
5
Addresses
1
Attack Window
57m 12s
Aug 13, 2025 17:39 UTC → Aug 13, 2025 18:36 UTC

Exploit Transactions

TX 1BSC
0x40bccc37eae68cd6a53d5d896b1af3d37b170dba923fabc8384733a262cf8528
Aug 13, 2025 17:39 UTCExplorer
TX 2BSC
0x4302de51c8126e7934da9be1affbde73e5153fe1f9d0200a738a269fe07d22c7
Aug 13, 2025 17:43 UTCExplorer
TX 3BSC
0x36438165d701c883fd9a03631ee0cdeec35a138153720006ab59264db7e075c1
Aug 13, 2025 18:30 UTCExplorer
TX 4BSC
0x52567db016dc48bc27441665c25e1ad51efea72a34aef2bbb4e0bb4bdd1ada31
Aug 13, 2025 18:36 UTCExplorer
TX 5BSC
0xdb5296b19693c3c5032abe5c385a4f0cd14e863f3d44f018c1ed318fa20058f7
Aug 13, 2025 18:34 UTCExplorer

Victim Addresses

0x21ab8943380b752306abf4d49c203b011a89266bBSC

Loss Breakdown

48,900USDT

Similar Incidents

Root Cause Analysis

Grizzifi Sybil Milestone Drain

1. Incident Overview TL;DR

An unprivileged BSC attacker built a synthetic referral tree inside Grizzifi and then withdrew milestone bonuses that were meant to reward genuine team growth. The attacker EOA 0xe2336b08a43f87a4ac8de7707ab7333ba4dbaf7c deployed helper contracts, funded fresh child contracts with 20 USDT, registered those contracts under attacker-controlled referrers, and finally aggregated the withdrawals back to the attacker. In transaction 0xdb5296b19693c3c5032abe5c385a4f0cd14e863f3d44f018c1ed318fa20058f7, Grizzifi lost 48,900 USDT and the attacker EOA gained the same amount according to the collected balance diff.

The root cause is a milestone-accounting flaw, not privileged access. Grizzifi credits milestone progress from raw address-count growth. Because contract creation and referral registration are permissionless, the attacker could deterministically create sybil addresses that advanced teamsCount, unlocked milestoneReward, and later withdrew those balances through collectRefBonus().

2. Key Background

Grizzifi is an on-chain USDT investment/referral system at 0x21ab8943380b752306abf4d49c203b011a89266b. Users invest through harvestHoney(uint256,uint256,address), can nominate a previously registered referrer, and accrue referral-side rewards through both level commissions and milestone bonuses.

The relevant milestone settings are embedded directly in the verified contract. minInvestForMilestone is 10 * 1e18, minDirect is 2, teamMilestones tracks address-count thresholds, and rewardAmounts maps each threshold to a fixed USDT reward. Once credited, milestone rewards become immediately withdrawable through collectRefBonus().

The collected seed traces show the attacker using ordinary helper contracts rather than any privileged protocol component. The helper at 0x03ba640c955ebb07520a31ea1ef572c404a3f9ae seeded child contracts with 20 USDT each, while 0xed35746f389177ecd52a16987b2aac74aa0c1128 extended the referral tree and later withdrew bonuses. The attacker EOA owns and drives these contracts through standard externally owned transactions.

3. Vulnerability Analysis & Root Cause Summary

The vulnerability is a sybilable milestone design. Grizzifi assumes that each newly registered address under a referral tree represents genuine incremental team growth, but the contract never verifies that those addresses are economically independent or even human-controlled. Any attacker can cheaply deploy fresh contracts, fund them with the minimum deposit, and register them under attacker-controlled referrers.

The concrete breakpoint is inside harvestHoney(). For any deposit that meets the 10 USDT milestone threshold, the function calls _incrementUplineTeamCount(msg.sender) before finalizing the investment bookkeeping. _incrementUplineTeamCount() then walks the upline chain, marks the new address in inTeam, increments teamsCount, and when a threshold is met, credits users[upline].milestoneReward += rewardAmounts[index]. No uniqueness check, sybil resistance, or conservation rule prevents the same attacker cluster from manufacturing all of those addresses.

Finally, collectRefBonus() combines claimableReferralBonus and milestoneReward, zeros those balances, and transfers the total USDT amount to msg.sender. That makes the inflated milestone balances directly realizable as protocol-funded withdrawals.

4. Detailed Root Cause Analysis

The verified Grizzifi source shows the exploit path directly:

function harvestHoney(uint256 _planId, uint256 _amount, address _referrer) external {
    ...
    if (!registered[msg.sender]) {
        registered[msg.sender] = true;
        ...
        if (_referrer != address(0) && _referrer != msg.sender && registered[_referrer]) {
            users[msg.sender].referrer = _referrer;
            users[_referrer].referralCount++;
            users[_referrer].directReferrals.push(msg.sender);
            _updateReferralCounts(_referrer);
        }
    }
    if (_amount >= minInvestForMilestone) {
        _incrementUplineTeamCount(msg.sender);
    }
    require(USDT.transferFrom(msg.sender, address(this), _amount), "USDT transfer failed");
    ...
}

That call enters the milestone counter:

function _incrementUplineTeamCount(address _user) internal {
    address upline = users[_user].referrer;
    for (uint8 i = 0; i < 30; i++) {
        if (upline == address(0)) break;
        if (users[upline].totalInvested >= minInvestForMilestone) {
            if (!users[upline].inTeam[_user]) {
                if (i == 0 && !users[_user].inDirect) {
                    users[_user].inDirect = true;
                    users[upline].directCount++;
                }
                users[upline].inTeam[_user] = true;
                users[upline].teamsCount++;
                uint256 index = users[upline].milestoneIndex;
                if (index < teamMilestones.length && users[upline].teamsCount == teamMilestones[index]) {
                    if (users[upline].directCount >= minDirect) {
                        uint256 reward = rewardAmounts[index];
                        users[upline].milestoneReward += reward;
                    }
                    users[upline].milestoneIndex++;
                }
            } else {
                break;
            }
        }
        upline = users[upline].referrer;
    }
}

This code enforces only three conditions for milestone growth: the new address must be fresh to that upline, the upline must have invested at least 10 USDT, and for milestone payout the upline must have at least two directs. None of those conditions distinguish real users from attacker-created contracts. As a result, the attacker can recursively manufacture an entire referral graph with clean addresses that all belong to the same adversary cluster.

The seed trace for 0x36438165d701c883fd9a03631ee0cdeec35a138153720006ab59264db7e075c1 shows the expansion phase repeatedly calling:

Grizzifi::harvestHoney(0, 10000000000000000000, <attacker-controlled referrer>)

The corresponding balance diff shows multiple child contracts losing 20 USDT each while Grizzifi gains 108000000000000000000 units of USDT in that transaction, consistent with attacker-funded seeding of sybil nodes and their runner contracts. The exploit does not depend on a specific attacker artifact beyond the ability to deploy and fund fresh contracts.

The payout path is equally direct. collectRefBonus() transfers the already-accrued milestone balance:

function collectRefBonus() external {
    User storage user = users[msg.sender];
    uint256 referralAmount = user.claimableReferralBonus;
    uint256 milestoneAmount = user.milestoneReward;
    uint256 totalToClaim = referralAmount + milestoneAmount;
    require(totalToClaim > 0, "Grizzifi: No referral or milestone bonuses to claim");
    require(USDT.balanceOf(address(this)) >= totalToClaim, "Grizzifi: Insufficient contract balance");
    user.claimableReferralBonus = 0;
    user.milestoneReward = 0;
    user.totalReferralBonusWithdrawn += totalToClaim;
    totalPayouts += totalToClaim;
    require(USDT.transfer(msg.sender, totalToClaim), "Grizzifi: Bonus transfer failed");
}

The seed trace for 0xdb5296b19693c3c5032abe5c385a4f0cd14e863f3d44f018c1ed318fa20058f7 contains repeated Grizzifi::collectRefBonus() calls from attacker-controlled children. The withdrawal balance diff confirms the value transfer: Grizzifi’s USDT balance drops from 58193354084490740740745 to 9293354084490740740745, while the attacker EOA balance rises from 4660000000000000000000 to 53560000000000000000000, a net delta of 48900000000000000000000.

5. Adversary Flow Analysis

The attacker cluster consists of EOA 0xe2336b08a43f87a4ac8de7707ab7333ba4dbaf7c plus helper contracts 0xed35746f389177ecd52a16987b2aac74aa0c1128 and 0x03ba640c955ebb07520a31ea1ef572c404a3f9ae. The EOA sends every exploit transaction and is the final profit recipient in the balance diff evidence.

The observed flow is:

  1. 0x40bccc37eae68cd6a53d5d896b1af3d37b170dba923fabc8384733a262cf8528 The attacker deploys the helper used later for recursive tree construction.
  2. 0x4302de51c8126e7934da9be1affbde73e5153fe1f9d0200a738a269fe07d22c7 The attacker seeds child contracts with 20 USDT each so they can satisfy the minimum 10 USDT investment and still fund the nested runner call.
  3. 0x36438165d701c883fd9a03631ee0cdeec35a138153720006ab59264db7e075c1 The attacker recursively grows the sybil referral chain through repeated harvestHoney(0, 10e18, referrer) calls.
  4. 0x52567db016dc48bc27441665c25e1ad51efea72a34aef2bbb4e0bb4bdd1ada31 The attacker continues the mass-creation phase using selector 0xd4ad2673 on the main helper to expand the fabricated network further.
  5. 0xdb5296b19693c3c5032abe5c385a4f0cd14e863f3d44f018c1ed318fa20058f7 The attacker triggers withdrawals from the attacker-controlled child contracts, aggregates the payouts through the helper, and realizes the USDT drain.

The exploit is ACT because every step uses permissionless on-chain actions available to any unprivileged searcher: deploy contracts, transfer tokens, call harvestHoney(), and call collectRefBonus(). No private key compromise, allowlist bypass, or privileged admin path appears anywhere in the evidence.

6. Impact & Losses

The direct measured loss is 48,900 USDT from Grizzifi in the final withdrawal transaction. In raw token units, the recorded loss is:

{
  "token_symbol": "USDT",
  "amount": "48900000000000000000000",
  "decimal": 18
}

The broader impact is that milestone payouts intended to reward organic network growth can be minted synthetically by any attacker willing to fund enough minimum deposits. That means the protocol’s milestone treasury is structurally drainable whenever the contract holds sufficient USDT to satisfy the withdrawals.

7. References

  • Verified Grizzifi contract source: https://bscscan.com/address/0x21ab8943380b752306abf4d49c203b011a89266b#code
  • Expansion trace: 0x36438165d701c883fd9a03631ee0cdeec35a138153720006ab59264db7e075c1
  • Withdrawal trace: 0xdb5296b19693c3c5032abe5c385a4f0cd14e863f3d44f018c1ed318fa20058f7
  • Withdrawal balance diff showing the 48,900 USDT transfer from victim to attacker
  • Attacker transaction history around blocks 57477000 to 57483000