CVE-2026-49064

Payment forms, Buy now buttons, and Invoicing System | GetPaid <= 2.8.49 - Unauthenticated Information Exposure

mediumExposure of Sensitive Information to an Unauthorized Actor
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.8.50
Patched in
11d
Time to patch

Description

The Payment forms, Buy now buttons, and Invoicing System | GetPaid plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 2.8.49. This makes it possible for unauthenticated attackers to extract sensitive user or configuration data.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Low
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=2.8.49
PublishedJune 8, 2026
Last updatedJune 18, 2026
Affected plugininvoicing

What Changed in the Fix

Changes introduced in v2.8.50

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Vulnerability Research Plan: CVE-2026-49064 (GetPaid Information Exposure) ## 1. Vulnerability Summary The **Payment forms, Buy now buttons, and Invoicing System | GetPaid** plugin (<= 2.8.49) contains an unauthenticated information exposure vulnerability. The plugin's AJAX handler routing logic …

Show full research plan

Vulnerability Research Plan: CVE-2026-49064 (GetPaid Information Exposure)

1. Vulnerability Summary

The Payment forms, Buy now buttons, and Invoicing System | GetPaid plugin (<= 2.8.49) contains an unauthenticated information exposure vulnerability. The plugin's AJAX handler routing logic in WPInv_Ajax::do_wpinv_ajax allows unauthenticated users to trigger specific internal actions via the wpinv-ajax query parameter.

Several actions, specifically get_payment_form and payment_form, are registered as unauthenticated hooks ($nopriv => true in WPInv_Ajax::add_ajax_events). When these actions are called for an existing invoice ID, the plugin renders a payment form pre-filled with the customer's sensitive billing information (First Name, Last Name, Email, Address, Phone, VAT Number) without verifying that the requester is the owner of the invoice or an authorized administrator.

