All incidents

SHIBA Lock Bypass

Share
Nov 16, 2023 00:41 UTCAttackLoss: 507,677,278.57 SHIBA, 30,948.07 USDTPending manual check1 exploit txWindow: Atomic
Estimated Impact
507,677,278.57 SHIBA, 30,948.07 USDT
Label
Attack
Exploit Tx
1
Addresses
3
Attack Window
Atomic
Nov 16, 2023 00:41 UTC → Nov 16, 2023 00:41 UTC

Exploit Transactions

TX 1BSC
0x75a26224da9faf37c2b3a4a634a096af7fec561f631a02c93e11e4a19d159477
Nov 16, 2023 00:41 UTCExplorer

Victim Addresses

0x13b1f2e227ca6f8e08ac80368fd637f5084f10a5BSC
0xa4227de36398851aebf4a2506008d0aab2dd0e71BSC
0xa19d2674a8e2709a92e04403f721d8448f802e1fBSC

Loss Breakdown

507,677,278.57SHIBA
30,948.07USDT

Similar Incidents

Root Cause Analysis

SHIBA Lock Bypass

1. Incident Overview TL;DR

In BSC transaction 0x75a26224da9faf37c2b3a4a634a096af7fec561f631a02c93e11e4a19d159477 at block 33528883, an unprivileged adversary used a public DODO flash loan to buy SHIBA from the public sale contract at 0xa4227de36398851aebf4a2506008d0aab2dd0e71, then immediately sold that supposedly locked inventory into the Pancake SHIBA/USDT pair at 0xa19d2674a8e2709a92e04403f721d8448f802e1f. The transaction left the adversary cluster with a deterministic net gain of 101.695682799492366219 WBNB-equivalent after gas.

The root cause is a token-locking failure in SHIBA at 0x13b1f2e227ca6f8e08ac80368fd637f5084f10a5. The token enforces getAvailableBalance(...) on transfer and transferFrom, but its public helper functions transferLockToken and batchTransferLockToken call super.transfer(...) directly without any sender-side spendability check. That mismatch lets locked balances move anyway, so the sale contract's "locked presale inventory" guarantee does not hold.

2. Key Background

The public sale contract distributes SHIBA through transferLockToken, not through ordinary transfer. Buyers are therefore supposed to receive SHIBA that remains non-transferable until the token's unlock schedule reduces users[wallet].lockedBalance.

The critical design detail is that SHIBA exposes three outbound paths with different enforcement:

  • transfer and transferFrom require getAvailableBalance(sender) >= amount.
  • transferLockToken and batchTransferLockToken increase the recipient's locked accounting and then execute super.transfer(...).
  • Because those helper paths are public, any holder of locked SHIBA can call them directly.

The exploit only needed public infrastructure: DODO flash liquidity from 0xfeafe253802b77456b4627f8c2306a9cebb5d681, the public sale, Pancake liquidity, and the public router at 0x10ed43c718714eb63d5aa57b78b54704e256024e.

3. Vulnerability Analysis & Root Cause Summary

This incident is an ACT attack caused by inconsistent enforcement of a lock invariant. The intended invariant is straightforward: if users[x].lockedBalance is positive, that locked portion must not leave x until unlock() reduces it. SHIBA enforces that rule only on the standard ERC-20 transfer paths, not on the public lock-helper paths. As a result, the sale contract can successfully deliver "locked" SHIBA to a buyer, while the buyer can immediately move the same balance through batchTransferLockToken into an AMM pair. The pair accepts the balance as ordinary inventory and pays out USDT, even though the token still records the sender as locked. The exploit is therefore not a pricing bug, oracle bug, or access-control bug in the sale or DEX; it is a code-level lock-bypass in the token contract that turns discounted presale inventory into instantly dumpable inventory.

4. Detailed Root Cause Analysis

The core code-path mismatch is visible in the verified SHIBA token source:

function transferLockToken(address _wallet, uint256 _amount) public {
    users[_wallet].lockedBalance = users[_wallet].lockedBalance.add(_amount);
    users[_wallet].unlockPerSecond = users[_wallet].lockedBalance.mul(unlockPercent).div(100).div(duration);
    super.transfer(_wallet, _amount);
}

function batchTransferLockToken(Airdrop[] memory _airdrops) public {
    for (uint256 i = 0; i < _airdrops.length; i++) {
        address wallet = _airdrops[i].wallet;
        uint256 amount = _airdrops[i].amount;
        users[wallet].lockedBalance = users[wallet].lockedBalance.add(amount);
        users[wallet].unlockPerSecond = users[wallet].lockedBalance.mul(unlockPercent).div(100).div(duration);
        super.transfer(wallet, amount);
    }
}

