CashioApp Attack - What’s the Vulnerability and How Soteria Detects It
March 24, 2022

The Cashio stablecoin (CASH) protocol recently lost $50M in an attack. The attacker was able to mint 2,000,000,000 CASH tokens for almost free. The root cause is a vulnerability in the Cashio’s brrr smart contract.

Soteria team conducted an in-depth analysis of the attack and found that

(1) The attacker was sophisticated (started preparation five days before the attack)

(2) The vulnerability lies deeply in a missing check of an input (bank) account

(3) C̶a̶s̶h̶i̶o̶’̶s̶ ̶p̶a̶t̶c̶h̶ ̶t̶o̶ ̶f̶i̶x̶ ̶t̶h̶e̶ ̶m̶i̶s̶s̶i̶n̶g̶ ̶c̶h̶e̶c̶k̶ ̶i̶s̶ ̶s̶t̶i̶l̶l̶ ̶i̶n̶s̶u̶f̶f̶i̶c̶i̶e̶n̶t̶ (however, the brrr smart contract has been disabled at the time of writing). (Correction: after a careful discussion with @siintemal from Neodyme, we found that the patch indeed fixes the fake bank issue. More detail in Section “Cashio’s patch”. Credits: @sinntemal and @nick_soteria).

Importantly, the vulnerability can be automatically detected by Soteria’s Premium auto auditor. This article elaborates on the details.

The Vulnerability

The Cashio brrr program’s functionality is simple: print and burn CASH tokens. It has only two instructions: print_cash and burn_cash :

The complex part is that Cashio uses Saber LP Arrows as collateral. Both instructions must provide a sequence of input accounts related to Saber and Arrow. In total, there are 12 accounts declared in a struct BrrrCommon:

# Account
1 bank
2 collateral
3 crate_token
4 crate_mint
5 crate_collateral_tokens
6 arrow
7 saber_swap
8 pool_mint
9 reserve_a
10 reserve_b
11 token_program
12 crate_token_program

(The accounts 6–10 are wrapped in a struct SaberSwapAccounts).

Because all input accounts are supplied by untrusted users and thus could be faked, a challenge here is how to properly validate them. The brrr program includes the following checks:

note: arrow.mint is checked

Although these assert_keys_eq checks look a bit overwhelming, they are still insufficient: a crucial check on the validity of the bank account is missing! If the attacker could supply a fake bank account, then all the other checks become meaningless since bank is the root of trust.

In fact, that’s exactly what the attacker did: out of these 12 input accounts, the attacker created 8 fake accounts to pass all the validity checks.

The Attack

Input accounts (BrrrCommon):

# Account Address Note
1 bank (faked) 5ahaayrV5epV3oKChn4S4F5oek2vzoMbRpuC2fB4Q2kv created by bankman with faked crate_mint and faked crate_token
2 collateral (faked) HrCe9oUYRJKpfWiUwrkRNCxHSRx8gDX1bSF98Aq8qqjq created by the token program with faked collateral.mint
3 crate_token J77Nq48nbq4Etf1voss38R3dTdR3yD7y5F6W6TaVHvmb valid crate_token with valid CASH mint
4 crate_mint CASHVDm2wsJXfhj6VWxb7GiMdoLc17Du7paH4bNr5woT CASH mint
5 crate_collateral_tokens (faked) EAYzx8dqABiNdZKtfavg16rdyShHQB2k5hUa6UmXHiky created by the token program with faked crate_collateral_tokens.mint == collateral.mint
6 arrow (faked) HnWb284fT2yw2jjWyw6Ex7cf72PJvjBSYsK5H4fHEGpw created by the arrow program with faked arrow.mint == collateral.mint
7 saber_swap (faked) 8uBqLjfRrwKxDG92nxDVGbkhbsZfaBqJ8Y2wJoXuHmHU faked swap_info account initialized by the Saber Stable Swap Program with faked pool_mint, reserve_a and reserve_b
8 pool_mint (faked) GoSK6XvdKquQwVYokYz8sKhFgkJAYwjq4i8ttjeukBmp
9 reserve_a (faked) DBgB7Bw7mQ5Qk7VVcV51qL8FyLDsJDHV5bnJNsPSwVgL
10 reserve_b (faked) 3efHXgB12zP1EzKivsYTZeqAWc5YYCio9Dri9XATFsu3
11 token_program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
12 crate_token_program CRATwLpu6YZEeiVq9ajjxs61wPQ9f29s1UoQR9siJCRs

How to fix the vulnerability?

