Cashio brrr Fake-Bank Account-Confusion Infinite Mint (2022-03-23)

  • Name: Cashio brrr fake-bank account-confusion infinite mint
  • Date: 2022-03-23
  • Protocol: Cashio
  • Chain: Solana
  • Category: account confusion / root-of-trust substitution / counterfeit collateral / infinite mint
  • Loss: about 52.8M
  • Summary: Cashio’s brrr program minted CASH against Saber LP collateral routed through Arrow accounts, but its validation logic only checked relationships among a large caller-supplied account graph. It never proved that the supplied bank account was the canonical bank state the program meant to trust — the one bankman object that was supposed to anchor which crate_mint, crate_token, and downstream Arrow / Saber collateral path could legitimately back CASH. The attacker therefore built a self-consistent fake graph — including a fake bank, fake collateral mint, fake Arrow account, fake Saber swap account, fake pool mint, and fake reserve accounts — then used that counterfeit universe to mint 2,000,000,000 CASH against worthless inputs. The printed CASH was swapped and partially burned through Saber for real USDC and UST, then bridged to Ethereum and converted into ETH.
  • Trigger / entry point:
    • Cashio’s brrr program accepted a BrrrCommon account set covering bank state, collateral state, crate token state, and Arrow / Saber collateral plumbing.
    • Validation enforced pairwise relationships among those accounts, such as bank == collateral.bank, crate_token == crate_collateral_tokens.owner, crate_collateral_tokens.mint == collateral.mint, and collateral.mint == saber_swap.arrow.mint.
    • The critical omission was that the root bank account itself was never proven canonical before the rest of the graph inherited trust from it.
    • Once that root-of-trust check was missing, the attacker could fabricate the whole dependency graph and make every local equality hold inside attacker-authored state.
  • Root cause:
    • Cashio relied on relative consistency checks among user-supplied accounts instead of anchoring the graph to a canonical bank account.
    • Public analyses from SEC3 and CertiK describe the decisive miss as incomplete validation of the bank / collateral path, with the supplied graph able to satisfy all checks while still being fake.
    • The sharper broken object was the reserve-certifying root itself: the program never proved that the bank account driving the rest of BrrrCommon was the one bankman state whose crate_mint and crate_token were legitimately tied to real CASH issuance.
    • In practice, the protocol authenticated structure rather than provenance: these accounts refer to one another correctly replaced these are the exact accounts the protocol meant to trust.
    • That account-authenticity failure turned worthless fake LP collateral into real CASH mint authority.
  • Exploit path:
    1. The attacker prepared fake SPL assets and helper accounts in the days before the exploit, including fake pool components and a CASH token account that public analysis says was created five days early.
    2. They assembled a fake bank account (5ahaayrV5epV3oKChn4S4F5oek2vzoMbRpuC2fB4Q2kv) plus dependent fake accounts highlighted by SEC3: fake collateral account HrCe9oUYRJKpfWiUwrkRNCxHSRx8gDX1bSF98Aq8qqjq, fake collateral mint GCnK63zpqfGwpmikGBWRSMJLGLW8dsW97N4VAXKaUSSC, fake Arrow account HnWb284fT2yw2jjWyw6Ex7cf72PJvjBSYsK5H4fHEGpw, fake Saber swap account 8uBqLjfRrwKxDG92nxDVGbkhbsZfaBqJ8Y2wJoXuHmHU, fake pool mint GoSK6XvdKquQwVYokYz8sKhFgkJAYwjq4i8ttjeukBmp, and fake reserves DBgB7Bw7mQ5Qk7VVcV51qL8FyLDsJDHV5bnJNsPSwVgL and 3efHXgB12zP1EzKivsYTZeqAWc5YYCio9Dri9XATFsu3.
    3. Because BrrrCommon::validate() checked only local relationships among those accounts, the counterfeit graph passed validation even though none of the attacker-controlled roots represented real protocol collateral.
    4. Public writeups say the attacker then used Arrow’s deposit_vendor() path to deposit 2,000,000,000 fake LP tokens and mint 2,000,000,000 fake collateral tokens under the forged graph.
    5. In transaction 4fgL8D6QXKH1q3Gt9GPzeRDpTgq4cE5hxf1hNDUWrJVUe4qDJ1xmUZE7KJWDANT99jD8UvwNeBb1imvujz3Pz2K5, the attacker called Cashio’s print_cash path and minted 2,000,000,000 CASH.
    6. The attacker then swapped CASH through Saber into real USDC and UST; CertiK highlights swap transaction 3qeUYN3sjxxhZFTEGoDYEe4YNwqqQH8tpaa4UGdAqfVNWautK9fQ5JRoo4W1YKZ6ouVkk3sC51WQiwmxbpuinXm3.
    7. Rekt reported that part of the CASH was first burned back into Saber LP collateral and cashed out, while the remainder was sold directly, yielding roughly 10.8M UST + 16.4M USDC through one leg and another 8.6M UST + 17M USDC through swaps.
    8. Proceeds were bridged to Ethereum and consolidated in address 0x86766247ba3405c5f15f06b895294200809e9cfb, where they were largely converted into ETH.
  • What the attacker needed to be true:
    • Cashio had to accept a caller-supplied bank account as the root of trust for the collateral graph without proving that it was the canonical bank PDA / config state.
    • Arrow / Saber helper accounts had to be accepted from the user and validated only relative to one another.
    • print_cash had to trust the result of that graph validation strongly enough to mint CASH one-for-one against the forged collateral amount.
    • Saber pools still needed enough real stablecoin liquidity for newly printed CASH to be cashed out before operators could pause the system.
  • Technical anatomy worth remembering:
    • This was not a generic stablecoin depeg story. The depeg came after a runtime account-authenticity failure had already turned fake state into mint authority.
    • The important lesson is not merely validate more fields; it is anchor the graph to a canonical root. Pairwise equality checks among attacker-supplied accounts can all succeed at once.
    • SEC3’s reconstruction is especially useful here because it makes the healthier invariant concrete: bankman already intended bank, crate_mint, and crate_token to be one bound object, with bank derived from crate_token and bankman state maintaining the bank.crate_mint == bank.crate_token.mint relationship. brrr lost that stronger object when it accepted an arbitrary bank and then only checked how the rest of the graph pointed back to it.
    • Cashio is a strong case study in Solana-style account confusion: if the attacker can author the root account, they can often author every dependent account needed to make the rest of the checks look rigorous.
    • The post-incident patch shows how small the decisive fix can be. Cashio added assert_keys_eq!(self.bank.crate_mint, self.crate_mint);, then temporarily disabled print_cash and burn_cash while the protocol was halted. Per SEC3’s later correction, that one extra equality was sufficient because bankman already enforced the invariant that made crate_mint uniquely determine the legitimate bank / crate_token pair.
  • Representative transactions and addresses:
    • Attacker Solana address: 6D7fgzpPZXtDB6Zqg3xRwfbohzerbytB2U5pFchnVuzw
    • Attacker Ethereum address: 0x86766247ba3405c5f15f06b895294200809e9cfb
    • Fake bank account highlighted by SEC3: 5ahaayrV5epV3oKChn4S4F5oek2vzoMbRpuC2fB4Q2kv
    • Fake collateral mint highlighted by SEC3 and CertiK: GCnK63zpqfGwpmikGBWRSMJLGLW8dsW97N4VAXKaUSSC
    • print_cash exploit transaction: 4fgL8D6QXKH1q3Gt9GPzeRDpTgq4cE5hxf1hNDUWrJVUe4qDJ1xmUZE7KJWDANT99jD8UvwNeBb1imvujz3Pz2K5
    • Representative CASH-to-stables swap transaction: 3qeUYN3sjxxhZFTEGoDYEe4YNwqqQH8tpaa4UGdAqfVNWautK9fQ5JRoo4W1YKZ6ouVkk3sC51WQiwmxbpuinXm3
    • Patch commit: 7df658184c2610139fa2c0058363c66b28add4c4
  • Response / mitigation:
    • Cashio warned users on the day of the exploit not to mint additional CASH and to withdraw from pools while the incident was investigated.
    • The public patch commit both added the missing bank-to-mint validation and temporarily disabled print_cash and burn_cash using explicit invariants.
    • Public reporting later focused heavily on the unusual partial refund process, where the attacker returned some funds to smaller users while other losses remained unresolved.

