All incidents

AISPACE Pair Spoofing and Vault Drain

Share
Nov 29, 2023 12:58 UTCAttackLoss: 62,186.88 USDTPending manual check1 exploit txWindow: Atomic
Estimated Impact
62,186.88 USDT
Label
Attack
Exploit Tx
1
Addresses
3
Attack Window
Atomic
Nov 29, 2023 12:58 UTC → Nov 29, 2023 12:58 UTC

Exploit Transactions

TX 1BSC
0x0be817b6a522a111e06293435c233dab6576d7437d0e148b45efcf7ab8a10de0
Nov 29, 2023 12:58 UTCExplorer

Victim Addresses

0x6844ef18012a383c14e9a76a93602616ee9d6132BSC
0xffac2ed69d61cf4a92347dcd394d36e32443d9d7BSC
0x1219f2699893bd05fe03559aa78e0923559cf0cfBSC

Loss Breakdown

62,186.88USDT

Similar Incidents

Root Cause Analysis

AISPACE Pair Spoofing and Vault Drain

1. Incident Overview TL;DR

On BSC block 33916688, transaction 0x0be817b6a522a111e06293435c233dab6576d7437d0e148b45efcf7ab8a10de0 executed a fully permissionless drain against AISPACE. The attacker used a PancakeV3 USDT flash loan, marked an attacker-controlled helper as an AISPACE trading pair through the public setSwapPairs(address) entrypoint, recycled the same AIS through the public Pancake pair skim(address) function to inflate PendingMint, harvested newly minted AIS into the AISPACE market vault, took over that vault through a permissionless setAdmin(address), withdrew the harvested AIS, sold it for USDT, repaid the flash loan plus fee, and kept 60686.884783691370295991 USDT.

The root cause is an access-control failure on two separate public privilege surfaces. AISPACE allowed arbitrary callers to mutate the trusted-pair set that drives mint accounting, and the market vault allowed arbitrary callers to replace the vault admin. Together those bugs made the mint-credit path and the withdrawal path fully exploitable by any unprivileged actor.

2. Key Background

AISPACE is not a plain ERC-20. Its transfer path branches on Pairs[address], and those branches mutate PendingMint and PendingBrun. When Pairs[from] is true, the transfer credits PendingMint by 4% of the transfer amount and sets a 5% pending burn on the receiver. When Pairs[to] is true, the transfer credits PendingMint by 8% and sets a 10% pending burn on the sender. harvestMarket() later mints PendingMint - MintPosition directly into MarketAddress.

The public PancakeSwap V2 pair at 0x1219f2699893bd05fe03559aa78e0923559cf0cf exposes skim(address), which transfers any token balance above recorded reserves to an arbitrary recipient. That matters because the attacker could send AIS into the pair, let AISPACE account the transfer as pair-related, and then immediately pull the same AIS back out without changing reserves.

The market vault at 0xffac2ed69d61cf4a92347dcd394d36e32443d9d7 held the harvested AIS. Pre-state permission checks in the collected RPC observations show setAdmin(address) succeeded for a random caller, while transferToken(address,address,uint256) reverted only until the caller first rewrote the mutable admin slot.

The ACT pre-state is BSC mainnet immediately before block 33916688, which is block 33916687 in the collected fork configuration. In that pre-state, the real AIS/USDT pair was already flagged in Pairs, the attacker helper was not, the flash pool 0x4f31fa980a675570939b737ebdde0471a4be40eb exposed a USDT/USDC pool with fee 500, and AISPACE's MarketAddress already pointed at the vulnerable market vault.

3. Vulnerability Analysis & Root Cause Summary

The exploit is an ATTACK-class ACT opportunity, not a privileged insider event. The first broken invariant is that only protocol-approved AMM pairs should be able to trigger AISPACE's pair-tax accounting. AISPACE violates that invariant because setSwapPairs(address) is public and directly flips Pairs[_address] = true.

The second broken invariant is that only protocol governance should control market-vault withdrawals. The vault violates that invariant because a public caller can replace the admin and then satisfy the only authorization check on transferToken.

Once the attacker controls both surfaces, the rest of the exploit becomes deterministic. The attacker buys AIS with public flash liquidity, loops transfer -> skim to manufacture PendingMint without consuming the AIS principal, harvests that protocol-side mint into the vault, seizes vault admin, withdraws the harvested AIS, and dumps it into the public AIS/USDT pair for profit.

The exploit conditions were also public and concrete: AISPACE had to expose setSwapPairs(address) without access control, the market vault had to keep setAdmin(address) permissionless while gating withdrawals only by the mutable admin slot, the public AIS/USDT pair had to expose skim(address) and hold enough USDT liquidity, and a public flash-liquidity venue had to exist for the initial AIS purchase. Those are exactly the conditions observed in the validated artifacts.

The violated security principles are clear: arbitrary callers must not be able to assign protocol privileges, mint-accounting logic must not trust attacker-controlled role classification, and vault-withdrawal authority must not be separable from governance through a public setter.

4. Detailed Root Cause Analysis

The relevant AISPACE code from the verified source is straightforward:

function setSwapPairs(address _address) public {
    Pairs[_address] = true;
}

function harvestMarket() public {
    require(PendingMint > MintPosition, "No Pending available");
    _mint(MarketAddress, PendingMint - MintPosition);
    MintPosition = PendingMint;
}

function _transferAIS(address from, address to, uint256 value) private returns (bool) {
    if (Pairs[from]) {
        _transfer(from, to, value);
        PendingBrun[to] = value * 5 / 100;
        PendingMint += value * 4 / 100;
        IMarketVault(MarketAddress).addMarketValue(value * 4 / 100);
        return true;
    }
    if (Pairs[to]) {
        require(balanceOf(from) > value * 111 / 100, "insufficient funds for burn!");
        _transfer(from, to, value);
        PendingBrun[from] = value * 10 / 100;
        PendingMint += value * 8 / 100;
        IMarketVault(MarketAddress).addMarketValue(value * 8 / 100);
        return true;
    }
    ...
}