function transfer(address _to, uint256 _amount) public override returns (bool) {
    uint256 availableAmount = getAvailableBalance(_msgSender());
    require(availableAmount >= _amount, "Not Enough Available Token");
    return super.transfer(_to, _amount);
}

That is the breakpoint. The lock invariant is checked in transfer and transferFrom, but bypassed in the public helper functions that the sale itself depends on.

The incident trace shows the full exploit chain:

flashLoan(20000000000000000000, 0, attacker_contract, abi.encode(1))
buyByBnb(address(0))
transferLockToken(attacker_contract, 507677278570125202361500000)
batchTransferLockToken([{wallet: 0xa19d2674a8e2709a92e04403f721d8448f802e1f, amount: 507677278570125202361500000}])
PancakePair.swap(0, 30948073916467640719090, attacker_contract, bytes(0))
swapExactTokensForETHSupportingFeeOnTransferTokens(30948073916467640719090, 0, [USDT, WBNB], attacker_contract, 1700095314)

The balance diff confirms the economic effect:

{
  "sale_shiba_delta": "-507677278570125202361500000",
  "pair_shiba_delta": "507677278570125202361500000",
  "pair_usdt_delta": "-30948073916467640719090",
  "gas_cost_wei": "1541919000000000",
  "cluster_net_profit_wei": "101695682799492366219"
}

Post-state checks make the broken invariant explicit. After the dump, the attacker's SHIBA balance is zero, but getLockedBalance(attacker) is still 507677278570125202361500000. In the captured post-state, getAvailableBalance(attacker) would underflow because the token still considers the attacker locked after the tokens have already left through batchTransferLockToken.

5. Adversary Flow Analysis

The adversary lifecycle is deterministic and entirely on-chain:

  1. The gas-paying EOA 0xb9bdc2537c6f4b587a5c81a67e7e3a4e6ddda189 calls helper contract 0xda148143379ae54e06d2429a5c80b19d4a9d6734.
  2. The helper contract flash-borrows 20 WBNB from the DODO pool and unwraps it into native BNB.
  3. It calls buyByBnb(address(0)) on the SHIBA sale contract with the full 20 BNB.
  4. The sale contract transfers 507677278570125202361500000 SHIBA to the attacker through transferLockToken, marking the balance as locked.
  5. The attacker immediately calls batchTransferLockToken and re-lock-transfers the full balance into the Pancake SHIBA/USDT pair.
  6. The pair pays out 30948073916467640719090 USDT, which the attacker routes through Pancake into WBNB.
  7. The attacker repays the 20 WBNB flash loan and sends 101697224718492366219 WBNB to profit-recipient 0x1874726c8c9a501836929f495a8b44968fbfdad8.

Nothing in that sequence requires a privileged key, private orderflow, or attacker-specific off-chain artifact. The exploit predicate is simply the existence of purchasable locked SHIBA plus the public helper functions that allow those locked tokens to move.

6. Impact & Losses

The measurable losses are:

  • The sale contract lost 507677278570125202361500000 SHIBA (decimal=18) that it intended to distribute as locked inventory.
  • The Pancake SHIBA/USDT pair lost 30948073916467640719090 USDT (decimal=18) when it bought SHIBA delivered through the bypass path.
  • The adversary cluster realized 101695682799492366219 wei-equivalent of net profit after gas.

Affected components are the SHIBA token contract, the SHIBA sale contract, and the SHIBA/USDT Pancake pool. The token contract is the root-cause component; the sale and pair are the economically impacted components.

7. References

  • Incident transaction: 0x75a26224da9faf37c2b3a4a634a096af7fec561f631a02c93e11e4a19d159477
  • SHIBA token: 0x13b1f2e227ca6f8e08ac80368fd637f5084f10a5
  • SHIBA sale contract: 0xa4227de36398851aebf4a2506008d0aab2dd0e71
  • DODO flash-loan pool: 0xfeafe253802b77456b4627f8c2306a9cebb5d681
  • Pancake router: 0x10ed43c718714eb63d5aa57b78b54704e256024e
  • SHIBA/USDT pair: 0xa19d2674a8e2709a92e04403f721d8448f802e1f
  • Token source: /workspace/session/artifacts/collector/seed/56/0x13b1f2e227ca6f8e08ac80368fd637f5084f10a5/src/Token.sol
  • Trace and decoded evidence: /workspace/session/artifacts/collector/seed/56/0x75a26224da9faf37c2b3a4a634a096af7fec561f631a02c93e11e4a19d159477/trace.cast.log and /workspace/session/artifacts/auditor/iter_0/key_evidence.json
  • Balance deltas: /workspace/session/artifacts/collector/seed/56/0x75a26224da9faf37c2b3a4a634a096af7fec561f631a02c93e11e4a19d159477/balance_diff.json