T07 · Tool Hijacking and Spoofing
- Location
scripts/compound.ts:88- Finding
Unvalidated Remote Transaction Payload Is Signed and Broadcast
- Content
View full analysis
{ const response = await rateLimitedFetch('https://api.odos.xyz/sor/assemble', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userAddr: userAddress, pathId, simulate: false, }), }); if (!response.ok) return null; const data = await response.json() as OdosAssembleResponse; if (!data.transaction?.to || !data.transaction?.data) return null; return data; } ``` ```ts const assembled = await assembleOdosTransaction(quote.pathId, account.address); if (!assembled) { console.log(` ⚠️ Could not assemble transaction\n`); continue; } console.log(` Executing swap...`); const gasEstimate = BigInt(assembled.transaction.gas); const gasWithBuffer = gasEstimate + (gasEstimate * 50n / 100n); const nonce = await getFreshNonce(publicClient, account.address); try { const swapHash = await walletClient.sendTransaction({ to: assembled.transaction.to as Address, data: assembled.transaction.data as Hex, value: BigInt(assembled.transaction.value), gas: gasWithBuffer, nonce, }); const receipt = await waitForTransaction(publicClient, swapHash); ``` ### Technical Analysis The auto-compound operation obtains the destination address, calldata, native-token value, and gas estimate from the external Odos assembly API. The response is trusted after checking only that the `to` and `data` fields exist. The code does not verify that: - `transaction.to` equals the declared `ODOS_ROUTER`; - `transaction.value` is zero or otherwise expected and bounded; - the calldata invokes an approved Odos function; - the calldata spends only the intended reward to ...[truncated 2294 chars]- Remediation
View remediation
