GHSA-xff3-5c9p-2mr4: New API: Stripe Webhook Signature Bypass via Empty Secret Enables Unlimited Quota Fraud
SummaryA critical vulnerability exists in the Stripe webhook handler that allows an **unauthenticated attacker to forge webhook events** and credit arbitrary quota to their account without making any payment. The vulnerability stems from three compounding flaws:The Stripe webhook endpoint does not reject requests when `StripeWebhookSecret` is empty (the default).When the HMAC secret is empty, any attacker can compute valid webhook signatures, effectively **bypassing signature verification entirely**.The `Recharge` function does not validate that the order's `PaymentMethod` matches the callback source, enabling **cross-gateway exploitation** — an order created via any payment method (e.g., Epay) can be fulfilled through a forged Stripe webhook.Affected Components`controller/topup_stripe.go` — `StripeWebhook()`, `sessionCompleted()``model/topup.go` — `Recharge()`, `RechargeCreem()`, `RechargeWaffo()``controller/topup.go` — `EpayNotify()``controller/topup_creem.go` — `CreemAdaptor.RequestPay()` (missing `PaymentMethod` field)`router/api-router.go` — webhook route registered without any guardCWE Classification**CWE-345**: Insufficient Verification of Data Authenticity**CWE-1188**: Initialization with an Insecure Default (empty webhook secret)**CWE-863**: Incorrect Authorization (cross-gateway order fulfillment)Vulnerability DetailsFlaw 1: Empty Webhook Secret Bypasses Signature VerificationThe `StripeWebhookSecret` setting defaults to an empty string `""`. The Stripe Go SDK (`webhook.ConstructEventWithOptions`) does **not** reject empty secrets — it computes `HMAC-SHA256` with an empty key, producing a deterministic and publicly computable signature.**Vulnerable code** (`controller/topup_stripe.go`):The webhook route is unconditionally registered with **no authentication middleware and no rate limiting**:Flaw 2: Missing `payment_status` VerificationThe `sessionCompleted` handler only checks `status == "complete"` but does **not** verify `payment_status == "paid"`. Stripe's `checkout.session.completed` event can fire with `payment_status = "unpaid"` for delayed payment methods (bank transfer, SEPA, Boleto, etc.) or `payment_status = "no_payment_required"` for 100% discount coupons.Additionally, `checkout.session.async_payment_succeeded` and `checkout.session.async_payment_failed` events are not handled, so delayed payments that ultimately fail are never rolled back.Flaw 3: Cross-Gateway Order Fulfillment (No PaymentMethod Validation)The `model.Recharge()` function (called by the Stripe webhook) looks up orders solely by `trade_no` and does **not** validate that the order's `PaymentMethod` is `"stripe"`:This allows an attacker to create orders through **any** configured payment gateway (Epay, Creem, Waffo) and then complete them via a forged Stripe webhook — even if Stripe itself was never configured.Attack Scenario**Prerequisites**: Any payment method is configured (e.g., Epay) + `StripeWebhookSecret` is empty (default).Attacker registers a user account.Attacker calls `POST /api/user/pay` to create an Epay top-up order (e.g., `amount=10000`). The order is stored with `status=pending`.Attacker queries `GET /api/user/topup/self` to retrieve the `trade_no` of the pending order.Attacker computes `HMAC-SHA256` with an empty key over a crafted `checkout.session.completed` payload containing the stolen `trade_no` as `client_reference_id`.Attacker sends `POST /api/stripe/webhook` with the forged payload and signature header.The server verifies the signature (passes because the secret is empty), calls `Recharge()`, which finds the Epay order by `trade_no`, marks it as `success`, and credits the full quota.Attacker repeats steps 2–6 indefinitely for unlimited credits.**Proof of concept** (pseudocode):RemediationFix 1: Reject webhooks when secret is emptyFix 2: Verify `payment_status` and handle async payment eventsAdd handlers for `checkout.session.async_payment_succeeded` and `checkout.session.async_payment_failed`.Fix 3: Validate PaymentMethod in all recharge functionsAdditional fix: Set PaymentMethod on Creem order creationThe Creem order creation was missing the `PaymentMethod` field entirely:Patched Versions**v0.12.10** — includes all three fixes described above.All users are strongly encouraged to upgrade immediately.Workaround (for users unable to upgrade immediately)If users cannot upgrade to v0.12.10 right away, apply **all** of the following mitigations:**Set `StripeWebhookSecret` to any non-empty value.** Go to the admin panel → Payment → Stripe, and set the Webhook Signing Secret to **any random string** (e.g., `whsec_placeholder_do_not_leave_empty`). It does **not** need to be a real Stripe secret — any non-empty value will prevent the empty-key HMAC forgery. **This is the single most important step** — it closes the primary attack vector. If Stripe payments are used in production, replace with the real secret from the project's [Stripe Dashboard → Webhooks](https://dashboard.stripe.com/webhooks) to ensure legitimate webhooks continue to work.**If Stripe is not in use, block the webhook endpoint.** If users have not configured Stripe payments, use a reverse proxy (Nginx, Caddy, etc.) to deny access to `/api/stripe/webhook`:**Note**: The workaround only mitigates Flaw 1 (empty secret bypass). Flaws 2 (missing `payment_status` check) and 3 (cross-gateway fulfillment) are only fully addressed in v0.12.10. **Upgrading is the only complete fix.**Impact**Financial fraud**: Attacker obtains unlimited API quota without payment.**Operator financial loss**: Fraudulent quota is consumed against upstream AI providers (OpenAI, Anthropic, Google, etc.), charged to the operator.**Silent exploitation**: Fraudulent top-ups appear as normal successful transactions in system logs, making detection difficult.**Wide exposure**: The default insecure configuration means virtually all deployments with any payment method enabled are vulnerable.Timeline**2025-04-15**: Vulnerability reported by [@ChangeYu0229](https://github.com/ChangeYu0229)**2025-04-15**: Vulnerability confirmed and root cause analysis completed**2025-04-15**: Fix developed and applied**2025-04-15**: Patched in v0.12.10Resources[Stripe Webhook Signature Verification Docs](https://docs.stripe.com/webhooks#verify-official-libraries)[Stripe Checkout Fulfillment Guide — Handle async payment methods](https://docs.stripe.com/checkout/fulfillment#async-payment-methods)[CWE-345: Insufficient Verification of Data Authenticity](https://cwe.mitre.org/data/definitions/345.html)[CWE-1188: Initialization with an Insecure Default](https://cwe.mitre.org/data/definitions/1188.html)
CVSS v3.1: 7.1