GHSA-95jh-7r58-xmxw: AVideo has an Authorize.Net Webhook Signature Bypass that Enables Wallet Balance Inflation via Forged Payment Data
SummaryThe Authorize.Net webhook handler at `plugin/AuthorizeNet/webhook.php` contains a signature verification bypass that allows an attacker to forge webhook requests with arbitrary payment amounts and target user IDs. By supplying a valid transaction ID from a small legitimate purchase, the attacker bypasses signature validation and credits arbitrary wallet balances to any user account via attacker-controlled payload fields.DetailsThree flaws combine into an exploit chain:1. Signature Bypass via OR Logic (webhook.php:33)The webhook is rejected only when **both** conditions are true: the signature is invalid **AND** the transaction lookup fails. If the attacker supplies a real transaction ID (e.g., from their own $1 purchase), `getTransactionDetails()` succeeds and returns valid data, so the second condition is false. The invalid signature is silently ignored.2. Payload Values Override API-Fetched Values (AuthorizeNet.php:169-171, webhook.php:44-48)In `analyzeTransactionFromWebhook()`, `users_id` and `amount` are extracted from the attacker-controlled webhook **payload** first:The fallback logic in webhook.php only applies when the analysis values are empty/falsy:Since the forged payload already provides both values, the authoritative API-fetched values are never used.3. Missing Approval Check (webhook.php:61-75)The code checks only that `users_id` and `amount` are non-empty before calling `processSinglePayment()`. The `isApproved` field is computed in `analyzeTransactionFromWebhook()` (line 222-228) but **never verified** before crediting the wallet at line 68-75.PoC**Prerequisites:** Attacker has a low-privileged account on the AVideo instance and has made at least one legitimate small Authorize.Net purchase (e.g., $1.00), noting the transaction ID (e.g., `60123456789`).Immediately after the purchase completes (to race the legitimate webhook), send a forged webhook:The signature check fails (no `X-ANET-Signature` header), but `getTransactionDetails('60123456789')` succeeds because it is a real transaction. The OR condition on line 33 is not fully satisfied, so execution continues.`analyzeTransactionFromWebhook()` uses the forged payload's `amount: 99999.99` and `metadata.users_id: 2`.`processSinglePayment()` credits $99,999.99 to user ID 2's wallet via `addBalance()`.The dedup key is `sha1('net.authorize.payment.authcapture.created' . '60123456789')`, so the legitimate webhook arriving later is silently discarded as a duplicate.The attacker can repeat with new transaction IDs from additional small purchases for cumulative balance inflation.Impact**Wallet balance inflation:** Attacker credits arbitrary amounts to any user's wallet without corresponding payment, bypassing the payment gateway's actual charge amount.**Premium content access:** Inflated wallet balance allows purchasing all paid/premium video content without real payment.**Subscription fraud:** By including `plans_id` in forged metadata, the attacker can activate premium subscriptions (webhook.php:86-134) without corresponding payment.**Financial loss:** Platform owner loses revenue from fraudulently accessed premium content and services.Recommended Fix**1. Reject webhooks with invalid signatures unconditionally** — the transaction lookup should only be used for data enrichment *after* signature validation passes:**2. Use API-fetched values as authoritative** — in webhook.php lines 44-55, invert the precedence so `$txnInfo` values always override payload values:**3. Check `isApproved` before processing** — add a gate before `processSinglePayment()`:
CVSS v3.1: 6.5