# Aurora Withdrawal Logic Error Bugfix Review

Aurora's view() call set the Aurora contract as executor, letting an attacker craft a fake burn proof via a contract returning arbitrary bytes, draining 50,550 ETH from EthCustodian.

- Payout: $1,000,000
- Severity: critical
- Program: Aurora
- Disclosed: 2022-09-20
- Write-up quality: 9/10
- Original: https://immunefi.com/blog/bug-fix-reviews/aurora-withdrawal-logic-error-bugfix-review/
- Library page: https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-aurora-withdrawal-logic-error-bugfix-review
- Tags: domain:web3 impact:funds-loss kind:bounty platform:blog platform:immunefi sev:critical tech:evm tech:rust technique:chained technique:source-review vuln:accounting-error vuln:business-logic vuln:signature-replay

**Target:** Rainbow Bridge EthCustodian withdraw / Aurora NEAR view()
**Root cause:** Aurora's view() execution also sets the Aurora contract as executor_id, so any successful view transaction on NEAR produces a valid execution proof whose return data can be attacker-controlled and deserialized as a BurnResult by EthCustodian.
**Steps:**
1. Deploy Echo contract on Aurora that returns 51 bytes of caller-supplied payload via assembly return.
2. Craft Borsh-serialized BurnResult payload: `0x0000000000f06381960a00000000000011111111222222223333333344444444555555556666666677777777888888889999999911111111` (amount, recipient, ethCustodian).
3. Call Aurora `view()` invoking Echo with the payload; NEAR records a successful tx with executor_id = Aurora and output `0x0033000000f063...` (5-byte status prefix + payload).
4. Wait for the block to propagate to the Ethereum light client, fetch the Merkle proof via NEAR's proof API.
5. Call `withdraw(proofData)` on EthCustodian; require checks pass (executor_id matches, proof valid, deserializes to BurnResult with matching ethCustodian) and ETH is transferred to attacker recipient.
**How found:** Noticed that both call() and view() set the Aurora contract as executor via make_executor, so any view execution yields a valid execution proof; realized the return bytes of a view call are attacker-controllable and only the first 5 bytes (Borsh status) are fixed, which still deserializes cleanly into BurnResult.
**Exploitation:** No burn on NEAR required. Attacker deploys a contract that echoes arbitrary bytes, invokes it via Aurora's view() so the NEAR-side execution proof carries attacker-chosen return data with executor_id = Aurora, then submits that proof to EthCustodian.withdraw on Ethereum to release ETH to an attacker address. 50,550.9 ETH (~$62.9M) was at risk.
**Insight:** When a bridge validates execution proofs by executor_id, audit every entrypoint that sets that executor — read-only/view paths that return attacker-influenced bytes can mint valid proofs without any state change. Check whether the deserializer tolerates a fixed prefix before attacker-controlled fields.