Pattern tags

Why the exploit worked

  • Cashio never proved one non-user-authorable root fact before it started reasoning over the rest of the BrrrCommon graph. Once the attacker could supply the bank object itself, every downstream has_one-style relationship became only a consistency check inside attacker-authored state.
  • The fake bank did more than spoof one field. It certified the whole reserve path — crate_mint, crate_token, Arrow collateral wiring, Saber swap state, pool mint, and reserves — so the program ended up treating counterfeit backing as if it were the real protocol-owned collateral graph.
  • print_cash trusted graph admission strongly enough to mint a money-good liability immediately, while Saber still offered deep real stablecoin exit liquidity. That made the root-object authenticity failure instantly monetizable instead of leaving it as a merely local accounting bug.
  • The patch shape matters because it exposes the missing security object cleanly: once bank.crate_mint had to match the canonical crate_mint, the attacker could no longer choose a fake root and let the rest of the graph inherit authority from that counterfeit origin.

Detection and audit pivots

  • In any Solana-style account graph, ask what the first trusted fact is. If every answer is another caller-supplied account, rerun the flow assuming the attacker created all of them together; Cashio’s exploit works precisely because the validation remained circular under that thought experiment.
  • Treat bank == collateral.bank, owner checks, mint checks, and Arrow / Saber pointer checks as secondary evidence only. The primary audit question is whether the program recomputes or otherwise pins the exact bank / crate root that is allowed to certify reserve backing before any local graph checks run.
  • Preserve the full counterfeit graph when reviewing traces or reproductions, not just the final mint tx: fake bank, fake collateral mint, fake Arrow account, fake Saber swap account, fake pool mint, and fake reserves. That bundle makes the exploit family legible as counterfeit reserve-certifier root rather than a vague validation omission.
  • When a stablecoin or wrapper mint path can be cashed out through real external liquidity, review the mint-side root object and the exit-side reserve path as one joined solvency surface. A fake-collateral admission bug is already treasury-grade if downstream pools will honor the newly issued claim at par.

