# Instamojo CampTix: fail-open MAC + webhook Credit without binding payment to order

CampTix Instamojo addon never binds Instamojo payment to the CampTix order; bad MAC fails open to pending and a cheap Credit webhook can complete another order.

- Payout: hidden
- Severity: high
- Program: WordPress
- Found by: 0xbs0d27
- Disclosed: 2026-11-05
- Write-up quality: 7/10
- Original: https://hackerone.com/reports/3941626
- Library page: https://bugbountylibrary.com/r/h1%3A3941626
- Tags: area:web domain:web impact:integrity kind:bounty platform:hackerone sev:high tech:payment tech:php technique:incomplete-fix technique:source-review vuln:auth-bypass vuln:business-logic vuln:signature-replay

**Target:** CampTix Instamojo payment gateway plugin (PHP)
**Root cause:** The Instamojo gateway never persists payment_request_id/amount on the attendee, so payment_return and payment_notify trust the query-string tix_payment_token; the MAC covers only the POST body and the invalid-MAC branch fails open to PENDING.
**Steps:**
1. Open `campt-indian-payment-gateway/inc/instamojo/class-camptix-payment-method-instamojo.php`.
2. In `payment_return()`, note the draft branch calls `payment_result($payment_token, PAYMENT_STATUS_PENDING, ...)` with no Instamojo API verification.
3. In `payment_notify()`, note the `else` branch (MAC mismatch) still calls `payment_result($_REQUEST['tix_payment_token'], PAYMENT_STATUS_PENDING, ...)` — fail-open.
4. In the valid-MAC Credit branch, `COMPLETED` is applied using `$_REQUEST['tix_payment_token']` from the query string, which the MAC (POST body only) does not cover.
5. Confirm checkout builds `$notify_url` with `tix_payment_token` in the query and never `update_post_meta` a payment_request_id/amount.
6. Compare sibling `razorpay/class-camptix-payment-method-razorpay.php`: stores `_razorpay_order_id`/`_razorpay_amount`, uses `hash_equals`, `wp_die` on mismatch/unpaid.
**How found:** Source review of the Instamojo addon against its sibling Razorpay gateway in the same plugin; the missing payment_request_id binding and fail-open else branch stood out versus the bind+hash_equals+wp_die pattern used by Razorpay/SSLCommerz/SurjoPay/Stripe.
**Exploitation:** Attacker submits a draft order and hits payment_return with its token to get PENDING without paying; or sends a forged webhook with a bad MAC to reach PENDING. Strongest path: replay a legitimate Credit webhook for a cheap payment on the same merchant, swapping the query-string tix_payment_token to a different (expensive) order, which is marked COMPLETED → publish.
**Escalation:** Cheap legitimate payment on same merchant reused to complete a different, more expensive order.
**Insight:** When auditing payment plugins, diff each gateway against the most hardened sibling in the same codebase: look for missing persisted order-id/amount binding, MAC computed over body only while the order token rides in the query string, and fail-open else branches that still advance status.

