Payment forms, Buy now buttons, and Invoicing System | GetPaid <= 2.8.49 - Unauthenticated Information Exposure
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:NTechnical Details
What Changed in the Fix
Changes introduced in v2.8.50
Source Code
WordPress.org SVN# 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 byWPInv_Ajax::do_wpinv_ajaxon thetemplate_redirecthook). - Action:
get_payment_form - Method:
GETorPOST(GetPaid typically uses$_REQUEST). - Required Parameters:
wpinv-ajax:get_payment_forminvoice_id: The ID of the invoice to exploit._wpnonce: Thewpinv-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
- Entry Point: A request is made to
/?wpinv-ajax=get_payment_form. - Trigger:
WPInv_Ajax::init()hooksdo_wpinv_ajax()totemplate_redirect. - Routing:
do_wpinv_ajax()reads$_GET['wpinv-ajax'], sets the query var, and executesdo_action( 'wpinv_ajax_get_payment_form' ). - Hook Registration: In
WPInv_Ajax::add_ajax_events(), the loop identifiesget_payment_formas a$noprivaction and adds:add_action( 'wpinv_ajax_get_payment_form', array( __CLASS__, 'get_payment_form' ) ); - Execution:
WPInv_Ajax::get_payment_form()(not fully shown in snippet, but inferred) retrieves the invoice object using the providedinvoice_idand callswpinv_get_payment_form()or a similar template function. - Information Leak: The template functions (e.g.,
getpaid_invoice_details_maininincludes/wpinv-template-functions.php) render the invoice data. When rendering a payment form for an existing invoice, the customer's billing metadata (stored inwp_postmeta) is extracted and placed into thevalueattributes 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.
- Identify Trigger: GetPaid enqueues
wpinv_vars(containing the nonce) on pages using the[getpaid_payment_form]shortcode. - 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]' - Navigate: Use
browser_navigateto visit the newly created page. - Extract: Use
browser_evalto extract the nonce from the localized JavaScript variable.- Variable:
wpinv_vars - Key:
nonce - JS Command:
window.wpinv_vars?.nonce
- Variable:
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_idfails, trypost_idas 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
- Create a Customer User:
wp user create victim victim@example.com --role=subscriber --
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
@@ -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 ); - } @@ -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.