Failure-prevention checklist

  • Derive or load the canonical reserve-certifying root inside the mint path itself. For Cashio that meant proving the real bankman bank object before any user-supplied Arrow / Saber collateral graph was allowed to matter.
  • Validate the root object’s bound fields first, not last. bank.crate_mint == crate_mint and the corresponding bankman-bound crate_token relation should be established before owner, mint, reserve, or pool-pointer checks are allowed to carry any security weight.
  • Treat user-supplied integration accounts as route details hanging off a canonical root, not as evidence of authenticity by themselves. Arrow account shape, Saber swap ownership, pool mint pointers, and reserve wiring are secondary checks.
  • Add counterfeit-root regression tests explicitly: fake bank + fake collateral mint + fake Arrow account + fake Saber swap + fake pool mint + fake reserves should fail before any mint amount is computed.
  • Keep reserve-backed claim issuance and external-cash-out liquidity in the same threat model. If newly minted claims can immediately exit through deep stablecoin pools, root-object admission bugs deserve treasury-grade controls such as rate limits, rapid pauses, and route-local mint caps.
  • Minimize attacker freedom over helper accounts on security-critical mint paths. If a field can be derived from the canonical bank, crate, or route config, recompute it instead of inheriting it from the caller.
  • Preserve a narrow truth domain for the certifying root. One bank object should define one reserve-backed collateral universe, not become a generic permission for any self-consistent Arrow / Saber-shaped graph.

Audit prompts

  • What exact account is the first non-user-authorable fact in print_cash, and where is its canonical identity proven before the rest of BrrrCommon is read?
  • If the attacker creates every supplied account fresh, which single check still fails because the program recomputed or pinned one root object the attacker cannot counterfeit?
  • Are owner, mint, has_one, and Saber / Arrow pointer checks being used as authenticity proofs when they are only consistency checks inside caller-authored state?
  • Could a fake bank still certify a fake crate_mint, fake collateral mint, fake Arrow account, and fake Saber pool/reserve bundle while every local equality passes?
  • Which fields should be derived from bankman state or canonical protocol config instead of arriving as arbitrary user inputs?
  • Does the mint path bind reserve-backed issuance to bounded loss controls, or would one counterfeit-root admission immediately authorize full-value stablecoin exits through external pools?
  • After a live mint, can operators reconstruct which exact bank object authorized the issuance, or is root provenance still mostly implicit?
  • If Cashio had accepted one wrong root once, what onchain control would have limited cumulative minting before downstream Saber liquidity was drained?

Lessons that should already be standard

  • Reserve-backed stablecoin issuance is only as real as the object that certifies reserve reality. If the protocol authenticates the wrong root account, every downstream collateral check becomes bookkeeping inside fiction.
  • Self-consistent account graphs are not evidence. The attacker does not need to break each local relation independently when they are free to author both sides of every relation together.
  • In account-oriented runtimes, provenance checks often dominate arithmetic checks. Cashio’s decisive bug was not bad LP math; it was wrong bank authenticated.
  • External integrations do not need to be buggy to become part of a catastrophic loss. Arrow and Saber mainly supplied the realistic-looking graph shape and the real stablecoin exit liquidity that monetized Cashio’s missing root check.
  • Root-object validation belongs at the top of the instruction, before mint rights, collateral amounts, or reserve paths become actionable.
  • A patch that looks tiny can still expose the real security boundary. Cashio’s added bank.crate_mint == crate_mint check matters because it reintroduced the one canonical fact the attacker could not freely fabricate once bankman invariants were honored.
  • Counterfeit-collateral bugs and counterfeit-root bugs are often the same incident seen from two levels. The deeper prevention lesson is usually about who gets to define the collateral-certifying root object at all.
  • If a protocol can print claims that other venues will honor at par, admission bugs on the mint side are already treasury bugs and should be reviewed with the same paranoia as direct reserve withdrawals.

Why this incident matters

  • It is one of the cleanest examples of a protocol performing many validations and still authenticating nothing decisive.
  • The incident is especially useful because the fake graph was elaborate: bank, collateral, Arrow, Saber swap, pool mint, and reserve accounts all looked mutually consistent, which is exactly why this exploit family is easy to underestimate.
  • It also preserves a reserve-backed design lesson, not just an account-model lesson: the protocol-owned object that says this Arrow / Saber collateral path is allowed to back this CASH mint has to stay canonical before any local accounting starts.
  • It also broadens the exploit corpus beyond EVM arithmetic bugs. In account-oriented runtimes, state provenance and account identity are often the real security boundary.

Internal linkages

Control surface