That source shows the full code-level breakpoint. Any caller can mark an arbitrary helper as a trusted pair, and once either side of a transfer is treated as a pair, AISPACE credits PendingMint directly from transfer volume rather than from any real economic gain.

The vault-side evidence is equally explicit in the collected pre-state checks:

"permission_checks_pre": {
  "set_admin_from_random_address": "success",
  "transfer_token_from_random_address": "revert Not admin",
  "transfer_token_from_existing_admin": "success"
}

This means a random caller could first take the admin role and then use the vault's token-withdrawal function.

The seed transaction data shows the exploit parameters and post-state:

  • Flash borrow: 3000000000000000000000000 USDT.
  • Initial AIS buy: 142406161283037117313874 AIS.
  • Skim loop amount: 128165545154733405582486 AIS.
  • Skim loop count: 100.
  • Harvested AIS minted to vault: 1543728293469283826814527.
  • AIS withdrawn from vault: 1389357514498347255584772.
  • Final AIS sold back to the pair: 1531763675781384372898646.
  • Flash repayment: 3001500000000000000000000 USDT.
  • Final profit transfer: 60686884783691370295991 USDT.

The on-chain trace confirms the exploit sequence includes repeated pair skim calls, then market harvesting and vault seizure:

PancakeV3_USDT_USDC::flash(..., 3000000000000000000000000, ...)
PancakePair::skim(0x15FFd1D02B3918C9e56f75E30D23786D3eF2B5bc)   // repeated 100 times
AISPACE::harvestMarket()
AIS_MarketVault::setAdmin(0x15FFd1D02B3918C9e56f75E30D23786D3eF2B5bc)
AIS_MarketVault::transferToken(
  AISPACE,
  0x15FFd1D02B3918C9e56f75E30D23786D3eF2B5bc,
  1389357514498347255584772
)

The exploit therefore breaks two explicit invariants:

  1. Pair status must not be mutable by arbitrary callers when pair status controls mint accounting.
  2. Vault withdrawal authority must not be rewritable by arbitrary callers.

Because both invariants were broken in the public pre-state, the exploit was reproducible by any unprivileged actor from the same block state. The success predicate is both monetary and non-monetary: the attacker realized 60686.884783691370295991 USDT profit, and an arbitrary caller could force unauthorized AIS minting plus USDT reserve drain from the public pair.

5. Adversary Flow Analysis

The adversary cluster in the validated analysis has three roles:

  • 0x7cb74265e3e2d2b707122bf45aea66137c6c8891: the EOA that sent the exploit transaction and paid the BNB gas cost.
  • 0x15ffd1d02b3918c9e56f75e30d23786d3ef2b5bc: the helper contract that executed the flash loan, pair spoofing, skim loop, vault takeover, token withdrawal, liquidation, and repayment.
  • 0x859444a27eff21b443f6213ec54fd2f1a09de346: the EOA that received the final USDT profit transfer.

The full exploit flow is:

  1. The helper contract borrows 3,000,000 USDT from PancakeV3 pool 0x4f31fa980a675570939b737ebdde0471a4be40eb.
  2. It swaps the borrowed USDT through PancakeRouterV2 0x10ed43c718714eb63d5aa57b78b54704e256024e into 142406.161283037117313874 AIS.
  3. It calls AISPACE.setSwapPairs(helper) to whitelist itself as a trusted pair even though it is attacker-controlled.
  4. It loops helper -> pair transfer followed by pair.skim(helper) 100 times, recycling 128165.545154733405582486 AIS per loop while repeatedly crediting PendingMint.
  5. It calls AISPACE.harvestMarket(), which mints 1543728.293469283826814527 AIS into the market vault.
  6. It calls the vault's public setAdmin(helper), then transferToken(AIS, helper, 1389357.514498347255584772).
  7. It sells 1531763.675781384372898646 AIS back into the AIS/USDT pair, receives 3062186.884783691370295991 USDT, repays 3001500 USDT to the flash pool, and transfers 60686.884783691370295991 USDT to the profit EOA.

Every step above used public contracts and public state. No private key compromise, governance privilege, or attacker-only bytecode dependency was required.

6. Impact & Losses

The measurable loss is the AIS/USDT pair depletion of 62186.884783691370295991 USDT, recorded in the balance diff as raw amount "62186884783691370295991" with 18 decimals. The flash pool also collected the 1500 USDT fee that explains the difference between pair loss and attacker profit.

The attack also created protocol-side inflation that should never have existed. AISPACE.harvestMarket() minted 1543728.293469283826814527 AIS into the market vault, and the attacker then withdrew 1389357.514498347255584772 AIS from that vault before dumping 1531763.675781384372898646 AIS into the public pair. That sequence both drained USDT reserves and left the market vault and pair holding distorted AIS balances.

7. References

  • Seed exploit transaction: 0x0be817b6a522a111e06293435c233dab6576d7437d0e148b45efcf7ab8a10de0 on BSC block 33916688.
  • AISPACE token contract: 0x6844ef18012a383c14e9a76a93602616ee9d6132.
  • AISPACE market vault: 0xffac2ed69d61cf4a92347dcd394d36e32443d9d7.
  • AIS/USDT Pancake pair: 0x1219f2699893bd05fe03559aa78e0923559cf0cf.
  • PancakeV3 flash-loan pool: 0x4f31fa980a675570939b737ebdde0471a4be40eb.
  • Evidence used: AISPACE verified source, seed transaction trace, balance diff, and validated RPC observations collected in this session.