2. Attack Vector Analysis

  • Endpoint: Frontend root with query parameter /?wpinv-ajax=ACTION (handled by WPInv_Ajax::do_wpinv_ajax on the template_redirect hook).
  • Action: get_payment_form
  • Method: GET or POST (GetPaid typically uses $_REQUEST).
  • Required Parameters:
    • wpinv-ajax: get_payment_form
    • invoice_id: The ID of the invoice to exploit.
    • _wpnonce: The wpinv-nonce (required by GetPaid's AJAX handlers).
  • Authentication: Unauthenticated.
  • Preconditions: An invoice must exist in the system, and the attacker must obtain a valid wpinv-nonce (which is publicly exposed on pages containing GetPaid shortcodes).

3. Code Flow

  1. Entry Point: A request is made to /?wpinv-ajax=get_payment_form.
  2. Trigger: WPInv_Ajax::init() hooks do_wpinv_ajax() to template_redirect.
  3. Routing: do_wpinv_ajax() reads $_GET['wpinv-ajax'], sets the query var, and executes do_action( 'wpinv_ajax_get_payment_form' ).
  4. Hook Registration: In WPInv_Ajax::add_ajax_events(), the loop identifies get_payment_form as a $nopriv action and adds:
    add_action( 'wpinv_ajax_get_payment_form', array( __CLASS__, 'get_payment_form' ) );
  5. Execution: WPInv_Ajax::get_payment_form() (not fully shown in snippet, but inferred) retrieves the invoice object using the provided invoice_id and calls wpinv_get_payment_form() or a similar template function.
  6. Information Leak: The template functions (e.g., getpaid_invoice_details_main in includes/wpinv-template-functions.php) render the invoice data. When rendering a payment form for an existing invoice, the customer's billing metadata (stored in wp_postmeta) is extracted and placed into the value attributes of the HTML form inputs.

4. Nonce Acquisition Strategy

The wpinv-nonce is required for AJAX requests but is available to unauthenticated users on any page that initializes GetPaid scripts.

  1. Identify Trigger: GetPaid enqueues wpinv_vars (containing the nonce) on pages using the [getpaid_payment_form] shortcode.
  2. Setup Page: Create a public page containing the shortcode.
    wp post create --post_type=page --post_title="Payment Page" --post_status=publish --post_content='[getpaid_payment_form]'
    
  3. Navigate: Use browser_navigate to visit the newly created page.
  4. Extract: Use browser_eval to extract the nonce from the localized JavaScript variable.
    • Variable: wpinv_vars
    • Key: nonce
    • JS Command: window.wpinv_vars?.nonce

5. Exploitation Strategy

Step 1: Discover Target Invoice ID

Invoices are a custom post type (wpinv_invoice). An attacker can enumerate IDs or look for existing IDs in the environment.

Step 2: Perform the Information Leak Request

Once the wpinv-nonce is obtained, send a request to the wpinv-ajax endpoint.

  • Request Type: http_request
  • Method: GET
  • URL: http://vulnerable.test/?wpinv-ajax=get_payment_form&invoice_id=<TARGET_ID>&_wpnonce=<NONCE>
  • Note: If invoice_id fails, try post_id as the parameter name (used interchangeably in some GetPaid versions).

Step 3: Parse Response

The response will be HTML. The attacker looks for input fields containing customer data:

  • <input ... name="first_name" value="[SENSITIVE]">
  • <input ... name="email" value="[SENSITIVE]">
  • <input ... name="address" value="[SENSITIVE]">

6. Test Data Setup

  1. Create a Customer User:
    wp user create victim victim@example.com --role=subscriber --
    
Research Findings
Static analysis — not yet PoC-verified

Summary

The GetPaid plugin for WordPress is vulnerable to unauthenticated sensitive information exposure via the 'get_payment_form' AJAX action. An attacker can provide an arbitrary invoice ID to retrieve a payment form pre-filled with a customer's billing details, including their name, email, physical address, and phone number, without authorization.

Vulnerable Code

// includes/class-wpinv-ajax.php (~line 118 in add_ajax_events)
'get_payment_form'              => true,

// ... 

// includes/class-wpinv-ajax.php (~line 333 in get_payment_form)
} elseif ( ! empty( $_GET['invoice'] ) ) {
	getpaid_display_invoice_payment_form( (int) urldecode( $_GET['invoice'] ) );
} else {

---

// includes/wpinv-template-functions.php (~line 1286)
function getpaid_display_invoice_payment_form( $invoice_id ) {

    $invoice = wpinv_get_invoice( $invoice_id );

    if ( empty( $invoice ) ) {
		aui()->alert(
			array(
				'type'    => 'warning',
				'content' => __( 'Invoice not found', 'invoicing' ),
            ),
            true
        );
        return;
    }

    if ( $invoice->is_paid() ) {
		aui()->alert(
			array(
				'type'    => 'warning',
				'content' => __( 'Invoice has already been paid', 'invoicing' ),
            ),
            true
        );
        return;
    }

    $form = new GetPaid_Payment_Form( wpinv_get_default_payment_form() );
    $form->invoice = $invoice;
    $form->set_items( $invoice->get_items() );

    $form->display();
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/invoicing/2.8.49/includes/class-wpinv-ajax.php /home/deploy/wp-safety.org/data/plugin-versions/invoicing/2.8.50/includes/class-wpinv-ajax.php
--- /home/deploy/wp-safety.org/data/plugin-versions/invoicing/2.8.49/includes/class-wpinv-ajax.php	2025-11-27 14:43:32.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/invoicing/2.8.50/includes/class-wpinv-ajax.php	2026-06-05 11:09:32.000000000 +0000
@@ -328,10 +328,21 @@
 			} else {
 				getpaid_display_payment_form( $form );
 			}
-} elseif ( ! empty( $_GET['invoice'] ) ) {
-		    getpaid_display_invoice_payment_form( (int) urldecode( $_GET['invoice'] ) );
-        } else {
+		} else if ( ! empty( $_GET['invoice'] ) ) {
+			if ( ! ( ! empty( $_REQUEST['_ajax_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_REQUEST['_ajax_nonce'] ), 'getpaid_form_nonce' ) ) ) {
+				aui()->alert(
+					array(
+						'type'    => 'warning',
+						'content' => __( 'You are not allowed to perform this action.', 'invoicing' ),
+					),
+					true
+				);
+
+				exit;
+			}
+
+			getpaid_display_invoice_payment_form( (int) urldecode( $_GET['invoice'] ) );
+		} else {
 			$items = getpaid_convert_items_to_array( sanitize_text_field( urldecode( $_GET['item'] ) ) );
 		    getpaid_display_item_payment_form( $items );
-        }
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/invoicing/2.8.49/includes/wpinv-template-functions.php /home/deploy/wp-safety.org/data/plugin-versions/invoicing/2.8.50/includes/wpinv-template-functions.php
--- /home/deploy/wp-safety.org/data/plugin-versions/invoicing/2.8.49/includes/wpinv-template-functions.php	2026-06-04 14:56:46.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/invoicing/2.8.50/includes/wpinv-template-functions.php	2026-06-05 11:09:32.000000000 +0000
@@ -1284,36 +1284,46 @@
  * Helper function to display an invoice payment form on the frontend.
  */
 function getpaid_display_invoice_payment_form( $invoice_id ) {
-
-    $invoice = wpinv_get_invoice( $invoice_id );
-
-    if ( empty( $invoice ) ) {
+	$invoice = wpinv_get_invoice( $invoice_id );
 
+	if ( empty( $invoice ) ) {
 		aui()->alert(
 			array(
 				'type'    => 'warning',
 				'content' => __( 'Invoice not found', 'invoicing' ),
-            ),
-            true
-        );
-        return;
-    }
+			),
+			true
+		);
+		return;
+	}
+
+	if ( ! wpinv_user_can_view_invoice( $invoice ) ) {
+		aui()->alert(
+			array(
+				'type'    => 'warning',
+				'content' => __( 'You are not allowed to view this invoice.', 'invoicing' ),
+			),
+			true
+		);
+		return;
+	}
 
-    if ( $invoice->is_paid() ) {
+	if ( $invoice->is_paid() ) {
 		aui()->alert(
 			array(
 				'type'    => 'warning',
 				'content' => __( 'Invoice has already been paid', 'invoicing' ),
-            ),
-            true
-        );
-        return;
-    }
-
-    $form = new GetPaid_Payment_Form( wpinv_get_default_payment_form() );
-    $form->invoice = $invoice;
-    $form->set_items( $invoice->get_items() );
+			),
+			true
+		);
+		return;
+	}
+
+	$form = new GetPaid_Payment_Form( wpinv_get_default_payment_form() );
+	$form->invoice = $invoice;
+	$form->set_items( $invoice->get_items() );
 
-    $form->display();
+	$form->display();
 }

Exploit Outline

The exploit involves three main steps: nonce acquisition, invoice discovery, and data extraction. An attacker first obtains a valid 'wpinv-nonce' (or 'wpinv_vars.nonce' in newer JS localization) by visiting any public page on the target site that uses a GetPaid shortcode, such as [getpaid_payment_form]. Next, the attacker identifies a target invoice ID, which can be discovered via ID enumeration or other leaks. Finally, the attacker sends an unauthenticated GET or POST request to the root frontend URL with the query parameters 'wpinv-ajax=get_payment_form', 'invoice=<TARGET_ID>', and the obtained nonce. The server's response contains the HTML for a payment form where customer billing metadata (Name, Email, Address, Phone, VAT number) is automatically populated into the 'value' attributes of form input fields, allowing the attacker to harvest the sensitive data from the response body.

Check if your site is affected.

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