To establish the bank account’s validity, the input accounts must satisfy two conditions:

Condition 1: The bank account’s crate_token must be valid:

bank.crate_token == crate_token

Condition 2. The bank account’s crate_mint must be valid:

bank.crate_mint == crate_mint

Condition (1) ensures that bank cannot be faked if the supplied crate_token account is valid. The reason is that bank is a PDA initialized in the bankman smart contract, with crate_token as a part of the seeds:

Condition (2) ensures that the supplied crate_mint cannot be faked — it is the same as the bank account’s crate_mint . In this context, it ensures that bank.crate_mint is indeed the CASH token mint.

Cashio’s patch  i̶s̶ ̶i̶n̶s̶u̶f̶f̶i̶c̶i̶e̶n̶t̶

T̶h̶e̶ ̶p̶a̶t̶c̶h̶ ̶i̶n̶ ̶v̶0̶.̶2̶.̶1̶ ̶i̶s̶ ̶i̶n̶s̶u̶f̶f̶i̶c̶i̶e̶n̶t̶ ̶b̶e̶c̶a̶u̶s̶e̶ ̶i̶t̶ ̶o̶n̶l̶y̶ ̶e̶n̶s̶u̶r̶e̶s̶ ̶C̶o̶n̶d̶i̶t̶i̶o̶n̶ ̶2̶,̶ ̶b̶u̶t̶ ̶n̶o̶t̶ ̶C̶o̶n̶d̶i̶t̶i̶o̶n̶ ̶1̶.̶ ̶T̶h̶e̶ ̶a̶t̶t̶a̶c̶k̶e̶r̶ ̶c̶o̶u̶l̶d̶ ̶s̶t̶i̶l̶l̶ ̶c̶r̶e̶a̶t̶e̶ ̶a̶ ̶f̶a̶k̶e̶ ̶b̶a̶n̶k̶ ̶t̶h̶a̶t̶ ̶s̶a̶t̶i̶s̶f̶i̶e̶s̶ ̶C̶o̶n̶d̶i̶t̶i̶o̶n̶ ̶2̶,̶ ̶a̶n̶d̶ ̶s̶i̶m̶i̶l̶a̶r̶l̶y̶ ̶c̶r̶e̶a̶t̶e̶ ̶7̶ ̶o̶t̶h̶e̶r̶ ̶f̶a̶k̶e̶d̶ ̶a̶c̶c̶o̶u̶n̶t̶s̶ ̶t̶o̶ ̶p̶a̶s̶s̶ ̶a̶l̶l̶ ̶t̶h̶e̶ ̶o̶t̶h̶e̶r̶ ̶v̶a̶l̶i̶d̶i̶t̶y̶ ̶c̶h̶e̶c̶k̶s̶.̶

Correction: After this post, @siintemal and Soteria team had a careful discussion on the patch in v0.2.1. It turns out the patch is indeed sufficient to also ensure Condition 1 (due to an invariant enforced in bankman, as pointed out by @nick_soteria)!

Specifically, bank and crate_token are both PDAs uniquely determined by crate_mint , due to an invariant bank.crate_mint == bank.crate_token.mint enforced in the new_bank instruction of bankman:

bankman source code: crate_mint is assigned to bank.crate_token.mint
bankman source code: crate_mint is assigned to bank.crate_mint

Thus, crate_mint to crate_token and bank relations are both 1:1.

Additional Note: With the patch, the input bank account can no longer be faked, but the bank’s curator may still mint CASH tokens for free.

The bankman contract has an authorize_collateral instruction to add a bank to a collateral account by bank’s curator. Thus, a fake collateral account can be authorized by the bank’s curator to satisfy the validity check:

However, we expect that the bank’s curator is a part of the trust base and must not be compromised.

How Soteria Premium detects the vulnerability?

The Soteria Premium Auto Auditor has a checker that automatically detects untrustful accounts (such as bank in this case) .

The checker uses an algorithm that infers the relationships and constraints among all input accounts. If any input account is not validated (i.e., it does not satisfy the inferred constraints) along any code path, then a potential vulnerability will be flagged.

The following shows a screenshot of a vulnerability detected on the bank account:

The tool also flags the other unvalidated accounts, such as the arrow account:

The premium version is currently open to a small number of pilot customers. sec3 team has been working hard with pilot customers to release a version to the community as soon as possible.


sec3 Audit

sec3 (formerly Soteria)is founded by leading minds in the fields of blockchain security and software verification.

We are pleased to provide full audit services to high-impact Dapps on Solana. Please visit sec3.dev or email contact@sec3.dev