Cashio brrr Fake-Bank Account-Confusion Infinite Mint (2022-03-23)
- Name: Cashio
brrrfake-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
brrrprogram mintedCASHagainst 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 suppliedbankaccount was the canonical bank state the program meant to trust — the one bankman object that was supposed to anchor whichcrate_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 mint2,000,000,000 CASHagainst worthless inputs. The printed CASH was swapped and partially burned through Saber for realUSDCandUST, then bridged to Ethereum and converted into ETH. - Trigger / entry point:
- Cashio’s
brrrprogram accepted aBrrrCommonaccount 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, andcollateral.mint == saber_swap.arrow.mint. - The critical omission was that the root
bankaccount 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.
- Cashio’s
- 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
bankaccount driving the rest ofBrrrCommonwas the one bankman state whosecrate_mintandcrate_tokenwere legitimately tied to real CASH issuance. - In practice, the protocol authenticated structure rather than provenance:
these accounts refer to one another correctlyreplacedthese 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:
- 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.
- They assembled a fake
bankaccount (5ahaayrV5epV3oKChn4S4F5oek2vzoMbRpuC2fB4Q2kv) plus dependent fake accounts highlighted by SEC3: fake collateral accountHrCe9oUYRJKpfWiUwrkRNCxHSRx8gDX1bSF98Aq8qqjq, fake collateral mintGCnK63zpqfGwpmikGBWRSMJLGLW8dsW97N4VAXKaUSSC, fake Arrow accountHnWb284fT2yw2jjWyw6Ex7cf72PJvjBSYsK5H4fHEGpw, fake Saber swap account8uBqLjfRrwKxDG92nxDVGbkhbsZfaBqJ8Y2wJoXuHmHU, fake pool mintGoSK6XvdKquQwVYokYz8sKhFgkJAYwjq4i8ttjeukBmp, and fake reservesDBgB7Bw7mQ5Qk7VVcV51qL8FyLDsJDHV5bnJNsPSwVgLand3efHXgB12zP1EzKivsYTZeqAWc5YYCio9Dri9XATFsu3. - 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. - Public writeups say the attacker then used Arrow’s
deposit_vendor()path to deposit2,000,000,000fake LP tokens and mint2,000,000,000fake collateral tokens under the forged graph. - In transaction
4fgL8D6QXKH1q3Gt9GPzeRDpTgq4cE5hxf1hNDUWrJVUe4qDJ1xmUZE7KJWDANT99jD8UvwNeBb1imvujz3Pz2K5, the attacker called Cashio’sprint_cashpath and minted2,000,000,000 CASH. - The attacker then swapped CASH through Saber into real
USDCandUST; CertiK highlights swap transaction3qeUYN3sjxxhZFTEGoDYEe4YNwqqQH8tpaa4UGdAqfVNWautK9fQ5JRoo4W1YKZ6ouVkk3sC51WQiwmxbpuinXm3. - 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 USDCthrough one leg and another8.6M UST + 17M USDCthrough swaps. - 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
bankaccount 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_cashhad 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.
- Cashio had to accept a caller-supplied
- 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 isanchor 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, andcrate_tokento be one bound object, withbankderived fromcrate_tokenand bankman state maintaining thebank.crate_mint == bank.crate_token.mintrelationship.brrrlost that stronger object when it accepted an arbitrarybankand 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 disabledprint_cashandburn_cashwhile the protocol was halted. Per SEC3’s later correction, that one extra equality was sufficient because bankman already enforced the invariant that madecrate_mintuniquely determine the legitimatebank/crate_tokenpair.
- 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_cashexploit transaction:4fgL8D6QXKH1q3Gt9GPzeRDpTgq4cE5hxf1hNDUWrJVUe4qDJ1xmUZE7KJWDANT99jD8UvwNeBb1imvujz3Pz2K5- Representative CASH-to-stables swap transaction:
3qeUYN3sjxxhZFTEGoDYEe4YNwqqQH8tpaa4UGdAqfVNWautK9fQ5JRoo4W1YKZ6ouVkk3sC51WQiwmxbpuinXm3 - Patch commit:
7df658184c2610139fa2c0058363c66b28add4c4
- Attacker Solana address:
- 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_cashandburn_cashusing 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
BrrrCommongraph. Once the attacker could supply thebankobject itself, every downstreamhas_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_cashtrusted 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_minthad to match the canonicalcrate_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 rootrather than a vaguevalidation 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
bankobject 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_mintand the corresponding bankman-boundcrate_tokenrelation 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 ofBrrrCommonis 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 waswrong 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_mintcheck 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 minthas 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
- Core recurring pattern: account-confusion-root-of-trust-substitution.
- Broad root-authenticity companion: root-object-authenticity-and-trust-anchor-recomputation-discipline — the healthy-side review question here is what exact canonical bank / bankman root Cashio should have recomputed before any caller-supplied Arrow / Saber graph was allowed to define reserve reality.
- Positive-design companion: reserve-backed-claim-truth-security-object — the healthier boundary was one canonical reserve-certifying object where
bank,crate_mint,crate_token, and the Arrow / Saber collateral path stayed fused beforeprint_cashcould create CASH liabilities. - Adjacent pattern: counterfeit-collateral-admission — both turn fake collateral into solvency, but Cashio did it at runtime through a forged account graph rather than through a bad offchain listing decision.
- Adjacent pattern: bridge-verification-failure — Wormhole shows the same
wrong root account trustedmistake on a bridge verifier path instead of a collateral-minting path.
Control surface
-
The leverage sat in Cashio’s
BrrrCommonaccount validation and, more specifically, in which supplied account was allowed to define the root truth for collateral authenticity. -
The sharper question was not merely
did these Saber / Arrow pointers line up?butwhich exact bank object is allowed to certify that this collateral graph belongs to real CASH backing? -
Cashio did not mainly fail because Saber or Arrow were intrinsically unsafe. It failed because the protocol let the attacker choose the state universe inside which those integrations were judged valid.
-
Sources:
-
Last reviewed: 2026-07-21 UTC