Phase 6: AIOS security plugin with conservative login lockdown config (10 attempts)

This commit is contained in:
Hanson.xyz Dev
2025-11-28 17:19:54 -06:00
parent 78a744ef06
commit abbd3502e8
430 changed files with 137111 additions and 7 deletions
@@ -808,11 +808,31 @@ This implementation plan is a draft for review. Please confirm:
- Site icon/favicon managed via WordPress Customizer (Appearance > Customize > Site Identity)
- Theme color set to #0A0A0A (background dark) for mobile browser chrome
### Phase 6: Performance & Security - PENDING
### Phase 6: Performance & Security - COMPLETED (Partial)
- [x] WebP image conversion via "Converter for Media" plugin
- [x] Nginx rewrite rules for serving WebP to supported browsers
- [x] Server dependencies documented in DEPENDENCIES.md
- [ ] Caching plugin - SKIPPED (not requested)
- [ ] Security plugin - SKIPPED (not requested)
- [ ] Backups plugin - SKIPPED (not requested)
**Technical Details:**
- Plugin: Converter for Media v6.3.2
- Conversion method: PHP GD/Imagick (both available, WebP supported)
- WebP files stored in: `/wp-content/uploads-webpc/`
- Nginx serves WebP when browser sends `Accept: image/webp` header
- No external APIs or services used (fully local processing)
**Files Created:**
- /var/www/html/DEPENDENCIES.md (server dependency documentation)
**Files Modified:**
- /etc/nginx/sites-available/default (WebP rewrite rules added)
### Phase 7: Testing & Launch - PENDING
---
*Document Version: 1.4*
*Document Version: 1.5*
*Last Updated: November 28, 2025*
*Prepared by: Hanson.xyz Development Team*
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
If you believe that you have found a security issue associated with the current release of this plugin, then please report it to the email address with local part security-reports-only and the domain updraftplus.com. If receipt of the email is not acknowledged within 3 working days, then you can try again and also use the inquiry form on the plugin's website.
Do not send emails on any other subject to this address. They will not be acknowledged, regardless of whether they contain pleas to do otherwise; there are inquiry forms and support forums available which are linked within the plugin and easy to find on the plugin website.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting)
* Affected version(s)
* Impact of the issue, including how an attacker might exploit the issue
* Step-by-step instructions to reproduce the issue
* The location of the affected code
* Full paths of source file(s) related to the manifestation of the issue
* Any special configuration required to reproduce the issue
* Any log files that are related to this issue (if possible)
* Proof-of-concept or exploit code (if possible)
This information will help us triage your report more quickly.
Thank you!
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,4 @@
<?php
/**
* Do not modify the files in this folder.
*/
@@ -0,0 +1,626 @@
<?php
/**
* Inits the admin dashboard side of things.
* Main admin file which loads all settings panels and sets up admin menus.
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_Admin_Init {
/**
* Whether the page is admin dashboard page.
*
* @var boolean
*/
private $is_admin_dashboard_page;
/**
* Whether the page is admin AIOS page.
*
* @var boolean
*/
private $is_aiowps_admin_page;
/**
* An array of submenu items
*
* @var array
*/
private $menu_items = array();
/**
* Used in the premium plugin to add submenus
*
* @var string
*/
public $main_menu_page;
/**
* Includes admin dependencies and hook admin actions.
*
* @return void
*/
public function __construct() {
// This class is only initialized if is_admin() is true
if (AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
$this->admin_includes();
add_action('admin_menu', array($this, 'setup_menu_items'));
add_action('admin_menu', array($this, 'create_admin_menus'));
add_action('admin_menu', array($this, 'premium_upgrade_submenu'), 40);
add_action('admin_init', array($this, 'aiowps_csv_download'));
}
add_action('admin_init', array($this, 'hook_admin_notices'));
// Make sure we are on our plugin's menu pages
if ($this->is_aiowps_admin_page()) {
add_action('admin_print_scripts', array($this, 'admin_menu_page_scripts'));
add_action('admin_print_styles', array($this, 'admin_menu_page_styles'));
add_action('init', array($this, 'init_hook_handler_for_admin_side'));
if (class_exists('AIOWPS_PREMIUM')) {
add_filter('admin_footer_text', array($this, 'display_footer_review_message'));
}
}
}
/**
* Sets up the menu items array which is used to build admin menus
*
* @return void
*/
public function setup_menu_items() {
$menu_items = array(
array(
'page_title' => __('Dashboard', 'all-in-one-wp-security-and-firewall'),
'menu_title' => __('Dashboard', 'all-in-one-wp-security-and-firewall'),
'menu_slug' => AIOWPSEC_MAIN_MENU_SLUG,
'render_callback' => array($this, 'handle_dashboard_menu_rendering'),
'icon' => 'dashboard',
'order' => 20,
),
array(
'page_title' => __('Settings', 'all-in-one-wp-security-and-firewall'),
'menu_title' => __('Settings', 'all-in-one-wp-security-and-firewall'),
'menu_slug' => AIOWPSEC_SETTINGS_MENU_SLUG,
'render_callback' => array($this, 'handle_settings_menu_rendering'),
'icon' => 'settings',
'order' => 30,
),
array(
'page_title' => __('User Security', 'all-in-one-wp-security-and-firewall'),
'menu_title' => __('User Security', 'all-in-one-wp-security-and-firewall'),
'menu_slug' => AIOWPSEC_USER_SECURITY_MENU_SLUG,
'render_callback' => array($this, 'handle_user_security_menu_rendering'),
'icon' => 'user_security',
'order' => 40,
),
array(
'page_title' => __('Database Security', 'all-in-one-wp-security-and-firewall'),
'menu_title' => __('Database Security', 'all-in-one-wp-security-and-firewall'),
'menu_slug' => AIOWPSEC_DB_SEC_MENU_SLUG,
'render_callback' => array($this, 'handle_database_menu_rendering'),
'icon' => 'database_security',
'display_condition_callback' => 'is_super_admin',
'order' => 50,
),
array(
'page_title' => __('File Security', 'all-in-one-wp-security-and-firewall'),
'menu_title' => __('File Security', 'all-in-one-wp-security-and-firewall'),
'menu_slug' => AIOWPSEC_FILESYSTEM_MENU_SLUG,
'render_callback' => array($this, 'handle_filesystem_menu_rendering'),
'icon' => 'filesystem_security',
'order' => 60,
),
array(
'page_title' => __('Firewall', 'all-in-one-wp-security-and-firewall'),
'menu_title' => __('Firewall', 'all-in-one-wp-security-and-firewall'),
'menu_slug' => AIOWPSEC_FIREWALL_MENU_SLUG,
'render_callback' => array($this, 'handle_firewall_menu_rendering'),
'icon' => 'firewall',
'order' => 70,
),
array(
'page_title' => __('Brute Force', 'all-in-one-wp-security-and-firewall'),
'menu_title' => __('Brute Force', 'all-in-one-wp-security-and-firewall'),
'menu_slug' => AIOWPSEC_BRUTE_FORCE_MENU_SLUG,
'render_callback' => array($this, 'handle_brute_force_menu_rendering'),
'icon' => 'brute_force',
'order' => 80,
),
array(
'page_title' => __('Spam Prevention', 'all-in-one-wp-security-and-firewall'),
'menu_title' => __('Spam Prevention', 'all-in-one-wp-security-and-firewall'),
'menu_slug' => AIOWPSEC_SPAM_MENU_SLUG,
'render_callback' => array($this, 'handle_spam_menu_rendering'),
'icon' => 'spam_prevention',
'order' => 90,
),
array(
'page_title' => __('Scanner', 'all-in-one-wp-security-and-firewall'),
'menu_title' => __('Scanner', 'all-in-one-wp-security-and-firewall'),
'menu_slug' => AIOWPSEC_FILESCAN_MENU_SLUG,
'render_callback' => array($this, 'handle_filescan_menu_rendering'),
'icon' => 'scanner',
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
'order' => 100,
),
array(
'page_title' => __('Tools', 'all-in-one-wp-security-and-firewall'),
'menu_title' => __('Tools', 'all-in-one-wp-security-and-firewall'),
'menu_slug' => AIOWPSEC_TOOLS_MENU_SLUG,
'render_callback' => array($this, 'handle_tools_menu_rendering'),
'icon' => 'tools',
'order' => 110,
),
);
$menu_items = apply_filters('aiowpsecurity_menu_items', $menu_items);
$this->menu_items = array_filter($menu_items, 'AIOWPSecurity_Utility::should_display_item');
}
/**
* Function to get the menu items array
*
* @return array
*/
public function get_menu_items() {
return $this->menu_items;
}
/**
* This function creates and outputs the csv file for download
*
* @param array $items - the content
* @param array $export_keys - the keys for the content
* @param string $filename - the filename
*
* @return void
*/
public static function aiowps_output_csv($items, $export_keys, $filename = 'data.csv') {
header("Content-Type: text/csv; charset=utf-8");
header("Content-Disposition: attachment; filename=".$filename);
header("Pragma: no-cache");
header("Expires: 0");
$output = fopen('php://output', 'w'); //open output stream
fputcsv($output, $export_keys, ',', '"', '\\'); // let's put column names first
foreach ($items as $item) {
$csv_line = array();
foreach ($export_keys as $key => $value) {
if (isset($item[$key])) {
$csv_line[] = ('created' == $key) ? AIOWPSecurity_Utility::convert_timestamp($item[$key]) : $item[$key];
}
}
fputcsv($output, $csv_line, ',', '"', '\\');
}
}
/**
* This function will get the content that we want to export as CSV and send it to the download function
*
* @return void
*/
public function aiowps_csv_download() {
global $aio_wp_security;
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- PCP warning. Nonce check in two lines.
if (isset($_POST['aiowps_export_404_event_logs_to_csv'])) {//Export 404 event logs
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- It IS the nonce, so...
$nonce = isset($_REQUEST['_wpnonce']) ? sanitize_key(wp_unslash($_REQUEST['_wpnonce'])) : '';
$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($nonce, 'aiowpsec-export-404-event-logs-to-csv-nonce');
if (is_wp_error($result)) {
$aio_wp_security->debug_logger->log_debug($result->get_error_message(), 4);
die(esc_html($result->get_error_message()));
}
include_once 'wp-security-list-404.php'; //For rendering the AIOWPSecurity_List_Table in tab1
$event_list_404 = new AIOWPSecurity_List_404(); //For rendering the AIOWPSecurity_List_Table in tab1
$event_list_404->prepare_items(true);
$export_keys = array(
'id' => __('Id', 'all-in-one-wp-security-and-firewall'),
'event_type' => __('Event Type', 'all-in-one-wp-security-and-firewall'),
'ip_or_host' => __('IP Address', 'all-in-one-wp-security-and-firewall'),
'url' => __('Attempted URL', 'all-in-one-wp-security-and-firewall'),
'referer_info' => __('Referer', 'all-in-one-wp-security-and-firewall'),
'created' => __('Date and time', 'all-in-one-wp-security-and-firewall'),
'status' => __('Lock Status', 'all-in-one-wp-security-and-firewall'),
);
AIOWPSecurity_Utility::output_csv($event_list_404->items, $export_keys, '404_event_logs.csv');
exit();
}
}
/**
* Check whether current admin page is All In One WP Security admin page or not.
*
* @return boolean True if All In One WP Security admin page, Otherwise false.
*/
private function is_aiowps_admin_page() {
if (isset($this->is_aiowps_admin_page)) {
return $this->is_aiowps_admin_page;
}
global $pagenow;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce on this _GET.
$this->is_aiowps_admin_page = (AIOWPSecurity_Utility_Permissions::has_manage_cap() && 'admin.php' == $pagenow && isset($_GET['page']) && false !== strpos(sanitize_title(wp_unslash($_GET['page'])), AIOWPSEC_MENU_SLUG_PREFIX));
return $this->is_aiowps_admin_page;
}
/**
* Hook admin notices on admin dashboard page and admin AIOS pages.
*
* @return void
*/
public function hook_admin_notices() {
if (!current_user_can('update_plugins')) {
return;
}
// If none of the admin dashboard page or the AIOS page, Then bail
if (!$this->is_admin_dashboard_page() && !$this->is_aiowps_admin_page()) {
return;
}
add_action('all_admin_notices', array($this, 'render_admin_notices'));
}
/**
* Check whether current admin page is Admin Dashboard page or not.
*
* @return boolean True if Admin Dashboard page, Otherwise false.
*/
private function is_admin_dashboard_page() {
if (isset($this->is_admin_dashboard_page)) {
return $this->is_admin_dashboard_page;
}
global $pagenow;
$this->is_admin_dashboard_page = 'index.php' == $pagenow;
return $this->is_admin_dashboard_page;
}
/**
* Render admin notices.
*
* @return void
*/
public function render_admin_notices() {
global $aio_wp_security;
$custom_notice_ids = array_merge(AIOS_Abstracted_Ids::custom_admin_notice_ids(), AIOS_Abstracted_Ids::htaccess_to_php_feature_notice_ids());
foreach ($custom_notice_ids as $custom_admin_notice_id) {
$aio_wp_security->notices->do_notice($custom_admin_notice_id, $custom_admin_notice_id);
}
// Bail if the premium plugin is active and does not show ads.
if (AIOWPSecurity_Utility_Permissions::is_premium_installed()) return;
$installed_at = $aio_wp_security->notices->get_aiowps_plugin_installed_timestamp();
$time_now = $aio_wp_security->notices->get_time_now();
$installed_for = $time_now - $installed_at;
$dismissed_dash_notice_until = (int) $aio_wp_security->configs->get_value('dismissdashnotice');
if ($this->is_admin_dashboard_page() && ($installed_at && $time_now > $dismissed_dash_notice_until && $installed_for > (14 * 86400)) || (defined('AIOWPSECURITY_FORCE_DASHNOTICE') && AIOWPSECURITY_FORCE_DASHNOTICE)) {
$aio_wp_security->include_template('notices/thanks-for-using-main-dash.php');
} elseif ($this->is_aiowps_admin_page() && $installed_at && $installed_for > 14*86400) {
$aio_wp_security->notices->do_notice(false, 'top');
}
}
/**
* This function will include any files needed for the admin dashboard
*
* @return void
*/
private function admin_includes() {
include_once('wp-security-admin-menu.php');
}
/**
* Enqueue admin JavaScripts.
*
* @return Void
*/
public function admin_menu_page_scripts() {
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
return;
}
wp_enqueue_script('jquery');
wp_enqueue_script('postbox');
wp_enqueue_script('dashboard');
wp_enqueue_script('thickbox');
wp_enqueue_script('media-upload');
wp_enqueue_script('chart-bundle', AIO_WP_SECURITY_URL . '/includes/chartjs/Chart.bundle.min.js', array(), AIO_WP_SECURITY_VERSION, true);
wp_enqueue_script('chartjs-gauge', AIO_WP_SECURITY_URL . '/includes/chartjs/chartjs-gauge.min.js', array(), AIO_WP_SECURITY_VERSION, true);
wp_register_script('jquery-blockui', AIO_WP_SECURITY_URL.'/includes/blockui/jquery.blockUI.js', array('jquery'), AIO_WP_SECURITY_VERSION, true);
wp_enqueue_script('jquery-blockui');
wp_register_script('aiowpsec-admin-js', AIO_WP_SECURITY_URL. '/js/wp-security-admin-script.js', array('jquery'), AIO_WP_SECURITY_VERSION, true);
wp_enqueue_script('aiowpsec-admin-js');
wp_localize_script('aiowpsec-admin-js',
'aios_data',
array(
'ajax_nonce' => wp_create_nonce('wp-security-ajax-nonce'),
)
);
wp_localize_script('aiowpsec-admin-js',
'aios_trans',
array(
'unexpected_response' => __('Unexpected response:', 'all-in-one-wp-security-and-firewall'),
'copied' => __('Copied', 'all-in-one-wp-security-and-firewall'),
'no_import_file' => __('You have not yet selected a file to import.', 'all-in-one-wp-security-and-firewall'),
'processing' => __('Processing...', 'all-in-one-wp-security-and-firewall'),
'invalid_domain' => __('Please enter a valid IP address or domain name.', 'all-in-one-wp-security-and-firewall'),
'logo' => AIO_WP_SECURITY_URL.'/images/plugin-logos/icon-aios-rgb.svg',
'saving' => __('Saving...', 'all-in-one-wp-security-and-firewall'),
'deleting' => __('Deleting...', 'all-in-one-wp-security-and-firewall'),
'blocking' => __('Blocking...', 'all-in-one-wp-security-and-firewall'),
'unlocking' => __('Unlocking...', 'all-in-one-wp-security-and-firewall'),
'clearing' => __('Clearing...', 'all-in-one-wp-security-and-firewall'),
'importing' => __('Importing...', 'all-in-one-wp-security-and-firewall'),
'exporting' => __('Exporting...', 'all-in-one-wp-security-and-firewall'),
'refreshing' => __('Refreshing...', 'all-in-one-wp-security-and-firewall'),
'scanning' => __('Scanning...', 'all-in-one-wp-security-and-firewall'),
'close' => __('Close', 'all-in-one-wp-security-and-firewall'),
'completed' => __('Completed.', 'all-in-one-wp-security-and-firewall'),
'refreshed' => __('Refreshed.', 'all-in-one-wp-security-and-firewall'),
'deleted' => __('Deleted.', 'all-in-one-wp-security-and-firewall'),
'show_info' => __('show more', 'all-in-one-wp-security-and-firewall'),
'hide_info' => __('hide', 'all-in-one-wp-security-and-firewall'),
'show_notices' => __('But the following notices have been raised', 'all-in-one-wp-security-and-firewall'),
'disabling' => __('Disabling...', 'all-in-one-wp-security-and-firewall'),
'setting_up_firewall' => __('Setting up firewall...', 'all-in-one-wp-security-and-firewall'),
'downgrading_firewall' => __('Downgrading firewall...', 'all-in-one-wp-security-and-firewall'),
'maintenance_mode_enabled' => __('Maintenance mode is currently enabled.', 'all-in-one-wp-security-and-firewall') . ' ' . __('Remember to disable it when you are done.', 'all-in-one-wp-security-and-firewall'),
'maintenance_mode_disabled' => __('Maintenance mode is currently disabled.', 'all-in-one-wp-security-and-firewall'),
)
);
wp_register_script('aiowpsec-pw-tool-js', AIO_WP_SECURITY_URL. '/js/password-strength-tool.js', array('jquery'), AIO_WP_SECURITY_VERSION, true); // We will enqueue this in the user acct menu class
wp_localize_script('aiowpsec-pw-tool-js',
'aios_pwtool_trans',
array(
'years' => __('year(s)', 'all-in-one-wp-security-and-firewall'),
'months' => __('month(s)', 'all-in-one-wp-security-and-firewall'),
'days' => __('day(s)', 'all-in-one-wp-security-and-firewall'),
'hours' => __('hour(s)', 'all-in-one-wp-security-and-firewall'),
'minutes' => __('minute(s)', 'all-in-one-wp-security-and-firewall'),
'seconds' => __('second(s)', 'all-in-one-wp-security-and-firewall'),
'less_than_one_second' => __('less than one second', 'all-in-one-wp-security-and-firewall')
)
);
}
/**
* Enqueue admin styles.
*
* @return Void
*/
public function admin_menu_page_styles() {
wp_enqueue_style('dashboard');
wp_enqueue_style('thickbox');
wp_enqueue_style('global');
wp_enqueue_style('wp-admin');
$admin_css_version = (defined('WP_DEBUG') && WP_DEBUG) ? time() : filemtime(AIO_WP_SECURITY_PATH. '/css/wp-security-admin-styles.css');
wp_enqueue_style('aiowpsec-admin-css', AIO_WP_SECURITY_URL. '/css/wp-security-admin-styles.css', array(), $admin_css_version);
}
/**
* Sets up various class and tasks needed for the admin dashboard
*
* @return void
*/
public function init_hook_handler_for_admin_side() {
$this->initialize_feature_manager();
$this->do_other_admin_side_init_tasks();
}
/**
* Show footer review message and link.
*
* @return string
*/
public function display_footer_review_message() {
$message = sprintf(
/* translators: 1: Product Name, 2: Rating, 3: Trustpilot URL, 4: G2 URL */
__('Enjoyed %1$s? Please leave us a %2$s rating on %3$s or %4$s', 'all-in-one-wp-security-and-firewall').' '.__('We really appreciate your support!', 'all-in-one-wp-security-and-firewall'),
'<b>' . htmlspecialchars('All In One Security') . '</b>',
'<span style="color:#2271b1">&starf;&starf;&starf;&starf;&starf;</span>',
'<a href="https://uk.trustpilot.com/review/aiosplugin.com" target="_blank">Trustpilot</a>',
'<a href="https://www.g2.com/products/all-in-one-wp-security-firewall/reviews" target="_blank">G2.com</a>'
);
return $message;
}
/**
* This function checks if the feature manager is initialized and initializes it if it is not then checks the feature status and recalculates the points
*
* @return void
*/
private function initialize_feature_manager() {
if (!isset($aiowps_feature_mgr)) {
$aiowps_feature_mgr = new AIOWPSecurity_Feature_Item_Manager();
$aiowps_feature_mgr->check_feature_status_and_recalculate_points();
$GLOBALS['aiowps_feature_mgr'] = $aiowps_feature_mgr;
}
}
/**
* Other admin side init tasks.
*
* @return Void
*/
private function do_other_admin_side_init_tasks() {
global $aio_wp_security;
//***New Feature improvement for Cookie Based Brute Force Protection***//
// The old "test cookie" used to be too easy to guess because someone could just read the code and get the value.
//So now we will drop a more secure test cookie using a 10 digit random string
if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention')) {
// This code is for users who had this feature saved using an older release. This will drop the new more secure test cookie to the browser
$test_cookie_name_saved = $aio_wp_security->configs->get_value('aiowps_cookie_brute_test');
if (empty($test_cookie_name_saved)) {
$random_suffix = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(10);
$test_cookie_name = 'aiowps_cookie_test_'.$random_suffix;
$aio_wp_security->configs->set_value('aiowps_cookie_brute_test', $test_cookie_name, true);
AIOWPSecurity_Utility::set_cookie_value($test_cookie_name, '1');
}
}
// For cookie test form submission case
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- PCP warning. No nonce on this _GET.
if (isset($_GET['page']) && AIOWPSEC_BRUTE_FORCE_MENU_SLUG == $_GET['page'] && isset($_GET['tab']) && 'cookie-based-brute-force-prevention' == $_GET['tab']) {
if (isset($_POST['aiowps_do_cookie_test_for_bfla'])) {
$random_suffix = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(10);
$test_cookie_name = 'aiowps_cookie_test_'.$random_suffix;
$aio_wp_security->configs->set_value('aiowps_cookie_brute_test', $test_cookie_name, true);
AIOWPSecurity_Utility::set_cookie_value($test_cookie_name, '1');
$cur_url = "admin.php?page=".AIOWPSEC_BRUTE_FORCE_MENU_SLUG."&tab=cookie-based-brute-force-prevention";
$redirect_url = AIOWPSecurity_Utility::add_query_data_to_url($cur_url, 'aiowps_cookie_test', "1");
AIOWPSecurity_Utility::redirect_to_url($redirect_url);
}
if (isset($_REQUEST['aiowps_cookie_test'])) {
$test_cookie = $aio_wp_security->configs->get_value('aiowps_cookie_brute_test');
$cookie_val = AIOWPSecurity_Utility::get_cookie_value($test_cookie);
if (empty($cookie_val)) {
$aio_wp_security->configs->set_value('aiowps_cookie_test_success', '');
} else {
$aio_wp_security->configs->set_value('aiowps_cookie_test_success', '1');
}
$aio_wp_security->configs->save_config();//save the value
}
}
}
/**
* Adds admin menu page and all submenus to the WordPress dashboard
*
* @return void
*/
public function create_admin_menus() {
$menu_icon_url = AIO_WP_SECURITY_URL.'/images/aios-plugin-icon.svg';
$this->main_menu_page = add_menu_page(__('AIOS', 'all-in-one-wp-security-and-firewall'), __('AIOS', 'all-in-one-wp-security-and-firewall'), apply_filters('aios_management_permission', 'manage_options'), AIOWPSEC_MAIN_MENU_SLUG, array($this, 'handle_dashboard_menu_rendering'), $menu_icon_url);
foreach ($this->menu_items as $menu_item) {
add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, $menu_item['page_title'], $menu_item['menu_title'], apply_filters('aios_management_permission', 'manage_options'), $menu_item['menu_slug'], $menu_item['render_callback'], $menu_item['order']);
}
do_action('aiowpsecurity_admin_menu_created');
}
/**
* Adds submenu link for premium upgrade tab.
*
* @return Void
*/
public function premium_upgrade_submenu() {
if (!AIOWPSecurity_Utility_Permissions::is_premium_installed()) {
global $submenu;
$submenu[AIOWPSEC_MAIN_MENU_SLUG][] = array(__('Premium Upgrade', 'all-in-one-wp-security-and-firewall'), apply_filters('aios_management_permission', 'manage_options'), 'admin.php?page='.AIOWPSEC_MAIN_MENU_SLUG.'&tab=premium-upgrade');
}
}
/**
* Renders 'Dashboard' submenu page.
*
* @return Void
*/
public function handle_dashboard_menu_rendering() {
include_once('wp-security-dashboard-menu.php');
new AIOWPSecurity_Dashboard_Menu();
}
/**
* Renders 'Settings' submenu page.
*
* @return Void
*/
public function handle_settings_menu_rendering() {
include_once('wp-security-settings-menu.php');
new AIOWPSecurity_Settings_Menu();
}
/**
* Renders 'User Security' submenu page.
*
* @return Void
*/
public function handle_user_security_menu_rendering() {
include_once('wp-security-user-security-menu.php');
new AIOWPSecurity_User_Security_Menu();
}
/**
* Renders 'Database Security' submenu page.
*
* @return Void
*/
public function handle_database_menu_rendering() {
include_once('wp-security-database-menu.php');
new AIOWPSecurity_Database_Menu();
}
/**
* Renders 'Filesystem Security' submenu page.
*
* @return Void
*/
public function handle_filesystem_menu_rendering() {
include_once('wp-security-filesystem-menu.php');
new AIOWPSecurity_Filesystem_Menu();
}
/**
* Renders 'Firewall' submenu page.
*
* @return Void
*/
public function handle_firewall_menu_rendering() {
include_once('wp-security-firewall-menu.php');
new AIOWPSecurity_Firewall_Menu();
}
/**
* Renders 'Brute Force' submenu page.
*
* @return Void
*/
public function handle_brute_force_menu_rendering() {
include_once('wp-security-brute-force-menu.php');
new AIOWPSecurity_Brute_Force_Menu();
}
/**
* Renders 'Spam Prevention' submenu page.
*
* @return Void
*/
public function handle_spam_menu_rendering() {
include_once('wp-security-spam-menu.php');
new AIOWPSecurity_Spam_Menu();
}
/**
* Renders 'Scanner' submenu page.
*
* @return Void
*/
public function handle_filescan_menu_rendering() {
include_once('wp-security-filescan-menu.php');
new AIOWPSecurity_Filescan_Menu();
}
/**
* Renders 'Tools' submenu page.
*
* @return Void
*/
public function handle_tools_menu_rendering() {
include_once('wp-security-tools-menu.php');
new AIOWPSecurity_Tools_Menu();
}
} // End of class
@@ -0,0 +1,280 @@
<?php
/**
* Parent class for all admin menu classes
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
abstract class AIOWPSecurity_Admin_Menu {
/**
* Specify the menu slug
*
* @var string
*/
protected $menu_page_slug;
/**
* Specify all the tabs of this menu
*
* @var array
*/
protected $menu_tabs;
/**
* Constructor adds a admin menu
*
* @param string $title - Title of menu to be rendered
*/
public function __construct($title) {
$this->setup_menu_tabs();
$this->render_page($title);
}
/**
* Render the menu page
*
* @param string $title - the page title
*
* @return void
*/
protected function render_page($title) {
$current_tab = $this->get_current_tab();
?>
<div class="wrap">
<h2><?php echo esc_html($title); ?></h2>
<?php $this->render_tabs($current_tab); ?>
<div id="poststuff">
<div id="post-body">
<?php call_user_func($this->menu_tabs[$current_tab]['render_callback']); ?>
</div>
</div>
</div>
<?php
}
/**
* Render the menu tabs for this page
*
* @param string $current_tab - the current tab
*
* @return void
*/
protected function render_tabs($current_tab) {
echo '<h2 class="nav-tab-wrapper">';
foreach ($this->menu_tabs as $tab_key => $tab_info) {
$active = $current_tab == $tab_key ? 'nav-tab-active' : '';
echo '<a class="nav-tab ' . esc_attr($active) . '" href="?page=' . esc_attr($this->menu_page_slug) . '&tab=' . esc_attr($tab_key) . '">' . esc_html($tab_info['title']) . '</a>';
}
echo '</h2>';
}
/**
* Get valid current tab slug.
*
* @return string - current valid tab slug or empty string
*/
protected function get_current_tab() {
if (is_array($this->menu_tabs) && !empty($this->menu_tabs)) {
$tab_keys = array_keys($this->menu_tabs);
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce available.
if (empty($_GET['tab'])) {
return $tab_keys[0];
} else {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce available.
$current_tab = sanitize_text_field(wp_unslash($_GET['tab']));
return in_array($current_tab, $tab_keys) ? $current_tab : $tab_keys[0];
}
} else {
return '';
}
}
/**
* This function checks to see if there is a display condition for the tab and if so runs it otherwise it returns true to display the tab
*
* @param array $tab_info - the tab information array contains keys like title, render_callback and display_condition_callback
*
* @return boolean - true if the tab should be displayed or false to hide it
*/
protected function should_display_tab($tab_info) {
return AIOWPSecurity_Utility::apply_callback_filter($tab_info, 'display_condition_callback');
}
/**
* Shows postbox for settings menu
*
* @param string $id - css ID for postbox
* @param string $title - title of the postbox section
* @param string $content - the content of the postbox
**/
protected function postbox_toggle($id, $title, $content) {
//Always send string with translation markers in it
?>
<div id="<?php echo esc_attr($id); ?>" class="postbox">
<div class="handlediv" title="<?php echo esc_html__('Press to toggle', 'all-in-one-wp-security-and-firewall'); ?>"><br /></div>
<h3 class="hndle"><span><?php echo esc_html($title); ?></span></h3>
<div class="inside">
<?php echo wp_kses_post($content); ?>
</div>
</div>
<?php
}
/**
* Display a postbox with a title and content.
*
* This function generates and outputs HTML markup for a postbox with a specified title
* and content. The title and content should be provided as parameters, and they will be
* automatically escaped for security. The function ensures that translation markers are
* included in the output strings.
*
* @param string $title - The title of the postbox.
* @param string $content - The content to be displayed inside the postbox.
*
* @return void
*/
public function postbox($title, $content) {
// Always send string with translation markers in it
?>
<div class="postbox">
<h3 class="hndle"><label for="title"><?php echo esc_html($title); ?></label></h3>
<div class="inside">
<?php echo wp_kses_post($content); ?>
</div>
</div>
<?php
}
/**
* Render settings successfully updated message
*
* @param bool $return_instead_of_echo - This is used for when the function needs to return the message
*
* @return string|void
*/
public function show_msg_settings_updated($return_instead_of_echo = false) {
$message = '<div id="aios_message" class="updated fade"><p><strong>';
$message .= esc_html__('The settings have been successfully updated.', 'all-in-one-wp-security-and-firewall');
$message .= '</strong></p></div>';
if ($return_instead_of_echo) return $message;
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Variable contains escaped HTML.
echo $message;
}
/**
* Render settings successfully updated message
*
* @param bool $return_instead_of_echo - This is used for when the function needs to return the message
*
* @return string|void
*/
public static function show_msg_settings_updated_st($return_instead_of_echo = false) {
$message = '<div id="aios_message" class="updated fade"><p><strong>';
$message .= esc_html__('The settings have been successfully updated.', 'all-in-one-wp-security-and-firewall');
$message .= '</strong></p></div>';
if ($return_instead_of_echo) return $message;
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Variable contains escaped HTML.
echo $message;
}
/**
* Renders record(s) successfully deleted message at top of page.
*
* @param bool $return_instead_of_echo - This is used for when the function needs to return the message
* @return mixed
*/
public static function show_msg_record_deleted_st($return_instead_of_echo = false) {
return AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected record(s) has been deleted successfully.', 'all-in-one-wp-security-and-firewall'), $return_instead_of_echo);
}
/**
* Renders record(s) unsuccessfully deleted message at top of page.
*
* @param bool $return_instead_of_echo - This is used for when the function needs to return the message
* @return mixed
*/
public static function show_msg_record_not_deleted_st($return_instead_of_echo = false) {
return AIOWPSecurity_Admin_Menu::show_msg_error_st(__('The selected record(s) have failed to delete.', 'all-in-one-wp-security-and-firewall'), $return_instead_of_echo);
}
/**
* Render successfully updated message
*
* @param string $msg - This contains the message to show
* @param bool $return_instead_of_echo - This is used for when the function needs to return the message
*
* @return string|void
*/
public function show_msg_updated($msg, $return_instead_of_echo = false) {
$message = '<div id="aios_message" class="updated fade"><p><strong>';
$message .= wp_kses_post($msg);
$message .= '</strong></p></div>';
if ($return_instead_of_echo) return $message;
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Variable contains escaped HTML.
echo $message;
}
/**
* Render successfully updated message
*
* @param string $msg - This contains the message to show
* @param bool $return_instead_of_echo - This is used for when the function needs to return the message
*
* @return string|void
*/
public static function show_msg_updated_st($msg, $return_instead_of_echo = false) {
$message = '<div id="aios_message" class="updated fade"><p><strong>';
$message .= wp_kses_post($msg);
$message .= '</strong></p></div>';
if ($return_instead_of_echo) return $message;
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Variable contains escaped HTML.
echo $message;
}
/**
* Render error message
*
* @param string $error_msg - This contains the message to show
* @param bool $return_instead_of_echo - This is used for when the function needs to return the message
*
* @return string|void
*/
public function show_msg_error($error_msg, $return_instead_of_echo = false) {
$message = '<div id="aios_message" class="error"><p><strong>';
$message .= wp_kses_post($error_msg);
$message .= '</strong></p></div>';
if ($return_instead_of_echo) return $message;
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Variable contains escaped HTML.
echo $message;
}
/**
* Render error message
*
* @param string $error_msg - This contains the message to show
* @param bool $return_instead_of_echo - This is used for when the function needs to return the message
*
* @return string|void
*/
public static function show_msg_error_st($error_msg, $return_instead_of_echo = false) {
$message = '<div id="aios_message" class="error"><p><strong>';
$message .= wp_kses_post($error_msg);
$message .= '</strong></p></div>';
if ($return_instead_of_echo) return $message;
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Variable contains escaped HTML.
echo $message;
}
protected function start_buffer() {
ob_start();
}
protected function end_buffer_and_collect() {
$output = ob_get_contents();
ob_end_clean();
return $output;
}
}
@@ -0,0 +1,176 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
/**
* AIOWPSecurity_Brute_Force_Menu class for brute force prevention.
*
* @access public
*/
class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu {
/**
* Blacklist menu slug
*
* @var string
*/
protected $menu_page_slug = AIOWPSEC_BRUTE_FORCE_MENU_SLUG;
/**
* Constructor adds menu for Brute force
*/
public function __construct() {
parent::__construct(__('Brute force', 'all-in-one-wp-security-and-firewall'));
}
/**
* This function will setup the menus tabs by setting the array $menu_tabs
*
* @return void
*/
protected function setup_menu_tabs() {
$menu_tabs = array(
'rename-login' => array(
'title' => __('Rename login page', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_rename_login'),
),
'cookie-based-brute-force-prevention' => array(
'title' => __('Cookie based brute force prevention', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_cookie_based_brute_force_prevention'),
'display_condition_callback' => 'is_main_site',
),
'captcha-settings' => array(
'title' => __('CAPTCHA settings', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_captcha_settings'),
),
'login-whitelist' => array(
'title' => __('Login whitelist', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_login_whitelist'),
),
'404-detection' => array(
'title' => __('404 detection', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_404_detection'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'honeypot' => array(
'title' => __('Honeypot', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_honeypot'),
),
);
$this->menu_tabs = array_filter($menu_tabs, array($this, 'should_display_tab'));
}
/**
* Rename login page tab.
*
* @global $aio_wp_security
* @global $aiowps_feature_mgr
*/
protected function render_rename_login() {
global $aio_wp_security, $aiowps_feature_mgr;
if (get_option('permalink_structure')) {
$home_url = trailingslashit(home_url());
} else {
$home_url = trailingslashit(home_url()) . '?';
}
$aio_wp_security->include_template('wp-admin/brute-force/rename-login.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr, 'home_url' => $home_url));
}
/**
* Cookie based brute force prevention tab.
*
* @global $aio_wp_security
* @global $aiowps_feature_mgr
*
* @return void
*/
protected function render_cookie_based_brute_force_prevention() {
global $aio_wp_security;
global $aiowps_feature_mgr;
$aio_wp_security->include_template('wp-admin/brute-force/cookie-based-brute-force-prevention.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr));
}
/**
* Login captcha tab.
*
* @global $aio_wp_security
* @global $aiowps_feature_mgr
*
* @return void
*/
protected function render_captcha_settings() {
global $aio_wp_security, $aiowps_feature_mgr;
$supported_captchas = $aio_wp_security->captcha_obj->get_supported_captchas();
$captcha_themes = $aio_wp_security->captcha_obj->get_captcha_themes();
$captcha_theme = 'auto';
if ('cloudflare-turnstile' == $aio_wp_security->configs->get_value('aiowps_default_captcha')) $captcha_theme = $aio_wp_security->configs->get_value('aiowps_turnstile_theme');
if ('cloudflare-turnstile' == $aio_wp_security->configs->get_value('aiowps_default_captcha') && false === $aio_wp_security->captcha_obj->cloudflare_turnstile_verify_configuration($aio_wp_security->configs->get_value('aiowps_turnstile_site_key'), $aio_wp_security->configs->get_value('aiowps_turnstile_secret_key'))) {
echo '<div class="notice notice-warning aio_red_box"><p>' . esc_html__('Your Cloudflare Turnstile configuration is invalid.', 'all-in-one-wp-security-and-firewall').' ' . esc_html__('Please enter the correct Cloudflare Turnstile keys below to use the Turnstile feature.', 'all-in-one-wp-security-and-firewall').'</p></div>';
}
if ('1' == $aio_wp_security->configs->get_value('aios_google_recaptcha_invalid_configuration')) {
echo '<div class="notice notice-warning aio_red_box"><p>' . esc_html__('Your Google reCAPTCHA configuration is invalid.', 'all-in-one-wp-security-and-firewall') . ' ' . esc_html__('Please enter the correct reCAPTCHA keys below to use the reCAPTCHA feature.', 'all-in-one-wp-security-and-firewall').'</p></div>';
}
$default_captcha = $aio_wp_security->configs->get_value('aiowps_default_captcha');
$aio_wp_security->include_template('wp-admin/brute-force/captcha-settings.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr, 'supported_captchas' => $supported_captchas, 'default_captcha' => $default_captcha, 'captcha_themes' => $captcha_themes, 'captcha_theme' => $captcha_theme));
}
/**
* Login whitelist tab.
*
* @return void
* @global $aio_wp_security
* @global $aiowps_feature_mgr
*/
protected function render_login_whitelist() {
global $aio_wp_security, $aiowps_feature_mgr;
$ip_v4 = false;
$your_ip_address = AIOWPSecurity_Utility_IP::get_user_ip_address();
if (filter_var($your_ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) $ip_v4 = true;
$aiowps_allowed_ip_addresses = $aio_wp_security->configs->get_value('aiowps_allowed_ip_addresses');
$aio_wp_security->include_template('wp-admin/brute-force/login-whitelist.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr, 'your_ip_address' => $your_ip_address, 'ip_v4' => $ip_v4, 'aiowps_allowed_ip_addresses' => $aiowps_allowed_ip_addresses));
}
/**
* Renders the 404 Detection tab
*
* @return void
*/
protected function render_404_detection() {
global $aio_wp_security;
include_once 'wp-security-list-404.php'; // For rendering the AIOWPSecurity_List_Table in basic-firewall tab
$event_list_404 = new AIOWPSecurity_List_404(); // For rendering the AIOWPSecurity_List_Table in basic-firewall tab
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce available.
$page = isset($_REQUEST['page']) ? sanitize_text_field(wp_unslash($_REQUEST['page'])) : '';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce available.
$tab = isset($_REQUEST["tab"]) ? sanitize_text_field(wp_unslash($_REQUEST["tab"])) : '';
$aio_wp_security->include_template('wp-admin/brute-force/404-detection.php', false, array('event_list_404' => $event_list_404, 'page' => $page, 'tab' => $tab));
}
/**
* Honeypot tab.
*
* @global $aio_wp_security
* @global $aiowps_feature_mgr
*
* @return void
*/
protected function render_honeypot() {
global $aio_wp_security, $aiowps_feature_mgr;
$aio_wp_security->include_template('wp-admin/brute-force/honeypot.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr));
}
}
@@ -0,0 +1,653 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_Dashboard_Menu extends AIOWPSecurity_Admin_Menu {
/**
* Dashboard menu slug
*
* @var string
*/
protected $menu_page_slug = AIOWPSEC_MAIN_MENU_SLUG;
/**
* Constructor adds menu for Dashboard
*/
public function __construct() {
parent::__construct(__('Dashboard', 'all-in-one-wp-security-and-firewall'));
}
/**
* This function will setup the menus tabs by setting the array $menu_tabs
*
* @return void
*/
protected function setup_menu_tabs() {
$menu_tabs = array(
'dashboard' => array(
'title' => __('Dashboard', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_dashboard'),
),
'locked-ip' => array(
'title' => __('Locked IP addresses', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_locked_ip'),
),
'permanent-block' => array(
'title' => __('Permanent block list', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_permanent_block'),
),
'audit-logs' => array(
'title' => __('Audit logs', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_audit_logs'),
),
'debug-logs' => array(
'title' => __('Debugging', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_debug_logs'),
),
'premium-upgrade' => array(
'title' => __('Premium upgrade', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_premium_upgrade_tab'),
'display_condition_callback' => function() {
return !AIOWPSecurity_Utility_Permissions::is_premium_installed();
}
),
);
$this->menu_tabs = array_filter($menu_tabs, array($this, 'should_display_tab'));
}
/**
* Renders the submenu's dashboard tab
*
* @return Void
*/
protected function render_dashboard() {
/**
* Load WordPress dashboard API
*/
require_once(ABSPATH . 'wp-admin/includes/dashboard.php');
$this->wp_dashboard_setup();
wp_enqueue_script('dashboard');
if (wp_is_mobile()) wp_enqueue_script('jquery-touch-punch');
?>
<?php // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- Needed for dashboard widget. ?>
<script type='text/javascript' src='https://www.google.com/jsapi'></script>
<div id="dashboard-widgets-wrap">
<?php $this->wp_dashboard(); ?>
</div><!-- dashboard-widgets-wrap -->
<?php
}
/**
* Renders the submenu's locked IP addresses tab
*
* @return Void
*/
protected function render_locked_ip() {
global $aio_wp_security;
include_once 'wp-security-list-locked-ip.php';
$locked_ip_list = new AIOWPSecurity_List_Locked_IP();
$tab = isset($_REQUEST["tab"]) ? sanitize_text_field(wp_unslash($_REQUEST["tab"])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce for tabs.
$page = isset($_REQUEST['page']) ? sanitize_text_field(wp_unslash($_REQUEST['page'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce for page.
$aio_wp_security->include_template('wp-admin/dashboard/locked-ip.php', false, array('locked_ip_list' => $locked_ip_list, 'page' => $page, 'tab' => $tab));
}
/**
* Renders the submenu's permanent block tab
*
* @return Void
*/
protected function render_permanent_block() {
global $aio_wp_security;
include_once 'wp-security-list-permanent-blocked-ip.php'; // For rendering the AIOWPSecurity_List_Table
$blocked_ip_list = new AIOWPSecurity_List_Blocked_IP(); // For rendering the AIOWPSecurity_List_Table
$tab = isset($_REQUEST["tab"]) ? sanitize_text_field(wp_unslash($_REQUEST["tab"])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce for tab.
$page = isset($_REQUEST['page']) ? sanitize_text_field(wp_unslash($_REQUEST['page'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce for page.
$aio_wp_security->include_template('wp-admin/dashboard/permanent-block.php', false, array('blocked_ip_list' => $blocked_ip_list, 'page' => $page, 'tab' => $tab));
}
/**
* Renders the submenu's audit logs tab
*
* @return void
*/
protected function render_audit_logs() {
global $aio_wp_security;
// Needed for rendering the audit log table
include_once 'wp-security-list-audit.php';
$data = array();
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- PCP warning. Processing form data without nonce verification. No nonce.
if (isset($_GET['event-filter'])) $data['event-filter'] = sanitize_text_field(wp_unslash($_GET['event-filter'])); // Failed logins and logins only to show as audit log
$audit_log_list = new AIOWPSecurity_List_Audit_Log($data);
$tab = isset($_REQUEST["tab"]) ? sanitize_text_field(wp_unslash($_REQUEST["tab"])) : '';
$page = isset($_REQUEST['page']) ? sanitize_text_field(wp_unslash($_REQUEST['page'])) : '';
// phpcs:enable WordPress.Security.NonceVerification.Recommended -- PCP warning. Processing form data without nonce verification. No nonce.
$aio_wp_security->include_template('wp-admin/dashboard/audit-logs.php', false, array('audit_log_list' => $audit_log_list, 'page' => $page, 'tab' => $tab));
}
/**
* Renders the submenu's debug logs tab
*
* @return void
*/
protected function render_debug_logs() {
// Needed for rendering the debug log table
include_once 'wp-security-list-debug.php';
$debug_log_list = new AIOWPSecurity_List_Debug_Log();
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/dashboard/debug-logs.php', false, array('debug_log_list' => $debug_log_list));
}
/**
* Renders the submenu's premium-upgrade tab body.
*
* @return Void
*/
protected function render_premium_upgrade_tab() {
global $aio_wp_security;
$enqueue_version = (defined('WP_DEBUG') && WP_DEBUG) ? AIO_WP_SECURITY_VERSION.'.'.time() : AIO_WP_SECURITY_VERSION;
wp_enqueue_style('aiowpsec-admin-premium-upgrade-css', AIO_WP_SECURITY_URL.'/css/wp-security-premium-upgrade.css', array(), $enqueue_version);
echo '<div class="postbox wpo-tab-postbox">';
$aio_wp_security->include_template('wp-admin/dashboard/may-also-like.php');
echo '</div><!-- END .postbox -->';
}
/**
* Function to customize the layout of the WordPress dashboard.
* Organizes meta-boxes into different containers based on screen columns.
*/
private function wp_dashboard() {
$screen = get_current_screen();
$columns = absint($screen->get_columns());
$columns_css = '';
if ($columns) {
$columns_css = " columns-$columns";
}
?>
<div id="dashboard-widgets" class="metabox-holder<?php echo esc_attr($columns_css); ?>">
<div id="postbox-container-1" class="postbox-container">
<?php do_meta_boxes($screen->id, 'normal', ''); ?>
</div>
<div id="postbox-container-2" class="postbox-container">
<?php do_meta_boxes($screen->id, 'side', ''); ?>
</div>
<div id="postbox-container-3" class="postbox-container">
<?php do_meta_boxes($screen->id, 'column3', ''); ?>
</div>
<div id="postbox-container-4" class="postbox-container">
<?php do_meta_boxes($screen->id, 'column4', ''); ?>
</div>
</div>
<?php
wp_nonce_field('closedpostboxes', 'closedpostboxesnonce', false);
wp_nonce_field('meta-box-order', 'meta-box-order-nonce', false);
}
private function wp_dashboard_setup() {
global $aio_wp_security, $wp_registered_widgets, $wp_registered_widget_controls, $wp_dashboard_control_callbacks;
$wp_dashboard_control_callbacks = array();
get_current_screen();
// Add widgets
wp_add_dashboard_widget('security_strength_meter', __('Security strength meter', 'all-in-one-wp-security-and-firewall'), array($this, 'widget_security_strength_meter'));
wp_add_dashboard_widget('security_points_breakdown', __('Security points breakdown', 'all-in-one-wp-security-and-firewall'), array($this, 'widget_security_points_breakdown'));
wp_add_dashboard_widget('spread_the_word', __('Spread the word', 'all-in-one-wp-security-and-firewall'), array($this, 'widget_spread_the_word'));
wp_add_dashboard_widget('know_developers', __('Get to know the developers', 'all-in-one-wp-security-and-firewall'), array($this, 'widget_know_developers'));
wp_add_dashboard_widget('critical_feature_status', __('Critical feature status', 'all-in-one-wp-security-and-firewall'), array($this, 'widget_critical_feature_status'));
wp_add_dashboard_widget('last_5_logins', __('Last 5 login summary', 'all-in-one-wp-security-and-firewall'), array($this, 'widget_last_5_logins'));
wp_add_dashboard_widget('maintenance_mode_status', __('Maintenance mode status', 'all-in-one-wp-security-and-firewall'), array($this, 'widget_maintenance_mode_status'));
if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention')
|| '1' == $aio_wp_security->configs->get_value('aiowps_enable_rename_login_page')
) {
wp_add_dashboard_widget('brute_force', __('Brute force prevention login page', 'all-in-one-wp-security-and-firewall'), array($this, 'widget_brute_force'));
}
wp_add_dashboard_widget('logged_in_users', __('Logged in users', 'all-in-one-wp-security-and-firewall'), array($this, 'widget_logged_in_users'));
wp_add_dashboard_widget('locked_ip_addresses', __('Locked IP addresses', 'all-in-one-wp-security-and-firewall'), array($this, 'widget_locked_ip_addresses'));
do_action('aiowps_dashboard_setup');
$dashboard_widgets = apply_filters('aiowps_dashboard_widgets', array());
foreach ($dashboard_widgets as $widget_id) {
$name = empty($wp_registered_widgets[$widget_id]['all_link']) ? $wp_registered_widgets[$widget_id]['name'] : $wp_registered_widgets[$widget_id]['name'] . " <a href='{$wp_registered_widgets[$widget_id]['all_link']}' class='edit-box open-box'>" . __('View all', 'all-in-one-wp-security-and-firewall') . '</a>';
wp_add_dashboard_widget($widget_id, $name, $wp_registered_widgets[$widget_id]['callback'], $wp_registered_widget_controls[$widget_id]['callback']);
}
}
public function widget_security_strength_meter() {
global $aiowps_feature_mgr;
$total_site_security_points = $aiowps_feature_mgr->get_total_site_points();
$total_security_points_achievable = $aiowps_feature_mgr->get_total_achievable_points();
?>
<script type='text/javascript'>
var total_security_points_achievable = <?php echo (int) $total_security_points_achievable; ?>;
var section = total_security_points_achievable / 8;
var config = {
type: 'gauge',
data: {
datasets: [{
value: <?php echo esc_js($total_site_security_points); ?>,
minValue: 0,
data: [section, section * 2, section * 3, section * 4, section * 5, section * 6, total_security_points_achievable],
backgroundColor: ['#26ddfd', '#26ddfd', '#00b0ea', '#00b0ea', '#2680ca', '#2680ca', '#563c82'],
borderWidth: 2.5
}]
},
options: {
cutoutPercentage: 75,
layout: {
padding: {
bottom: 20
}
},
needle: {
radiusPercentage: 5,
widthPercentage: 6,
lengthPercentage: 80,
color: '#3e3e3e'
},
valueLabel: {
display: false
}
}
};
window.onload = function() {
var ctx = document.getElementById('chart').getContext('2d');
window.myGauge = new Chart(ctx, config);
};
</script>
<div id="canvas-holder">
<canvas id="chart"></canvas>
</div>
<h2 id="website-strength-text"><?php echo esc_html__('Website strength:', 'all-in-one-wp-security-and-firewall') . ' ' . '<strong>' . esc_html($total_site_security_points) . '</strong>'; ?></h2>
<div id='security_strength_chart_div'></div>
<div class="aiowps_dashboard_widget_footer">
<?php
echo esc_html__('Total Achievable Points:', 'all-in-one-wp-security-and-firewall') . ' ' . '<strong>' . esc_html($total_security_points_achievable) . '</strong><br />';
echo esc_html__('Current Score of Your Site:', 'all-in-one-wp-security-and-firewall') . ' ' . '<strong>' . esc_html($total_site_security_points) . '</strong>';
?>
</div>
<?php
}
public function widget_security_points_breakdown() {
global $aiowps_feature_mgr;
$feature_mgr = $aiowps_feature_mgr;
$feature_items = $feature_mgr->feature_items;
$pt_src_chart_data = "";
$pt_src_chart_data .= "['Feature Name', 'Points'],";
foreach ($feature_items as $item) {
if ($item->is_active()) {
$pt_src_chart_data .= "['" . esc_html($item->feature_name) . "', " . esc_html($item->item_points) . "],";
}
}
?>
<?php // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- PCP error. Direct enqueue necessary. ?>
<script type='text/javascript' src='https://www.google.com/jsapi'></script>
<script type="text/javascript">
google.load("visualization", "1", {packages: ["corechart"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
<?php echo $pt_src_chart_data; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JS array data. Variables escaped earlier. ?>
]);
var options = {
// height: '250',
// width: '450',
backgroundColor: 'F6F6F6',
pieHole: 0.4,
chartArea: {
width: '95%',
height: '95%',
}
};
var chart = new google.visualization.PieChart(document.getElementById('points_source_breakdown_chart_div'));
chart.draw(data, options);
}
</script>
<div id='points_source_breakdown_chart_div'></div>
<?php
}
public function widget_spread_the_word() {
?>
<p><?php echo esc_html__('We are working hard to make your WordPress site more secure.', 'all-in-one-wp-security-and-firewall') . ' ' . esc_html__('Please support us, here is how:', 'all-in-one-wp-security-and-firewall');?></p>
<p><a href="https://x.com/TeamUpdraftWP" target="_blank"><?php esc_html_e('Follow us on', 'all-in-one-wp-security-and-firewall');?> X</a>
</p>
<p>
<a href="https://x.com/intent/tweet?url=https://wordpress.org/plugins/all-in-one-wp-security-and-firewall&text=I love the All In One WP Security and Firewall plugin!"
target="_blank" class="aio_tweet_link"><?php esc_html_e('Post to X', 'all-in-one-wp-security-and-firewall');?></a>
</p>
<p>
<a href="http://wordpress.org/support/view/plugin-reviews/all-in-one-wp-security-and-firewall/"
target="_blank" class="aio_rate_us_link"><?php esc_html_e('Give us a good rating', 'all-in-one-wp-security-and-firewall');?></a>
</p>
<?php
}
public function widget_know_developers() {
?>
<p><?php esc_html_e('Wanna know more about the developers behind this plugin?', 'all-in-one-wp-security-and-firewall');?></p>
<p><a href="https://teamupdraft.com/" target="_blank">Team UpdraftPlus</a></p>
<?php
}
/**
* This outputs the critical feature status widget
*
* @return void
*/
public function widget_critical_feature_status() {
global $aiowps_feature_mgr;
$critical_features = array(
'user-accounts-change-admin-user' => array(
'name' => __('Admin username', 'all-in-one-wp-security-and-firewall'),
'url' => AIOWPSEC_USER_SECURITY_MENU_SLUG,
),
'user-login-login-lockdown' => array(
'name' => __('Login lockout', 'all-in-one-wp-security-and-firewall'),
'url' => AIOWPSEC_USER_SECURITY_MENU_SLUG . '&tab=login-lockout',
),
'filesystem-file-permissions' => array(
'name' => __('File permission', 'all-in-one-wp-security-and-firewall'),
'url' => AIOWPSEC_FILESYSTEM_MENU_SLUG,
'feature_callback' => 'is_main_site'
),
'firewall-basic-rules' => array(
'name' => __('Basic firewall', 'all-in-one-wp-security-and-firewall'),
'url' => AIOWPSEC_FIREWALL_MENU_SLUG . '&tab=htaccess-rules',
'feature_callback' => array('AIOWPSecurity_Utility', 'allow_to_write_to_htaccess')
),
'db-security-db-prefix' => array(
'name' => __('Database prefix', 'all-in-one-wp-security-and-firewall'),
'url' => AIOWPSEC_DB_SEC_MENU_SLUG,
'feature_callback' => 'is_main_site'
),
'filesystem-file-editing' => array(
'name' => __('PHP file editing', 'all-in-one-wp-security-and-firewall'),
'url' => AIOWPSEC_FILESYSTEM_MENU_SLUG . '&tab=file-protection',
'feature_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin')
),
'bf-rename-login-page' => array(
'name' => __('Renamed login page', 'all-in-one-wp-security-and-firewall'),
'url' => AIOWPSEC_BRUTE_FORCE_MENU_SLUG,
),
'wp-generator-meta-tag' => array(
'name' => __('Hidden WP meta info', 'all-in-one-wp-security-and-firewall'),
'url' => AIOWPSEC_SETTINGS_MENU_SLUG . '&tab=wp-version-info',
),
);
$critical_features = apply_filters('aiowps_filter_critical_features_array', $critical_features);
$critical_features = array_filter($critical_features, array($this, 'should_add_feature'));
esc_html_e('Below is the current status of the critical features that you should activate on your site to achieve a minimum level of recommended security', 'all-in-one-wp-security-and-firewall');
echo '<div class="aiowps_features_grid">';
foreach ($critical_features as $key => $feature) {
$feature_item = $aiowps_feature_mgr->get_feature_item_by_id($key);
if (!$feature_item) continue;
echo '<a href="admin.php?page=' . esc_attr($feature['url']) . '" class="aiowps_critical_feature_link">';
echo '<div class="aiowps_critical_feature_status_container">';
echo '<div class="aiowps_critical_feature_status_name">' . esc_html($feature['name']) . '</div>';
echo '<div class="aiowps_feature_status_circle">';
if ($feature_item->is_active()) {
echo '<div class="aiowps_feature_status_circle_on"></div>';
} else {
echo '<div class="aiowps_feature_status_circle_off"></div>';
}
echo '</div>';
echo '</div>';
echo '</a>';
}
echo "</div>";
}
/**
* This outputs the latest logins dashboard widget
*
* @return void
*/
public function widget_last_5_logins() {
global $wpdb;
$audit_log_table = AIOWPSEC_TBL_AUDIT_LOG;
$where_sql = (is_super_admin()) ? '' : ' and site_id = '.get_current_blog_id().' ';
$last_days = 7;
$days_before_time = strtotime('-'.$last_days.' days', time());
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Direct query necessary.
$login_data_lastx_days = $wpdb->get_results(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- pcp Warning. Ignore.
$wpdb->prepare("SELECT id,created FROM $audit_log_table WHERE event_type = %s $where_sql and created > %s", 'successful_login', $days_before_time),
ARRAY_A
); // Get the last x days records
if (!empty($login_data_lastx_days)) {
$chart_data = array();
$chart_data['columns'] = array(__('Date', 'all-in-one-wp-security-and-firewall'), __('Logins', 'all-in-one-wp-security-and-firewall'));
$chart_data['data'] = $login_data_lastx_days;
$chart_data['last_days'] = $last_days;
$chart_data['id'] = 'logins_last_'.$last_days.'days';
$this->dashboard_widget_chart($chart_data, 'bar');
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP Error. Ignore.
$data = $wpdb->get_results(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- PCP error. Ignore.
$wpdb->prepare("SELECT * FROM $audit_log_table WHERE event_type = %s ORDER BY created DESC LIMIT %d", 'successful_login', 5),
ARRAY_A
); //Get the last 5 records
if (null == $data) {
echo '<p>' . esc_html__('No data found.', 'all-in-one-wp-security-and-firewall') . '</p>';
} else {
$login_summary_table_data = array();
//$login_summary_table_data['title'] = __('Last 5 login summary:', 'all-in-one-wp-security-and-firewall');
$login_summary_table_data['columns'] = array(__('User', 'all-in-one-wp-security-and-firewall'), __('Date', 'all-in-one-wp-security-and-firewall'), 'IP');
foreach ($data as $entry) {
$login_summary_table_data['data'][] = array($entry['username'], gmdate('Y-m-d H:i:s', $entry['created']), $entry['ip']);
}
$login_summary_table_data = apply_filters('aios_last5_logins_summary', $login_summary_table_data, $data);
$this->dashboard_widget($login_summary_table_data);
// View all login logs
echo '<p><a class="button" href="' . esc_url('admin.php?page=' . AIOWPSEC_MAIN_MENU_SLUG . '&tab=audit-logs&event-filter=successful_login') . '">' . esc_html__('View all', 'all-in-one-wp-security-and-firewall') . '</a></p>';
}
echo '<div class="aio_clear_float"></div>';
}
public function widget_maintenance_mode_status() {
global $aio_wp_security;
?>
<p id="aiowpsec-dashboard-maintenance-mode-status-message">
<?php
if ('1' == $aio_wp_security->configs->get_value('aiowps_site_lockout')) {
echo esc_html__('Maintenance mode is currently enabled.', 'all-in-one-wp-security-and-firewall') . ' ' . esc_html__('Remember to disable it when you are done.', 'all-in-one-wp-security-and-firewall');
} else {
echo esc_html__('Maintenance mode is currently disabled.', 'all-in-one-wp-security-and-firewall');
}
?>
</p>
<table class="form-table">
<tr valign="top">
<th scope="row"><?php esc_html_e('Enable maintenance mode', 'all-in-one-wp-security-and-firewall'); ?>:</th>
<td>
<div id="aiowpsec-dashboard-maintenance-mode-switch-container" class="aiowps_switch_container">
<?php AIOWPSecurity_Utility_UI::setting_checkbox('', 'aiowps_site_lockout', '1' == $aio_wp_security->configs->get_value('aiowps_site_lockout')); ?>
</div>
</td>
</tr>
</table>
<?php
echo '<a href="admin.php?page=' . esc_attr(AIOWPSEC_TOOLS_MENU_SLUG) . '&tab=visitor-lockout">' . esc_html__('Configure', 'all-in-one-wp-security-and-firewall') . '</a>';
}
public function widget_brute_force() {
global $aio_wp_security;
if ($aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention') == '1') {
$brute_force_login_feature_link = '<a href="admin.php?page=' . esc_attr(AIOWPSEC_BRUTE_FORCE_MENU_SLUG) . '&tab=cookie-based-brute-force-prevention" target="_blank">' . __('Cookie-based brute force', 'all-in-one-wp-security-and-firewall') . '</a>';
$brute_force_feature_secret_word = $aio_wp_security->configs->get_value('aiowps_brute_force_secret_word');
echo '<div class="aio_yellow_box">';
/* translators: %s: Brute Force Login URL */
echo '<p>' . sprintf(esc_html__('The %s feature is currently active.', 'all-in-one-wp-security-and-firewall'), $brute_force_login_feature_link) . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above.
echo '<p>' . esc_html__('Your new WordPress login URL is now:', 'all-in-one-wp-security-and-firewall') . '</p>';
echo '<p><strong>' . esc_url(AIOWPSEC_WP_URL) . '/?' . esc_html($brute_force_feature_secret_word) . '=1</strong></p>';
echo '</div>'; //yellow box div
echo '<div class="aio_clear_float"></div>';
}// End if statement for Cookie Based Brute Prevention box
// Insert Rename Login Page feature box if this feature is active
if ($aio_wp_security->configs->get_value('aiowps_enable_rename_login_page') == '1') {
if (get_option('permalink_structure')) {
$home_url = trailingslashit(home_url());
} else {
$home_url = trailingslashit(home_url()) . '?';
}
$rename_login_feature_link = '<a href="admin.php?page=' . esc_attr(AIOWPSEC_BRUTE_FORCE_MENU_SLUG) . '&tab=rename-login" target="_blank">' . esc_html__('Rename login page', 'all-in-one-wp-security-and-firewall') . '</a>';
echo '<div class="aio_yellow_box">';
/* translators: %s: Rename Login URL */
echo '<p>' . sprintf(esc_html__('The %s feature is currently active.', 'all-in-one-wp-security-and-firewall'), $rename_login_feature_link) . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above.
echo '<p>' . esc_html__('Your new WordPress login URL is now:', 'all-in-one-wp-security-and-firewall') . '</p>';
echo '<p><strong>' . esc_url($home_url) . esc_html($aio_wp_security->configs->get_value('aiowps_login_page_slug')) . '</strong></p>';
echo '</div>'; //yellow box div
echo '<div class="aio_clear_float"></div>';
} // End if statement for Rename Login box
}
/**
* This outputs the logged in users dashboard widget
*
* @return void
*/
public function widget_logged_in_users() {
$users_online_link = '<a href="admin.php?page=' . AIOWPSEC_USER_SECURITY_MENU_SLUG . '&tab=logged-in-users">'.esc_html__('Logged in users', 'all-in-one-wp-security-and-firewall').'</a>';
// default display messages
$multiple_users_info_msg = esc_html__('Number of users currently logged into your site (including you) is:', 'all-in-one-wp-security-and-firewall');
$single_user_info_msg = esc_html__('There are no other users currently logged in.', 'all-in-one-wp-security-and-firewall');
if (is_multisite()) {
$current_blog_id = get_current_blog_id();
$is_main = is_main_site($current_blog_id);
if (empty($is_main)) {
// Subsite - only get logged in users for this blog_id
$logged_in_users = AIOWPSecurity_User_Login::get_logged_in_users(false);
} else {
// Main site - get sitewide users
$logged_in_users = AIOWPSecurity_User_Login::get_logged_in_users();
// If viewing AIOS from multisite main network dashboard, then display a different message
$multiple_users_info_msg = __('Number of users currently logged in site-wide (including you) is:', 'all-in-one-wp-security-and-firewall');
$single_user_info_msg = __('There are no other site-wide users currently logged in.', 'all-in-one-wp-security-and-firewall');
}
} else {
$logged_in_users = AIOWPSecurity_User_Login::get_logged_in_users();
}
if (empty($logged_in_users)) {
$num_users = 0;
} else {
$num_users = count($logged_in_users);
}
if ($num_users > 1) {
echo '<div class="aio_red_box"><p>' . esc_html($multiple_users_info_msg) . ' <strong>' . esc_html($num_users) . '</strong></p>';
/* translators: %s: Users Online URL */
$info_msg = '<p>' . sprintf(esc_html__('Go to the %s menu to see more details', 'all-in-one-wp-security-and-firewall'), $users_online_link) . '</p>';
echo $info_msg . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above.
} else {
echo '<div class="aio_green_box"><p>' . esc_html($single_user_info_msg) . '</p></div>';
}
}
public function widget_locked_ip_addresses() {
$locked_ips_link = '<a href="admin.php?page=' . esc_attr(AIOWPSEC_MAIN_MENU_SLUG) . '&tab=locked-ip">'. esc_html__('Locked IP addresses', 'all-in-one-wp-security-and-firewall').'</a>';
$locked_ips = AIOWPSecurity_Utility::get_locked_ips();
if (false === $locked_ips) {
echo '<div class="aio_green_box"><p>' . esc_html__('There are no IP addresses currently locked out.', 'all-in-one-wp-security-and-firewall') . '</p></div>';
} else {
$num_ips = count($locked_ips);
echo '<div class="aio_red_box"><p>' . esc_html__('Number of temporarily locked out IP addresses:', 'all-in-one-wp-security-and-firewall') . ' ' . ' <strong>' . esc_html($num_ips) . '</strong></p>';
/* translators: %s: Number of locked out IPs */
$info_msg = '<p>' . sprintf(esc_html__('Go to the %s menu to see more details', 'all-in-one-wp-security-and-firewall'), $locked_ips_link) . '</p>';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above.
echo $info_msg . '</div>';
}
}
/**
* Determines whether a security feature should be activated based on its callback.
*
* This method checks if a feature should be added by evaluating its callback function.
* If no callback is set, the feature is added by default. If a callback is set,
* it must be callable and return a boolean value.
*
* @param array $feature An array containing feature details with the following keys:
* 'name' => (string) Name of the feature
* 'feature_callback' => (callable|null) Optional callback to determine if feature should be added
*
* @return bool True if the feature should be added, false otherwise
*/
public static function should_add_feature($feature) {
if (empty($feature['feature_callback'])) {
return true;
} elseif (is_callable($feature['feature_callback'])) {
return call_user_func($feature['feature_callback']);
} else {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Part of internal error reporting system.
error_log("Callback function set but not callable (coding error). Feature: " . $feature['name']);
return false;
}
}
/**
* This function creates summary for dashboard widget in table format
*
* @param array $widget_data title, column names and row data
*
* @return void
*/
private function dashboard_widget($widget_data) {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/dashboard/widget-summary.php', false, array('widget_data' => $widget_data));
}
/**
* This function creates chart for dashboard widget
*
* @param array $chart_data column names, chart data, last_days and id
* @param string $type bar chart
*
* @return void
*/
private function dashboard_widget_chart($chart_data, $type = 'bar') {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/dashboard/widget-'.$type.'-chart.php', false, array('chart_data' => $chart_data));
}
}
@@ -0,0 +1,408 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu {
/**
* Database menu slug
*
* @var string
*/
protected $menu_page_slug = AIOWPSEC_DB_SEC_MENU_SLUG;
/**
* Constructor adds menu for Database security
*/
public function __construct() {
parent::__construct(__('Database security', 'all-in-one-wp-security-and-firewall'));
}
/**
* Return installation or activation link of UpdraftPlus plugin
*
* @return String
*/
private function get_install_activate_link_of_updraft_plugin() {
// If UpdraftPlus is activated, then return empty.
if (class_exists('UpdraftPlus')) return '';
// Generally it is 'updraftplus/updraftplus.php',
// but we can't assume that the user hasn't renamed the plugin folder - with 3 million UDP users and 1 million AIOWPS, there will be some who have.
$updraftplus_plugin_file_rel_to_plugins_dir = $this->get_updraftplus_plugin_file_rel_to_plugins_dir();
// If UpdraftPlus is installed but not activated, then return activate link.
if ($updraftplus_plugin_file_rel_to_plugins_dir) {
$activate_url = add_query_arg(array(
'_wpnonce' => wp_create_nonce('activate-plugin_'.$updraftplus_plugin_file_rel_to_plugins_dir),
'action' => 'activate',
'plugin' => $updraftplus_plugin_file_rel_to_plugins_dir,
), network_admin_url('plugins.php'));
// If is network admin then add to link network activation.
if (is_network_admin()) {
$activate_url = add_query_arg(array('networkwide' => 1), $activate_url);
}
return sprintf('<a href="%s">%s</a>',
$activate_url,
__('UpdraftPlus is installed but currently not active.', 'all-in-one-wp-security-and-firewall') .' '. __('Follow this link to activate UpdraftPlus, to take a backup.', 'all-in-one-wp-security-and-firewall')
);
}
// If UpdraftPlus is not activated or installed, then return the installation link
return '<a href="'.wp_nonce_url(self_admin_url('update.php?action=install-plugin&plugin=updraftplus'), 'install-plugin_updraftplus').'">'.__('Follow this link to install UpdraftPlus, to take a database backup.', 'all-in-one-wp-security-and-firewall').'</a>';
}
/**
* Get path to the UpdraftPlus plugin file relative to the plugins directory.
*
* @return String|false path to the UpdraftPlus plugin file relative to the plugins directory
*/
private function get_updraftplus_plugin_file_rel_to_plugins_dir() {
if (!function_exists('get_plugins')) {
include_once ABSPATH . '/wp-admin/includes/plugin.php';
}
$installed_plugins = get_plugins();
$installed_plugins_keys = array_keys($installed_plugins);
foreach ($installed_plugins_keys as $plugin_file_rel_to_plugins_dir) {
$temp_plugin_file_name = substr($plugin_file_rel_to_plugins_dir, strpos($plugin_file_rel_to_plugins_dir, '/') + 1);
if ('updraftplus.php' == $temp_plugin_file_name) {
return $plugin_file_rel_to_plugins_dir;
}
}
return false;
}
/**
* This function will setup the menus tabs by setting the array $menu_tabs
*
* @return void
*/
protected function setup_menu_tabs() {
$menu_tabs = array(
'database-prefix' => array(
'title' => __('Database prefix', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_database_prefix'),
'display_condition_callback' => 'is_main_site',
),
'database-backup' => array(
'title' => __('Database backup', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_database_backup'),
),
);
$this->menu_tabs = array_filter($menu_tabs, array($this, 'should_display_tab'));
}
/**
* Renders the submenu's database prefix tab
*
* @return Void
*/
protected function render_database_prefix() {
global $wpdb, $aio_wp_security, $aiowps_feature_mgr;
$old_db_prefix = $wpdb->prefix;
$new_db_prefix = '';
$perform_db_change = false;
if (isset($_POST['aiowps_db_prefix_change'])) { // Do form submission tasks
$nonce = $_REQUEST['_wpnonce'];
if (!wp_verify_nonce($nonce, 'aiowpsec-db-prefix-change-nonce')) {
$aio_wp_security->debug_logger->log_debug("Nonce check failed for DB prefix change operation.", 4);
die(__('Nonce check failed for DB prefix change operation.', 'all-in-one-wp-security-and-firewall'));
}
// Let's first check if user's system allows writing to wp-config.php file. If plugin cannot write to wp-config we will not do the prefix change.
$config_file = AIOWPSecurity_Utility_File::get_wp_config_file_path();
$file_write = AIOWPSecurity_Utility_File::is_file_writable($config_file);
if (!$file_write) {
$this->show_msg_error(__('The plugin has detected that it cannot write to the wp-config.php file.', 'all-in-one-wp-security-and-firewall') . ' ' . __('This feature can only be used if the plugin can successfully write to the wp-config.php file.', 'all-in-one-wp-security-and-firewall'));
} else {
if (isset($_POST['aiowps_enable_random_prefix'])) { // User has elected to generate a random DB prefix
$string = AIOWPSecurity_Utility::generate_alpha_random_string('5');
$new_db_prefix = $string . '_';
$perform_db_change = true;
} else {
if (empty($_POST['aiowps_new_manual_db_prefix'])) {
$this->show_msg_error(__('Please enter a value for the DB prefix.', 'all-in-one-wp-security-and-firewall'));
} else {
// User has chosen their own DB prefix value
$new_db_prefix = wp_strip_all_tags(trim($_POST['aiowps_new_manual_db_prefix']));
if ($new_db_prefix !== $_POST['aiowps_new_manual_db_prefix']) {
wp_die("<strong>".__('Error:', 'all-in-one-wp-security-and-firewall')."</strong> ".__('prefix contains HTML tags', 'all-in-one-wp-security-and-firewall'));
}
if (preg_match('|[^a-z0-9_]|i', $new_db_prefix)) {
wp_die("<strong>".__('Error:', 'all-in-one-wp-security-and-firewall')."</strong> ".__('prefix contains invalid characters, the prefix should only contain alphanumeric and underscore characters.', 'all-in-one-wp-security-and-firewall'));
}
$error = $wpdb->set_prefix($new_db_prefix); // validate the user chosen prefix
if (is_wp_error($error)) {
wp_die("<strong>".__('Error:', 'all-in-one-wp-security-and-firewall')."</strong> (".$error->get_error_code()."): ".$error->get_error_message());
}
$wpdb->set_prefix($old_db_prefix);
$perform_db_change = true;
}
}
}
}
if ($perform_db_change) {
// Do the DB prefix change operations
$this->change_db_prefix($old_db_prefix, $new_db_prefix);
}
$aio_wp_security->include_template('wp-admin/database-security/database-prefix.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr, 'old_db_prefix' => $old_db_prefix));
}
/**
* Renders the submenu's database backup tab
*
* @return Void
*/
protected function render_database_backup() {
global $aio_wp_security;
$updraftplus_admin = !empty($GLOBALS['updraftplus_admin']) ? $GLOBALS['updraftplus_admin'] : null;
if ($updraftplus_admin) {
$updraftplus_admin->add_backup_scaffolding(__('Take a database backup using UpdraftPlus', 'all-in-one-wp-security-and-firewall'), array($updraftplus_admin, 'backupnow_modal_contents'));
}
$install_activate_link = $this->get_install_activate_link_of_updraft_plugin();
$aio_wp_security->include_template('wp-admin/database-security/database-backup.php', false, array('install_activate_link' => $install_activate_link));
}
/*
* Changes the DB prefix
*/
/**
* This function will change the DB prefix
*
* @param string $table_old_prefix - the old table prefix
* @param string $table_new_prefix - the new table prefix
*
* @return void
*/
private function change_db_prefix($table_old_prefix, $table_new_prefix) {
global $wpdb, $aio_wp_security;
$old_prefix_length = strlen($table_old_prefix);
$error = 0;
// Config file path
$config_file = AIOWPSecurity_Utility_File::get_wp_config_file_path();
// Get the table resource
// $result = mysql_list_tables(DB_NAME);
$result = $this->get_mysql_tables(DB_NAME); //Fix for deprecated php mysql_list_tables function
// Count the number of tables
if (is_array($result) && count($result) > 0) {
$num_rows = count($result);
} else {
echo '<div class="aio_red_box"><p>'.__('Error - Could not get tables or no tables found!', 'all-in-one-wp-security-and-firewall').'</p></div>';
return;
}
$table_count = 0;
$info_msg_string = '<p class="aio_info_with_icon">'.__('Starting DB prefix change operations.....', 'all-in-one-wp-security-and-firewall').'</p>';
$info_msg_string .= '<p class="aio_info_with_icon">'.sprintf(__('Your WordPress system has a total of %s tables and your new DB prefix will be: %s', 'all-in-one-wp-security-and-firewall'), '<strong>'.$num_rows.'</strong>', '<strong>'.$table_new_prefix.'</strong>').'</p>';
echo $info_msg_string;
// Do a back of the config file
if (!AIOWPSecurity_Utility_File::backup_and_rename_wp_config($config_file)) {
echo '<div class="aio_red_box"><p>'.__('Failed to make a backup of the wp-config.php file.', 'all-in-one-wp-security-and-firewall') . ' ' .__('This operation will not go ahead.', 'all-in-one-wp-security-and-firewall').'</p></div>';
return;
} else {
echo '<p class="aio_success_with_icon">'.__('A backup copy of your wp-config.php file was created successfully!', 'all-in-one-wp-security-and-firewall').'</p>';
}
// Get multisite blog_ids if applicable
if (is_multisite()) {
$blog_ids = AIOWPSecurity_Utility::get_blog_ids();
}
// Rename all the table names
foreach ($result as $db_table) {
// Get table name with old prefix
$table_old_name = $db_table;
if (strpos($table_old_name, $table_old_prefix) === 0) {
// Get table name with new prefix
$table_new_name = AIOWPSecurity_Utility::backquote($table_new_prefix . substr($table_old_name, $old_prefix_length));
$table_old_name = AIOWPSecurity_Utility::backquote($table_old_name);
// Write query to rename tables name
$sql = "RENAME TABLE ".$table_old_name." TO ".$table_new_name;
// $sql = "RENAME TABLE %s TO %s";
// Execute the query
if (false === $wpdb->query($sql)) {
$error = 1;
echo '<p class="aio_error_with_icon">'.sprintf(__('%s table name update failed', 'all-in-one-wp-security-and-firewall'), '<strong>'.$table_old_name.'</strong>').'</p>';
$aio_wp_security->debug_logger->log_debug("DB Security Feature - Unable to change prefix of table ".$table_old_name, 4);
} else {
$table_count++;
}
} else {
continue;
}
}
if (1 == $error) {
echo '<p class="aio_error_with_icon">'.sprintf(__('Please change the prefix manually for the above tables to: %s', 'all-in-one-wp-security-and-firewall'), '<strong>'.$table_new_prefix.'</strong>').'</p>';
} else {
echo '<p class="aio_success_with_icon">'.sprintf(__('%s tables had their prefix updated successfully!', 'all-in-one-wp-security-and-firewall'), '<strong>'.$table_count.'</strong>').'</p>';
}
// Let's check for mysql tables of type "view"
$this->alter_table_views($table_old_prefix, $table_new_prefix);
// Get wp-config.php file contents and modify it with new info
$config_contents = file($config_file);
$prefix_match_string = '$table_prefix='; //this is our search string for the wp-config.php file
foreach ($config_contents as $line_num => $line) {
$no_ws_line = preg_replace('/\s+/', '', $line); //Strip white spaces
if (false !== strpos($no_ws_line, $prefix_match_string)) {
$prefix_parts = explode("=", $line);
$prefix_parts[1] = str_replace($table_old_prefix, $table_new_prefix, $prefix_parts[1]);
$config_contents[$line_num] = implode("=", $prefix_parts);
break;
}
}
// Now let's modify the wp-config.php file
if (AIOWPSecurity_Utility_File::write_content_to_file($config_file, $config_contents)) {
echo '<p class="aio_success_with_icon">'. __('wp-config.php file was updated successfully!', 'all-in-one-wp-security-and-firewall').'</p>';
} else {
echo '<p class="aio_error_with_icon">'.sprintf(__('The "wp-config.php" file was not able to be modified.', 'all-in-one-wp-security-and-firewall').' '.__('Please modify this file manually using your favourite editor and search for variable "$table_prefix" and assign the following value to that variable: %s', 'all-in-one-wp-security-and-firewall'), '<strong>'.$table_new_prefix.'</strong>').'</p>';
$aio_wp_security->debug_logger->log_debug("DB Security Feature - Unable to modify wp-config.php", 4);
}
// Now let's update the options table
$update_option_table_query = $wpdb->prepare("UPDATE " . $table_new_prefix . "options SET option_name = '".$table_new_prefix ."user_roles' WHERE option_name = %s LIMIT 1", $table_old_prefix."user_roles");
if (false === $wpdb->query($update_option_table_query)) {
echo '<p class="aio_error_with_icon">'.sprintf(__('Update of table %s failed: unable to change %s to %s', 'all-in-one-wp-security-and-firewall'), $table_new_prefix.'options', $table_old_prefix.'user_roles', $table_new_prefix.'user_roles').'</p>';
$aio_wp_security->debug_logger->log_debug("DB Security Feature - Error when updating the options table", 4);//Log the highly unlikely event of DB error
} else {
echo '<p class="aio_success_with_icon">'. __('The options table records which had references to the old DB prefix were updated successfully!', 'all-in-one-wp-security-and-firewall') .'</p>';
}
// Now let's update the options tables for the multisite subsites if applicable
if (is_multisite()) {
if (!empty($blog_ids)) {
$main_site_id = get_main_site_id();
foreach ($blog_ids as $blog_id) {
if ($blog_id == $main_site_id) continue;
$new_pref_and_site_id = $table_new_prefix.$blog_id.'_';
$old_pref_and_site_id = $table_old_prefix.$blog_id.'_';
$update_ms_option_table_query = $wpdb->prepare("UPDATE " . $new_pref_and_site_id . "options SET option_name = '".$new_pref_and_site_id."user_roles' WHERE option_name = %s LIMIT 1", $old_pref_and_site_id."user_roles");
if (false === $wpdb->query($update_ms_option_table_query)) {
echo '<p class="aio_error_with_icon">'.sprintf(__('Update of table %s failed: unable to change %s to %s', 'all-in-one-wp-security-and-firewall'), $new_pref_and_site_id.'options', $old_pref_and_site_id.'user_roles', $new_pref_and_site_id.'user_roles').'</p>';
$aio_wp_security->debug_logger->log_debug("DB change prefix feature - Error when updating the subsite options table: ".$new_pref_and_site_id.'options', 4);//Log the highly unlikely event of DB error
} else {
echo '<p class="aio_success_with_icon">'.sprintf(__('The %s table records which had references to the old DB prefix were updated successfully!', 'all-in-one-wp-security-and-firewall'), $new_pref_and_site_id.'options').'</p>';
}
}
}
}
//Now let's update the user meta table
$custom_sql = "SELECT user_id, meta_key FROM " . $table_new_prefix . "usermeta WHERE meta_key LIKE '" . $table_old_prefix . "%'";
$meta_keys = $wpdb->get_results($custom_sql);
$error_update_usermeta = '';
// Update all meta_key field values which have the old table prefix in user_meta table
foreach ($meta_keys as $meta_key) {
// Create new meta key
$new_meta_key = $table_new_prefix . substr($meta_key->meta_key, $old_prefix_length);
$update_user_meta_sql = $wpdb->prepare("UPDATE " . $table_new_prefix . "usermeta SET meta_key='" . $new_meta_key . "' WHERE meta_key=%s AND user_id=%s", $meta_key->meta_key, $meta_key->user_id);
if (false === $wpdb->query($update_user_meta_sql)) {
$error_update_usermeta .= '<p class="aio_error_with_icon">'.sprintf(__('Error updating user_meta table where new meta_key = %s, old meta_key = %s and user_id = %s.', 'all-in-one-wp-security-and-firewall'), $new_meta_key, $meta_key->meta_key, $meta_key->user_id).'</p>';
echo $error_update_usermeta;
$aio_wp_security->debug_logger->log_debug("DB Security Feature - Error updating user_meta table where new meta_key = ".$new_meta_key." old meta_key = ".$meta_key->meta_key." and user_id = ".$meta_key->user_id, 4);//Log the highly unlikely event of DB error
}
}
echo '<p class="aio_success_with_icon">'.__('The usermeta table records which had references to the old DB prefix were updated successfully!', 'all-in-one-wp-security-and-firewall').'</p>';
// Display tasks finished message
$tasks_finished_msg_string = '<p class="aio_info_with_icon">'. __('The database prefix change tasks have been completed.', 'all-in-one-wp-security-and-firewall').'</p>';
echo $tasks_finished_msg_string;
}
/**
* This is an alternative to the deprecated "mysql_list_tables
*
* @param string $database - database name
*
* @returns array - an array of table names
*/
public function get_mysql_tables($database = '') {
global $aio_wp_security;
$tables = array();
$list_tables_sql = "SHOW TABLES FROM `{$database}`;";
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
if ($mysqli->connect_errno) {
$aio_wp_security->debug_logger->log_debug("AIOWPSecurity_Database_Menu->get_mysql_tables() - DB connection error.", 4);
return false;
}
$result = $mysqli->query($list_tables_sql, MYSQLI_USE_RESULT);
if ($result) {
//Alternative way to get the tables
while ($row = $result->fetch_assoc()) {
foreach ($row as $value) {
$tables[] = $value;
}
}
$result->close();
}
$mysqli->close();
return $tables;
}
/**
* Will modify existing table view definitions to reflect the new DB prefix change
*
* @param string $old_db_prefix - old database prefix
* @param string $new_db_prefix - new database prefix
*
* @returns void
*/
private function alter_table_views($old_db_prefix, $new_db_prefix) {
global $wpdb, $aio_wp_security;
$db_name = $wpdb->dbname;
$info_msg_string = '<p class="aio_info_with_icon">'.__('Checking for MySQL tables of type "view".....', 'all-in-one-wp-security-and-firewall').'</p>';
echo $info_msg_string;
// get tables which are views
$query = "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA LIKE '".$db_name."'";
$res = $wpdb->get_results($query);
if (empty($res)) return;
$view_count = 0;
foreach ($res as $item) {
$old_def = $item->VIEW_DEFINITION;
$new_def = AIOWPSecurity_Utility::str_replace_once($old_db_prefix, $new_db_prefix, $old_def);
$new_def = AIOWPSecurity_Utility::backquote($new_def);
$view_name = AIOWPSecurity_Utility::backquote($item->TABLE_NAME);
$chg_view_sql = "ALTER VIEW $view_name AS $new_def";
$view_res = $wpdb->query($chg_view_sql);
if (false === $view_res) {
echo '<p class="aio_error_with_icon">'.sprintf(__('Update of the following MySQL view definition failed: %s', 'all-in-one-wp-security-and-firewall'), $old_def).'</p>';
$aio_wp_security->debug_logger->log_debug("Update of the following MySQL view definition failed: ".$old_def, 4);//Log the highly unlikely event of DB error
} else {
$view_count++;
}
}
if ($view_count > 0) {
echo '<p class="aio_success_with_icon">'.sprintf(__('%s view definitions were updated successfully.', 'all-in-one-wp-security-and-firewall'), '<strong>'.$view_count.'</strong>').'</p>';
}
return;
}
}
@@ -0,0 +1,75 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
/**
* AIOWPSecurity_Filescan_Menu class for scanning file changes, maleware and available updates.
*
* @access public
*/
class AIOWPSecurity_Filescan_Menu extends AIOWPSecurity_Admin_Menu {
/**
* File scan menu slug
*
* @var string
*/
protected $menu_page_slug = AIOWPSEC_FILESCAN_MENU_SLUG;
/**
* Constructor adds menu for Scanner
*/
public function __construct() {
parent::__construct(__('Scanner', 'all-in-one-wp-security-and-firewall'));
}
/**
* This function will setup the menus tabs by setting the array $menu_tabs
*
* @return void
*/
protected function setup_menu_tabs() {
$menu_tabs = array(
'file-change-detect' => array(
'title' => __('File change detection', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_file_change_detect'),
),
'malware-scan' => array(
'title' => __('Malware scan', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_malware_scan'),
),
);
$this->menu_tabs = array_filter($menu_tabs, array($this, 'should_display_tab'));
}
/**
* File change detection on your system files.
*
* @global $wpdb
* @global $aio_wp_security
* @global $aiowps_feature_mgr
*/
protected function render_file_change_detect() {
global $aio_wp_security;
$aios_commands = new AIOWPSecurity_Commands();
$scanner_data = $aios_commands->get_scanner_data();
$aio_wp_security->include_template('wp-admin/scanner/file-change-detect.php', false, $scanner_data);
}
/**
* Malware code scan on your system files.
*
* @return void
*/
protected function render_malware_scan() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/scanner/malware-scan.php', false, array());
}
}
@@ -0,0 +1,144 @@
<?php
if (!defined('ABSPATH')) {
exit; // Prevent direct access to file
}
class AIOWPSecurity_Filesystem_Menu extends AIOWPSecurity_Admin_Menu {
/**
* Filesystem menu slug
*
* @var string
*/
protected $menu_page_slug = AIOWPSEC_FILESYSTEM_MENU_SLUG;
/**
* Constructor adds menu for Filesystem security
*/
public function __construct() {
parent::__construct(__('File security', 'all-in-one-wp-security-and-firewall'));
add_action('admin_footer', array($this, 'filesystem_menu_footer_code'));
}
/**
* This function will setup the menus tabs by setting the array $menu_tabs
*
* @return void
*/
protected function setup_menu_tabs() {
$menu_tabs = array(
'file-permissions' => array(
'title' => __('File permissions', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_file_permissions'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'file-protection' => array(
'title' => __('File protection', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_file_protection'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'host-system-logs' => array(
'title' => __('Host system logs', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_host_system_logs'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'copy-protection' => array(
'title' => __('Copy protection', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_copy_protection'),
),
'frames' => array(
'title' => __('Frames', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_frames'),
),
);
$this->menu_tabs = array_filter($menu_tabs, array($this, 'should_display_tab'));
}
/**
* Renders the submenu's file permissions tab
*
* @return Void
*/
protected function render_file_permissions() {
// if this is the case there is no need to display a "fix permissions" button
global $aio_wp_security, $aiowps_feature_mgr;
$files_dirs_to_check = AIOWPSecurity_Utility_File::get_files_and_dirs_to_check();
$aio_wp_security->include_template('wp-admin/filesystem-security/file-permissions.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr, 'files_dirs_to_check' => $files_dirs_to_check, 'file_utility' => new AIOWPSecurity_Utility_File()));
}
/**
* Renders the submenu's 'File protection' tab
*
* @return void
*/
protected function render_file_protection() {
global $aio_wp_security;
$show_disallow_file_edit_warning = defined('DISALLOW_FILE_EDIT') && DISALLOW_FILE_EDIT && '1' != $aio_wp_security->configs->get_value('aiowps_disable_file_editing');
$aio_wp_security->include_template('wp-admin/filesystem-security/file-protection.php', false, array('show_disallow_file_edit_warning' => $show_disallow_file_edit_warning));
}
/**
* Renders the submenu's copy protection tab
*
* @return Void
*/
protected function render_copy_protection() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/filesystem-security/copy-protection.php', false, array());
}
/**
* Renders the submenu's render frames tab
*
* @return Void
*/
protected function render_frames() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/filesystem-security/frames.php', false, array());
}
/**
* Renders the submenu's host system logs tab
*
* @return Void
*/
protected function render_host_system_logs() {
global $aio_wp_security;
$sys_log_file = basename($aio_wp_security->configs->get_value('aiowps_system_log_file'));
$aio_wp_security->include_template('wp-admin/filesystem-security/host-system-logs.php', false, array('sys_log_file' => $sys_log_file));
}
/**
* Called via filter admin_footer, this adds the needed javascript to page
*
* @return void
*/
public function filesystem_menu_footer_code() {
?>
<script type="text/javascript">
/* <![CDATA[ */
jQuery(function($) {
loading_span = $('.aiowps_loading_1');
loading_span.hide(); //hide the spinner gif after page has successfully loaded
$('.search-error-files').on("click",function(){
loading_span.show();
});
});
function set_file_permission_tochange(path, recommended) {
jQuery('#aiowps_permission_chg_file').val(path);
jQuery('#aiowps_recommended_permissions').val(recommended);
return true;
}
/* ]]> */
</script>
<?php
}
}
@@ -0,0 +1,188 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
use AIOWPS\Firewall\Allow_List;
class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu {
/**
* Firewall menu slug
*
* @var string
*/
protected $menu_page_slug = AIOWPSEC_FIREWALL_MENU_SLUG;
/**
* Constructor adds menu for Firewall
*/
public function __construct() {
parent::__construct(__('Firewall', 'all-in-one-wp-security-and-firewall'));
}
/**
* This function will setup the menus tabs by setting the array $menu_tabs
*
* @return void
*/
protected function setup_menu_tabs() {
$menu_tabs = array(
'php-rules' => array(
'title' => __('PHP rules', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_php_rules'),
),
'htaccess-rules' => array(
'title' => __('.htaccess rules', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_htaccess_rules'),
'display_condition_callback' => array('AIOWPSecurity_Utility', 'allow_to_write_to_htaccess'),
),
'6g-firewall' => array(
'title' => __('6G firewall rules', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_6g_firewall'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'5g-firewall' => array(
'title' => __('5G legacy rules', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_5g_firewall'),
'display_condition_callback' => array('AIOWPSecurity_Utility', 'render_5g_legacy_tab'),
),
'internet-bots' => array(
'title' => __('Internet bots', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_internet_bots'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'block-and-allow-lists' => array(
'title' => __('Block & allow lists', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_block_and_allow_lists'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'advanced-settings' => array(
'title' => __('Advanced settings', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_advanced_settings'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
)
);
$this->menu_tabs = array_filter($menu_tabs, array($this, 'should_display_tab'));
}
/**
* Renders the PHP Firewall settings tab
*
* @return void
*/
protected function render_php_rules() {
global $aio_wp_security;
$aios_commands = new AIOWPSecurity_Commands();
$php_firewall_data = $aios_commands->get_php_firewall_data();
$aio_wp_security->include_template('wp-admin/firewall/php-firewall-rules.php', false, compact('php_firewall_data'));
}
/**
* Renders the Htaccess Firewall tab
*
* @return void
*/
protected function render_htaccess_rules() {
global $aio_wp_security;
$aios_commands = new AIOWPSecurity_Commands();
$htaccess_rules_data = $aios_commands->get_htaccess_rules_data();
$aio_wp_security->include_template('wp-admin/firewall/htaccess-firewall-rules.php', false, compact('htaccess_rules_data'));
}
/**
* Renders the 6G Blacklist Firewall Rules tab
*
* @return void
*/
protected function render_6g_firewall() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/general/moved.php', false, array('key' => '6g'));
}
/**
* Renders the 5G Blacklist Firewall Rules tab
*
* @return void
*/
protected function render_5g_firewall() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/firewall/5g.php');
}
/**
* Renders the Internet Bots tab
*
* @return void
*/
protected function render_internet_bots() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/general/moved.php', false, array('key' => 'internet-bots'));
}
/**
* Renders the Advanced settings tab.
*
* @return void
*/
protected function render_advanced_settings() {
global $aio_wp_security;
$aios_commands = new AIOWPSecurity_Commands();
$advanced_settings_data = $aios_commands->get_firewall_advanced_settings_data();
$aio_wp_security->include_template('wp-admin/firewall/advanced-settings.php', false, compact('advanced_settings_data'));
}
/**
* Renders ban user tab for blacklist IPs and user agents
*
* @global $aio_wp_security
* @global $aiowps_feature_mgr
*
* @return void
*/
protected function render_block_and_allow_lists() {
global $aio_wp_security;
$aios_commands = new AIOWPSecurity_Commands();
$block_allowlist_data = $aios_commands->get_block_allow_lists_data();
$aio_wp_security->include_template('wp-admin/firewall/block-and-allow-lists.php', false, $block_allowlist_data);
}
/**
* Validates posted user agent list and set, save as config.
*
* @global $aio_wp_security
* @global $aiowps_firewall_config
*
* @param string $banned_user_agents
*
* @return int
*/
private function validate_user_agent_list($banned_user_agents) {
global $aio_wp_security;
$aiowps_firewall_config = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONFIG);
$submitted_agents = AIOWPSecurity_Utility::splitby_newline_trim_filter_empty($banned_user_agents);
$agents = array_unique(array_filter(array_map('sanitize_text_field', $submitted_agents), 'strlen'));
$aio_wp_security->configs->set_value('aiowps_banned_user_agents', implode("\n", $agents));
$aiowps_firewall_config->set_value('aiowps_blacklist_user_agents', $agents);
$_POST['aiowps_banned_user_agents'] = ''; // Clear the post variable for the banned address list
return 1;
}
}
@@ -0,0 +1,646 @@
<?php
if (!defined('ABSPATH')) {
exit; //Exit if accessed directly
}
class AIOWPSecurity_Firewall_Setup_Notice {
/**
* Holds reference to an instance of itself
*
* @var AIOWPSecurity_Firewall_Setup_Notice
*/
private static $instance = null;
/**
* Holds our wp-config file wrapped in our manager class
*
* @var AIOWPSecurity_Block_WpConfig
*/
private $wpconfig;
/**
* Holds our mu-plugin file wrapped in our manager class
*
* @var AIOWPSecurity_Block_Muplugin
*/
private $muplugin;
/**
* Holds our bootstrap file wrapped in our manager class
*
* @var AIOWPSecurity_Block_Bootstrap
*/
private $bootstrap;
/**
* Constants for the different notice types
*
* @var string
*/
const NOTICE_BOOTSTRAP = 'manual_bootstrap';
const NOTICE_MANUAL = 'manual';
const NOTICE_INSTALLED = 'success';
const NOTICE_DIRECTIVE_SET = 'userini_directive';
/**
* Constructs our object by setting up our essential files
*/
private function __construct() {
$this->bootstrap = AIOWPSecurity_Utility_Firewall::get_bootstrap_file();
$this->wpconfig = AIOWPSecurity_Utility_Firewall::get_wpconfig_file();
$this->muplugin = AIOWPSecurity_Utility_Firewall::get_muplugin_file();
AIOWPSecurity_Utility_Firewall::get_firewall_rules_path(true); // Creates the needed directories for the first time.
}
/**
* Entry point for the dashboard notice
*
* @return void
*/
public function start_firewall_setup() {
global $aio_wp_security;
$firewall_files = array(
'server' => AIOWPSecurity_Utility_Firewall::get_server_file(),
'bootstrap' => $this->bootstrap,
'wpconfig' => $this->wpconfig,
'muplugin' => $this->muplugin,
);
//Check each file and update the contents if necessary
foreach ($firewall_files as $name => $file) {
${'is_firewall_in_'.$name} = false;
if (AIOWPSecurity_Utility_Firewall::MANUAL_SETUP === $file) {
continue;
}
${'is_firewall_in_'.$name} = $file->contains_contents();
if (true === ${'is_firewall_in_'.$name}) {
$file->update_contents();
}
}
if (!$aio_wp_security->is_aiowps_admin_page()) {
return;
}
if (AIOWPSecurity_Utility_Firewall::is_firewall_setup()) {
if (true !== $is_firewall_in_server) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- variable is set in the foreach loop
$this->render_upgrade_protection_notice();
}
} else {
$this->render_automatic_setup_notice();
}
$this->render_notices();
}
/**
* Will execute when the user presses 'Set up now' button
*
* @return void
*/
public function do_setup() {
$is_inserted_firewall_file = false;
$is_inserted_bootstrap_file = $this->bootstrap->contains_contents();
if (true !== $is_inserted_bootstrap_file) {
$is_inserted_bootstrap_file = $this->bootstrap->insert_contents();
if (true !== $is_inserted_bootstrap_file) {
$this->log_wp_error($is_inserted_bootstrap_file);
$this->show_notice(self::NOTICE_BOOTSTRAP);
return;
}
}
$firewall_file = AIOWPSecurity_Utility_Firewall::get_server_file();
if ($firewall_file instanceof AIOWPSecurity_Block_Userini) {
$directive = AIOWPSecurity_Utility_Firewall::get_already_set_directive($firewall_file);
if (!empty($directive)) {
if (AIOWPSecurity_Utility_Firewall::get_bootstrap_path() === $directive) {
$is_inserted_firewall_file = true;
} else {
$this->show_notice(self::NOTICE_DIRECTIVE_SET, array('directive' => $directive));
}
} else {
$is_inserted_firewall_file = $firewall_file->insert_contents();
}
} else {
if (AIOWPSecurity_Utility_Firewall::MANUAL_SETUP !== $firewall_file) {
$is_inserted_firewall_file = $firewall_file->insert_contents(); // attempts to insert firewall into required file
}
}
//Set up the firewall in the wp-config file
$is_inserted_wpconfig = $this->wpconfig->contains_contents();
if (true !== $is_inserted_wpconfig) {
$is_inserted_wpconfig = $this->wpconfig->insert_contents();
}
$this->log_wp_error($is_inserted_wpconfig);
//Set up the firewall in the mu-plugin
$is_inserted_muplugin = $this->muplugin->contains_contents();
if (true !== $is_inserted_muplugin) {
$is_inserted_muplugin = $this->muplugin->insert_contents();
}
if (false === $is_inserted_muplugin) {
$this->log_wp_error(new \WP_Error(
'file-mu-plugin-failed',
'Unable to create the mu-plugin',
$this->muplugin
));
}
$this->log_wp_error($is_inserted_muplugin);
if (true === $is_inserted_firewall_file) {
$this->show_notice(self::NOTICE_INSTALLED);
} else {
$this->log_wp_error($is_inserted_firewall_file);
$this->show_notice(self::NOTICE_MANUAL);
}
}
/**
* Dismisses the notice.
*
* @return void
*/
private function do_dismiss() {
global $aio_wp_security;
$aio_wp_security->configs->set_value('aios_firewall_dismiss', true, true);
}
/**
* Checks whether the notice is dismissed
*
* @return boolean
*/
private function is_dismissed() {
global $aio_wp_security;
return (true === $aio_wp_security->configs->get_value('aios_firewall_dismiss'));
}
/**
* Handles the form submission for the 'Set up now' notice
*
* @return void
*/
public function handle_setup_form() {
$nonce = isset($_POST['_wpnonce']) ? $_POST['_wpnonce'] : '';
$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($nonce, 'aiowpsec-firewall-setup');
if (!is_wp_error($result)) {
$this->do_setup();
$this->do_redirect();
}
}
/**
* Handles the dismiss form
*
* @return void
*/
public function handle_dismiss_form() {
$nonce = isset($_POST['_wpnonce']) ? $_POST['_wpnonce'] : '';
$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($nonce, 'aiowpsec-firewall-setup-dismiss');
if (!is_wp_error($result)) {
$this->do_dismiss();
$this->do_redirect();
}
}
/**
* Handles the form that downgrades the firewall's protection.
*
* @return void
*/
public function handle_downgrade_protection_form() {
$nonce = isset($_POST['_wpnonce']) ? $_POST['_wpnonce'] : '';
$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($nonce, 'aiowpsec-firewall-downgrade');
if (!is_wp_error($result)) {
AIOWPSecurity_Utility_Firewall::remove_firewall();
$this->do_redirect();
}
}
/**
* Handles the redirect
*
* @return void
*/
private function do_redirect() {
// Go back to the previous page and tab if set
if (isset($_POST['_wp_http_referer'])) {
$matches = array();
if (preg_match('/\?page='.AIOWPSEC_MENU_SLUG_PREFIX.'(?<page>.*)(&tab=(?<tab>.*))?$/m', $_POST['_wp_http_referer'], $matches)) {
$url = 'admin.php?page='.AIOWPSEC_MENU_SLUG_PREFIX;
if (isset($matches['page'])) {
$url .= sanitize_text_field($matches['page']);
if (isset($matches['tab'])) {
$url .= '&tab='.sanitize_text_field($matches['tab']);
}
}
AIOWPSecurity_Utility::redirect_to_url(admin_url(sanitize_url($url)));
}
}
AIOWPSecurity_Utility::redirect_to_url(admin_url('admin.php?page='.AIOWPSEC_MENU_SLUG_PREFIX));
}
/**
* Wrapper function to log WP_Errors to debug log
*
* @param WP_Error $wp_error - Our error which gets logged
* @return void
*/
private function log_wp_error($wp_error) {
if (is_wp_error($wp_error)) {
global $aio_wp_security;
$error_message = $wp_error->get_error_message();
$error_message .= ' - ';
$error_message .= $wp_error->get_error_data();
$aio_wp_security->debug_logger->log_debug($error_message, 4);
}
}
/**
* Sets the flags to show notices
*
* @param string $type - the type of notice we want to set
* @param array $values - any values that need to be passed
* @return void
*/
private function show_notice($type, $values = array()) {
global $aio_wp_security;
$aio_wp_security->configs->set_value('firewall_notice_'.$type, true);
if (!empty($values)) {
$aio_wp_security->configs->set_value('firewall_notice_values', $values);
}
$aio_wp_security->configs->save_config();
}
/**
* Renders any necessary notices
*
* @return void
*/
public function render_notices() {
global $aio_wp_security;
$notices = array(
self::NOTICE_BOOTSTRAP,
self::NOTICE_MANUAL,
self::NOTICE_INSTALLED,
self::NOTICE_DIRECTIVE_SET,
);
foreach ($notices as $notice) {
if ($aio_wp_security->configs->get_value('firewall_notice_'.$notice)) {
switch ($notice) {
case self::NOTICE_BOOTSTRAP:
$this->render_bootstrap_notice();
break;
case self::NOTICE_MANUAL:
if (!$this->any_pending_notices(self::NOTICE_MANUAL)) {
$this->render_manual_setup_notice();
}
break;
case self::NOTICE_INSTALLED:
$this->render_firewall_installed_notice();
break;
case self::NOTICE_DIRECTIVE_SET:
$values = $aio_wp_security->configs->get_value('firewall_notice_values');
$this->render_userini_directive_set_notice($values['directive']);
$aio_wp_security->configs->delete_value('firewall_notice_values');
break;
}
$aio_wp_security->configs->delete_value('firewall_notice_'.$notice);
}
}
$aio_wp_security->configs->save_config();
}
/**
* Detects if we have any notices pending to display
*
* @param string ...$exclude - do not check the status of these notices
*
* @return boolean
*/
private function any_pending_notices(...$exclude) {
global $aio_wp_security;
$notices = array(
self::NOTICE_BOOTSTRAP,
self::NOTICE_MANUAL,
self::NOTICE_INSTALLED,
self::NOTICE_DIRECTIVE_SET,
);
$notices = array_diff($notices, $exclude);
foreach ($notices as $notice) {
if (true === $aio_wp_security->configs->get_value('firewall_notice_'.$notice)) {
return true;
}
}
return false;
}
/**
* Notice is shown if we are unable to write to the bootstrap file
*
* @return void
*/
private function render_bootstrap_notice() {
?>
<div class="notice notice-error is-dismissible">
<p>
<strong><?php _e('All-In-One Security', 'all-in-one-wp-security-and-firewall'); ?></strong>
</p>
<p><?php _e('We were unable to create the file necessary to give you the highest level of protection.', 'all-in-one-wp-security-and-firewall');?></p>
<p><?php _e('Your firewall will have reduced protection which means some of your firewall\'s functionality will be unavailable.', 'all-in-one-wp-security-and-firewall');?></p>
<p><?php _e('If you would like to manually set up the necessary file, please follow these steps:', 'all-in-one-wp-security-and-firewall');?></p>
<p>
<?php
/* translators: %s Bootstrap file name. */
printf(__('1. Create a file with the name %s in the same directory as your WordPress install is in, i.e.:', 'all-in-one-wp-security-and-firewall'), pathinfo($this->bootstrap, PATHINFO_BASENAME));
?>
</p>
<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo esc_html($this->bootstrap); ?></pre>
<p><?php _e('2. Paste in the following code:', 'all-in-one-wp-security-and-firewall');?></p>
<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo htmlentities($this->bootstrap->get_contents()); ?></pre>
<p><?php _e('3. Save the file and press the \'Try again\' button below:', 'all-in-one-wp-security-and-firewall');?></p>
<?php
$this->render_try_again_button();
$this->render_manual_notice_footer();
}
/**
* Notice is shown if auto_prepend_file directive is already set in user.ini
*
* @param string $directive_value
* @return void
*/
private function render_userini_directive_set_notice($directive_value) {
$firewall_file = AIOWPSecurity_Utility_Firewall::get_server_file();
$this->render_manual_notice_header();
?>
<p>
<?php _e('1. Open the following file:', 'all-in-one-wp-security-and-firewall'); ?>
</p>
<p><code><?php echo esc_html($firewall_file); ?></code></p>
<?php if (empty($directive_value)) {?>
<p>
<?php _e('2. Look for the auto_prepend_file directive.', 'all-in-one-wp-security-and-firewall'); ?>
</p>
<?php } else {?>
<p>
<?php _e('2. Look for the following:', 'all-in-one-wp-security-and-firewall'); ?>
</p>
<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo "auto_prepend_file='".esc_html($directive_value)."'";?></pre>
<?php } ?>
<p>
<?php _e('3. Change it to the following:', 'all-in-one-wp-security-and-firewall'); ?>
</p>
<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo esc_html(AIOWPSecurity_Utility_Firewall::get_server_file()->get_contents()); ?></pre>
<p>
<?php echo __('4. Save the file and press the \'Try again\' button below:', 'all-in-one-wp-security-and-firewall').' '.__('You may have to wait up to 5 minutes before the settings take effect.', 'all-in-one-wp-security-and-firewall'); ?>
</p>
<?php
$this->render_try_again_button();
$this->render_manual_notice_footer();
}
/**
* Shows when the firewall has successfully installed
*
* @return void
*/
private function render_firewall_installed_notice() {
global $aio_wp_security;
$aio_wp_security->include_template('notices/firewall-installed-notice.php', false);
}
/**
* Renders the 'manual setup' dashboard notice
*
* @return void
*/
private function render_manual_setup_notice() {
$firewall_file = AIOWPSecurity_Utility_Firewall::get_server_file();
if (AIOWPSecurity_Utility_Firewall::MANUAL_SETUP === $firewall_file) {
//Show users how to manually add the firewall via php.ini if we can't detect their server
$bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
$this->render_manual_notice_header();
?>
<p>
<?php _e('1. Open your php.ini file.', 'all-in-one-wp-security-and-firewall'); ?>
</p>
<p>
<?php _e('2. Set the auto_prepend_file directive like below:', 'all-in-one-wp-security-and-firewall'); ?>
</p>
<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo "auto_prepend_file='".esc_html($bootstrap_path)."'";?></pre>
<p>
<?php echo __('3. Restart the webserver and refresh the page', 'all-in-one-wp-security-and-firewall').' '.__('You may have to wait up to 5 minutes before the settings take effect.', 'all-in-one-wp-security-and-firewall'); ?>
</p>
<?php
$this->render_manual_notice_footer();
} else {
//Show users how to manually add the firewall via their own server file
$this->render_manual_notice_header();
$firewall_file_name = pathinfo($firewall_file, PATHINFO_BASENAME);
?>
<p>
<?php
/* translators: %s Firewall file name. */
printf(__('1. Create a file with the name %s in the same directory as your WordPress install is in, i.e.:', 'all-in-one-wp-security-and-firewall'), $firewall_file_name);
?>
<p><code><?php echo esc_html($firewall_file); ?></code></p>
</p>
<p>
<?php _e('2. Paste in the following directives:', 'all-in-one-wp-security-and-firewall'); ?>
</p>
<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo htmlentities($firewall_file->get_contents()); ?></pre>
<p>
<?php echo __('3. Save the file and press the \'Try again\' button below:', 'all-in-one-wp-security-and-firewall'); ?>
</p>
<?php
$this->render_try_again_button();
$this->render_manual_notice_footer();
}
}
/**
* The header for notices that require manual intervention
*
* @return void
*/
private function render_manual_notice_header() {
?>
<div class="notice notice-warning is-dismissible">
<p>
<strong><?php _e('All-In-One Security', 'all-in-one-wp-security-and-firewall'); ?></strong>
</p>
<p>
<?php echo __('We were unable to set up your firewall with the highest level of protection.', 'all-in-one-wp-security-and-firewall').' '.
__('Your firewall will have reduced functionality.', 'all-in-one-wp-security-and-firewall');
?>
</p>
<p>
<?php _e('To give your site the highest level of protection, please follow these steps:', 'all-in-one-wp-security-and-firewall'); ?>
</p>
<?php
}
/**
* The footer for notices that require manual intervention
*
* @return void
*/
private function render_manual_notice_footer() {
?>
<p>
<strong><?php _e('Note: if you\'re unable to perform any of the aforementioned steps, please ask your web hosting provider for further assistance.', 'all-in-one-wp-security-and-firewall'); ?></strong>
</p>
</div>
<?php
}
/**
* Render Try again button.
*
* @return void
*/
private function render_try_again_button() {
?>
<form action="<?php echo esc_url(admin_url('admin-post.php')); ?>" method="POST">
<?php wp_nonce_field('aiowpsec-firewall-setup'); ?>
<input type="hidden" name="action" value="aiowps_firewall_setup">
<div style="padding-top: 10px; padding-bottom: 10px;">
<input class="button button-primary" type="submit" name="btn_try_again" value="<?php _e('Try again', 'all-in-one-wp-security-and-firewall'); ?>">
</div>
</form>
<?php
}
/**
* Renders the warning that users do not have the highest level of protection
*
* @return void
*/
private function render_upgrade_protection_notice() {
if ($this->should_not_show_notice()) {
return;
}
?>
<div class="notice notice-warning">
<form action="<?php echo esc_url(admin_url('admin-post.php')); ?>" method="POST">
<?php wp_nonce_field('aiowpsec-firewall-setup'); ?>
<input type="hidden" name="action" value="aiowps_firewall_setup">
<p>
<?php _e('We have detected that your AIOS firewall is not fully installed, and therefore does not have the highest level of protection.', 'all-in-one-wp-security-and-firewall');?>
<?php echo ' ' . __('Your firewall will have reduced functionality until it has been upgraded.', 'all-in-one-wp-security-and-firewall');?>
<div style="padding-top: 10px;">
<input class="button button-primary" type="submit" name="btn_upgrade_now" value="<?php _e('Upgrade your protection now', 'all-in-one-wp-security-and-firewall'); ?>">
</div>
</p>
</form>
</div>
<?php
}
/**
* Whether the firewall notice should not be shown.
*
* @return boolean True if the firewall notice should not be shown otherwise false.
*/
private function should_not_show_notice() {
if (!is_main_site()) {
return true;
}
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
return true;
}
if ($this->is_dismissed() && !AIOWPSecurity_Utility_Firewall::is_firewall_page()) {
return true;
}
if ($this->any_pending_notices()) {
return true; //only display if there are no other notices waiting to be displayed
}
return false;
}
/**
* Renders the 'Set up now' dashboard notice
*
* @return void
*/
private function render_automatic_setup_notice() {
global $aio_wp_security;
if ($this->should_not_show_notice()) {
return;
}
$aio_wp_security->include_template('notices/firewall-setup-notice.php', false, array('show_dismiss' => !AIOWPSecurity_Utility_Firewall::is_firewall_page()));
}
/**
* Ensures only one instance of the class can be created (singleton)
*
* @return AIOWPSecurity_Firewall_Setup_Notice|null
*/
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
}
@@ -0,0 +1,362 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
public function __construct() {
//Set parent defaults
parent::__construct(array(
'singular' => 'item', //singular name of the listed records
'plural' => 'items', //plural name of the listed records
'ajax' => false //does this table support ajax?
));
}
/**
* Returns created column in datetime format as per user setting time zone.
*
* @param array $item - data for the columns on the current row
*
* @return string - the datetime
*/
public function column_created($item) {
return AIOWPSecurity_Utility::convert_timestamp($item['created']);
}
public function column_default($item, $column_name) {
return $item[$column_name];
}
/**
* Returns id column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - html string for column rendered
*/
public function column_id($item) {
$ip = $item['ip_or_host'];
$is_locked = AIOWPSecurity_Utility::check_locked_ip($ip, '404');
$blacklist_tab = 'blacklist';
$is_blacklist = AIOWPSecurity_Utility::check_blacklist_ip($ip);
$actions = array();
$actions['delete'] = '<a class="aios-delete-404" data-id="' . esc_attr($item['id']) . '" data-message="' . esc_js(__('Are you sure you want to delete this item?', 'all-in-one-wp-security-and-firewall')) . '" href="#">' . __('Delete', 'all-in-one-wp-security-and-firewall') . '</a>';
if ($is_locked) {
// Build row actions for locked items
$actions['unblock'] = '<a class="aios-unblock-404" data-ip="' . esc_attr($ip) . '" data-message="' . esc_js(__('Are you sure you want to unblock this item?', 'all-in-one-wp-security-and-firewall')) . '" href="#">' . __('Unblock', 'all-in-one-wp-security-and-firewall') . '</a>';
} elseif ($is_blacklist) {
$unblock_url_nonce = wp_nonce_url(sprintf('admin.php?page=%s&tab=%s', AIOWPSEC_FIREWALL_MENU_SLUG, $blacklist_tab), "404_log_item_action", "aiowps_nonce");
$actions = array(
'unblock' => '<a href="'.$unblock_url_nonce.'" onclick="return confirm(\'' . esc_js(__('Are you sure you want to unblock this item?', 'all-in-one-wp-security-and-firewall')) . '\')">'.__('Unblock', 'all-in-one-wp-security-and-firewall').'</a>',
);
} else {
// Build row actions for other items
$actions['temp_block'] = '<a class="aios-temp-block-404" data-ip="' . esc_attr($ip) . '" data-message="' . esc_js(__('Are you sure you want to block this IP address?', 'all-in-one-wp-security-and-firewall')) . '" data-username="' . esc_attr($item['username']) . '" href="#">' . __('Temporarily block', 'all-in-one-wp-security-and-firewall') . '</a>';
$actions['blacklist_ip'] = '<a class="aios-blacklist-404" data-ip="' . esc_attr($ip) . '" data-message="' . esc_js(__('Are you sure you want to permanently block this IP address?', 'all-in-one-wp-security-and-firewall')) . '" href="#">' . __('Blacklist IP', 'all-in-one-wp-security-and-firewall') . '</a>';
}
//Return the user_login contents
return sprintf('%1$s <span style="color:silver"></span>%2$s',
/* $1%s */ $item['id'],
/* $2%s */ $this->row_actions($actions)
);
}
/**
* Returns status column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - html string for column rendered
*/
public function column_status($item) {
global $aio_wp_security;
$ip = $item['ip_or_host'];
//Check if this IP address is locked
$is_locked = AIOWPSecurity_Utility::check_locked_ip($ip, '404');
$blacklisted_string = $aio_wp_security->configs->get_value('aiowps_banned_ip_addresses');
$banned = strpos($blacklisted_string, $ip);
if (false !== $banned) {
return 'blacklisted';
} elseif ($is_locked) {
return 'temporarily blocked';
} else {
return '';
}
}
/**
* Returns checkbox column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - html string for column rendered
*/
public function column_cb($item) {
return sprintf('<input type="checkbox" name="%1$s[]" value="%2$s" />',
/* $1%s */ $this->_args['singular'], //Let's simply repurpose the table's singular label
/* $2%s */ $item['id'] //The value of the checkbox should be the record's id
);
}
public function get_columns() {
$columns = array(
'cb' => '<input type="checkbox" />', //Render a checkbox
'id' => 'ID',
'event_type' => __('Event type', 'all-in-one-wp-security-and-firewall'),
'ip_or_host' => __('IP address', 'all-in-one-wp-security-and-firewall'),
'url' => __('Attempted URL', 'all-in-one-wp-security-and-firewall'),
'referer_info' => __('Referer', 'all-in-one-wp-security-and-firewall'),
'created' => __('Date and time', 'all-in-one-wp-security-and-firewall'),
'status' => __('Lock status', 'all-in-one-wp-security-and-firewall'),
);
$columns = apply_filters('list_404_get_columns', $columns);
return $columns;
}
public function get_sortable_columns() {
$sortable_columns = array(
'id' => array('id', false),
'event_type' => array('event_type', false),
'ip_or_host' => array('ip_or_host', false),
'url' => array('url', false),
'referer_info' => array('referer_info', false),
'created' => array('created', false),
);
$sortable_columns = apply_filters('list_404_get_sortable_columns', $sortable_columns);
return $sortable_columns;
}
/**
* Get bulk actions for the current WordPress screen.
*
* @return array An associative array of bulk actions where the keys are action names
* and the values are the corresponding action labels.
*/
public function get_bulk_actions() {
return array(
//'unlock' => 'Unlock',
'bulk_block_ip' => __('Temporarily block IP', 'all-in-one-wp-security-and-firewall'),
'bulk_blacklist_ip' => __('Blacklist IP', 'all-in-one-wp-security-and-firewall'),
'delete' => __('Delete', 'all-in-one-wp-security-and-firewall')
);
}
/**
* Process bulk actions for the current WordPress screen.
*
* This method checks for the presence of a valid nonce and user capabilities,
* then performs the appropriate action based on the selected bulk action.
*
* @return void
*/
private function process_bulk_action() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- PCP warning. This is the nonce.
if (empty($_REQUEST['_wpnonce']) || !isset($_REQUEST['_wp_http_referer'])) return;
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- PCP warning. Ignore.
$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($_REQUEST['_wpnonce'], 'bulk-items');
if (is_wp_error($result)) return;
if ('bulk_block_ip' === $this->current_action()) {//Process delete bulk actions
if (!isset($_REQUEST['item'])) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'));
} else {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- PCP warning, ignore. Sanitized later.
$this->block_ip(wp_unslash($_REQUEST['item']));
}
}
if ('bulk_blacklist_ip' === $this->current_action()) {//Process delete bulk actions
if (!isset($_REQUEST['item'])) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'));
} else {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- PCP warning, ignore. Sanitized later.
$this->blacklist_ip_address(wp_unslash($_REQUEST['item']));
}
}
if ('delete' === $this->current_action()) {//Process delete bulk actions
if (!isset($_REQUEST['item'])) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'));
} else {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- PCP warning, ignore. Sanitized later.
$this->delete_404_event_records(wp_unslash($_REQUEST['item']));
}
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended -- PCP warning. This is the nonce.
}
/**
* Locks an IP address by adding it to the AIOWPSEC_TBL_LOGIN_LOCKOUT table.
*
* @param array|string $entries - ids that correspond to ip addresses in the AIOWPSEC_TBL_EVENTS table or a single ip address
* @param string $username - (optional)username of user being locked
*
* @return boolean|void
*/
public function block_ip($entries, $username = '') {
global $wpdb;
if (is_array($entries)) {
//lock multiple records
$entries = array_filter($entries, 'is_numeric'); //discard non-numeric ID values
$id_list = "(" .implode(",", $entries) .")"; //Create comma separate list for DB operation
$events_table = AIOWPSEC_TBL_EVENTS;
// phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$results = $wpdb->get_col("SELECT ip_or_host FROM $events_table WHERE ID IN " . $id_list);
if (empty($results)) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Could not process the request because the IP addresses for the selected entries could not be found.', 'all-in-one-wp-security-and-firewall'));
return false;
} else {
foreach ($results as $entry) {
if (filter_var($entry, FILTER_VALIDATE_IP)) {
AIOWPSecurity_Utility::lock_IP($entry, '404', $username);
}
}
}
AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected IP addresses are now temporarily blocked.', 'all-in-one-wp-security-and-firewall'));
}
}
/**
* Permanently blocks an IP address by adding it to the blacklist and writing rules to the htaccess file.
*
* @param array|string $entries - ids that correspond to ip addresses in the AIOWPSEC_TBL_EVENTS table or a single ip address
*
* @return boolean|void
*/
public function blacklist_ip_address($entries) {
global $wpdb, $aio_wp_security;
$aiowps_firewall_config = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONFIG);
$bl_ip_addresses = $aio_wp_security->configs->get_value('aiowps_banned_ip_addresses'); //get the currently saved blacklisted IPs
$ip_list_array = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($bl_ip_addresses);
if (is_array($entries)) {
//Get the selected IP addresses
$entries = array_filter($entries, 'is_numeric'); //discard non-numeric ID values
$id_list = "(" .implode(",", $entries) .")"; //Create comma separate list for DB operation
$events_table = AIOWPSEC_TBL_EVENTS;
// phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$results = $wpdb->get_col("SELECT ip_or_host FROM $events_table WHERE ID IN " . $id_list);
if (empty($results)) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Could not process the request because the IP addresses for the selected entries could not be found.', 'all-in-one-wp-security-and-firewall'));
return false;
} else {
foreach ($results as $entry) {
$ip_list_array[] = $entry;
}
}
}
$validated_ip_list_array = AIOWPSecurity_Utility_IP::validate_ip_list($ip_list_array, 'blacklist');
if (is_wp_error($validated_ip_list_array)) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(nl2br($validated_ip_list_array->get_error_message()));
} else {
$banned_ip_data = implode("\n", $validated_ip_list_array);
$aio_wp_security->configs->set_value('aiowps_enable_blacklisting', '1'); // Force blacklist feature to be enabled.
$aio_wp_security->configs->set_value('aiowps_banned_ip_addresses', $banned_ip_data);
$aio_wp_security->configs->save_config();
$aiowps_firewall_config->set_value('aiowps_blacklist_ips', $validated_ip_list_array);
AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected IP addresses have been added to the blacklist and will be permanently blocked.', 'all-in-one-wp-security-and-firewall'));
}
}
/**
* Deletes one or more records from the AIOWPSEC_TBL_EVENTS table.
*
* @param array|string|integer $entries - ids or a single id
*
* @return void|string
*/
public function delete_404_event_records($entries) {
global $wpdb, $aio_wp_security;
$events_table = AIOWPSEC_TBL_EVENTS;
if (is_array($entries)) {
//Delete multiple records
$entries = array_map('esc_sql', $entries); //escape every array element
$entries = array_filter($entries, 'is_numeric'); //discard non-numeric ID values
$id_list = "(" . implode(",", $entries) . ")"; //Create comma separate list for DB operation
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$result = $wpdb->query("DELETE FROM " . $events_table . " WHERE id IN " . $id_list);
if ($result) {
AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
} else {
// Error on bulk delete
$aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Events table. Database error: '.$wpdb->last_error, 4);
AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st();
}
}
}
/**
* Retrieves all items from AIOWPSEC_TBL_EVENTS according to a search term inside $_REQUEST['s'] and only '404' events if there is no search term. It then assigns to $this->items.
*
* @param Boolean $ignore_pagination - whether to not paginate
*
* @return Void
*/
public function prepare_items($ignore_pagination = false) {
/**
* First, lets decide how many records per page to show
*/
$per_page = 100;
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. Nonce checked in previous function.
$search_term = isset($_REQUEST['s']) ? sanitize_text_field(wp_unslash($_REQUEST['s'])) : '';
$this->_column_headers = array($columns, $hidden, $sortable);
$this->process_bulk_action();
global $wpdb;
$events_table_name = AIOWPSEC_TBL_EVENTS;
// Ordering parameters
// Parameters that are going to be used to order the result
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
$orderby = isset($_GET['orderby']) ? sanitize_text_field(wp_unslash($_GET['orderby'])) : '';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
$order = isset($_GET['order']) ? sanitize_text_field(wp_unslash($_GET['order'])) : '';
$orderby = !empty($orderby) ? esc_sql($orderby) : 'id';
$order = !empty($order) ? esc_sql($order) : 'DESC';
$orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
$order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
if (empty($search_term)) {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$data = $wpdb->get_results("SELECT * FROM $events_table_name WHERE `event_type` = '404' ORDER BY $orderby $order", ARRAY_A);
} else {
// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQueryWithPlaceholder, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$data = $wpdb->get_results($wpdb->prepare("SELECT * FROM $events_table_name WHERE `ip_or_host` LIKE '%%%s%%' OR `url` LIKE '%%%s%%' OR `referer_info` LIKE '%%%s%%' ORDER BY $orderby $order", $wpdb->esc_like($search_term), $wpdb->esc_like($search_term), $wpdb->esc_like($search_term)), ARRAY_A);
}
if (!$ignore_pagination) {
$current_page = $this->get_pagenum();
$total_items = count($data);
$data = array_slice($data, (($current_page - 1) * $per_page), $per_page);
$this->set_pagination_args(array(
'total_items' => $total_items, //WE have to calculate the total number of items
'per_page' => $per_page, //WE have to determine how many items to show on a page
'total_pages' => ceil($total_items / $per_page) //WE have to calculate the total number of pages
));
}
foreach ($data as $index => $row) {
// Insert an empty status column - we will use later
$data[$index]['status'] = '';
}
$this->items = $data;
}
}
@@ -0,0 +1,527 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_List_Audit_Log extends AIOWPSecurity_Ajax_Data_Table {
/**
* Constructs the table object and sets up its attributes.
*
* @param array $data - An array containing additional data for the table. Default is an empty array.
* @return void
*/
public function __construct($data = array()) {
// Set parent defaults
parent::__construct(array(
'singular' => 'item', // singular name of the listed records
'plural' => 'items', // plural name of the listed records
'ajax' => true, // does this table support ajax?
'data' => $data // Request data
));
}
/**
* Returns the default column item
*
* @param object $item - item from which column data is returned
* @param string $column_name - column name to be fetched from item
* @return string
*/
public function column_default($item, $column_name) {
return $item[$column_name];
}
/**
* Returns cb column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - the html to be rendered
*/
public function column_cb($item) {
return sprintf(
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
/* $1%s */ $this->_args['singular'], // Let's simply repurpose the table's singular label
/* $2%s */ $item['id'] // The value of the checkbox should be the record's id
);
}
/**
* Returns created column html to be rendered.
*
* @param array $item Data for the columns on the current row.
*
* @return string The html to be rendered.
*/
public function column_created($item) {
$actions = array(
'delete' => '<a class="aios-delete-audit-log" data-id="' . esc_attr($item['id']) . '" data-message="' . esc_js(__('Are you sure you want to delete this item?', 'all-in-one-wp-security-and-firewall')) . '" href="">' . esc_html__('Delete', 'all-in-one-wp-security-and-firewall') . '</a>'
);
return AIOWPSecurity_Utility::convert_timestamp($item['created']) . '<span style="color:silver"></span>' . $this->row_actions($actions);
}
/**
* Returns ip column html to be rendered.
*
* @param array $item Data for the columns on the current row.
*
* @return string The html to be rendered.
*/
public function column_ip($item) {
$ip = $item['ip'];
$unblacklist_ip_warning_translation = __('Are you sure you want to unblacklist this IP address?', 'all-in-one-wp-security-and-firewall');
$unlock_ip_warning_translation = __('Are you sure you want to unlock this IP address?', 'all-in-one-wp-security-and-firewall');
$lock_ip_warning_translation = __('Are you sure you want to temporarily lock this IP address?', 'all-in-one-wp-security-and-firewall');
$blacklist_ip_warning_translation = __('Are you sure you want to blacklist this IP address?', 'all-in-one-wp-security-and-firewall');
// Build row actions.
if (AIOWPSecurity_Utility_Permissions::is_main_site_and_super_admin() && AIOWPSecurity_Utility::check_blacklist_ip($ip)) {
$actions = array(
'unblacklist' => '<a class="aios-unblacklist-ip-button" data-ip="' . esc_attr($ip) . '" data-message="' . esc_js($unblacklist_ip_warning_translation) . '" href="">' . esc_html__('Unblacklist', 'all-in-one-wp-security-and-firewall') . '</a>',
);
} elseif (AIOWPSecurity_Utility::check_locked_ip($ip, 'audit-log')) {
$actions = array(
'unlock' => '<a class="aios-unlock-ip-button" data-ip="' . esc_attr($ip) . '" data-message="' . esc_js($unlock_ip_warning_translation) . '" href="">' . esc_html__('Unlock', 'all-in-one-wp-security-and-firewall') . '</a>',
);
} else {
$actions = array(
'lock_ip' => '<a class="aios-lock-ip-button" data-ip="' . esc_attr($ip) . '" data-message="' . esc_js($lock_ip_warning_translation) . '" href="">' . esc_html__('Lock IP', 'all-in-one-wp-security-and-firewall') . '</a>',
);
if (AIOWPSecurity_Utility_Permissions::is_main_site_and_super_admin()) {
$actions['blacklist_ip'] = '<a class="aios-blacklist-ip-button" data-ip="' . esc_attr($ip) . '" data-message="' . esc_js($blacklist_ip_warning_translation) . '" href="">' . esc_html__('Blacklist IP', 'all-in-one-wp-security-and-firewall') . '</a>';
}
}
return $ip . '<span style="color:silver"></span>' . $this->row_actions($actions);
}
/**
* Returns event type column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - the html to be rendered
*/
public function column_event_type($item) {
if (empty($item['event_type'])) return __('No event type available.', 'all-in-one-wp-security-and-firewall');
$output = isset(AIOWPSecurity_Audit_Events::$event_types[$item['event_type']]) ? AIOWPSecurity_Audit_Events::$event_types[$item['event_type']] : $item['event_type'];
return $output;
}
/**
* Returns details column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - the html to be rendered
*/
public function column_details($item) {
$details = json_decode($item['details'], true);
if (!is_array($details)) return $item['details'];
$key = array_keys($details)[0];
if (method_exists("AIOWPSecurity_Audit_Text_Handler", "{$key}_to_text")) {
return call_user_func("AIOWPSecurity_Audit_Text_Handler::{$key}_to_text", $details[$key]);
}
return $item['details'];
}
/**
* Returns stack trace column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - the html to be rendered
*/
public function column_stacktrace($item) {
if (empty($item['stacktrace'])) return __('No stack trace available.', 'all-in-one-wp-security-and-firewall');
if (is_serialized($item['stacktrace'])) {
$stacktrace = AIOWPSecurity_Utility::unserialize($item['stacktrace']);
} else {
$stacktrace = $item['stacktrace'];
}
ob_start();
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_dump -- Part of error reporting system.
var_dump($stacktrace);
$stacktrace_output = ob_get_contents();
ob_end_clean();
$output = sprintf('<a href="#TB_inline?&inlineId=trace-%s" title="%s" class="thickbox">%s</a>', $item['id'], esc_html__('Stack trace', 'all-in-one-wp-security-and-firewall'), esc_html__('Show trace', 'all-in-one-wp-security-and-firewall'));
$output .= sprintf('<div id="trace-%s" style="display: none"><pre>%s</pre></div>', $item['id'], htmlspecialchars($stacktrace_output));
return $output;
}
/**
* Sets the columns for the table
*
* @return array
*/
public function get_columns() {
$columns = array(
'cb' => '<input type="checkbox">', //Render a checkbox
'created' => __('Date and time', 'all-in-one-wp-security-and-firewall'),
'level' => __('Level', 'all-in-one-wp-security-and-firewall'),
'network_id' => __('Network ID', 'all-in-one-wp-security-and-firewall'),
'site_id' => __('Site ID', 'all-in-one-wp-security-and-firewall'),
'username' => __('Username', 'all-in-one-wp-security-and-firewall'),
'ip' => __('IP', 'all-in-one-wp-security-and-firewall'),
'event_type' => __('Event', 'all-in-one-wp-security-and-firewall'),
'details' => __('Details', 'all-in-one-wp-security-and-firewall'),
'stacktrace' => __('Stack trace', 'all-in-one-wp-security-and-firewall')
);
$columns = apply_filters('list_auditlogs_get_columns', $columns);
return $columns;
}
/**
* Sets which of the columns the table data can be sorted by
*
* @return array
*/
public function get_sortable_columns() {
$sortable_columns = array(
'created' => array('created', false),
'network_id' => array('network_id', false),
'site_id' => array('site_id', false),
'level' => array('level', false),
'username' => array('username', false),
'ip' => array('ip', false),
'event_type' => array('event_type', false),
'details' => array('details', false),
'stacktrace' => array('stacktrace', false)
);
$sortable_columns = apply_filters('list_auditlogs_get_sortable_columns', $sortable_columns);
return $sortable_columns;
}
/**
* This function will display a list of bulk actions for the list table
*
* @return array
*/
public function get_bulk_actions() {
$actions = array(
'delete_all' => __('Delete all', 'all-in-one-wp-security-and-firewall'),
'delete_selected' => __('Delete selected', 'all-in-one-wp-security-and-firewall'),
'delete_filtered' => __('Delete filtered', 'all-in-one-wp-security-and-firewall')
);
return $actions;
}
/**
* This function will process the bulk action request, $search_term and $filters are only used if the user is trying to bulk delete the filtered items
*
* @param string $search_term - The search term used for filtering records.
* @param array $filters - An array containing filters applied to the records.
* @param string $action - The bulk action to be performed.
* @param array $items - An array of record IDs on which the action will be performed. Default is an empty array.
*
* @return void
*/
private function process_bulk_action($search_term, $filters, $action, $items = array()) {
global $wpdb;
if ('delete_selected' === $action) { // Process delete bulk actions
if (!isset($items)) {
AIOS_Helper::set_message('aios_list_message', __('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'), 'error');
} else {
$this->delete_audit_event_records($items);
}
} elseif ('delete_filtered' === $action) {
if (!empty($filters) || '' !== $search_term) {
$audit_log_tbl = AIOWPSEC_TBL_AUDIT_LOG;
$where_sql = $this->get_audit_list_where_sql($search_term, $filters);
// phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$results = $wpdb->get_results("SELECT id FROM {$audit_log_tbl} {$where_sql}", 'ARRAY_A');
$items = array_column($results, 'id');
$this->delete_audit_event_records($items);
} else {
AIOS_Helper::set_message('aios_list_message', __('Please select the level or the event type filter or filter by a search term', 'all-in-one-wp-security-and-firewall'), 'error');
}
} elseif ('delete_all' === $action) {
$this->delete_audit_event_records(null, true);
}
}
/**
* Outputs extra controls to be displayed between bulk actions and pagination
*
* @param string $which - where we are outputting content (top or bottom)
*
* @return void
*/
protected function extra_tablenav($which) {
switch ($which) {
case 'top':
?>
<div class="alignleft actions">
<select name="level-filter" class="audit-filter-level">
<?php $selected = !isset($this->_args['data']['level-filter']) ? ' selected = "selected"' : ''; ?>
<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- No user input to escape. ?>
<option value="-1" <?php echo $selected; ?>><?php esc_html_e('All levels', 'all-in-one-wp-security-and-firewall'); ?></option>
<?php
foreach (AIOWPSecurity_Audit_Events::$log_levels as $level) {
$selected = isset($this->_args['data']['level-filter']) && $this->_args['data']['level-filter'] == $level ? ' selected = "selected"' : '';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- No user input to escape.
echo '<option value="'. esc_attr($level) .'" '. $selected .'>'. esc_html($level) .'</option>';
}
?>
</select>
<select name="event-filter" class="audit-filter-event">
<?php $selected = !isset($this->_args['data']['event-filter']) ? ' selected = "selected"' : ''; ?>
<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- No user input to escape. ?>
<option value="-1" <?php echo $selected; ?>><?php esc_html_e('All events', 'all-in-one-wp-security-and-firewall'); ?></option>
<?php
foreach (AIOWPSecurity_Audit_Events::$event_types as $event_type => $event) {
$selected = isset($this->_args['data']['event-filter']) && $this->_args['data']['event-filter'] == $event_type ? ' selected = "selected"' : '';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- No user input to escape.
echo '<option value="'. esc_attr($event_type) .'" '. $selected .'>'. esc_html($event) .'</option>';
}
?>
</select>
<?php submit_button(esc_html__('Filter', 'all-in-one-wp-security-and-firewall'), 'action', '', false); ?>
</div>
<?php
break;
case 'bottom':
submit_button(esc_html__('Export to CSV', 'all-in-one-wp-security-and-firewall'), 'primary', 'aiowps_export_audit_event_logs_to_csv', false);
break;
}
}
/**
* This function will process the delete request for the audit event records
*
* @param integer|array $entries - an ID or array of IDs to be deleted
* @param boolean $delete_all - indicates if all entries should be deleted or not (if true, then $entries will be ignored)
*
* @return void|string
*/
public function delete_audit_event_records($entries, $delete_all = false) {
global $wpdb, $aio_wp_security;
$audit_log_tbl = AIOWPSEC_TBL_AUDIT_LOG;
$result = false;
if ($delete_all) {
// Delete all records
$site_id_where_sql = (!is_super_admin()) ? ' WHERE site_id = ' . get_current_blog_id() : '';
$delete_command = "DELETE FROM " . $audit_log_tbl . $site_id_where_sql;
// phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$result = $wpdb->query($delete_command);
} elseif (is_array($entries)) {
// Delete multiple records
$entries = array_map('esc_sql', $entries); // Escape every array element
$entries = array_filter($entries, 'is_numeric'); // Discard non-numeric ID values
$chunks = array_chunk($entries, 1000);
$site_id_where_sql = (!is_super_admin()) ? ' AND site_id = ' . get_current_blog_id() : '';
// Processing each chunk
foreach ($chunks as $chunk) {
$id_list = "(" . implode(",", $chunk) . ")"; // Create comma separate list for DB operation
$delete_command = "DELETE FROM " . $audit_log_tbl . " WHERE id IN " . $id_list . $site_id_where_sql;
// phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$result = $wpdb->query($delete_command);
if (!$result) {
$aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Audit log table. Database error: '.$wpdb->last_error, 4);
AIOS_Helper::set_message('aios_list_message', __('The selected record(s) have failed to delete.', 'all-in-one-wp-security-and-firewall'), 'error');
return;
}
}
} elseif (!empty($entries)) {
// Delete single record
$site_id_where_sql = (!is_super_admin()) ? ' AND site_id = ' . get_current_blog_id() : '';
$delete_command = "DELETE FROM " . $audit_log_tbl . " WHERE id = '" . absint($entries) . "'" . $site_id_where_sql;
// phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$result = $wpdb->query($delete_command);
}
if ($result || 0 < $result) {
$aios_list_message = __('The selected record(s) has been deleted successfully.', 'all-in-one-wp-security-and-firewall');
AIOS_Helper::set_message('aios_list_message', $aios_list_message);
} else {
$aios_list_message = __('The selected record(s) have failed to delete.', 'all-in-one-wp-security-and-firewall');
$aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Audit log table. Database error: '.$wpdb->last_error, 4);
AIOS_Helper::set_message('aios_list_message', $aios_list_message, 'error');
}
return $aios_list_message;
}
/**
* This function will build and return the SQL WHERE statement
*
* @param string $search_term - the search term applied
* @param array $filters - the filters applied
*
* @return string - the SQL WHERE statement
*/
private function get_audit_list_where_sql($search_term, $filters) {
$where_sql = '';
if ('' == $search_term) {
$where_sql = (!is_super_admin()) ? 'WHERE site_id = '.get_current_blog_id() : '';
$extra_where = '';
if (!empty($filters)) {
$where_sql = empty($where_sql) ? 'WHERE ' : $where_sql . ' AND ';
foreach ($filters as $filter => $value) {
if (!empty($extra_where)) $extra_where .= ' AND ';
$extra_where .= "`{$filter}` = '".esc_sql($value)."'";
}
}
$where_sql .= $extra_where;
} else {
$where_sql = (!is_super_admin()) ? 'WHERE site_id = '.get_current_blog_id().' AND ' : 'WHERE ';
$extra_where = '';
if (!empty($filters)) {
foreach ($filters as $filter => $value) {
if (!empty($extra_where)) $extra_where .= ' AND ';
$extra_where .= "`{$filter}` = '".esc_sql($value)."'";
}
$where_sql .= $extra_where . ' AND (';
$extra_where = '';
}
// We don't use FILTER_VALIDATE_IP here as we want to be able to search for partial IP's
if (preg_match('/^[0-9a-f:\.]+$/i', $search_term)) {
$extra_where .= "`ip` LIKE '".esc_sql($search_term)."%'";
}
if (in_array($search_term, AIOWPSecurity_Audit_Events::$log_levels) && !isset($filters['level'])) {
if (!empty($extra_where)) $extra_where .= ' OR ';
$extra_where .= "`level` = '".esc_sql($search_term)."'";
}
if (!empty($extra_where)) $extra_where .= ' OR ';
if (isset($filters['event_type'])) {
$extra_where .= "`username` LIKE '".esc_sql($search_term)."%'";
} else {
$extra_where .= "(`username` LIKE '".esc_sql($search_term)."%' or `event_type` LIKE '%".esc_sql($search_term)."%')";
}
if (!empty($filters)) $extra_where .= ')';
$where_sql .= $extra_where;
}
return $where_sql;
}
/**
* Grabs the data from database and handles the pagination
*
* @param boolean $ignore_pagination - whether to not paginate
*
* @return void
*/
public function prepare_items($ignore_pagination = false) {
/**
* First, lets decide how many records per page to show
*/
$no_action = -1;
$per_page = defined('AIOWPSEC_AUDIT_LOG_PER_PAGE') ? absint(AIOWPSEC_AUDIT_LOG_PER_PAGE) : 100;
$per_page = empty($per_page) ? 100 : $per_page;
$current_page = $this->get_pagenum();
$offset = (!$ignore_pagination && $per_page > 0) ? ($current_page - 1) * $per_page : 0;
$columns = $this->get_columns();
$hidden = array('id'); // we really don't need the IDs of the log entries displayed
if (!is_multisite()) {
$hidden[] = 'network_id';
$hidden[] = 'site_id';
}
$sortable = $this->get_sortable_columns();
$filters = array();
if (isset($this->_args['data']['level-filter']) && $no_action != $this->_args['data']['level-filter']) $filters['level'] = sanitize_text_field($this->_args['data']['level-filter']);
if (isset($this->_args['data']['event-filter']) && $no_action != $this->_args['data']['event-filter']) $filters['event_type'] = sanitize_text_field($this->_args['data']['event-filter']);
$search_term = isset($this->_args['data']['s']) ? sanitize_text_field(stripslashes($this->_args['data']['s'])) : '';
$this->_column_headers = array($columns, $hidden, $sortable);
$items = array();
if (isset($this->_args['data']['items'])) {
if (is_array($this->_args['data']['items'])) {
foreach ($this->_args['data']['items'] as $item) {
$sanitized_item = sanitize_text_field($item);
$items[] = $sanitized_item;
}
} else {
$sanitized_item = sanitize_text_field($this->_args['data']['items']);
$items[] = $sanitized_item;
}
} else {
$items = null;
}
if (isset($this->_args['data']['action'])) $action = sanitize_text_field($this->_args['data']['action']);
else $action = $no_action;
if (isset($action) && $no_action !== $action) {
$this->process_bulk_action($search_term, $filters, $action, $items);
}
global $wpdb;
$audit_log_tbl = AIOWPSEC_TBL_AUDIT_LOG;
// Parameters that are going to be used to order the result
isset($this->_args['data']["orderby"]) ? $orderby = wp_strip_all_tags($this->_args['data']["orderby"]) : $orderby = '';
isset($this->_args['data']["order"]) ? $order = wp_strip_all_tags($this->_args['data']["order"]) : $order = '';
// By default show the most recent audit log entries.
$orderby = !empty($orderby) ? esc_sql($orderby) : 'created';
$order = !empty($order) ? esc_sql($order) : 'DESC';
$orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
$order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
$orderby = sanitize_sql_orderby($orderby);
$order = sanitize_sql_orderby($order);
$where_sql = $this->get_audit_list_where_sql($search_term, $filters);
// phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$total_items = $wpdb->get_var("SELECT COUNT(*) FROM {$audit_log_tbl} {$where_sql}");
if ($ignore_pagination) {
// phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$data = $wpdb->get_results("SELECT * FROM {$audit_log_tbl} {$where_sql} ORDER BY {$orderby} {$order}", 'ARRAY_A');
} else {
// phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$data = $wpdb->get_results("SELECT * FROM {$audit_log_tbl} {$where_sql} ORDER BY {$orderby} {$order} LIMIT {$per_page} OFFSET {$offset}", 'ARRAY_A');
}
// Filter the 'details' section
foreach ($data as $key => $entry) {
$details = json_decode($entry['details'], true);
$details = is_null($details) ? $entry['details'] : $details; // check if the decode worked, if not pass the json string
$data[$key]['details'] = wp_json_encode(apply_filters('aios_audit_filter_details', $details, $entry['event_type']));
}
$this->items = $data;
if ($ignore_pagination) return;
$this->set_pagination_args(array(
'total_items' => $total_items, // We have to calculate the total number of items
'per_page' => $per_page, // We have to determine how many items to show on a page
'total_pages' => ceil($total_items / $per_page) // We have to calculate the total number of pages
));
}
}
@@ -0,0 +1,221 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_List_Comment_Spammer_IP extends AIOWPSecurity_List_Table {
public function __construct() {
//Set parent defaults
parent::__construct(array(
'singular' => 'item', // singular name of the listed records
'plural' => 'items', // plural name of the listed records
'ajax' => false // does this table support ajax?
));
}
public function column_default($item, $column_name) {
return $item[$column_name];
}
public function column_comment_author_IP($item) {
//Build row actions
if (!is_main_site() || 'blocked' === $item['status']) {
//Suppress the block link if site is a multi site AND not the main site or the status is blocked
$actions = array(); //blank array
} else {
//Add IP to block URL
$ip = $item['comment_author_IP'];
$actions = array(
'block' => '<a class="aios-block-author-ip" data-ip="'.esc_attr($ip).'" data-message="'.esc_js(__('Are you sure you want to permanently block this IP address?', 'all-in-one-wp-security-and-firewall')).'" href="">'.__('Block', 'all-in-one-wp-security-and-firewall').'</a>',
);
}
//Return the user_login contents
return sprintf('%1$s <span style="color:silver"></span>%2$s',
/*$1%s*/ $item['comment_author_IP'],
/*$2%s*/ $this->row_actions($actions)
);
}
public function column_cb($item) {
return sprintf(
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
/*$1%s*/ $this->_args['singular'], //Let's simply repurpose the table's singular label
/*$2%s*/ esc_attr($item['comment_author_IP']) //The value of the checkbox should be the record's id
);
}
public function get_columns() {
$columns = array(
'cb' => '<input type="checkbox" />', //Render a checkbox
'comment_author_IP' => __('Spammer IP', 'all-in-one-wp-security-and-firewall'),
'amount' => __('Number of spam comments from this IP', 'all-in-one-wp-security-and-firewall'),
'status' => __('Status', 'all-in-one-wp-security-and-firewall'),
);
return $columns;
}
public function get_sortable_columns() {
$sortable_columns = array(
'comment_author_IP' => array('comment_author_IP',false),
'amount' => array('amount',false),
'status' => array('status',false),
);
return $sortable_columns;
}
public function get_bulk_actions() {
if (!is_main_site()) {
//Suppress the block link if site is a multi site AND not the main site
$actions = array(); //blank array
} else {
$actions = array(
'block' => __('Block', 'all-in-one-wp-security-and-firewall')
);
}
return $actions;
}
/**
* This function handles bulk actions on the table
*
* @return void
*/
private function process_bulk_action() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This IS the nonce check.
if (empty($_REQUEST['_wpnonce']) || !isset($_REQUEST['_wp_http_referer'])) return;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput -- This IS the nonce check.
$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($_REQUEST['_wpnonce'], 'bulk-items');
if (is_wp_error($result)) return;
if ('block' === $this->current_action()) {
//Process block bulk actions
if (!isset($_REQUEST['item'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce already checked above.
$error_msg = '<div id="message" class="error"><p><strong>';
$error_msg .= esc_html__('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall');
$error_msg .= '</strong></p></div>';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP error. Output already escaped.
echo $error_msg;
} else {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce already checked above.
$this->block_spammer_ip_records((filter_var(wp_unslash($_REQUEST['item']), FILTER_VALIDATE_IP)));
}
}
}
/**
* This function will add the selected IP addresses to the blacklist.
*
* @param int|array $entries - either an array of IDs or a single ID of ip to be blocked
*
* @return void
*/
public function block_spammer_ip_records($entries) {
if (is_array($entries)) {
$entries = array_map('esc_sql', $entries); // Escape every array element
//Bulk selection using checkboxes were used
foreach ($entries as $ip_add) {
AIOWPSecurity_Blocking::add_ip_to_block_list($ip_add, 'spam');
}
}
AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected IP addresses are now permanently blocked.', 'all-in-one-wp-security-and-firewall'));
}
/**
* This function prepare the items rendered on the table
*
* @return void
*/
public function prepare_items() {
//First, lets decide how many records per page to show
$per_page = 100;
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array($columns, $hidden, $sortable);
$this->process_bulk_action();
global $wpdb;
global $aio_wp_security;
$minimum_comments_per_ip = $aio_wp_security->configs->get_value('aiowps_spam_ip_min_comments');
if (empty($minimum_comments_per_ip)) {
$minimum_comments_per_ip = 5;
}
// Ordering parameters
//Parameters that are going to be used to order the result
isset($_GET["orderby"]) ? $orderby = wp_strip_all_tags(wp_unslash($_GET["orderby"])) : $orderby = ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No nonce to check.
isset($_GET["order"]) ? $order = wp_strip_all_tags(wp_unslash($_GET["order"])) : $order = ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No nonce to check.
$orderby = !empty($orderby) ? esc_sql($orderby) : 'amount';
$order = !empty($order) ? esc_sql($order) : 'DESC';
$orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
$order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
// status is not a key in the database so we don't want to sort the database results, but sort the array later
if ('status' == $orderby) {
$sql = $wpdb->prepare("SELECT comment_author_IP, COUNT(*) AS amount
FROM $wpdb->comments
WHERE comment_approved = 'spam'
GROUP BY comment_author_IP
HAVING amount >= %d
", $minimum_comments_per_ip);
} else {
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $orderby cannot be prepared.
$sql = $wpdb->prepare("SELECT comment_author_IP, COUNT(*) AS amount
FROM $wpdb->comments
WHERE comment_approved = 'spam'
GROUP BY comment_author_IP
HAVING amount >= %d
ORDER BY $orderby $order
", $minimum_comments_per_ip);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $orderby cannot be prepared.
}
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- Preparing done in conditional above.
$data = $wpdb->get_results($sql, ARRAY_A);
// Get all permanently blocked IP addresses
$block_list = AIOWPSecurity_Blocking::get_list_blocked_ips();
foreach ($data as $key => $value) {
if (in_array($value['comment_author_IP'], $block_list)) {
$data[$key]['status'] = 'blocked';
} else {
$data[$key]['status'] = 'not blocked';
}
}
if ('status' == $orderby) {
$keys = array_column($data, 'status');
if ('asc' == $order) {
array_multisort($keys, SORT_ASC, SORT_STRING, $data);
} else {
array_multisort($keys, SORT_DESC, SORT_STRING, $data);
}
}
$current_page = $this->get_pagenum();
$total_items = count($data);
$data = array_slice($data, (($current_page - 1) * $per_page), $per_page);
$this->items = $data;
$this->set_pagination_args(array(
'total_items' => $total_items, //WE have to calculate the total number of items
'per_page' => $per_page, //WE have to determine how many items to show on a page
'total_pages' => ceil($total_items / $per_page) //WE have to calculate the total number of pages
));
}
}
@@ -0,0 +1,149 @@
<?php
if (!defined('ABSPATH')) {
exit;//Exit if accessed directly
}
class AIOWPSecurity_List_Debug_Log extends AIOWPSecurity_List_Table {
/**
* Sets up some table attributes (i.e: the plurals and whether it's ajax or not)
*/
public function __construct() {
//Set parent defaults
parent::__construct(array(
'singular' => 'entry', //singular name of the listed records
'plural' => 'entries', //plural name of the listed records
'ajax' => false //does this table support ajax?
));
}
/**
* Returns logtime column in datetime format as per user setting time zone.
*
* @param array $item - data for the columns on the current row
*
* @return string - the datetime
*/
public function column_logtime($item) {
return AIOWPSecurity_Utility::convert_timestamp($item['logtime']);
}
/**
* This function renders a default column item
*
* @param array $item - Item object
* @param string $column_name - Column name to be rendered from item object
*
* @return mixed - data to be rendered for column
*/
public function column_default($item, $column_name) {
return $item[$column_name];
}
/**
* Sets the columns for the table
*
* @return array
*/
public function get_columns() {
return array(
'logtime' => __('Date and time', 'all-in-one-wp-security-and-firewall'),
'level' => __('Level', 'all-in-one-wp-security-and-firewall'),
'network_id' => __('Network ID', 'all-in-one-wp-security-and-firewall'),
'site_id' => __('Site ID', 'all-in-one-wp-security-and-firewall'),
'message' => __('Message', 'all-in-one-wp-security-and-firewall'),
'type' => __('Type', 'all-in-one-wp-security-and-firewall')
);
}
/**
* Sets which of the columns the table data can be sorted by
*
* @return array
*/
public function get_sortable_columns() {
return array(
'logtime' => array('logtime', false),
'level' => array('level', false),
'network_id' => array('network_id', false),
'site_id' => array('site_id', false),
'message'=>array('message', false),
'type' => array('type', false)
);
}
/**
* Grabs the data from database and handles the pagination
*
* @param boolean $ignore_pagination - whether to not paginate
*
* @return void
*/
public function prepare_items($ignore_pagination = false) {
/**
* First, lets decide how many records per page to show
*/
if (defined('AIOWPSEC_DEBUG_LOG_PER_PAGE')) {
$per_page = absint(AIOWPSEC_DEBUG_LOG_PER_PAGE);
}
$per_page = empty($per_page) ? 15 : $per_page;
$current_page = $this->get_pagenum();
$offset = ($current_page - 1) * $per_page;
$columns = $this->get_columns();
$hidden = array('id'); // we really don't need the IDs of the log entries displayed
if (!is_multisite()) {
$hidden[] = 'network_id';
$hidden[] = 'site_id';
}
$sortable = $this->get_sortable_columns();
$this->_column_headers = array($columns, $hidden, $sortable);
global $wpdb;
$debug_log_tbl = AIOWPSEC_TBL_DEBUG_LOG;
/* -- Ordering parameters -- */
//Parameters that are going to be used to order the result
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
isset($_GET["orderby"]) ? $orderby = sanitize_text_field(wp_unslash($_GET["orderby"])) : $orderby = '';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
isset($_GET["order"]) ? $order = sanitize_text_field(wp_unslash($_GET["order"])) : $order = '';
// By default show the most recent debug log entries.
$orderby = !empty($orderby) ? esc_sql($orderby) : 'logtime';
$order = !empty($order) ? esc_sql($order) : 'DESC';
$orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
$order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
$orderby = sanitize_sql_orderby($orderby);
$order = sanitize_sql_orderby($order);
$where_sql = (!is_super_admin()) ? 'WHERE site_id = '.get_current_blog_id() : '';
if ($ignore_pagination) {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$data = $wpdb->get_results("SELECT * FROM {$debug_log_tbl}$where_sql ORDER BY $orderby $order", 'ARRAY_A');
} else {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$data = $wpdb->get_results("SELECT * FROM {$debug_log_tbl} $where_sql ORDER BY $orderby $order LIMIT $per_page OFFSET $offset", 'ARRAY_A');
}
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$total_items = $wpdb->get_var("SELECT COUNT(*) FROM {$debug_log_tbl} $where_sql");
$this->items = $data;
if ($ignore_pagination) return;
$this->set_pagination_args(array(
'total_items' => $total_items, //WE have to calculate the total number of items
'per_page' => $per_page, //WE have to determine how many items to show on a page
'total_pages' => ceil($total_items / $per_page) //WE have to calculate the total number of pages
));
}
}
@@ -0,0 +1,318 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_List_Locked_IP extends AIOWPSecurity_List_Table {
public function __construct() {
// Set parent defaults
parent::__construct(array(
'singular' => 'item', //singular name of the listed records
'plural' => 'items', //plural name of the listed records
'ajax' => false //does this table support ajax?
));
}
/**
* Returns created column in datetime format as per user setting time zone.
*
* @param array $item - data for the columns on the current row
*
* @return string - the datetime
*/
public function column_created($item) {
return AIOWPSecurity_Utility::convert_timestamp($item['created']);
}
/**
* Returns released column in datetime format as per user setting time zone.
*
* @param array $item - data for the columns on the current row
*
* @return string - the datetime
*/
public function column_released($item) {
return AIOWPSecurity_Utility::convert_timestamp($item['released']);
}
/**
* This function renders a column
*
* @param array $item - Item object
* @param string $column_name - Column name to be rendered from item object
*
* @return string - data to be rendered for column_name
*/
public function column_default($item, $column_name) {
return $item[$column_name];
}
/**
* Function to populate the locked ip actions column in the table
*
* @param array $item - Contains the current item data
*
* @return string
*/
public function column_failed_login_ip($item) {
$actions = array(
'unlock' => '<a href="" data-ip="'.esc_attr($item['failed_login_ip']).'" data-message="'.esc_js(__('Are you sure you want to unlock this address range?', 'all-in-one-wp-security-and-firewall')).'" class="aios-unlock-ip-button">'.esc_html__('Unlock', 'all-in-one-wp-security-and-firewall').'</a>',
'delete' => '<a href="" data-id="'.esc_attr($item['id']).'" data-message="'.esc_js(__('Are you sure you want to delete this item?', 'all-in-one-wp-security-and-firewall')).'" class="aios-delete-locked-ip-record">'.esc_html__('Delete', 'all-in-one-wp-security-and-firewall').'</a>',
);
//Return the user_login contents
return sprintf('%1$s <span style="color:silver"></span>%2$s',
/*$1%s*/ $item['failed_login_ip'],
/*$2%s*/ $this->row_actions($actions)
);
}
/**
* This function renders the checkbox column
*
* @param array $item - item object
*
* @return string
*/
public function column_cb($item) {
return sprintf(
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
/*$1%s*/ $this->_args['singular'], //Let's simply repurpose the table's singular label
/*$2%s*/ $item['id'] //The value of the checkbox should be the record's id
);
}
/**
* Returns ip_lookup_result column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - the html to be rendered
*/
public function column_ip_lookup_result($item) {
if (empty($item['ip_lookup_result'])) return __('There is no IP lookup result available.', 'all-in-one-wp-security-and-firewall');
$ip_lookup_result = json_decode($item['ip_lookup_result'], true);
// check that the json decode worked
if (null === $ip_lookup_result) return __('There is no IP lookup result available.', 'all-in-one-wp-security-and-firewall');
foreach ($ip_lookup_result as $key => $value) {
$ip_lookup_result[$key] = empty($value) ? __('Not Found', 'all-in-one-wp-security-and-firewall') : $value;
}
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- PCP warning. Part of error reporting system.
$ip_lookup_result = print_r($ip_lookup_result, true);
$output = sprintf('<a href="#TB_inline?&inlineId=trace-%s" title="%s" class="thickbox">%s</a>', esc_attr($item['id']), esc_html__('IP lookup result', 'all-in-one-wp-security-and-firewall'), esc_html__('Show result', 'all-in-one-wp-security-and-firewall'));
$output .= sprintf('<div id="trace-%s" style="display: none"><pre>%s</pre></div>', esc_attr($item['id']), esc_html($ip_lookup_result));
return $output;
}
/**
* Sets the columns for the table
*
* @return array
*/
public function get_columns() {
return array(
'cb' => '<input type="checkbox" />', //Render a checkbox
'failed_login_ip' => __('Locked IP/range', 'all-in-one-wp-security-and-firewall'),
'user_id' => __('User ID', 'all-in-one-wp-security-and-firewall'),
'user_login' => __('Username', 'all-in-one-wp-security-and-firewall'),
'lock_reason' => __('Reason', 'all-in-one-wp-security-and-firewall'),
'created' => __('Date locked', 'all-in-one-wp-security-and-firewall'),
'released' => __('Release date', 'all-in-one-wp-security-and-firewall'),
'ip_lookup_result' => __('IP lookup result', 'all-in-one-wp-security-and-firewall')
);
}
/**
* This function returns sortable columns
*
* @return array[]
*/
public function get_sortable_columns() {
return array(
'failed_login_ip' => array('failed_login_ip',false),
'user_id' => array('user_id',false),
'user_login' => array('user_login',false),
'lock_reason' => array('lock_reason',false),
'created' => array('created',false),
'released' => array('released',false)
);
}
/**
* This returns the bulk actions for the table
*
* @return array
*/
public function get_bulk_actions() {
return array(
'unlock' => __('Unlock', 'all-in-one-wp-security-and-firewall'),
'delete' => __('Delete', 'all-in-one-wp-security-and-firewall'),
);
}
/**
* Process bulk actions.
*
* @return void
*/
private function process_bulk_action() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- PCP warning. This is the nonce.
if (empty($_REQUEST['_wpnonce']) || !isset($_REQUEST['_wp_http_referer'])) return;
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- PCP warning. This is the nonce.
$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($_REQUEST['_wpnonce'], 'bulk-items');
if (is_wp_error($result)) return;
if ('delete' == $this->current_action()) { // Process delete bulk actions
if (!isset($_REQUEST['item'])) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'));
} else {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- PCP warning. Sanitized later.
$this->delete_lockout_records(wp_unslash($_REQUEST['item']));
}
}
if ('unlock' == $this->current_action()) { //Process unlock bulk actions
if (!isset($_REQUEST['item'])) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'));
} else {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- PCP warning. Sanitized later.
$this->unlock_ips((wp_unslash($_REQUEST['item'])));
}
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended -- PCP warning. This is the nonce.
}
/**
* Unlocks multiple IP addresses by modifying the released column of records in the AIOWPSEC_TBL_LOGIN_LOCKOUT table.
*
* @param array $entries IDs that correspond to IP addresses in the AIOWPSEC_TBL_LOGIN_LOCKOUT table.
*
* @return void
*/
public function unlock_ips($entries) {
global $wpdb;
$lockout_table = AIOWPSEC_TBL_LOGIN_LOCKOUT;
// Unlock multiple records
$entries = array_filter($entries, 'is_numeric'); // Discard non-numeric ID values
$id_list = '(' .implode(',', $entries) .')'; // Create comma separate list for DB operation
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$result = $wpdb->query("UPDATE $lockout_table SET `released` = UNIX_TIMESTAMP() WHERE `id` IN $id_list");
if (null != $result) {
AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected IP entries were unlocked successfully.', 'all-in-one-wp-security-and-firewall'));
}
}
/**
* Deletes one or more records from the AIOWPSEC_TBL_LOGIN_LOCKOUT table.
*
* @param array|string|integer $entries - ids or a single id
*
* @return void|string
*/
public function delete_lockout_records($entries) {
global $wpdb, $aio_wp_security;
$lockout_table = AIOWPSEC_TBL_LOGIN_LOCKOUT;
if (is_array($entries)) {
// Delete multiple records
$entries = array_filter($entries, 'is_numeric'); //discard non-numeric ID values
$id_list = "(" .implode(",", $entries) .")"; //Create comma separate list for DB operation
$delete_command = "DELETE FROM ".$lockout_table." WHERE id IN ".$id_list;
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$result = $wpdb->query($delete_command);
if ($result) {
AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
} else {
// Error on bulk delete
$aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from login lockout table. Database error: '.$wpdb->last_error, 4);
AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st();
}
} elseif (null != $entries) {
// Delete single record
$delete_command = "DELETE FROM ".$lockout_table." WHERE id = '".absint($entries)."'";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$result = $wpdb->query($delete_command);
if ($result) {
return AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st(true);
} elseif (false === $result) {
// Error on single delete
$aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from login lockout table. Database error: '.$wpdb->last_error, 4);
return AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st(true);
}
}
}
/**
* Retrieves all items from AIOWPSEC_TBL_LOGIN_LOCKOUT. It may paginate and then assigns to $this->items.
*
* @param Boolean $ignore_pagination - whether to not paginate
*
* @return Void
*/
public function prepare_items($ignore_pagination = false) {
global $wpdb;
$lockout_table = AIOWPSEC_TBL_LOGIN_LOCKOUT;
$this->process_bulk_action();
// How many records per page to show
$per_page = 100;
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array($columns, $hidden, $sortable);
// Parameters that are going to be used to order the result
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
$orderby = isset($_GET['orderby']) ? sanitize_text_field(wp_unslash($_GET['orderby'])) : '';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
$order = isset($_GET['order']) ? sanitize_text_field(wp_unslash($_GET['order'])) : '';
$orderby = !empty($orderby) ? esc_sql($orderby) : 'created';
$order = !empty($order) ? esc_sql($order) : 'DESC';
$orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
$order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
$current_page = $this->get_pagenum();
$offset = ($current_page - 1) * $per_page;
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$total_items = $wpdb->get_var("SELECT COUNT(*) FROM {$lockout_table} WHERE `released` > UNIX_TIMESTAMP()");
if ($ignore_pagination) {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$data = $wpdb->get_results("SELECT * FROM {$lockout_table} WHERE `released` > UNIX_TIMESTAMP() ORDER BY {$orderby} {$order}", 'ARRAY_A');
} else {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$data = $wpdb->get_results("SELECT * FROM {$lockout_table} WHERE `released` > UNIX_TIMESTAMP() ORDER BY {$orderby} {$order} LIMIT {$per_page} OFFSET {$offset}", 'ARRAY_A');
}
$this->items = $data;
if ($ignore_pagination) return;
$this->set_pagination_args(array(
'total_items' => $total_items, // WE have to calculate the total number of items
'per_page' => $per_page, // WE have to determine how many items to show on a page
'total_pages' => ceil($total_items / $per_page) // WE have to calculate the total number of pages
));
}
}
@@ -0,0 +1,260 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_List_Logged_In_Users extends AIOWPSecurity_List_Table {
public function __construct() {
//Set parent defaults
parent::__construct(array(
'singular' => 'item', //singular name of the listed records
'plural' => 'items', //plural name of the listed records
'ajax' => false //does this table support ajax?
));
}
public function column_default($item, $column_name) {
return $item[$column_name];
}
/**
* Returns user id column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - the html to be rendered
*/
public function column_user_id($item) {
//Build row actions
$actions = array(
'logout' => '<a class="aios-force-logout-user" data-user-id="'.esc_attr($item['user_id']).'" data-message="'.esc_js(__('Are you sure you want to force this user to be logged out of this session?', 'all-in-one-wp-security-and-firewall')).'" href="">'.__('Force logout', 'all-in-one-wp-security-and-firewall').'</a>',
);
//Return the user_login contents
return sprintf('%1$s <span style="color:silver"></span>%2$s',
/*$1%s*/ $item['user_id'],
/*$2%s*/ $this->row_actions($actions)
);
}
/**
* Sets the columns for the table
*
* @return array
*/
public function get_columns() {
$columns = array(
'cb' => '<input type="checkbox">',
'user_id' => __('User ID', 'all-in-one-wp-security-and-firewall'),
'username' => __('Login name', 'all-in-one-wp-security-and-firewall'),
'ip_address' => __('IP address', 'all-in-one-wp-security-and-firewall'),
'site_id' => __('Site ID', 'all-in-one-wp-security-and-firewall'),
);
return $columns;
}
/**
* Returns cb column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - the html to be rendered
*/
public function column_cb($item) {
return sprintf(
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
/* $1%s */ $this->_args['singular'], // Let's simply repurpose the table's singular label
/* $2%s */ $item['user_id'] // The value of the checkbox should be the record's id and its ip address
);
}
/**
* Sets which of the columns the table data can be sorted by
*
* @return array
*/
public function get_sortable_columns() {
return array(
'user_id' => array('user_id',false),
'username' => array('username',false),
'ip_address' => array('ip_address',false),
'site_id' => array('site_id',false),
);
}
/**
* Adds a bulk action user interface
*
* @return array
*/
public function get_bulk_actions() {
return array(
'force_logout_all' => __('Logout all', 'all-in-one-wp-security-and-firewall'),
'force_logout_selected' => __('Logout selected', 'all-in-one-wp-security-and-firewall'),
);
}
/**
* Process Bulk action from menu
*
* @return void
*/
private function process_bulk_action() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput -- PCP warning. Nonce used.
if (empty($_REQUEST['_wpnonce']) || !isset($_REQUEST['_wp_http_referer'])) return;
$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($_REQUEST['_wpnonce'], 'bulk-items');
if (is_wp_error($result)) return;
if ('force_logout_all' === $this->current_action()) {
$this->force_user_logout(array(), true);
} elseif ('force_logout_selected' === $this->current_action()) {
if (isset($_REQUEST['item'])) {
if (is_array($_REQUEST['item'])) $this->force_user_logout(wp_unslash($_REQUEST['item']));
}
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended -- PCP warning. Nonce used.
}
/**
* This function will force selected user(s) to be logged out.
*
* @param int|array $users - id of selected user or array of user ids to be logged out
* @param bool $logout_all - Boolean to show if all users should be logged out
*
* @return void|string
*/
public function force_user_logout($users, $logout_all = false) {
global $wpdb, $aio_wp_security;
$logged_in_users_table = AIOWPSEC_TBL_LOGGED_IN_USERS;
if ($logout_all) {
// get all user_id(except for the admin) in the table and make it an array for users
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$users = $wpdb->get_col("SELECT user_id FROM $logged_in_users_table");
}
if (is_array($users)) {
if (empty($users)) {
AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st();
return;
}
$errors = 0;
// Escape the user IDs for security
$users = array_map('esc_sql', $users);
foreach ($users as $user_id) {
if (is_numeric($user_id) && !is_super_admin($user_id) && AIOWPSecurity_Utility::is_user_member_of_blog($user_id)) {
if ($aio_wp_security->user_login_obj->delete_logged_in_user($user_id)) {
$this->logout_user($user_id);
continue;
}
}
$errors++;
}
if ($errors > 0) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__("Some users were not logged out due to the ID being invalid, or them being a super admin or a member of a different subsite on a multisite", 'all-in-one-wp-security-and-firewall'));
return;
}
AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
}
}
/**
* This function handles logging out a user using user_id
*
* @param int $user_id - id of user being logged out
*
* @return void
*/
public function logout_user($user_id) {
$user_id = absint($user_id);
$manager = WP_Session_Tokens::get_instance($user_id);
$manager->destroy_all();
}
/**
* Prepares the items for the logged in users table
*
* @param bool $ignore_pagination - this is to check if data should be paginated or not
*
* @return void
*/
public function prepare_items($ignore_pagination = false) {
global $wpdb;
//First, lets decide how many records per page to show
$per_page = 100;
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$logged_in_users_table = AIOWPSEC_TBL_LOGGED_IN_USERS;
$current_page = $this->get_pagenum();
$offset = ($current_page - 1) * $per_page;
// Parameters that are going to be used to order the result
// phpcs:disable -- Rule won't be silenced any other way. No nonce.
$orderby = isset($_GET["orderby"]) ? sanitize_text_field(wp_unslash($_GET["orderby"])) : '';
$order = isset($_GET["order"]) ? sanitize_text_field(wp_unslash($_GET["order"])) : '';
// phpcs:enable -- Rule won't be silenced any other way. No nonce.
// By default show the most recent logged in user entries.
$orderby = empty($orderby) ? 'created' : esc_sql($orderby);
$order = empty($order) ? 'DESC' : esc_sql($order);
$orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
$order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
$orderby = sanitize_sql_orderby($orderby);
$order = sanitize_sql_orderby($order);
$this->_column_headers = array($columns, $hidden, $sortable);
$this->process_bulk_action(); // Process bulk actions
$where_sql = $this->get_where_sql();
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$total_items = $wpdb->get_var("SELECT COUNT(*) FROM `{$logged_in_users_table}` $where_sql");
if ($ignore_pagination) {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$data = $wpdb->get_results("SELECT * FROM `{$logged_in_users_table}` $where_sql ORDER BY $orderby $order", 'ARRAY_A');
} else {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$data = $wpdb->get_results("SELECT * FROM `{$logged_in_users_table}` $where_sql ORDER BY $orderby $order LIMIT $per_page OFFSET $offset", 'ARRAY_A');
}
$this->items = $data;
if ($ignore_pagination) return;
$this->set_pagination_args(array(
'total_items' => $total_items, //WE have to calculate the total number of items
'per_page' => $per_page, //WE have to determine how many items to show on a page
'total_pages' => ceil($total_items/$per_page) //WE have to calculate the total number of pages
));
}
/**
* This function will build and return the SQL WHERE statement
*
* @return string - the SQL WHERE statement
*/
private function get_where_sql() {
if (is_main_site() && is_super_admin()) return '';
return is_multisite() ? sprintf("WHERE site_id = %d", get_current_blog_id()) : '';
}
}
@@ -0,0 +1,238 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_List_Blocked_IP extends AIOWPSecurity_List_Table {
public function __construct() {
//Set parent defaults
parent::__construct(array(
'singular' => 'item', //singular name of the listed records
'plural' => 'items', //plural name of the listed records
'ajax' => false //does this table support ajax?
));
}
/**
* Returns created column in datetime format as per user setting time zone.
*
* @param array $item - data for the columns on the current row
*
* @return string - the datetime
*/
public function column_created($item) {
return AIOWPSecurity_Utility::convert_timestamp($item['created']);
}
public function column_default($item, $column_name) {
return $item[$column_name];
}
/**
* Function to populate the permanent blocked ip actions column in the table
*
* @param array $item - Contains the current item data
*
* @return string
*/
public function column_id($item) {
$actions = array(
'unblock' => '<a href="" class="aios-unblock-permanent-ip" data-id="'.esc_attr($item['id']).'" data-message="'.esc_js(__('Are you sure you want to unblock this IP address?', 'all-in-one-wp-security-and-firewall')).'">Unblock</a>',
);
//Return the user_login contents
return sprintf('%1$s <span style="color:silver"></span>%2$s',
/*$1%s*/
$item['id'],
/*$2%s*/
$this->row_actions($actions)
);
}
public function column_cb($item) {
return sprintf(
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
/*$1%s*/
$this->_args['singular'], //Let's simply repurpose the table's singular label
/*$2%s*/
$item['id'] //The value of the checkbox should be the record's id
);
}
public function get_columns() {
return array(
'cb' => '<input type="checkbox" />', //Render a checkbox
'id' => 'ID',
'blocked_ip' => __('Blocked IP', 'all-in-one-wp-security-and-firewall'),
'block_reason' => __('Reason', 'all-in-one-wp-security-and-firewall'),
'created' => __('Date and Time', 'all-in-one-wp-security-and-firewall')
);
}
public function get_sortable_columns() {
return array(
'id' => array('id', false),
'blocked_ip' => array('blocked_ip', false),
'block_reason' => array('block_reason', false),
'created' => array('created', false)
);
}
public function get_bulk_actions() {
return array(
'unblock' => __('Unblock', 'all-in-one-wp-security-and-firewall')
);
}
private function process_bulk_action() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- PCP warning. It IS the nonce. Ignore.
if (empty($_REQUEST['_wpnonce']) || !isset($_REQUEST['_wp_http_referer'])) return;
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- PCP warning. Ignore.
$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($_REQUEST['_wpnonce'], 'bulk-items');
if (is_wp_error($result)) return;
if ('unblock' === $this->current_action()) { // Process unlock bulk actions
if (!isset($_REQUEST['item'])) {
AIOS_Helper::set_message('aios_list_message', __('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'), 'error');
} else {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitized later
$this->unblock_ip_address(wp_unslash($_REQUEST['item']));
}
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended -- PCP warning. It IS the nonce. Ignore.
}
/**
* Deletes one or more records from the AIOWPSEC_TBL_PERM_BLOCK table.
*
* @param array|string|integer $entries - ids or a single id
*
* @return void|string
*/
public function unblock_ip_address($entries) {
global $wpdb, $aio_wp_security;
if (is_array($entries)) {
// multiple records
$entries = array_filter($entries, 'is_numeric'); //discard non-numeric ID values
$id_list = "(" . implode(",", $entries) . ")"; //Create comma separate list for DB operation
$delete_command = "DELETE FROM " . AIOWPSEC_TBL_PERM_BLOCK . " WHERE id IN " . $id_list; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $id_list cannot be prepared.
$result = $wpdb->query($delete_command);
if ($result) {
AIOS_Helper::set_message('aios_list_message', __('Successfully unblocked and deleted the selected record(s).', 'all-in-one-wp-security-and-firewall'));
} else {
// Error on bulk delete
$aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Perm Block table. Database error: '.$wpdb->last_error, 4);
AIOS_Helper::set_message('aios_list_message', __('Failed to unblock and delete the selected record(s).', 'all-in-one-wp-security-and-firewall'), 'error');
}
} elseif (!empty($entries)) {
//Delete single record
$delete_command = "DELETE FROM " . AIOWPSEC_TBL_PERM_BLOCK . " WHERE id = %d";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$result = $wpdb->query($wpdb->prepare($delete_command, absint($entries)));
if (false === $result) {
// Error on single delete
$aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Perm Block table. Database error: '.$wpdb->last_error, 4);
}
return $result;
}
}
/**
* This function will build and return the SQL WHERE statement
*
* @param string $search_term - the search term applied
*
* @return string - the SQL WHERE statement
*/
private function get_permanent_blocked_ip_list_where_sql($search_term) {
$where = '';
if (!empty($search_term)) {
$where = " WHERE";
// We don't use FILTER_VALIDATE_IP here as we want to be able to search for partial IP's
if (preg_match('/^[0-9a-f:\.]+$/i', $search_term)) {
$where .= " `blocked_ip` LIKE '%".esc_sql($search_term)."%' OR";
}
$where .= " `block_reason` LIKE '%".esc_sql($search_term)."%'";
$where .= " OR `country_origin` LIKE '%".esc_sql($search_term)."%'";
}
return $where;
}
/**
* Grabs the data from database and handles the pagination
*
* @param boolean $ignore_pagination - whether to not paginate
*
* @return void
*/
public function prepare_items($ignore_pagination = false) {
/**
* First, lets decide how many records per page to show
*/
$per_page = 100;
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
$search = isset($_REQUEST['s']) ? sanitize_text_field(wp_unslash($_REQUEST['s'])) : '';
$this->_column_headers = array($columns, $hidden, $sortable);
$this->process_bulk_action();
global $wpdb;
$block_table_name = AIOWPSEC_TBL_PERM_BLOCK;
// Ordering parameters
// Parameters that are going to be used to order the result
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
$orderby = isset($_GET["orderby"]) ? sanitize_text_field(wp_unslash($_GET["orderby"])) : '';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
$order = isset($_GET["order"]) ? sanitize_text_field(wp_unslash($_GET["order"])) : '';
$orderby = !empty($orderby) ? esc_sql($orderby) : 'id';
$order = !empty($order) ? esc_sql($order) : 'DESC';
$orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
$order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
$current_page = $this->get_pagenum();
$offset = ($current_page - 1) * $per_page;
$search_query = $this->get_permanent_blocked_ip_list_where_sql($search);
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$total_items = $wpdb->get_var("SELECT COUNT(*) FROM {$block_table_name}{$search_query}");
if ($ignore_pagination) {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$data = $wpdb->get_results("SELECT * FROM {$block_table_name} {$search_query} ORDER BY $orderby$order", 'ARRAY_A');
} else {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$data = $wpdb->get_results("SELECT * FROM {$block_table_name}{$search_query} ORDER BY $orderby $order LIMIT $per_page OFFSET $offset", 'ARRAY_A');
}
$this->items = $data;
if ($ignore_pagination) return;
$this->set_pagination_args(array(
'total_items' => $total_items, //WE have to calculate the total number of items
'per_page' => $per_page, //WE have to determine how many items to show on a page
'total_pages' => ceil($total_items / $per_page) //WE have to calculate the total number of pages
));
}
}
@@ -0,0 +1,355 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_List_Registered_Users extends AIOWPSecurity_List_Table {
public function __construct() {
//Set parent defaults
parent::__construct(array(
'singular' => 'item', //singular name of the listed records
'plural' => 'items', //plural name of the listed records
'ajax' => false //does this table support ajax?
));
}
public function column_default($item, $column_name) {
return $item[$column_name];
}
/**
* Returns ID column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - the html to be rendered
*/
public function column_ID($item) {
//Build row actions
$actions = array(
'view' => '<a href="user-edit.php?user_id='.$item['ID'].'" target="_blank">'.__('View', 'all-in-one-wp-security-and-firewall').'</a>',
'approve_acct' => '<a class="aios-approve-user-acct" href="" data-id="'.esc_attr($item['ID']).'" data-message="'.esc_js(__('Are you sure you want to approve this account?', 'all-in-one-wp-security-and-firewall')).'">'. __('Approve', 'all-in-one-wp-security-and-firewall') . '</a>',
'delete_acct' => '<a class="aios-delete-user-acct" href="" data-id="'.esc_attr($item['ID']).'" data-message="'.esc_js(__('Are you sure you want to delete this account?', 'all-in-one-wp-security-and-firewall')).'">'. __('Delete', 'all-in-one-wp-security-and-firewall') . '</a>',
'block_ip' => '<a class="aios-block-ip" href="" data-ip="'.esc_attr($item['ip_address']).'" data-message="'.esc_js(__('Are you sure you want to block this IP address?', 'all-in-one-wp-security-and-firewall')).'">'. __('Block IP', 'all-in-one-wp-security-and-firewall') . '</a>',
);
//Return the user_login contents
return sprintf('%1$s <span style="color:silver"></span>%2$s',
/*$1%s*/ $item['ID'],
/*$2%s*/ $this->row_actions($actions)
);
}
/**
* Returns IP address column html to be rendered.
*
* @param array $item - data for the columns on the current row
*
* @return string - the html to be rendered
*/
public function column_ip_address($item) {
if (AIOWPSecurity_Blocking::is_ip_blocked($item['ip_address'])) {
return $item['ip_address'].'<br /><span class="aiowps-label aiowps-label-success">'.__('blocked', 'all-in-one-wp-security-and-firewall').'</span>';
} else {
return $item['ip_address'];
}
}
public function column_cb($item) {
return sprintf(
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
/*$1%s*/ $this->_args['singular'], //Let's simply repurpose the table's singular label
/*$2%s*/ $item['ID'] //The value of the checkbox should be the record's id
);
}
/**
* Returns array of columns to be rendered.
*
* @return array
*/
public function get_columns() {
$columns = array(
'cb' => '<input type="checkbox">', // Render a checkbox
'ID' => __('User ID', 'all-in-one-wp-security-and-firewall'),
'user_login' => __('Login name', 'all-in-one-wp-security-and-firewall'),
'user_email' => __('Email', 'all-in-one-wp-security-and-firewall'),
'user_registered' => __('Register date', 'all-in-one-wp-security-and-firewall'),
'account_status' => __('Account status', 'all-in-one-wp-security-and-firewall'),
'ip_address' => __('IP address', 'all-in-one-wp-security-and-firewall')
);
return $columns;
}
public function get_sortable_columns() {
$sortable_columns = array(
// 'ID' => array('ID',false),
// 'user_login' => array('user_login',false),
// 'user_email' => array('user_email',false),
// 'user_registered' => array('user_registered',false),
// 'account_status' => array('account_status',false),
);
return $sortable_columns;
}
public function get_bulk_actions() {
$actions = array(
'approve' => __('Approve', 'all-in-one-wp-security-and-firewall'),
'delete' => __('Delete', 'all-in-one-wp-security-and-firewall'),
'block' => __('Block IP', 'all-in-one-wp-security-and-firewall')
);
return $actions;
}
/**
* Process bulk actions.
*
* @return void
*/
private function process_bulk_action() {
if (empty($_REQUEST['_wpnonce']) || !isset($_REQUEST['_wp_http_referer'])) return; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- pcp check ingore this
$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap(sanitize_text_field(wp_unslash($_REQUEST['_wpnonce'])), 'bulk-items'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- pcp check ingore this
if (is_wp_error($result)) return;
if ('approve' == $this->current_action()) { //Process approve bulk actions
if (!isset($_REQUEST['item'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- pcp check ingore this
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'));
} else {
$this->approve_selected_accounts(array_map('sanitize_text_field', wp_unslash($_REQUEST['item']))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- pcp check ingore this
}
}
if ('delete' == $this->current_action()) { //Process delete bulk actions
if (!isset($_REQUEST['item'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- pcp check ingore this
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'));
} else {
$this->delete_selected_accounts(array_map('sanitize_text_field', wp_unslash($_REQUEST['item']))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- pcp check ingore this
}
}
if ('block' == $this->current_action()) { //Process block bulk actions
if (!isset($_REQUEST['item'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- pcp check ingore this
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'));
} else {
$this->block_selected_ips(array_map('sanitize_text_field', wp_unslash($_REQUEST['item']))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- pcp check ingore this
}
}
}
/**
* This function approves selected user accounts
*
* @param array|int $entries - this is an array of users or user_id to be approved
*
* @return void|string
*/
public function approve_selected_accounts($entries) {
global $aio_wp_security;
$meta_key = 'aiowps_account_status';
$meta_value = 'approved'; // set account status
$failed_accts = ''; // string to store comma separated accounts which failed to update
$at_least_one_updated = false;
if (is_array($entries)) {
$entries = array_map('esc_sql', $entries); // Escape every array element
//Let's go through each entry and approve
foreach ($entries as $user_id) {
$result = update_user_meta($user_id, $meta_key, $meta_value);
if (false === $result) {
$failed_accts .= ' '.$user_id.', ';
$aio_wp_security->debug_logger->log_debug("AIOWPSecurity_List_Registered_Users::approve_selected_accounts() - could not approve account ID: $user_id", 4);
} else {
$at_least_one_updated = true;
$user = get_user_by('id', $user_id);
if (false === $user) {
//don't send mail
} else {
$this->send_email_upon_account_activation($user);
}
}
}
if ($at_least_one_updated) {
AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected accounts were approved successfully.', 'all-in-one-wp-security-and-firewall'));
}
if ('' != $failed_accts) {
//display any failed account updates
rtrim($failed_accts);
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('The following accounts failed to update successfully:', 'all-in-one-wp-security-and-firewall') . ' ' . $failed_accts);
}
}
}
/**
* This function sends an email to an approved user
*
* @param WP_User $user - the object for the approved user
*
* @return bool
*/
public function send_email_upon_account_activation($user) {
global $aio_wp_security;
if (!($user instanceof WP_User)) {
return false;
}
$to_email_address = $user->user_email;
$email_msg = '';
$subject = '['.network_site_url().'] '. __('Your account is now active', 'all-in-one-wp-security-and-firewall');
/* translators: %s: Username */
$email_msg .= sprintf(__('Your account with username: %s is now active', 'all-in-one-wp-security-and-firewall'), $user->user_login) . "\n";
$subject = apply_filters('aiowps_register_approval_email_subject', $subject);
$email_msg = apply_filters('aiowps_register_approval_email_msg', $email_msg, $user); //also pass the WP_User object
$sendMail = wp_mail($to_email_address, $subject, $email_msg);
if (false === $sendMail) {
$aio_wp_security->debug_logger->log_debug("Manual account approval notification email failed to send to " . $to_email_address, 4);
}
return $sendMail;
}
/**
* This function deletes selected entries pending approval
*
* @param array|int $entries - this is an array of users or single user to be deleted
*
* @return void|string
*/
public function delete_selected_accounts($entries) {
global $aio_wp_security;
if (is_array($entries)) {
$entries = array_map('esc_sql', $entries); // Escape every array element
//Let's go through each entry and delete account
foreach ($entries as $user_id) {
$result = wp_delete_user($user_id);
if (true !== $result) {
$aio_wp_security->debug_logger->log_debug("AIOWPSecurity_List_Registered_Users::delete_selected_accounts() - could not delete account ID: $user_id", 4);
}
}
AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected accounts were deleted successfully.', 'all-in-one-wp-security-and-firewall'));
}
}
/**
* This function blocks selected ip
*
* @param array|int $entries - this is an array of ips or ip to be blocked
*
* @return void|string
*/
public function block_selected_ips($entries) {
global $aio_wp_security;
if (is_array($entries)) {
$entries = array_filter($entries, function ($entry) {
return AIOWPSecurity_Utility_IP::get_user_ip_address() != $entry;
});
if (empty($entries)) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Only invalid IP addresses were provided: you can not block your own IP address', 'all-in-one-wp-security-and-firewall'));
return;
}
$entries = array_map('esc_sql', $entries); // Escape every array element
//Let's go through each entry and block IP
$total_success = 0;
foreach ($entries as $id) {
$ip_address = get_user_meta($id, 'aiowps_registrant_ip', true);
$result = AIOWPSecurity_Blocking::add_ip_to_block_list($ip_address, 'registration_spam');
if (false === $result) {
if (AIOWPSecurity_Utility_IP::get_user_ip_address() == $ip_address) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('You cannot block your own IP address:', 'all-in-one-wp-security-and-firewall') . ' ' . $ip_address);
}
$aio_wp_security->debug_logger->log_debug("AIOWPSecurity_List_Registered_Users::block_selected_ips() - could not block IP : $ip_address", 4);
} else {
$total_success++;
}
}
if ($total_success > 0) {
$msg = __('The selected IP addresses were successfully added to the permanent block list.', 'all-in-one-wp-security-and-firewall');
$msg .= ' <a href="admin.php?page='.AIOWPSEC_MAIN_MENU_SLUG.'&tab=permanent-block" target="_blank">'.__('View Blocked IPs', 'all-in-one-wp-security-and-firewall').'</a>';
AIOWPSecurity_Admin_Menu::show_msg_updated_st($msg);
}
}
}
/**
* Grabs the data from database and handles the pagination
*
* @param boolean $ignore_pagination - whether to not paginate
* @return void
*/
public function prepare_items($ignore_pagination = false) {
//First, lets decide how many records per page to show
$per_page = 100;
$columns = $this->get_columns();
$current_page = $this->get_pagenum();
$offset = ($current_page - 1) * $per_page;
$hidden = array();
$sortable = $this->get_sortable_columns();
$search = isset($_REQUEST['s']) ? sanitize_text_field(wp_unslash($_REQUEST['s'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- pcp check ingore this
$this->_column_headers = array($columns, $hidden, $sortable);
$this->process_bulk_action();
//Get registered users which have the special 'aiowps_account_status' meta key set to 'pending'
if ($ignore_pagination) {
$result = $this->get_registered_user_data('pending', $search);
} else {
$result = $this->get_registered_user_data('pending', $search, $per_page, $offset);
}
$total_items = $result['total'];
$this->items = $result['data'];
if ($ignore_pagination) return;
$this->set_pagination_args(array(
'total_items' => $total_items, //WE have to calculate the total number of items
'per_page' => $per_page, //WE have to determine how many items to show on a page
'total_pages' => ceil($total_items/$per_page) //WE have to calculate the total number of pages
));
}
/**
* Returns all users who have the special 'aiowps_account_status' meta key
*
* @param string $status - the status we want to search for
* @param string $search - the search query
* @param null $per_page - how many results per page
* @param int $offset - the page offset
*
* @return array - an array of users that match the search
*/
public function get_registered_user_data($status = '', $search = '', $per_page = null, $offset = 0) {
$user_fields = array( 'ID', 'user_login', 'user_email', 'user_registered');
$user_query = new WP_User_Query(array('meta_key' => 'aiowps_account_status', 'meta_value' => $status, 'fields' => $user_fields, 'number' => $per_page, 'offset' => $offset)); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- ignore this
$user_results = $user_query->results;
$user_total = $user_query->get_total();
$final_data = array();
foreach ($user_results as $user) {
$temp_array = get_object_vars($user); //Turn the object into array
$temp_array['account_status'] = get_user_meta($temp_array['ID'], 'aiowps_account_status', true);
$ip = get_user_meta($temp_array['ID'], 'aiowps_registrant_ip', true);
$temp_array['ip_address'] = empty($ip) ? '' : $ip;
if (empty($search)) {
$final_data[] = $temp_array;
} else {
$input = preg_quote($search, '~'); // don't forget to quote input string!
$result = preg_grep('~' . $input . '~', $temp_array);
if (!empty($result)) $final_data[] = $temp_array;
}
}
return array(
'data' => $final_data,
'total' => $user_total,
);
}
}
@@ -0,0 +1,90 @@
<?php
if (!defined('ABSPATH')) die('No direct access.');
if (!class_exists('AIOWPSecurity_Reset_Settings')) :
/**
* Reset Settings various methods
*/
class AIOWPSecurity_Reset_Settings {
/**
* Delete config option.
*
* @return boolean true if the aio_wp_security_configs option deleted successfully.
*/
public static function reset_options() {
$result_delete_option = false === get_option('aio_wp_security_configs', false) || delete_option('aio_wp_security_configs');
$result_reset_settings = AIOWPSecurity_Configure_Settings::set_default_settings();
return $result_delete_option && $result_reset_settings;
}
/**
* Delete htaccess rules.
*
* @param string $section - section used to find AIOS rules in .htaccess file
*
* @return boolean true if the aio_wp_security_configs option deleted successfully.
*/
public static function delete_htaccess($section = 'All In One WP Security') {
$htaccess = ABSPATH . '.htaccess';
if (!file_exists($htaccess)) {
return false;
}
// phpcs:disable WordPress.WP.AlternativeFunctions -- Cannot use wp_filesystem in the firewall.
$ht_contents = preg_split('/\r\n|\r|\n/', file_get_contents($htaccess));
if ($ht_contents) { // as long as there are lines in the file
$state = true;
$f = @fopen($htaccess, 'w+');
if (!$f) {
@chmod($htaccess, 0644);
$f = @fopen($htaccess, 'w+');
if (!$f) {
return false;
}
}
foreach ($ht_contents as $markerline) { // for each line in the file
if (strpos($markerline, '# BEGIN ' . $section) !== false) { // if we're at the beginning of the section
$state = false;
}
if (true == $state) { // as long as we're not in the section keep writing
fwrite($f, trim($markerline) . "\n");
}
if (strpos($markerline, '# END ' . $section) !== false) { // see if we're at the end of the section
$state = true;
}
}
@fclose($f);
return true;
}
// phpcs:enable WordPress.WP.AlternativeFunctions -- Cannot use wp_filesystem in the firewall.
return true;
}
/**
* Delete database tables
*
* @return boolean true
*/
public static function reset_db_tables() {
// Reset (TRUNCATE) all the db tables of the plugin.
global $wpdb;
$wpdb->query('TRUNCATE ' . $wpdb->prefix . 'aiowps_login_lockdown');
$wpdb->query('TRUNCATE ' . $wpdb->prefix . 'aiowps_global_meta');
$wpdb->query('TRUNCATE ' . $wpdb->prefix . 'aiowps_events');
$wpdb->query('TRUNCATE ' . $wpdb->prefix . 'aiowps_permanent_block');
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Ignore.
if (is_main_site()) {
$wpdb->query('TRUNCATE ' . AIOWPSEC_TBL_LOGGED_IN_USERS);
$wpdb->query('TRUNCATE ' . AIOWPSEC_TBL_MESSAGE_STORE);
$wpdb->query('TRUNCATE ' . AIOWPSEC_TBL_DEBUG_LOG);
$wpdb->query('TRUNCATE ' . AIOWPSEC_TBL_AUDIT_LOG);
}
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Ignore.
return true;
}
}
endif;
@@ -0,0 +1,157 @@
<?php
if (!defined('ABSPATH')) die('No direct access.');
/**
* AIOWPSecurity_Settings_Menu class for setting configs.
*
* @access public
*/
class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu {
/**
* Settings menu slug
*
* @var string
*/
protected $menu_page_slug = AIOWPSEC_SETTINGS_MENU_SLUG;
/**
* Constructor adds menu for Settings
*/
public function __construct() {
parent::__construct(__('Settings', 'all-in-one-wp-security-and-firewall'));
}
/**
* This function will setup the menus tabs by setting the array $menu_tabs
*
* @return void
*/
public function setup_menu_tabs() {
$menu_tabs = array(
'general-settings' => array(
'title' => __('General settings', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_general_settings'),
),
'htaccess-file-operations' => array(
'title' => '.htaccess '.__('file', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_htaccess_file_operations'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'wp-config-file-operations' => array(
'title' => 'wp-config.php '.__('file', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_wp_config_file_operations'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'delete-plugin-settings' => array(
'title' => __('Delete plugin settings', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_delete_plugin_settings_tab')
),
'wp-version-info' => array(
'title' => __('WP version info', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_wp_version_info'),
),
'settings-file-operations' => array(
'title' => __('Import/Export', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_settings_file_operations'),
),
'advanced-settings' => array(
'title' => __('Advanced settings', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_advanced_settings'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
);
$menu_tabs = apply_filters('aiowpsecurity_setting_tabs', $menu_tabs);
$this->menu_tabs = array_filter($menu_tabs, array($this, 'should_display_tab'));
}
/**
* Renders the submenu's general settings tab.
*
* @return void
*/
protected function render_general_settings() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/settings/general-settings.php', false, array());
}
/**
* Renders the submenu's htaccess file operations tab.
*
* @return void
*/
protected function render_htaccess_file_operations() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/settings/htaccess-file-operations.php', false, array());
}
/**
* Renders the submenu's wp config file operations tab.
*
* @return void
*/
protected function render_wp_config_file_operations() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/settings/wp-config-file-operations.php', false, array());
}
/**
* Renders the submenu's delete plugin settings tab.
*
* @return void
*/
protected function render_delete_plugin_settings_tab() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/settings/delete-plugin-settings.php', false, array());
}
/**
* Renders the submenu's wp version info tab.
*
* @return void
*/
protected function render_wp_version_info() {
global $aio_wp_security, $aiowps_feature_mgr;
$aio_wp_security->include_template('wp-admin/settings/wp-version-info.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr));
}
/**
* Renders the submenu's settings file operations tab.
*
* @return void
*/
protected function render_settings_file_operations() {
global $aio_wp_security;
$events_table_name = AIOWPSEC_TBL_EVENTS;
AIOWPSecurity_Utility::cleanup_table($events_table_name, 500);
$aio_wp_security->include_template('wp-admin/settings/settings-file-operations.php', false, array());
}
/**
* Renders advanced settings tab.
*
* @return void
*/
protected function render_advanced_settings() {
if (!is_main_site()) {
return;
}
global $aio_wp_security;
$aios_commands = new AIOWPSecurity_Commands();
$advanced_settings_data = $aios_commands->get_ip_address_detection_data();
$aio_wp_security->include_template('wp-admin/settings/advanced-settings.php', false, $advanced_settings_data);
}
}
@@ -0,0 +1,93 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu {
/**
* Spam menu slug
*
* @var string
*/
protected $menu_page_slug = AIOWPSEC_SPAM_MENU_SLUG;
/**
* Constructor adds menu for Spam prevention
*/
public function __construct() {
parent::__construct(__('Spam prevention', 'all-in-one-wp-security-and-firewall'));
}
/**
* This function will setup the menus tabs by setting the array $menu_tabs
*
* @return void
*/
protected function setup_menu_tabs() {
$menu_tabs = array(
'comment-spam' => array(
'title' => __('Comment spam', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_comment_spam'),
),
'comment-spam-ip-monitoring' => array(
'title' => __('Comment spam IP monitoring', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_comment_spam_ip_monitoring'),
),
);
$this->menu_tabs = array_filter($menu_tabs, array($this, 'should_display_tab'));
}
/**
* Renders the submenu's comment spam ip monitoring tab body.
*
* @return Void
*/
protected function render_comment_spam() {
global $aiowps_feature_mgr, $aio_wp_security;
$aio_wp_security->include_template('wp-admin/spam-prevention/comment-spam.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr));
}
/**
* Renders the submenu's comment spam ip monitoring tab body.
*
* @return Void
*/
protected function render_comment_spam_ip_monitoring() {
global $aio_wp_security, $aiowps_feature_mgr, $wpdb;
include_once 'wp-security-list-comment-spammer-ip.php'; // For rendering the AIOWPSecurity_List_Table in tab2
$spammer_ip_list = new AIOWPSecurity_List_Comment_Spammer_IP();
$block_comments_output = '';
$min_block_comments = $aio_wp_security->configs->get_value('aiowps_spam_ip_min_comments_block');
if (!empty($min_block_comments)) {
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$total_res = $wpdb->get_results($wpdb->prepare('SELECT * FROM '.AIOWPSEC_TBL_PERM_BLOCK.' WHERE block_reason=%s', 'spam'));
$block_comments_output = '<div class="aio_yellow_box">';
if (empty($total_res)) {
$block_comments_output .= '<p><strong>'.esc_html__('You currently have no IP addresses permanently blocked due to spam.', 'all-in-one-wp-security-and-firewall').'</strong></p></div>';
} else {
$total_count = count($total_res);
$todays_blocked_count = 0;
foreach ($total_res as $blocked_item) {
$now_date_time = new DateTime('now', new DateTimeZone('UTC'));
$blocked_date = new DateTime('@'.$blocked_item->created); //@ with timestamp creates correct DateTime
if ($blocked_date->format('Y-m-d') == $now_date_time->format('Y-m-d')) {
//there was an IP added to permanent block list today
++$todays_blocked_count;
}
}
$block_comments_output .= '<p><strong>'.esc_html__('Spammer IPs added to permanent block list today:', 'all-in-one-wp-security-and-firewall'). ' ' . esc_html($todays_blocked_count).'</strong></p>'.'<hr><p><strong>'.esc_html__('All time total:', 'all-in-one-wp-security-and-firewall'). ' ' .esc_html($total_count).'</strong></p>'.'<p><a class="button" href="admin.php?page='.esc_attr(AIOWPSEC_MAIN_MENU_SLUG).'&tab=permanent-block" target="_blank">'.esc_html__('View blocked IPs', 'all-in-one-wp-security-and-firewall').'</a></p></div>';
}
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
$page = isset($_REQUEST['page']) ? sanitize_text_field(wp_unslash($_REQUEST['page'])) : '';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
$tab = isset($_REQUEST['tab']) ? sanitize_text_field(wp_unslash($_REQUEST['tab'])) : '';
$aio_wp_security->include_template('wp-admin/spam-prevention/comment-spam-ip-monitoring.php', false, array('spammer_ip_list' => $spammer_ip_list, 'aiowps_feature_mgr' => $aiowps_feature_mgr, 'block_comments_output' => $block_comments_output, 'page' => $page, 'tab' => $tab));
}
}
@@ -0,0 +1,98 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_Tools_Menu extends AIOWPSecurity_Admin_Menu {
/**
* Tools menu slug
*
* @var string
*/
protected $menu_page_slug = AIOWPSEC_TOOLS_MENU_SLUG;
/**
* Constructor adds menu for Tools
*/
public function __construct() {
parent::__construct(__('Tools', 'all-in-one-wp-security-and-firewall'));
}
/**
* This function will setup the menus tabs by setting the array $menu_tabs
*
* @return void
*/
protected function setup_menu_tabs() {
$menu_tabs = array(
'password-tool' => array(
'title' => __('Password tool', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_password_tool'),
),
'whois-lookup' => array(
'title' => __('WHOIS lookup', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_whois_lookup_tab'),
),
'custom-rules' => array(
'title' => __('Custom .htaccess rules', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_custom_rules'),
'display_condition_callback' => array('AIOWPSecurity_Utility', 'allow_to_write_to_htaccess'),
),
'visitor-lockout' => array(
'title' => __('Visitor lockout', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_visitor_lockout'),
),
);
$this->menu_tabs = array_filter($menu_tabs, array($this, 'should_display_tab'));
}
/**
* Render the 'Custom (htaccess) rules' tab
*
* @return void
*/
protected function render_custom_rules() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/tools/custom-htaccess.php');
}
/**
* Renders the submenu's password tool tab
*
* @return Void
*/
protected function render_password_tool() {
global $aio_wp_security;
wp_enqueue_script('aiowpsec-pw-tool-js');
$aio_wp_security->include_template('wp-admin/tools/password-tool.php');
}
/**
* Renders the submenu's whois-lookup tab body.
*
* @return Void
*/
protected function render_whois_lookup_tab() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/tools/whois-lookup.php', false, array());
}
/**
* Renders the submenu's visitor lockout tab
*
* @return void
*/
protected function render_visitor_lockout() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/tools/visitor-lockout.php', false, array());
}
} // End of class
@@ -0,0 +1,296 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_User_Security_Menu extends AIOWPSecurity_Admin_Menu {
/**
* User Security menu slug
*
* @var string
*/
protected $menu_page_slug = AIOWPSEC_USER_SECURITY_MENU_SLUG;
/**
* Constructor adds menu for User Security
*/
public function __construct() {
parent::__construct(__('User Security', 'all-in-one-wp-security-and-firewall'));
}
/**
* Populates $menu_tabs array.
*
* @return Void
*/
protected function setup_menu_tabs() {
$menu_tabs = array(
'wp-user_accounts' => array(
'title' => __('User accounts', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_wp_user_account'),
),
'login-lockout' => array(
'title' => __('Login lockout', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_login_lockout'),
),
'force-logout' => array(
'title' => __('Force logout', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_force_logout'),
),
'logged-in-users' => array(
'title' => __('Logged in users', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_logged_in_users'),
),
'manual-approval' => array(
'title' => __('Manual approval', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_manual_approval'),
),
'salt' => array(
'title' => __('Salt', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_salt_tab'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'http-authentication' => array(
'title' => __('HTTP authentication', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_http_authentication'),
),
'hibp' => array(
'title' => __('HIBP', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_hibp'),
'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'additional' => array(
'title' => __('Additional settings', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_additional'),
),
);
$this->menu_tabs = array_filter($menu_tabs, array($this, 'should_display_tab'));
}
/**
* Renders the submenu's WP User Account tab
*
* @return Void
*/
protected function render_wp_user_account() {
global $aio_wp_security, $aiowps_feature_mgr;
if (is_multisite()) { // Multi-site: get admin accounts for current site
$blog_id = get_current_blog_id();
$user_accounts = $this->get_all_admin_accounts($blog_id);
} else {
$user_accounts = $this->get_all_admin_accounts();
}
$aio_wp_security->include_template('wp-admin/user-security/user-accounts.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr, 'user_accounts' => $user_accounts, 'AIOWPSecurity_User_Security_Menu' => $this));
}
/**
* This function will retrieve all user accounts which have 'administrator' role and will return html code with results in a table
*
* @param string $blog_id - the blog we want to get the user account information from
*
* @return string - the html from the result
*/
private function get_all_admin_accounts($blog_id = '') {
// TODO: Have included the "blog_id" variable for future use for cases where people want to search particular blog (eg, multi-site)
if ($blog_id) {
$admin_users = get_users('blog_id='.$blog_id.'&orderby=login&role=administrator');
} else {
$admin_users = get_users('orderby=login&role=administrator');
}
// now let's put the results in an HTML table
$account_output = "";
if (!empty($admin_users)) {
$account_output .= '<table>';
$account_output .= '<tr><th>'.esc_html(__('Account login name', 'all-in-one-wp-security-and-firewall')).'</th></tr>';
foreach ($admin_users as $entry) {
$account_output .= '<tr>';
if (strtolower($entry->user_login) == 'admin') {
$account_output .= '<td style="color:red; font-weight: bold;">'.esc_html($entry->user_login).'</td>';
} else {
$account_output .= '<td>'.esc_html($entry->user_login).'</td>';
}
$user_acct_edit_link = admin_url('user-edit.php?user_id=' . $entry->ID);
$account_output .= '<td><a href="'.esc_url($user_acct_edit_link).'" target="_blank">'.esc_html(__('Edit user', 'all-in-one-wp-security-and-firewall')).'</a></td>';
$account_output .= '</tr>';
}
$account_output .= '</table>';
}
return $account_output;
}
/**
* Login Lockout configuration to set.
*
* @global AIO_WP_Security $aio_wp_security
* @global AIOWPSecurity_Feature_Item_Manager $aiowps_feature_mgr
*
* @return Void
*/
protected function render_login_lockout() {
global $aio_wp_security, $aiowps_feature_mgr;
include_once 'wp-security-list-locked-ip.php'; // For rendering the AIOWPSecurity_List_Table in tab1
$locked_ip_list = new AIOWPSecurity_List_Locked_IP(); // For rendering the AIOWPSecurity_List_Table in tab1
$aiowps_lockdown_allowed_ip_addresses = $aio_wp_security->configs->get_value('aiowps_lockdown_allowed_ip_addresses');
$aio_wp_security->include_template('wp-admin/user-security/login-lockout.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr, 'locked_ip_list' => $locked_ip_list, "aiowps_lockdown_allowed_ip_addresses" => $aiowps_lockdown_allowed_ip_addresses));
}
/**
* Force logged user to logout after x minutes.
*
* @global AIO_WP_Security $aio_wp_security
* @global AIOWPSecurity_Feature_Item_Manager $aiowps_feature_mgr
* @return void
*/
protected function render_force_logout() {
global $aio_wp_security, $aiowps_feature_mgr;
$aio_wp_security->include_template('wp-admin/user-security/force-logout.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr));
}
/**
* Logged in users list.
*
* @global AIO_WP_Security $aio_wp_security
* @return void
*/
protected function render_logged_in_users() {
global $aio_wp_security;
include_once 'wp-security-list-logged-in-users.php'; // For rendering the AIOWPSecurity_List_Table
$user_list = new AIOWPSecurity_List_Logged_In_Users();
$aio_wp_security->include_template('wp-admin/user-security/logged-in-users.php', false, array('user_list' => $user_list));
}
/**
* Renders the submenu's manual approval tab
*
* @return Void
*/
protected function render_manual_approval() {
global $aio_wp_security, $aiowps_feature_mgr;
include_once 'wp-security-list-registered-users.php'; // For rendering the AIOWPSecurity_List_Table
$user_list = new AIOWPSecurity_List_Registered_Users();
$aio_wp_security->include_template('wp-admin/user-security/manual-approval.php', false, array('user_list' => $user_list, 'aiowps_feature_mgr' => $aiowps_feature_mgr));
}
/**
* Renders the submenu's salt tab
*
* @return Void
*/
protected function render_salt_tab() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/user-security/salt.php');
}
/**
* Renders the submenu's http authentication tab.
*
* @global AIO_WP_Security $aio_wp_security
*
* @return void
*/
protected function render_http_authentication() {
global $aio_wp_security, $aiowps_feature_mgr;
if (isset($_POST['aiowps_save_http_authentication_settings'])) {
$nonce_user_cap_result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($_POST['_wpnonce'], 'aiowpsec-http-authentication-settings-nonce');
if (is_wp_error($nonce_user_cap_result)) {
$aio_wp_security->debug_logger->log_debug($nonce_user_cap_result->get_error_message(), 4);
die($nonce_user_cap_result->get_error_message());
}
$error = false;
$aio_wp_security->configs->set_value('aiowps_http_authentication_admin', '');
if (isset($_POST['aiowps_http_authentication_admin'])) {
if (!is_ssl()) {
$this->show_msg_error(__('Failed to save \'Enable for WordPress dashboard\'.', 'all-in-one-wp-security-and-firewall') . ' ' . __('Your site is currently not using https.', 'all-in-one-wp-security-and-firewall'));
$error = true;
} else {
$aio_wp_security->configs->set_value('aiowps_http_authentication_admin', '1');
}
}
$aio_wp_security->configs->set_value('aiowps_http_authentication_frontend', '');
if (isset($_POST['aiowps_http_authentication_frontend'])) {
if (!is_ssl()) {
$this->show_msg_error(__('Failed to save \'Enable for frontend\'.', 'all-in-one-wp-security-and-firewall') . ' ' . __('Your site is currently not using https.', 'all-in-one-wp-security-and-firewall'));
$error = true;
} else {
$aio_wp_security->configs->set_value('aiowps_http_authentication_frontend', '1');
}
}
if (empty($_POST['aiowps_http_authentication_username'])) {
$this->show_msg_error(__('Failed to save \'Username\'.', 'all-in-one-wp-security-and-firewall') . ' ' . __('Please enter a value for the HTTP authentication username.', 'all-in-one-wp-security-and-firewall'));
$error = true;
} else {
$aio_wp_security->configs->set_value('aiowps_http_authentication_username', sanitize_text_field($_POST['aiowps_http_authentication_username']));
}
if (empty($_POST['aiowps_http_authentication_password'])) {
$this->show_msg_error(__('Failed to save \'Password\'.', 'all-in-one-wp-security-and-firewall') . ' ' . __('Please enter a value for the HTTP authentication password.', 'all-in-one-wp-security-and-firewall'));
$error = true;
} else {
$aio_wp_security->configs->set_value('aiowps_http_authentication_password', sanitize_text_field($_POST['aiowps_http_authentication_password']));
}
$aio_wp_security->configs->set_value('aiowps_http_authentication_failure_message', htmlentities(stripslashes($_POST['aiowps_http_authentication_failure_message']), ENT_COMPAT, 'UTF-8'));
$aio_wp_security->configs->save_config();
// Recalculate points after the feature status/options have been altered.
$aiowps_feature_mgr->check_feature_status_and_recalculate_points();
if (!$error) {
$this->show_msg_settings_updated();
}
}
wp_enqueue_script('aiowpsec-pw-tool-js');
$aio_wp_security->include_template('wp-admin/user-security/http-authentication.php');
}
/**
* Renders the submenu's hibp tab.
*
* @global AIO_WP_Security $aio_wp_security
*
* @return void
*/
protected function render_hibp() {
global $aio_wp_security;
$aio_wp_security->include_template('wp-admin/user-security/hibp.php');
}
/**
* Shows additional tab and field for the disable application password and saves on submit.
*
* @global AIO_WP_Security $aio_wp_security
* @global AIOWPSecurity_Feature_Item_Manager $aiowps_feature_mgr
* @return void
*/
protected function render_additional() {
global $aio_wp_security, $aiowps_feature_mgr;
$aio_wp_security->include_template('wp-admin/user-security/additional.php', false, array('aiowps_feature_mgr' => $aiowps_feature_mgr));
}
}
@@ -0,0 +1,561 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
if (trait_exists('AIOWPSecurity_Brute_Force_Commands_Trait')) return;
trait AIOWPSecurity_Brute_Force_Commands_Trait {
/**
* Perform saving rename login settings
*
* @param array $data - the request data contains PHP settings
*
* @return array
*/
public function perform_rename_login_page($data) {
global $aio_wp_security;
$success = true;
$options = array();
$args = array();
$aiowps_login_page_slug = '';
$error = '';
if ('' == $data['aiowps_login_page_slug'] && isset($data["aiowps_enable_rename_login_page"])) {
$error = __('Please enter a value for your login page slug.', 'all-in-one-wp-security-and-firewall');
} elseif ('' != $data['aiowps_login_page_slug']) {
$aiowps_login_page_slug = sanitize_text_field($data['aiowps_login_page_slug']);
if ('wp-admin' == $aiowps_login_page_slug) {
$error = '<br>' . __('You cannot use the value "wp-admin" for your login page slug.', 'all-in-one-wp-security-and-firewall');
} elseif (preg_match('/[^\p{L}\p{N}_\-]/u', $aiowps_login_page_slug)) {
$error = '<br>' . __('You must use alphanumeric characters for your login page slug.', 'all-in-one-wp-security-and-firewall');
}
}
if ($error) {
$success = false;
$message = $error;
} else {
$options['aiowps_enable_rename_login_page'] = isset($data["aiowps_enable_rename_login_page"]) ? '1' : '';
$options['aiowps_login_page_slug'] = $aiowps_login_page_slug;
$this->save_settings($options);
if (get_option('permalink_structure')) {
$home_url = trailingslashit(home_url());
} else {
$home_url = trailingslashit(home_url()) . '?';
}
$message = __('The settings have been successfully updated.', 'all-in-one-wp-security-and-firewall');
$args['badges'] = array("bf-rename-login-page");
$args['content'] = array('aios-rename-login-notice' => $aio_wp_security->include_template('wp-admin/brute-force/partials/rename-login-notice.php', true, array('home_url' => $home_url)));
}
return $this->handle_response($success, $message, $args);
}
/**
* Handles the AJAX request to enable or configure cookie-based brute force prevention.
*
* @param array $data The data received from the AJAX request.
*
* @return array The response containing the status, message, and badge.
*/
public function perform_cookie_based_brute_force_prevention($data) {
global $aio_wp_security;
$options = array();
$values = array();
$info = array();
$success = true;
$message = '';
$result = '';
if (isset($data['aiowps_enable_brute_force_attack_prevention'])) {
$brute_force_feature_secret_word = sanitize_text_field($data['aiowps_brute_force_secret_word']);
$redirect_url = sanitize_text_field($data['aiowps_cookie_based_brute_force_redirect_url']);
if (empty($brute_force_feature_secret_word)) {
$brute_force_feature_secret_word = AIOS_DEFAULT_BRUTE_FORCE_FEATURE_SECRET_WORD;
$info[] = __('You entered an invalid value for the secret word.', 'all-in-one-wp-security-and-firewall'). ' ' . __('It has been set to the default value.', 'all-in-one-wp-security-and-firewall');
} elseif (!ctype_alnum($brute_force_feature_secret_word)) {
$message = '<p>' . __('Settings have not been saved - your secret word must consist only of alphanumeric characters i.e., letters and/or numbers only.', 'all-in-one-wp-security-and-firewall') . '</p>';
$success = false;
}
if (filter_var($redirect_url, FILTER_VALIDATE_URL)) {
$redirect_url = esc_url_raw($redirect_url);
} else {
$redirect_url = 'http://127.0.0.1';
$info[] = __('You entered an invalid value for the redirect url.', 'all-in-one-wp-security-and-firewall'). ' ' . __('It has been set to the default value.', 'all-in-one-wp-security-and-firewall');
}
$options['aiowps_cookie_based_brute_force_redirect_url'] = $redirect_url;
if ($success) {
$options['aiowps_enable_brute_force_attack_prevention'] = '1';
$options['aiowps_brute_force_secret_word'] = $brute_force_feature_secret_word;
$result = '<p>' . __('You have successfully enabled the cookie based brute force prevention feature', 'all-in-one-wp-security-and-firewall') . '</p>';
$result .= '<p>' . __('From now on you will need to log into your WP Admin using the following URL:', 'all-in-one-wp-security-and-firewall') . '</p>';
$result .= '<p><strong>'.AIOWPSEC_WP_URL.'/?'.esc_html($brute_force_feature_secret_word).'=1</strong></p>';
$result .= '<p>' . __('It is important that you save this URL value somewhere in case you forget it, OR,', 'all-in-one-wp-security-and-firewall') . '</p>';
$result .= '<p>' . sprintf(__('simply remember to add a "?%s=1" to your current site URL address.', 'all-in-one-wp-security-and-firewall'), esc_html($brute_force_feature_secret_word)) . '</p>';
AIOWPSecurity_Utility::set_cookie_value(AIOWPSecurity_Utility::get_brute_force_secret_cookie_name(), AIOS_Helper::get_hash($brute_force_feature_secret_word));
}
} else {
$options['aiowps_enable_brute_force_attack_prevention'] = '';
$message = __('You have successfully saved cookie based brute force prevention feature settings.', 'all-in-one-wp-security-and-firewall');
$brute_force_feature_secret_word = $aio_wp_security->configs->get_value('aiowps_brute_force_secret_word');
$redirect_url = $aio_wp_security->configs->get_value('aiowps_cookie_based_brute_force_redirect_url');
}
$options['aiowps_brute_force_attack_prevention_pw_protected_exception'] = isset($data['aiowps_brute_force_attack_prevention_pw_protected_exception']) ? '1' : '';
$options['aiowps_brute_force_attack_prevention_ajax_exception'] = isset($data['aiowps_brute_force_attack_prevention_ajax_exception']) ? '1' : '';
if ($success) {
$this->save_settings($options);
AIOWPSecurity_Configure_Settings::set_cookie_based_bruteforce_firewall_configs();
$message = __('The settings have been successfully updated.', 'all-in-one-wp-security-and-firewall');
$values['aiowps_brute_force_secret_word'] = $brute_force_feature_secret_word;
$values['aiowps_cookie_based_brute_force_redirect_url'] = $redirect_url;
}
$content = array(
'aios-brute-force-info-box' => $result
);
$badges = array("firewall-enable-brute-force-attack-prevention");
$args = array(
'badges' => $badges,
'info' => $info,
'values' => $values,
'content' => $content
);
return $this->handle_response($success, $message, $args);
}
/**
* Handles the AJAX request for performing cookie test.
*
* @return array The response containing the status, message, and badge.
*/
public function perform_cookie_test() {
global $aio_wp_security;
$success = true;
$random_suffix = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(10);
$test_cookie_name = 'aiowps_cookie_test_'.$random_suffix;
$aio_wp_security->configs->set_value('aiowps_cookie_brute_test', $test_cookie_name, true);
$set_cookie = AIOWPSecurity_Utility::set_cookie_value($test_cookie_name, '1');
$aiowps_cookie_test_success = '';
$args = array();
if ($set_cookie) {
$aiowps_cookie_test_success = '1';
$message = __('The cookie test was successful, you can now enable this feature.', 'all-in-one-wp-security-and-firewall');
$result = '<div class="aio_green_box"><p>' . __('The cookie test was successful, you can now enable this feature.', 'all-in-one-wp-security-and-firewall') . '</p></div>';
} else {
$success = false;
$message = __('The cookie test failed.', 'all-in-one-wp-security-and-firewall') .' '. __('Consequently, this feature cannot be used on this site.', 'all-in-one-wp-security-and-firewall');
$result = '<div class="aio_red_box"><p>' . __('The cookie test failed on this server.', 'all-in-one-wp-security-and-firewall') .' '. __('Consequently, this feature cannot be used on this site.', 'all-in-one-wp-security-and-firewall') . '</p></div>';
}
$this->save_settings(array('aiowps_cookie_test_success' => $aiowps_cookie_test_success)); // save the value
$args['content'] = array(
'aios-perform-cookie-test-div' => $this->get_perform_cookie_test_content(),
'cookie-test-result-div' => $result
);
return $this->handle_response($success, $message, $args);
}
/**
* Handles the AJAX request to enable or configure login whitelist settings.
*
* @param array $data The data received from the AJAX request.
*
* @return array The response containing the status, message, and badge.
*/
public function perform_login_whitelist_settings($data) {
global $aio_wp_security;
$success = true;
$options = array();
$message = '';
if (!empty($data['aiowps_allowed_ip_addresses'])) {
$ip_addresses = sanitize_textarea_field(stripslashes($data['aiowps_allowed_ip_addresses']));
$ip_list_array = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($ip_addresses);
$validated_ip_list_array = AIOWPSecurity_Utility_IP::validate_ip_list($ip_list_array, 'whitelist');
if (is_wp_error($validated_ip_list_array)) {
$result = -1;
$success = false;
$message = nl2br($validated_ip_list_array->get_error_message());
} else {
$result = 1;
$whitelist_ip_data = implode("\n", $validated_ip_list_array);
$options['aiowps_allowed_ip_addresses'] = $whitelist_ip_data;
}
} else {
$result = 1;
$options['aiowps_allowed_ip_addresses'] = ''; // Clear the IP address config value
}
if (1 == $result) {
$options['aiowps_enable_whitelisting'] = isset($data["aiowps_enable_whitelisting"]) ? '1' : '';
if ('1' == $aio_wp_security->configs->get_value('aiowps_is_login_whitelist_disabled_on_upgrade')) {
$aio_wp_security->configs->delete_value('aiowps_is_login_whitelist_disabled_on_upgrade');
}
$this->save_settings($options);
}
$args = array(
'badges' => array('whitelist-manager-ip-login-whitelisting')
);
return $this->handle_response($success, $message, $args);
}
/**
* Handles the AJAX request to enable or configure honeypot brute force settings.
*
* @param array $data The data received from the AJAX request.
*
* @return array The response containing the status, message, and badge.
*/
public function perform_honeypot_settings($data) {
$options = array();
// Save all the form values to the options
$options['aiowps_enable_login_honeypot'] = isset($data["aiowps_enable_login_honeypot"]) ? '1' : '';
$options['aiowps_enable_registration_honeypot'] = isset($data["aiowps_enable_registration_honeypot"]) ? '1' : '';
$this->save_settings($options);
$args = array(
'badges' => array('login-honeypot', 'registration-honeypot')
);
return $this->handle_response(true, '', $args);
}
/**
* Handles the AJAX request to enable or configure captcha settings.
*
* @param array $data The data received from the AJAX request.
*
* @return array The response containing the status, message, and badge.
*/
public function perform_captcha_settings($data) {
global $aio_wp_security;
$captcha_themes = $aio_wp_security->captcha_obj->get_captcha_themes();
$supported_captchas = $aio_wp_security->captcha_obj->get_supported_captchas();
$options = array();
$default_captcha = isset($data['aiowps_default_captcha']) ? sanitize_text_field($data['aiowps_default_captcha']) : '';
$default_captcha = array_key_exists($default_captcha, $supported_captchas) ? $default_captcha : 'none';
$options['aiowps_default_captcha'] = $default_captcha;
// Save all the form values to the options
$random_20_digit_string = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(20); // Generate random 20 char string for use during CAPTCHA encode/decode
$options['aiowps_captcha_secret_key'] = $random_20_digit_string;
$options['aiowps_enable_login_captcha'] = isset($data["aiowps_enable_login_captcha"]) ? '1' : '';
$options['aiowps_enable_registration_page_captcha'] = isset($data["aiowps_enable_registration_page_captcha"]) ? '1' : '';
$options['aiowps_enable_comment_captcha'] = isset($data["aiowps_enable_comment_captcha"]) ? '1' : '';
$options['aiowps_enable_bp_register_captcha'] = isset($data["aiowps_enable_bp_register_captcha"]) ? '1' : '';
$options['aiowps_enable_bbp_new_topic_captcha'] = isset($data["aiowps_enable_bbp_new_topic_captcha"]) ? '1' : '';
$options['aiowps_enable_woo_login_captcha'] = isset($data["aiowps_enable_woo_login_captcha"]) ? '1' : '';
$options['aiowps_enable_woo_register_captcha'] = isset($data["aiowps_enable_woo_register_captcha"]) ? '1' : '';
$options['aiowps_enable_woo_lostpassword_captcha'] = isset($data["aiowps_enable_woo_lostpassword_captcha"]) ? '1' : '';
$options['aiowps_enable_woo_checkout_captcha'] = isset($data["aiowps_enable_woo_checkout_captcha"]) ? '1' : '';
$options['aiowps_enable_custom_login_captcha'] = isset($data["aiowps_enable_custom_login_captcha"]) ? '1' : '';
$options['aiowps_enable_lost_password_captcha'] = isset($data["aiowps_enable_lost_password_captcha"]) ? '1' : '';
$options['aiowps_enable_contact_form_7_captcha'] = isset($data["aiowps_enable_contact_form_7_captcha"]) ? '1' : '';
$options['aiowps_enable_password_protected_captcha'] = isset($data["aiowps_enable_password_protected_captcha"]) ? '1' : '';
$options['aiowps_turnstile_site_key'] = sanitize_text_field(stripslashes($data['aiowps_turnstile_site_key']));
$options['aiowps_recaptcha_site_key'] = sanitize_text_field(stripslashes($data['aiowps_recaptcha_site_key']));
$turnstile_theme = isset($data['aiowps_turnstile_theme']) ? sanitize_text_field($data['aiowps_turnstile_theme']) : '';
$turnstile_theme = array_key_exists($turnstile_theme, $captcha_themes) ? $turnstile_theme : 'auto';
$options['aiowps_turnstile_theme'] = $turnstile_theme;
// If secret key is masked then don't resave it
$turnstile_secret_key = sanitize_text_field($data['aiowps_turnstile_secret_key']);
if (strpos($turnstile_secret_key, '********') === false) {
$options['aiowps_turnstile_secret_key'] = $turnstile_secret_key;
}
// If secret key is masked then don't resave it
$recaptcha_secret_key = sanitize_text_field($data['aiowps_recaptcha_secret_key']);
if (strpos($recaptcha_secret_key, '********') === false) {
$options['aiowps_recaptcha_secret_key'] = $recaptcha_secret_key;
}
if ('google-recaptcha-v2' == $aio_wp_security->configs->get_value('aiowps_default_captcha') && false === $aio_wp_security->captcha_obj->google_recaptcha_verify_configuration($aio_wp_security->configs->get_value('aiowps_recaptcha_site_key'), $aio_wp_security->configs->get_value('aiowps_recaptcha_secret_key'))) {
$options['aios_google_recaptcha_invalid_configuration'] = '1';
} elseif ('1' == $aio_wp_security->configs->get_value('aios_google_recaptcha_invalid_configuration')) {
$aio_wp_security->configs->delete_value('aios_google_recaptcha_invalid_configuration');
}
$this->save_settings($options);
$success = false;
$message = '';
if ('cloudflare-turnstile' == $aio_wp_security->configs->get_value('aiowps_default_captcha') && false === $aio_wp_security->captcha_obj->cloudflare_turnstile_verify_configuration($aio_wp_security->configs->get_value('aiowps_turnstile_site_key'), $aio_wp_security->configs->get_value('aiowps_turnstile_secret_key'))) {
$message = __('Your Cloudflare Turnstile configuration is invalid.', 'all-in-one-wp-security-and-firewall').' '.__('Please enter the correct Cloudflare Turnstile keys below to use the Turnstile feature.', 'all-in-one-wp-security-and-firewall');
} elseif ('google-recaptcha-v2' == $aio_wp_security->configs->get_value('aiowps_default_captcha') && '1' == $aio_wp_security->configs->get_value('aios_google_recaptcha_invalid_configuration')) {
$message = __('Your Google reCAPTCHA configuration is invalid.', 'all-in-one-wp-security-and-firewall').' '.__('Please enter the correct reCAPTCHA keys below to use the reCAPTCHA feature.', 'all-in-one-wp-security-and-firewall');
} else {
$success = true;
}
$features = array(
"user-login-captcha",
"user-registration-captcha",
"lost-password-captcha",
"custom-login-captcha",
"comment-form-captcha",
"password_protected-captcha",
);
if (AIOWPSecurity_Utility::is_woocommerce_plugin_active()) {
$woocommerce_features = array(
"woo-login-captcha",
"woo-lostpassword-captcha",
"woo-register-captcha",
"woo-checkout-captcha",
);
$features = array_merge($features, $woocommerce_features);
}
if (AIOWPSecurity_Utility::is_buddypress_plugin_active()) {
$features[] = "bp-register-captcha";
}
if (AIOWPSecurity_Utility::is_bbpress_plugin_active()) {
$features[] = "bbp-new-topic-captcha";
}
if (AIOWPSecurity_Utility::is_contact_form_7_plugin_active()) {
$features[] = "contact-form-7-captcha";
}
$args = array(
'badges' => $features
);
return $this->handle_response($success, $message, $args);
}
/**
* Handles the AJAX request to enable or configure 404 detection and settings.
*
* @param array $data The data received from the AJAX request.
*
* @return array The response containing the status, message, and badge.
*/
public function perform_404_settings($data) {
$options = array();
$info = array();
$values = array();
$options['aiowps_enable_404_logging'] = isset($data["aiowps_enable_404_IP_lockout"]) ? '1' : ''; //the "aiowps_enable_404_IP_lockout" checkbox currently controls both the 404 lockout and 404 logging
$options['aiowps_enable_404_IP_lockout'] = isset($data["aiowps_enable_404_IP_lockout"]) ? '1' : '';
$lockout_time_length = isset($data['aiowps_404_lockout_time_length']) ? sanitize_text_field($data['aiowps_404_lockout_time_length']) : '';
$redirect_url = isset($data['aiowps_404_lock_redirect_url']) ? sanitize_text_field(trim($data['aiowps_404_lock_redirect_url'])) : '';
if (isset($data["aiowps_enable_404_IP_lockout"])) {
if (!is_numeric($lockout_time_length) || $lockout_time_length < 1) {
$info[] = __('You entered a non numeric or negative value for the lockout time length field.', 'all-in-one-wp-security-and-firewall'). ' ' . __('It has been set to the default value.', 'all-in-one-wp-security-and-firewall');
$lockout_time_length = '60'; // Set it to the default value for this field
}
if ('' == $redirect_url || '' == esc_url($redirect_url, array('http', 'https'))) {
$info[] = __('You entered an incorrect format for the "Redirect URL" field.', 'all-in-one-wp-security-and-firewall') . ' ' . __('It has been set to the default value.', 'all-in-one-wp-security-and-firewall');
$redirect_url = 'http://127.0.0.1';
}
}
$options['aiowps_404_lockout_time_length'] = absint($lockout_time_length);
$options['aiowps_404_lock_redirect_url'] = $redirect_url;
$this->save_settings($options);
$badges = array("firewall-enable-404-blocking");
$values['aiowps_404_lockout_time_length'] = $lockout_time_length;
$values['aiowps_404_lock_redirect_url'] = $redirect_url;
$args = array(
'badges' => $badges,
'info' => $info,
'values' => $values
);
return $this->handle_response(true, '', $args);
}
/**
* Handles the AJAX request to clear 404 logs.
*
* @return array The response containing the status, message, and badge.
*/
public function perform_delete_404_event_records() {
global $aio_wp_security, $wpdb;
$success = true;
$events_table_name = AIOWPSEC_TBL_EVENTS;
//Delete all 404 records from the events table
$where = array('event_type' => '404');
$result = $wpdb->delete($events_table_name, $where);
if (false === $result) {
$error = empty($wpdb->last_error) ? '' : $wpdb->last_error;
$aio_wp_security->debug_logger->log_debug("404 Detection Feature - Delete all 404 event logs operation failed. $error", 4);
$success = false;
$message = __('404 Detection Feature - The operation to delete all the 404 event logs failed', 'all-in-one-wp-security-and-firewall');
} else {
$message = __('All 404 event logs were deleted from the database successfully.', 'all-in-one-wp-security-and-firewall');
}
return $this->handle_response($success, $message);
}
/**
* Handles the AJAX request for 404 log item actions.
*
* @param array $data The data received from the AJAX request.
*
* @return array The response containing the status, message, and badge.
*/
public function perform_404_log_item_action($data) {
global $wpdb, $aio_wp_security, $aiowps_firewall_config;
if (empty($data['action']) || !in_array($data['action'], array('delete', 'temp_block', 'blacklist', 'unblock'))) {
return $this->handle_response(false, __('Invalid action provided for 404 log item.', 'all-in-one-wp-security-and-firewall'));
}
$action = $data['action'];
$message = false;
switch ($action) {
case 'delete':
if (!isset($data['id'])) {
return $this->handle_response(false, __('Invalid 404 event log ID provided.', 'all-in-one-wp-security-and-firewall'));
}
$events_table = AIOWPSEC_TBL_EVENTS;
$id = absint($data['id']);
//Delete single record
$delete_command = "DELETE FROM " . $events_table . " WHERE id = '" . absint($id) . "'";
$result = $wpdb->query($delete_command);
if (false === $result) {
// Error on single delete
$aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Events table. Database error: '.$wpdb->last_error, 4);
return $this->handle_response(false, __('The selected record(s) have failed to delete.', 'all-in-one-wp-security-and-firewall'));
} else {
$message = __('The selected record(s) has been deleted successfully.', 'all-in-one-wp-security-and-firewall');
}
break;
case 'temp_block':
if (!isset($data['ip'])) {
return $this->handle_response(false, __('Invalid IP provided.', 'all-in-one-wp-security-and-firewall'));
}
$ip = sanitize_text_field($data['ip']);
$username = isset($data['username']) ? sanitize_user($data['username']) : '';
if (AIOWPSecurity_Utility_IP::get_user_ip_address() == $ip) {
return $this->handle_response(false, __('You cannot block your own IP address:', 'all-in-one-wp-security-and-firewall') . ' ' . $ip);
}
//Block single record
if (filter_var($ip, FILTER_VALIDATE_IP)) {
AIOWPSecurity_Utility::lock_IP($ip, '404', $username);
$message = __('The selected IP address is now temporarily blocked.', 'all-in-one-wp-security-and-firewall');
} else {
$message = __('The selected entry is not a valid IP address.', 'all-in-one-wp-security-and-firewall');
return $this->handle_response(false, $message);
}
break;
case 'blacklist':
if (!isset($data['ip'])) {
return $this->handle_response(false, __('Invalid IP provided.', 'all-in-one-wp-security-and-firewall'));
}
$bl_ip_addresses = $aio_wp_security->configs->get_value('aiowps_banned_ip_addresses'); //get the currently saved blacklisted IPs
$ip_list_array = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($bl_ip_addresses);
$ip = sanitize_text_field($data['ip']);
$ip_list_array[] = $ip;
$validated_ip_list_array = AIOWPSecurity_Utility_IP::validate_ip_list($ip_list_array, 'blacklist');
if (is_wp_error($validated_ip_list_array)) {
$response = nl2br($validated_ip_list_array->get_error_message());
return $this->handle_response(false, $response);
} else {
$banned_ip_data = implode("\n", $validated_ip_list_array);
$aio_wp_security->configs->set_value('aiowps_enable_blacklisting', '1'); // Force blacklist feature to be enabled.
$aio_wp_security->configs->set_value('aiowps_banned_ip_addresses', $banned_ip_data);
$aio_wp_security->configs->save_config();
$aiowps_firewall_config->set_value('aiowps_blacklist_ips', $validated_ip_list_array);
$message = __('The selected IP addresses have been added to the blacklist and will be permanently blocked.', 'all-in-one-wp-security-and-firewall');
}
break;
case 'unblock':
if (!isset($data['ip'])) {
return $this->handle_response(false, __('Invalid log event ID provided.', 'all-in-one-wp-security-and-firewall'));
}
$ip_range = sanitize_text_field($data['ip']);
$lockout_table = AIOWPSEC_TBL_LOGIN_LOCKOUT;
// get the latest data with that ip in the table that's locked and reason is 404
$query = $wpdb->prepare("SELECT id FROM {$lockout_table} WHERE `released` > UNIX_TIMESTAMP() AND `lock_reason` = %s and failed_login_ip = %s ORDER BY id ASC LIMIT 1", '404', $ip_range);
$id = $wpdb->get_var($query);
if (null === $id) {
return $this->handle_response(false, __('Invalid log event ID provided.', 'all-in-one-wp-security-and-firewall'));
}
$result = $wpdb->query($wpdb->prepare("UPDATE $lockout_table SET `released` = UNIX_TIMESTAMP() WHERE `id` = %d", absint($id)));
if (null != $result) {
$message = __('Access from the selected IP address has been unblocked.', 'all-in-one-wp-security-and-firewall');
} else {
return $this->handle_response(false, __('The selected IP entry could not be unlocked', 'all-in-one-wp-security-and-firewall'));
}
break;
}
return $this->handle_response(true, $message);
}
/**
* Get the content for performing a cookie test.
*
* This method checks if the cookie test is successful or if the brute-force attack prevention feature is already enabled.
* If either condition is true, it returns an empty string. Otherwise, it displays a message prompting the user to perform
* a cookie test before enabling the feature, along with a button to initiate the test.
*
* @return string The HTML content for the cookie test section.
*/
private function get_perform_cookie_test_content() {
global $aio_wp_security;
$cookie_test_value = $aio_wp_security->configs->get_value('aiowps_cookie_test_success');
if ('1' == $cookie_test_value || '1' == $aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention')) {
return '';
} else {
return $aio_wp_security->include_template('wp-admin/brute-force/partials/cookie-test-container.php', true);
}
}
}
@@ -0,0 +1,221 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
if (trait_exists('AIOWPSecurity_Comment_Commands_Trait')) return;
trait AIOWPSecurity_Comment_Commands_Trait {
/**
* Perform the saving of comment spam prevention settings
*
* @param array $data - the request data contains the post data
*
* @return array
*/
public function perform_comment_spam_prevention($data) {
$response = array();
// Save settings
$options = array();
$info = array();
$options['aiowps_enable_spambot_detecting'] = isset($data["aiowps_enable_spambot_detecting"]) ? '1' : '';
$options['aiowps_spambot_detect_usecookies'] = isset($data["aiowps_spambot_detect_usecookies"]) ? '1' : '';
$options['aiowps_spam_comments_should'] = !empty($data["aiowps_spam_comments_should"]) ? '1' : '0';
$options['aiowps_enable_trash_spam_comments'] = isset($data['aiowps_enable_trash_spam_comments']) ? '1' : '';
if (isset($data['aiowps_trash_spam_comments_after_days'])) {
$aiowps_trash_spam_comments_after_days = sanitize_text_field($data['aiowps_trash_spam_comments_after_days']);
if (isset($data['aiowps_enable_trash_spam_comments']) && !is_numeric($aiowps_trash_spam_comments_after_days)) {
$error = __('You entered a non-numeric value for the "move spam comments to trash after number of days" field; it has been set to the default value.', 'all-in-one-wp-security-and-firewall');
//Set it to the default value for this field
$info[] = $error;
$aiowps_trash_spam_comments_after_days = 14;
}
$aiowps_trash_spam_comments_after_days = absint($aiowps_trash_spam_comments_after_days);
$options['aiowps_trash_spam_comments_after_days'] = $aiowps_trash_spam_comments_after_days;
$response['values'] = array(
'aiowps_trash_spam_comments_after_days' => $aiowps_trash_spam_comments_after_days
);
}
$response['status'] = 'success';
$response['message'] = __('The settings were successfully updated.', 'all-in-one-wp-security-and-firewall');
$response['info'] = $info;
// Commit the config settings
$this->save_settings($options);
AIOWPSecurity_Comment::trash_spam_comments();
$response['badges'] = $this->get_features_id_and_html(array('detect-spambots'));
return $response;
}
/**
* Perform the saving of comment auto block spammers ip settings
*
* @param array $data - the request data contains the post data
*
* @return array
*/
public function perform_auto_block_spam_ip($data) {
$response = array(
'status' => 'success',
'values' => array(),
'info' => array()
);
$enable_auto_block_ip = isset($data["aiowps_enable_autoblock_spam_ip"]) ? '1' : '';
$spam_ip_min_comments = sanitize_text_field($data['aiowps_spam_ip_min_comments_block']);
if (!is_numeric($spam_ip_min_comments)) {
$response['info'][] = __('You entered a non-numeric value for the "minimum number of spam comments" field; it has been set to the default value.', 'all-in-one-wp-security-and-firewall');
$spam_ip_min_comments = '3';// Set it to the default value for this field
} elseif ((int) $spam_ip_min_comments <= 0 || empty($spam_ip_min_comments)) {
$response['info'][] = __('You must enter an integer greater than zero for the "minimum number of spam comments" field; it has been set to the default value.', 'all-in-one-wp-security-and-firewall');
$spam_ip_min_comments = '3';// Set it to the default value for this field
}
// Save all the form values to the options
$options = array(
'aiowps_enable_autoblock_spam_ip' => $enable_auto_block_ip,
'aiowps_spam_ip_min_comments_block' => absint($spam_ip_min_comments),
);
$this->save_settings($options);
$response['message'] = __('The settings were successfully updated.', 'all-in-one-wp-security-and-firewall');
$response['badges'] = $this->get_features_id_and_html(array('auto-block-spam-ips'));
$response['values']['aiowps_spam_ip_min_comments_block'] = absint($spam_ip_min_comments);
return $response;
}
/**
* Perform the ip spam comment search
*
* @param array $data - the request data contains the post data
*
* @return array
*/
public function perform_ip_spam_search($data) {
$response = array(
'status' => 'success',
'info' => array()
);
$min_comments_per_ip = sanitize_text_field($data['aiowps_spam_ip_min_comments']);
$error = '';
if (!is_numeric($min_comments_per_ip)) {
$error = __('You entered a non-numeric value for the minimum spam comments per IP field; it has been set to the default value.', 'all-in-one-wp-security-and-firewall');
$min_comments_per_ip = '5'; // Set it to the default value for this field
} elseif ((int) $min_comments_per_ip <= 0 || empty($min_comments_per_ip)) {
$error = __('You must enter an integer greater than zero for the minimum spam comments per IP field; it has been set to the default value.', 'all-in-one-wp-security-and-firewall');
$min_comments_per_ip = '5'; // Set it to the default value for this field
}
$min_comments_per_ip = absint($min_comments_per_ip);
// Save all the form values to the options
$this->save_settings(array(
'aiowps_spam_ip_min_comments' => $min_comments_per_ip
));
if (!empty($error)) {
$response['message'] = $error;
}
$response['values']['aiowps_spam_ip_min_comments'] = $min_comments_per_ip;
return $response;
}
/**
* Perform the action of blocking a spam IP address.
*
* This function takes an IP address as input, checks if it is valid and not the user's own IP,
* and then attempts to add it to the block list for spam. It returns the status and message of the operation.
*
* @param array $data The data containing the IP address to block.
*
* @return array The result of the block operation, including status, message, and updated blocked comments output.
*/
public function perform_block_spam_ip($data) {
if (empty($data['ip'])) {
return array('status' => 'error', 'message' => __('Invalid IP address provided.', 'all-in-one-wp-security-and-firewall'));
}
$ip = wp_strip_all_tags($data['ip']);
if (AIOWPSecurity_Utility_IP::get_user_ip_address() == $ip) {
return array('status' => 'error', 'message' => __('You cannot block your own IP address:', 'all-in-one-wp-security-and-firewall') . ' ' . $ip);
}
$result = AIOWPSecurity_Blocking::add_ip_to_block_list($ip, 'spam');
if ($result) {
$status = 'success';
$message = __('The selected IP address is now permanently blocked.', 'all-in-one-wp-security-and-firewall');
} else {
$status = 'error';
$message = __('The selected IP address could not be blocked due to one of the following reasons:', 'all-in-one-wp-security-and-firewall');
$message .= ' ' . __('either it has already been blocked, or your user account lacks sufficient permissions to perform IP blocking.', 'all-in-one-wp-security-and-firewall');
}
return array(
'status' => $status,
'message' => $message,
'content' => array('aios-blocked-comments-output' => $this->get_blocked_comments_output())
);
}
/**
* Retrieves the output for displaying blocked comments due to spam.
*
* This function queries the database to get IP addresses that are permanently blocked due to spam.
* It returns HTML output that displays the count of IPs blocked today and the all-time total count.
*
* @global object $aio_wp_security The global instance of the aio_wp_security class.
* @global object $wpdb The global instance of the WordPress database class.
*
* @return string HTML output for the blocked comments section.
*/
private function get_blocked_comments_output() {
global $aio_wp_security, $wpdb;
$block_comments_output = '';
$min_block_comments = $aio_wp_security->configs->get_value('aiowps_spam_ip_min_comments_block');
if (!empty($min_block_comments)) {
$now_date = (new DateTime('now', new DateTimeZone('UTC')))->format('Y-m-d');
$sql = $wpdb->prepare(
"SELECT COUNT(*) AS total_count,
SUM(CASE WHEN DATE(FROM_UNIXTIME(created)) = %s THEN 1 ELSE 0 END) AS todays_blocked_count FROM ".AIOWPSEC_TBL_PERM_BLOCK." WHERE block_reason = %s",
$now_date,
'spam'
);
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore
$result = $wpdb->get_row($sql);
$block_comments_output = '<div class="aio_yellow_box">';
if (empty($result) || 0 == $result->total_count) {
$block_comments_output .= '<p><strong>'.esc_html__('You currently have no IP addresses permanently blocked due to spam.', 'all-in-one-wp-security-and-firewall').'</strong></p>';
} else {
$todays_blocked_count = $result->todays_blocked_count;
$total_count = $result->total_count;
$block_comments_output .= '<p><strong>'.esc_html__('Spammer IPs added to permanent block list today:', 'all-in-one-wp-security-and-firewall') . ' ' . esc_html($todays_blocked_count) . '</strong></p>';
$block_comments_output .= '<hr><p><strong>'.esc_html__('All time total:', 'all-in-one-wp-security-and-firewall'). ' ' . $total_count.'</strong></p>';
$block_comments_output .= '<p><a class="button" href="admin.php?page='.esc_attr(AIOWPSEC_MAIN_MENU_SLUG).'&tab=permanent-block" target="_blank">'.esc_html__('View blocked IPs', 'all-in-one-wp-security-and-firewall').'</a></p>';
}
$block_comments_output .= '</div>';
}
return $block_comments_output;
}
}
@@ -0,0 +1,280 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
if (trait_exists('AIOWPSecurity_File_Scan_Commands_Trait')) return;
trait AIOWPSecurity_File_Scan_Commands_Trait {
/**
* Perform the operation to save file change detection settings.
*
* @param array $data The data containing the file change detection settings.
*
* @return array An array containing the status of the operation, any relevant messages,
* and updated content.
*/
public function perform_save_file_detection_change_settings($data) {
global $aio_wp_security;
$info = array();
$content = array();
$options = array();
$errors = array();
$reset_scan_data = false;
$file_types = '';
$files = '';
$fcd_scan_frequency = sanitize_text_field($data['aiowps_fcd_scan_frequency']);
if (!is_numeric($fcd_scan_frequency)) {
$errors[] = __('You entered a non numeric value for the "backup time interval" field, it has been set to the default value.', 'all-in-one-wp-security-and-firewall');
$fcd_scan_frequency = '4'; // Set it to the default value for this field
}
if (!empty($data['aiowps_fcd_exclude_filetypes'])) {
$file_types = sanitize_textarea_field(trim($data['aiowps_fcd_exclude_filetypes']));
// Get the currently saved config value and check if this has changed. If so do another scan to reset the scan data so it omits these filetypes
if ($file_types != $aio_wp_security->configs->get_value('aiowps_fcd_exclude_filetypes')) {
$reset_scan_data = true;
}
}
if (!empty($data['aiowps_fcd_exclude_files'])) {
$files = sanitize_textarea_field(trim($data['aiowps_fcd_exclude_files']));
// Get the currently saved config value and check if this has changed. If so do another scan to reset the scan data so it omits these files/dirs
if ($files != $aio_wp_security->configs->get_value('aiowps_fcd_exclude_files')) {
$reset_scan_data = true;
}
}
// Explode by end-of-line character, then trim and filter empty lines
$email_list_array = array_filter(array_map('trim', explode("\n", $data['aiowps_fcd_scan_email_address'])), 'strlen');
foreach ($email_list_array as $key => $value) {
$email_sane = sanitize_email($value);
if (!is_email($email_sane)) {
$errors[] = __('The following address was removed because it is not a valid email address:', 'all-in-one-wp-security-and-firewall') . ' ' . htmlspecialchars($value);
unset($email_list_array[$key]);
}
}
$email_address = implode("\n", $email_list_array);
if (!empty($errors)) {
$info[] = implode('<br>', $errors);
}
// Save all the form values to the options
$options['aiowps_enable_automated_fcd_scan'] = isset($data["aiowps_enable_automated_fcd_scan"]) ? '1' : '';
$options['aiowps_fcd_scan_frequency'] = absint($fcd_scan_frequency);
$options['aiowps_fcd_scan_interval'] = sanitize_text_field($data["aiowps_fcd_scan_interval"]);
$options['aiowps_fcd_exclude_filetypes'] = $file_types;
$options['aiowps_fcd_exclude_files'] = $files;
$options['aiowps_send_fcd_scan_email'] = isset($data["aiowps_send_fcd_scan_email"]) ? '1' : '';
$options['aiowps_fcd_scan_email_address'] = $email_address;
$this->save_settings($options);
$content['aios-file-change-info-box'] = '';
// Let's check if backup interval was set to less than 24 hours
if (isset($data["aiowps_enable_automated_fcd_scan"]) && ($fcd_scan_frequency < 24) && 0 == $data["aiowps_fcd_scan_interval"]) {
$content['aios-file-change-info-box'] = '<div class="aio_yellow_box">';
$content['aios-file-change-info-box'] .= '<p>' . __('You have configured your file change detection scan to occur at least once daily.', 'all-in-one-wp-security-and-firewall') . '</p>';
$content['aios-file-change-info-box'] .= '<p>' . __('For most websites we recommended that you choose a less frequent schedule such as once every few days, once a week or once a month.', 'all-in-one-wp-security-and-firewall') . '</p>';
$content['aios-file-change-info-box'] .= '<p>' . __('Choosing a less frequent schedule will also help reduce your server load.', 'all-in-one-wp-security-and-firewall') . '</p>';
$content['aios-file-change-info-box'] .= '</div>';
}
if ($reset_scan_data) {
$aio_wp_security->scan_obj->execute_file_change_detection_scan();
$new_scan_alert = __('New scan completed: The plugin has detected that you have made changes to the "File Types To Ignore" or "Files To Ignore" fields.', 'all-in-one-wp-security-and-firewall').' '.__('In order to ensure that future scan results are accurate, the old scan data has been refreshed.', 'all-in-one-wp-security-and-firewall');
$info[] = $new_scan_alert;
}
$next_fcd_scan_time = AIOWPSecurity_Scan::get_next_scheduled_scan();
if (false == $next_fcd_scan_time) {
$next_scheduled_scan = '<span>' . esc_html__('Nothing is currently scheduled', 'all-in-one-wp-security-and-firewall') . '</span>';
} else {
$scan_time = AIOWPSecurity_Utility::convert_timestamp($next_fcd_scan_time, 'D, F j, Y H:i');
$next_scheduled_scan = '<span class="aiowps_next_scheduled_date_time">' . esc_html($scan_time) . '</span>';
}
$content['aiowps-next-files-scan-inner'] = $next_scheduled_scan;
$values = array('aiowps_fcd_scan_frequency' => absint($fcd_scan_frequency));
$badges = array('scan-file-change-detection');
$args = array(
'content' => $content,
'values' => $values,
'badges' => $badges,
'info' => $info
);
return $this->handle_response(true, '', $args);
}
/**
* Retrieves the last file scan data and returns the data to UDC.
*
* @param array $data The request data.
*
* @return array|string[]|WP_Error
*/
public function get_last_scan_data($data) {
global $aio_wp_security;
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
return new WP_Error(esc_html__('Sorry, you do not have enough privilege to execute the requested action.', 'all-in-one-wp-security-and-firewall'));
}
if ($data['reset_change_detected']) {
$aio_wp_security->configs->set_value('aiowps_fcds_change_detected', false, true);
}
$fcd_data = AIOWPSecurity_Scan::get_fcd_data();
$data = $fcd_data['last_scan_result'];
foreach (array('files_added', 'files_removed', 'files_changed') as $key) {
/* Normalize missing or non-array buckets to an empty array and skip processing */
if (!isset($data[$key]) || !is_array($data[$key])) {
$data[$key] = array();
continue;
}
/* Convert last_modified for each entry */
foreach ($data[$key] as &$info) {
if (is_array($info) && array_key_exists('last_modified', $info) && is_numeric($info['last_modified'])) {
$info['last_modified'] = AIOWPSecurity_Utility::convert_timestamp($info['last_modified']);
}
}
unset($info);
}
$fcd_data['last_scan_result'] = $data;
return $this->handle_response(true, false, array('extra_args' => $fcd_data));
}
/**
* Gets the last file scan result and returns the scan result HTML template
*
* @param array $data - the request data
*
* @return array
*/
public function get_last_scan_results($data) {
global $aio_wp_security;
if ($data['reset_change_detected']) $aio_wp_security->configs->set_value('aiowps_fcds_change_detected', false, true);
$fcd_data = AIOWPSecurity_Scan::get_fcd_data();
if (!$fcd_data || !isset($fcd_data['last_scan_result'])) {
// no fcd data found
$message = __('No previous scan data was found; either run a manual scan or schedule regular file scans', 'all-in-one-wp-security-and-firewall');
return $this->handle_response(false, $message);
}
$content = array('aiowps_previous_scan_wrapper' => $aio_wp_security->include_template('wp-admin/scanner/scan-result.php', true, array('fcd_data' => $fcd_data)));
return $this->handle_response(true, false, array('content' => $content));
}
/**
* Performs a file scan and returns the scan result
*
* @return array
*/
public function perform_file_scan() {
global $aio_wp_security;
$content = array();
$extra_args = array();
$result = $aio_wp_security->scan_obj->execute_file_change_detection_scan();
if (false === $result) {
// error case
$message = __('There was an error during the file change detection scan.', 'all-in-one-wp-security-and-firewall') . ' ' . __('Please check the plugin debug logs.', 'all-in-one-wp-security-and-firewall');
return $this->handle_response(false, $message);
}
$aio_wp_security->configs->set_value('aiowps_last_scan_time', time(), true);
// If this is first scan display special message
if (1 == $result['initial_scan']) {
$extra_args['result'] = __('This is your first file change detection scan.', 'all-in-one-wp-security-and-firewall').' '.__('The details from this scan will be used for future scans.', 'all-in-one-wp-security-and-firewall'). ' <a href="#" class="aiowps_view_last_fcd_results">' . __('View the file scan results', 'all-in-one-wp-security-and-firewall') . '</a>';
$content['aiowps-previous-files-scan-inner'] = '<a href="#" class="aiowps_view_last_fcd_results">' . __('View last file scan results', 'all-in-one-wp-security-and-firewall') . '</a>';
} elseif (!$aio_wp_security->configs->get_value('aiowps_fcds_change_detected')) {
$extra_args['result'] = __('The scan is complete - There were no file changes detected.', 'all-in-one-wp-security-and-firewall');
} elseif ($aio_wp_security->configs->get_value('aiowps_fcds_change_detected')) {
$extra_args['result'] = __('The scan has detected that there was a change in your website\'s files.', 'all-in-one-wp-security-and-firewall'). ' <a href="#" class="aiowps_view_last_fcd_results">' . __('View the file scan results', 'all-in-one-wp-security-and-firewall') . '</a>';
}
$args = array(
'extra_args' => $extra_args,
'content' => $content
);
return $this->handle_response(true, false, $args);
}
/**
* Render the legacy UDC Scanner.
*
* @return array
*/
public function get_scanner_contents() {
global $aio_wp_security;
$GLOBALS['aiowps_feature_mgr'] = $this->get_feature_mgr_object();
$scanner_data = $this->get_scanner_data();
$content = $aio_wp_security->include_template('wp-admin/scanner/file-change-detect.php', true, $scanner_data);
return array(
'status' => 'success',
'content' => $content,
);
}
/**
* Return file scanner data.
*
* @return array Array of option values,
*/
public function get_scanner_data() {
global $aio_wp_security;
$fcd_data = AIOWPSecurity_Scan::get_fcd_data();
$previous_scan = isset($fcd_data['last_scan_result']);
$next_fcd_scan_time = AIOWPSecurity_Scan::get_next_scheduled_scan();
$aiowps_fcds_change_detected = $aio_wp_security->configs->get_value('aiowps_fcds_change_detected');
$aiowps_enable_automated_fcd_scan = $aio_wp_security->configs->get_value('aiowps_enable_automated_fcd_scan');
$aiowps_fcd_scan_frequency = $aio_wp_security->configs->get_value('aiowps_fcd_scan_frequency');
$aiowps_fcd_scan_interval = $aio_wp_security->configs->get_value('aiowps_fcd_scan_interval');
$aiowps_fcd_exclude_filetypes = $aio_wp_security->configs->get_value('aiowps_fcd_exclude_filetypes');
$aiowps_fcd_exclude_files = $aio_wp_security->configs->get_value('aiowps_fcd_exclude_files');
$aiowps_send_fcd_scan_email = $aio_wp_security->configs->get_value('aiowps_send_fcd_scan_email');
$aiowps_fcd_scan_email_address = $aio_wp_security->configs->get_value('aiowps_fcd_scan_email_address');
$aiowps_last_scan_time = $aio_wp_security->configs->get_value('aiowps_last_scan_time');
return array(
'previous_scan' => $previous_scan,
'next_fcd_scan_time' => false === $next_fcd_scan_time ? '' : AIOWPSecurity_Utility::convert_timestamp($next_fcd_scan_time, 'D, F j, Y H:i'),
'aiowps_fcds_change_detected' => $aiowps_fcds_change_detected,
'aiowps_enable_automated_fcd_scan' => $aiowps_enable_automated_fcd_scan,
'aiowps_fcd_scan_frequency' => $aiowps_fcd_scan_frequency,
'aiowps_fcd_scan_interval' => $aiowps_fcd_scan_interval,
'aiowps_fcd_exclude_filetypes' => $aiowps_fcd_exclude_filetypes,
'aiowps_fcd_exclude_files' => $aiowps_fcd_exclude_files,
'aiowps_send_fcd_scan_email' => $aiowps_send_fcd_scan_email,
'aiowps_fcd_scan_email_address' => $aiowps_fcd_scan_email_address,
'aiowps_last_scan_time' => AIOWPSecurity_Utility::convert_timestamp($aiowps_last_scan_time, 'D, F j, Y H:i'),
);
}
}
@@ -0,0 +1,224 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
if (trait_exists('AIOWPSecurity_Files_Commands_Trait')) return;
trait AIOWPSecurity_Files_Commands_Trait {
/**
* This function performs file permission fixing
*
* @param array $data - the request data contains the files items
*
* @return array
*/
public function perform_fix_permissions($data) {
global $aio_wp_security;
$files_dirs_to_check = AIOWPSecurity_Utility_File::get_files_and_dirs_to_check();
$success = true;
$message = '';
if (isset($data['aiowps_permission_chg_file'])) {
$file_found = false;
$folder_or_file = sanitize_text_field($data['aiowps_permission_chg_file']);
$rec_perm_oct_string = '';
foreach ($files_dirs_to_check as $file_or_dir) {
if ($folder_or_file == $file_or_dir['path']) {
$file_found = true;
$rec_perm_oct_string = $file_or_dir['permissions'];
}
}
if ($file_found && !empty($rec_perm_oct_string)) {
$rec_perm_dec = octdec($rec_perm_oct_string); // Convert the octal string to dec so the chmod func will accept it
$perm_result = @chmod($folder_or_file, $rec_perm_dec);
if (true === $perm_result) {
$message = sprintf(__('The permissions for %s were successfully changed to %s', 'all-in-one-wp-security-and-firewall'), htmlspecialchars($folder_or_file), htmlspecialchars($rec_perm_oct_string));
} elseif (false === $perm_result) {
$message = sprintf(__('Unable to change permissions for %s', 'all-in-one-wp-security-and-firewall'), htmlspecialchars($folder_or_file));
$success = false;
}
} else {
$message = sprintf(__('Unable to change permissions for %s : not in list of valid files', 'all-in-one-wp-security-and-firewall'), htmlspecialchars($folder_or_file));
$success = false;
}
}
$badges = array("filesystem-file-permissions");
$content = array('aios_file_permissions_table' => $aio_wp_security->include_template('wp-admin/filesystem-security/partials/file-permissions-table.php', true, array('files_dirs_to_check' => $files_dirs_to_check, 'file_utility' => new AIOWPSecurity_Utility_File())));
$args = array(
'content' => $content,
'badges' => $badges,
);
return $this->handle_response($success, $message, $args);
}
/**
* This function performs file protection settings
*
* @param array $data - the request data contains the settings
*
* @return array
*/
public function perform_file_protection_settings($data) {
global $aio_wp_security;
$success = true;
$message = '';
$options = array();
// Update settings for delete readme.html and wp-config-sample.php.
$options['aiowps_auto_delete_default_wp_files'] = isset($data['aiowps_auto_delete_default_wp_files']) ? '1' : '';
// Update settings for prevent hotlinking.
$options['aiowps_prevent_hotlinking'] = isset($data['aiowps_prevent_hotlinking']) ? '1' : '';
// Update settings for php file editing
$disable_file_editing = isset($data["aiowps_disable_file_editing"]) ? '1' : '';
$disable_file_editing_status = $disable_file_editing ? AIOWPSecurity_Utility::disable_file_edits() : AIOWPSecurity_Utility::enable_file_edits();
if ($disable_file_editing_status) {
// Save settings if no errors
$options['aiowps_disable_file_editing'] = $disable_file_editing;
} else {
$message = __('Disable PHP file editing failed: unable to modify or make a backup of the wp-config.php file.', 'all-in-one-wp-security-and-firewall');
return $this->handle_response(false, $message);
}
$this->save_settings($options);
if (AIOWPSecurity_Utility_Htaccess::write_to_htaccess() && '' !== $options['aiowps_prevent_hotlinking']) {
// Now let's write the applicable rules to the .htaccess file
$res = AIOWPSecurity_Utility_Htaccess::write_to_htaccess();
if ($res) {
$message = __('The settings have been successfully updated', 'all-in-one-wp-security-and-firewall');
} else {
$success = false;
$message = __('Could not write to the .htaccess file.', 'all-in-one-wp-security-and-firewall');
// revert options affected by .htaccess write fail
$options['aiowps_prevent_hotlinking'] = $aio_wp_security->configs->get_value('aiowps_prevent_hotlinking');
$this->save_settings($options);
}
}
$features = array(
"auto-delete-wp-files",
"prevent-hotlinking",
"filesystem-file-editing",
);
return $this->handle_response($success, $message, array('badges' => $features));
}
/**
* This function performs deleting default wp files
*
* @return array
*/
public function perform_delete_default_wp_files() {
$success = true;
$message = __('The files have been deleted successfully.', 'all-in-one-wp-security-and-firewall');
$result = AIOWPSecurity_Utility::delete_unneeded_default_files();
if (!empty($result['error'])) {
$success = false;
$message = sprintf(__('Failed to delete the %s file(s).', 'all-in-one-wp-security-and-firewall'), $result['error']) . '<br>' . __('Please try to delete them manually.', 'all-in-one-wp-security-and-firewall');
}
return $this->handle_response($success, $message, array('info' => $result['info']));
}
/**
* This function performs save copy protection settings
*
* @param array $data - the request data
*
* @return array
*/
public function perform_save_copy_protection($data) {
$this->save_settings(array('aiowps_copy_protection' => isset($data["aiowps_copy_protection"]) ? '1' : ''));
return $this->handle_response(true, '', array('badges' => array('enable-copy-protection')));
}
/**
* This function performs save frame display prevent setting
*
* @param array $data - the request data
*
* @return array
*/
public function perform_save_frame_display_prevent($data) {
$this->save_settings(array('aiowps_prevent_site_display_inside_frame' => isset($data["aiowps_prevent_site_display_inside_frame"]) ? '1' : ''));
return $this->handle_response(true, '', array('badges' => array('enable-frame-protection')));
}
/**
* This function performs host system logs
*
* @param array $data - the request data contains the lgos settings
*
* @return array
*/
public function perform_host_system_logs($data) {
$content = array();
$success = true;
$message = false;
if (isset($data['aiowps_system_log_file'])) {
if ('' != $data['aiowps_system_log_file']) {
$sys_log_file = basename(sanitize_text_field($data['aiowps_system_log_file']));
} else {
$sys_log_file = 'error_log';
}
$this->save_settings(array('aiowps_system_log_file' => $sys_log_file));
}
$logResults = AIOWPSecurity_Utility_File::recursive_file_search($sys_log_file, 0, ABSPATH);
if (empty($logResults) || '' == $logResults) {
$success = false;
$message = __('No system logs were found.', 'all-in-one-wp-security-and-firewall');
} else {
$content['aios-host-system-logs-results'] = '';
foreach ($logResults as $file) {
$content['aios-host-system-logs-results'] .= $this->display_system_logs_in_table($file);
}
}
$values = array('aiowps_system_log_file' => $sys_log_file);
$args = array(
'content' => $content,
'values' => $values
);
return $this->handle_response($success, $message, $args);
}
/**
* Displays the last 50 entries of a system log file in a table format.
*
* This function reads the contents of the specified file and returns a
* rendered template displaying the last 50 entries of the log file.
*
* @param string $filepath The path to the log file to be read.
*
* @return string The rendered HTML template displaying the log entries.
*/
private function display_system_logs_in_table($filepath) {
global $aio_wp_security;
// Get contents of the error_log file
$last_50_entries = AIOWPSecurity_Utility_File::read_file_lines($filepath, -1, 50, true);
return $aio_wp_security->include_template('wp-admin/filesystem-security/filesystem-log-result.php', true, array('filepath' => $filepath, 'last_50_entries' => $last_50_entries));
}
}
@@ -0,0 +1,787 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
if (trait_exists('AIOWPSecurity_Firewall_Commands_Trait')) return;
trait AIOWPSecurity_Firewall_Commands_Trait {
/**
* Perform saving php firewall settings
*
* @param array $data - the request data contains PHP settings
*
* @return array - containing a status and message
*/
public function perform_php_firewall_settings($data) {
global $aio_wp_security;
$aiowps_firewall_config = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONFIG);
$options = array();
$enable_pingback = isset($data["aiowps_enable_pingback_firewall"]);
$info = array();
// Save settings
$aiowps_firewall_config->set_value('aiowps_enable_pingback_firewall', $enable_pingback);
$options['aiowps_disable_xmlrpc_pingback_methods'] = isset($data["aiowps_disable_xmlrpc_pingback_methods"]) ? '1' : ''; //this disables only pingback methods of xmlrpc but leaves other methods so that Jetpack and other apps will still work
$options['aiowps_disable_rss_and_atom_feeds'] = isset($data['aiowps_disable_rss_and_atom_feeds']) ? '1' : '';
$aiowps_firewall_config->set_value('aiowps_forbid_proxy_comments', isset($data['aiowps_forbid_proxy_comments']));
$aiowps_firewall_config->set_value('aiowps_deny_bad_query_strings', isset($data['aiowps_deny_bad_query_strings']));
$aiowps_firewall_config->set_value('aiowps_advanced_char_string_filter', isset($data['aiowps_advanced_char_string_filter']));
$block_request_methods = array_map('strtolower', AIOS_Abstracted_Ids::get_firewall_block_request_methods());
$current_request_methods_settings = $aiowps_firewall_config->get_value('aiowps_6g_block_request_methods');
$current_other_settings = array(
$aiowps_firewall_config->get_value('aiowps_6g_block_query'),
$aiowps_firewall_config->get_value('aiowps_6g_block_request'),
$aiowps_firewall_config->get_value('aiowps_6g_block_referrers'),
$aiowps_firewall_config->get_value('aiowps_6g_block_agents'),
);
$are_methods_set = !empty($current_request_methods_settings);
$are_others_set = array_reduce($current_other_settings, function($carry, $item) {
return $carry || $item;
});
if (($are_methods_set || $are_others_set) && '1' !== $aio_wp_security->configs->get_value('aiowps_enable_6g_firewall')) {
$options['aiowps_enable_6g_firewall'] = '1';
}
if (isset($data['aiowps_enable_6g_firewall'])) {
$aiowps_6g_block_request_methods = array_filter(AIOS_Abstracted_Ids::get_firewall_block_request_methods(), function($block_request_method) {
return ('PUT' != $block_request_method);
});
if (false === $are_methods_set && false === $are_others_set) {
$aiowps_firewall_config->set_value('aiowps_6g_block_request_methods', $aiowps_6g_block_request_methods);
$aiowps_firewall_config->set_value('aiowps_6g_block_query', true);
$aiowps_firewall_config->set_value('aiowps_6g_block_request', true);
$aiowps_firewall_config->set_value('aiowps_6g_block_referrers', true);
$aiowps_firewall_config->set_value('aiowps_6g_block_agents', true);
} else {
$methods = array();
foreach ($block_request_methods as $block_request_method) {
if (isset($data['aiowps_block_request_method_'.$block_request_method])) {
$methods[] = strtoupper($block_request_method);
}
}
$aiowps_firewall_config->set_value('aiowps_6g_block_request_methods', $methods);
$aiowps_firewall_config->set_value('aiowps_6g_block_query', isset($data['aiowps_block_query']));
$aiowps_firewall_config->set_value('aiowps_6g_block_request', isset($data['aiowps_block_request']));
$aiowps_firewall_config->set_value('aiowps_6g_block_referrers', isset($data['aiowps_block_refs']));
$aiowps_firewall_config->set_value('aiowps_6g_block_agents', isset($data['aiowps_block_agents']));
}
$options['aiowps_enable_6g_firewall'] = '1';
//shows the success notice
} else {
AIOWPSecurity_Configure_Settings::turn_off_all_6g_firewall_configs();
$options['aiowps_enable_6g_firewall'] = '';
}
$aiowps_firewall_config->set_value('aiowps_ban_post_blank_headers', isset($data['aiowps_ban_post_blank_headers']));
if (isset($data['aiowps_block_fake_googlebots'])) {
$validated_ip_list_array = AIOWPSecurity_Utility::get_googlebot_ip_ranges();
if (is_wp_error($validated_ip_list_array)) {
$info[] = __('The attempt to save the \'Block fake Googlebots\' settings failed, because it was not possible to validate the Googlebot IP addresses:', 'all-in-one-wp-security-and-firewall') . ' ' . $validated_ip_list_array->get_error_message();
} else {
$aiowps_firewall_config->set_value('aiowps_block_fake_googlebots', true);
$aiowps_firewall_config->set_value('aiowps_googlebot_ip_ranges', $validated_ip_list_array);
}
} else {
$aiowps_firewall_config->set_value('aiowps_block_fake_googlebots', false);
}
$options['aiowps_disallow_unauthorized_rest_requests'] = isset($data["aiowps_disallow_unauthorized_rest_requests"]) ? '1' : '';
$aios_whitelisted_rest_routes = array();
$route_namespaces = AIOWPSecurity_Utility::get_rest_namespaces();
foreach ($route_namespaces as $route_namespace) {
if (isset($data['aios_whitelisted_rest_routes_'.str_replace('-', '_', $route_namespace)])) {
$aios_whitelisted_rest_routes[] = $route_namespace;
}
}
$options['aios_whitelisted_rest_routes'] = $aios_whitelisted_rest_routes;
$aios_roles_disallowed_rest_requests = array();
$user_roles = AIOWPSecurity_Utility_Permissions::get_user_roles();
foreach ($user_roles as $id => $name) {
if (!isset($data['aios_allowed_roles_rest_requests_'.$id])) {
$aios_roles_disallowed_rest_requests[] = $id;
}
}
$options['aios_roles_disallowed_rest_requests'] = $aios_roles_disallowed_rest_requests;
// Commit the config settings
$this->save_settings($options);
$block_request_methods = array_map('strtolower', AIOS_Abstracted_Ids::get_firewall_block_request_methods());
$methods = $aiowps_firewall_config->get_value('aiowps_6g_block_request_methods');
if (empty($methods)) {
$methods = array();
}
$blocked_query = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_query');
$blocked_request = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_request');
$blocked_referrers = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_referrers');
$blocked_agents = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_agents');
$content = array('aios-6g-firewall-settings-container .aios-advanced-options-panel' => $aio_wp_security->include_template('wp-admin/firewall/partials/advanced-settings-6g.php', true, compact('methods', 'blocked_query', 'blocked_request', 'blocked_referrers', 'blocked_agents', 'block_request_methods')));
$features = array(
'firewall-pingback-rules',
'firewall-disable-rss-and-atom',
'firewall-forbid-proxy-comments',
'firewall-deny-bad-queries',
'firewall-advanced-character-string-filter',
'firewall-enable-6g',
'firewall-block-fake-googlebots',
'firewall-ban-post-blank-headers',
'disallow-unauthorised-requests',
);
$args = array(
'badges' => $features,
'content' => $content,
'info' => $info,
'extra_args' => array('xmlprc_warning' => $enable_pingback ? $aio_wp_security->include_template('wp-admin/firewall/partials/xmlrpc-warning-notice.php', true) : '')
);
return $this->handle_response(true, '', $args);
}
/**
* Perform saving .htaccess firewall settings
*
* @param array $data - the request data contains the firewall settings
*
* @return array - containing a status and message
*/
public function perform_htaccess_firewall_settings($data) {
global $aio_wp_security;
$options = array();
$info = array();
$message = '';
$success = true;
// Max file upload size in basic rules
$upload_size = absint($data['aiowps_max_file_upload_size']);
$max_allowed = apply_filters('aiowps_max_allowed_upload_config', 250); // Set a filterable limit of 250MB
$max_allowed = absint($max_allowed);
if ($upload_size > $max_allowed) {
$upload_size = $max_allowed;
} elseif (empty($upload_size) || 0 > $upload_size) {
$upload_size = AIOS_FIREWALL_MAX_FILE_UPLOAD_LIMIT_MB;
$info[] = __('Max file upload limit was set to default value, because you entered a negative or zero value');
}
// Store the current value in case the .htaccess write operation fails and we need to revert it
$original_options = array(
'aiowps_enable_basic_firewall' => $aio_wp_security->configs->get_value("aiowps_enable_basic_firewall"),
'aiowps_max_file_upload_size' => $aio_wp_security->configs->get_value('aiowps_max_file_upload_size'),
'aiowps_block_debug_log_file_access' => $aio_wp_security->configs->get_value("aiowps_block_debug_log_file_access"),
'aiowps_disable_index_views' => $aio_wp_security->configs->get_value('aiowps_disable_index_views'),
);
// Save settings
$options['aiowps_enable_basic_firewall'] = isset($data["aiowps_enable_basic_firewall"]) ? '1' : '';
$options['aiowps_max_file_upload_size'] = $upload_size;
$options['aiowps_block_debug_log_file_access'] = isset($data["aiowps_block_debug_log_file_access"]) ? '1' : '';
$options['aiowps_disable_index_views'] = isset($data['aiowps_disable_index_views']) ? '1' : '';
// Commit the config settings
$this->save_settings($options);
//Now let's write the applicable rules to the .htaccess file
$res = AIOWPSecurity_Utility_Htaccess::write_to_htaccess();
if (!$res) {
$success = false;
$message = __('Could not write to the .htaccess file', 'all-in-one-wp-security-and-firewall');
$this->save_settings($original_options);
}
$features = array(
'firewall-basic-rules',
'firewall-block-debug-file-access',
'firewall-disable-index-views',
);
$values = array('aiowps_max_file_upload_size' => $upload_size);
$args = array(
'badges' => $features,
'info' => $info,
'values' => $values
);
return $this->handle_response($success, $message, $args);
}
/**
* Save and update the 5G firewall settings, and conditionally update the .htaccess file if needed.
*
* This function handles the saving of the 5G firewall settings based on user input. It checks if
* the 5G firewall setting has been modified and writes the applicable rules to the .htaccess file
* if necessary. In case of failure to write to the .htaccess file, it returns an error message.
*
* @param array $data The data array containing the 5G firewall setting.
*
* @global object $aio_wp_security The global instance of the All-In-One WP Security & Firewall plugin.
*
* @return array An array containing the status ('success' or 'error') and a message indicating
* the result of the operation.
*/
public function perform_save_5g_settings($data) {
global $aio_wp_security;
$response = array(
'status' => 'success',
'message' => __('The settings were successfully updated.', 'all-in-one-wp-security-and-firewall')
);
$options = array();
// If the user has changed the 5G firewall checkbox settings, then there is a need to write htaccess rules again.
$is_5G_firewall_option_changed = ((isset($data['aiowps_enable_5g_firewall']) && '1' != $aio_wp_security->configs->get_value('aiowps_enable_5g_firewall')) || (!isset($data['aiowps_enable_5g_firewall']) && '1' == $aio_wp_security->configs->get_value('aiowps_enable_5g_firewall')));
// Save settings
$options['aiowps_enable_5g_firewall'] = isset($data['aiowps_enable_5g_firewall']) ? '1' : '';
$this->save_settings($options);
$res = true;
if ($is_5G_firewall_option_changed) {
$res = AIOWPSecurity_Utility_Htaccess::write_to_htaccess(); // let's write the applicable rules to the .htaccess file
}
if (!$res) {
$response['status'] = 'error';
$response['message'] = __('Could not write to the .htaccess file for the 5G firewall settings, please check the file permissions.', 'all-in-one-wp-security-and-firewall');
// revert settings
$options['aiowps_enable_5g_firewall'] = '';
$this->save_settings($options);
}
return $response;
}
/**
* Perform saving blacklist settings
*
* @param array $data - the request data contains blacklist settings
*
* @return array - containing a status, message and feature badge html
*/
public function perform_save_blacklist_settings($data) {
global $aio_wp_security;
$aiowps_firewall_config = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONFIG);
$options = array();
$message = '';
$success = true;
$result = 1;
$aiowps_enable_blacklisting = isset($data["aiowps_enable_blacklisting"]) ? '1' : '';
if (!empty($data['aiowps_banned_ip_addresses'])) {
$ip_addresses = sanitize_textarea_field(stripslashes($data['aiowps_banned_ip_addresses']));
$ip_list_array = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($ip_addresses);
$validated_ip_list_array = AIOWPSecurity_Utility_IP::validate_ip_list($ip_list_array, 'blacklist');
if (is_wp_error($validated_ip_list_array)) {
$result = -1;
$success = false;
$message = nl2br($validated_ip_list_array->get_error_message());
} else {
$banned_ip_addresses_list = preg_split('/\R/', $aio_wp_security->configs->get_value('aiowps_banned_ip_addresses')); // Historical settings where the separator may have depended on PHP_EOL.
if ($banned_ip_addresses_list !== $validated_ip_list_array) {
$banned_ip_data = implode("\n", $validated_ip_list_array);
$options['aiowps_banned_ip_addresses'] = $banned_ip_data;
$aiowps_firewall_config->set_value('aiowps_blacklist_ips', $validated_ip_list_array);
}
$data['aiowps_banned_ip_addresses'] = ''; // Clear the post variable for the banned address list.
}
} else {
$options['aiowps_banned_ip_addresses'] = ''; // Clear the IP address config value
$aiowps_firewall_config->set_value('aiowps_blacklist_ips', array());
}
if (!empty($data['aiowps_banned_user_agents'])) {
$this->validate_user_agent_list(stripslashes($data['aiowps_banned_user_agents']));
} else {
// Clear the user agent list
$options['aiowps_banned_user_agents'] = '';
$aiowps_firewall_config->set_value('aiowps_blacklist_user_agents', array());
}
if (1 == $result) {
$aio_wp_security->configs->set_value('aiowps_enable_blacklisting', $aiowps_enable_blacklisting, true);
if ('1' == $aio_wp_security->configs->get_value('aiowps_is_ip_blacklist_settings_notice_on_upgrade')) {
$aio_wp_security->configs->delete_value('aiowps_is_ip_blacklist_settings_notice_on_upgrade');
}
}
$this->save_settings($options);
$args = array(
'badges' => array("blacklist-manager-ip-user-agent-blacklisting")
);
return $this->handle_response($success, $message, $args);
}
/**
* The AJAX function for storing ips in firewall allowlist
*
* @param array $data - the request data contains data to updated
*
* @return array - containing a status and message
*/
public function perform_firewall_allowlist($data) {
$aiowps_firewall_allow_list = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::ALLOW_LIST);
$message = '';
$success = true;
$allowlist = $data['aios_firewall_allowlist'];
if (empty($allowlist)) {
$aiowps_firewall_allow_list::add_ips('');
return $this->handle_response(true, '');
}
$ips = sanitize_textarea_field(wp_unslash($allowlist));
$ips = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($ips);
$validated_ip_list_array = AIOWPSecurity_Utility_IP::validate_ip_list($ips, 'firewall_allowlist');
if (is_wp_error($validated_ip_list_array)) {
$success = false;
$message = nl2br($validated_ip_list_array->get_error_message());
} else {
$aiowps_firewall_allow_list::add_ips($validated_ip_list_array);
}
return $this->handle_response($success, $message);
}
/**
* The AJAX function for saving PHP firewall and block and allowlists in UDC.
*
* @param array $data The data send from UDC.
*
* @return array|WP_Error
*/
public function perform_save_firewall($data) {
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
return new WP_Error(esc_html__('Sorry, you do not have enough privilege to execute the requested action.', 'all-in-one-wp-security-and-firewall'));
}
$response = $this->perform_firewall_allowlist($data);
if ('error' === $response['status']) {
return $response;
}
$response = $this->perform_save_blacklist_settings($data);
if ('error' === $response['status']) {
return $response;
}
return $this->perform_php_firewall_settings($data);
}
/**
* Perform the setup process for the firewall.
*
* This function handles the setup form for the firewall and renders notices accordingly.
*
* @return array An array containing the content and message for the response.
*/
public function perform_setup_firewall() {
global $aio_wp_security;
$firewall_setup = AIOWPSecurity_Firewall_Setup_Notice::get_instance();
$content = array('aiowps-firewall-status-container' => $aio_wp_security->include_template('wp-admin/firewall/partials/firewall-set-up-button.php', true));
$firewall_setup->do_setup();
ob_start();
$firewall_setup->render_notices();
$result = ob_get_clean();
$args = array(
'content' => $content,
'extra_args' => array('info_box' => $result)
);
$message = false;
if (AIOWPSecurity_Utility_Firewall::is_firewall_setup()) {
$content['aiowps-firewall-status-container'] = $aio_wp_security->include_template('wp-admin/firewall/partials/firewall-downgrade-button.php', true);
$message = __('Firewall has been setup successfully.', 'all-in-one-wp-security-and-firewall');
$args['content'] = $content;
}
return $this->handle_response(AIOWPSecurity_Utility_Firewall::is_firewall_setup(), $message, $args);
}
/**
* Perform the downgrade process for the firewall.
*
* This function removes the firewall and returns a response indicating success.
*
* @return array An array containing the status, content, and message for the response.
*/
public function perform_downgrade_firewall() {
global $aio_wp_security;
AIOWPSecurity_Utility_Firewall::remove_firewall();
$message = AIOWPSecurity_Utility_Firewall::is_firewall_setup() ? __('Something went wrong please try again later.', 'all-in-one-wp-security-and-firewall') : __('Firewall has been downgraded successfully.', 'all-in-one-wp-security-and-firewall');
$success = true;
$downgrade_button = $aio_wp_security->include_template('wp-admin/firewall/partials/firewall-set-up-button.php', true);
$extra_args = array();
if (AIOWPSecurity_Utility_Firewall::is_firewall_setup()) {
$success = false;
$downgrade_button = $aio_wp_security->include_template('wp-admin/firewall/partials/firewall-downgrade-button.php', true);
} else {
$extra_args['info_box'] = $aio_wp_security->include_template('notices/firewall-setup-notice.php', true, array('show_dismiss' => false));
}
$args = array(
'content' => array('aiowps-firewall-status-container' => $downgrade_button),
'extra_args' => $extra_args
);
return $this->handle_response($success, $message, $args);
}
/**
* Validates posted user agent list and set, save as config.
*
* @param string $banned_user_agents - List of banned user agents
*
* @return void
*/
private function validate_user_agent_list($banned_user_agents) {
$aiowps_firewall_config = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONFIG);
$submitted_agents = AIOWPSecurity_Utility::splitby_newline_trim_filter_empty($banned_user_agents);
$agents = array_unique(
array_filter(
array_map(
'sanitize_text_field',
$submitted_agents
),
'strlen'
)
);
$aiowps_firewall_config->set_value('aiowps_blacklist_user_agents', $agents);
$this->save_settings(array(
'aiowps_banned_user_agents' => implode("\n", $agents)
));
}
/**
* This function performs save upgrade unsafe http calls settings.
*
* @param array $data - The request data.
*
* @return array
*/
public function perform_save_upgrade_unsafe_http_calls_settings($data) {
$upgrade_unsafe_http_calls_url_exceptions = sanitize_textarea_field(wp_unslash($data['aiowps_upgrade_unsafe_http_calls_url_exceptions']));
$errors = '';
if (!empty($upgrade_unsafe_http_calls_url_exceptions)) {
foreach (preg_split('/\R/', $upgrade_unsafe_http_calls_url_exceptions) as $url) {
$url = sanitize_url($url);
if (empty($url)) {
continue;
}
if (0 === strpos($url, '#')) {
continue;
}
$parsed_url = parse_url($url); // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Using the same function as WordPress in order to not preclude URLs that would be allowed by WordPress.
if (empty($parsed_url['scheme'])) { // The same weak sanity check used by the WordPress wp_remote_* functions.
/* translators: %s URL entered by user. */
$errors .= "\n" . sprintf(__('%s is not a valid url.', 'all-in-one-wp-security-and-firewall'), $url);
continue;
}
}
}
if (!empty($errors)) {
return $this->handle_response(false, nl2br(trim($errors)), array('badges' => array('upgrade-unsafe-http-calls')));
}
$this->save_settings(array(
'aiowps_upgrade_unsafe_http_calls' => isset($data['aiowps_upgrade_unsafe_http_calls']) ? '1' : '',
'aiowps_upgrade_unsafe_http_calls_url_exceptions' => $upgrade_unsafe_http_calls_url_exceptions
));
return $this->handle_response(true, '', array('badges' => array('upgrade-unsafe-http-calls')));
}
/**
* Render the PHP firewall rules for the legacy UDC theme.
*
* @return array
*/
public function get_php_firewall_contents() {
global $aio_wp_security;
$GLOBALS['aiowps_feature_mgr'] = $this->get_feature_mgr_object();
$php_firewall_data = $this->get_php_firewall_data();
$content = $aio_wp_security->include_template('/wp-admin/firewall/php-firewall-rules.php', true, compact('php_firewall_data'));
return array(
'status' => 'success',
'content' => $php_firewall_data['no_firewall'] . $content,
);
}
/**
* Render the .htaccess firewall rules for the legacy UDC theme.
*
* @return array
*/
public function get_htaccess_contents() {
global $aio_wp_security;
$GLOBALS['aiowps_feature_mgr'] = $this->get_feature_mgr_object();
$htaccess_rules_data = $this->get_htaccess_rules_data();
$content = $aio_wp_security->include_template('/wp-admin/firewall/htaccess-firewall-rules.php', true, compact('htaccess_rules_data'));
return array(
'status' => 'success',
'content' => $content,
);
}
/**
* Render the Block & Allow Lists for the legacy UDC theme.
*
* @return array
*/
public function get_block_allow_lists_contents() {
global $aio_wp_security;
/* Needed for submit_button() */
require_once(ABSPATH . 'wp-admin/includes/template.php');
$GLOBALS['aiowps_feature_mgr'] = $this->get_feature_mgr_object();
$block_allowlist_data = $this->get_block_allow_lists_data();
$content = $aio_wp_security->include_template('wp-admin/firewall/block-and-allow-lists.php', true, $block_allowlist_data);
return array(
'status' => 'success',
'content' => $content,
);
}
/**
* Render the Advanced Settings for the legacy UDC theme.
*
* @return array
*/
public function get_advanced_settings_contents() {
global $aio_wp_security;
$GLOBALS['aiowps_feature_mgr'] = $this->get_feature_mgr_object();
$advanced_settings_data = $this->get_firewall_advanced_settings_data();
$content = $aio_wp_security->include_template('wp-admin/firewall/advanced-settings.php', true, compact('advanced_settings_data'));
return array(
'status' => 'success',
'content' => $content,
);
}
/**
* Return data for the advanced firewall.
*
* @return array
*/
public function get_firewall_advanced_settings_data() {
global $aio_wp_security;
$aiowps_upgrade_unsafe_http_calls = $aio_wp_security->configs->get_value('aiowps_upgrade_unsafe_http_calls');
$aiowps_upgrade_unsafe_http_calls_url_exceptions = $aio_wp_security->configs->get_value('aiowps_upgrade_unsafe_http_calls_url_exceptions');
return array(
'aiowps_upgrade_unsafe_http_calls' => $aiowps_upgrade_unsafe_http_calls,
'aiowps_upgrade_unsafe_http_calls_url_exceptions' => $aiowps_upgrade_unsafe_http_calls_url_exceptions,
);
}
/**
* Return data for the allow & block lists.
*
* @return array
*/
public function get_block_allow_lists_data() {
global $aio_wp_security;
$aiowps_enable_blacklisting = $aio_wp_security->configs->get_value('aiowps_enable_blacklisting');
$aiowps_banned_ip_addresses = $aio_wp_security->configs->get_value('aiowps_banned_ip_addresses');
$aiowps_banned_user_agents = $aio_wp_security->configs->get_value('aiowps_banned_user_agents');
$aiowps_firewall_allow_list = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::ALLOW_LIST);
$allowlist = $aiowps_firewall_allow_list::get_ips();
return array(
'aiowps_enable_blacklisting' => $aiowps_enable_blacklisting,
'aiowps_banned_ip_addresses' => $aiowps_banned_ip_addresses,
'aiowps_banned_user_agents' => $aiowps_banned_user_agents,
'allowlist' => $allowlist,
);
}
/**
* Return data for the .htaccess rules.
*
* @return array
*/
public function get_htaccess_rules_data() {
global $aio_wp_security;
$aiowps_enable_basic_firewall = $aio_wp_security->configs->get_value('aiowps_enable_basic_firewall');
$aiowps_max_file_upload_size = $aio_wp_security->configs->get_value('aiowps_max_file_upload_size');
$aiowps_block_debug_log_file_access = $aio_wp_security->configs->get_value('aiowps_block_debug_log_file_access');
$aiowps_disable_index_views = $aio_wp_security->configs->get_value('aiowps_disable_index_views');
return array(
'aiowps_enable_basic_firewall' => $aiowps_enable_basic_firewall,
'aiowps_max_file_upload_size' => $aiowps_max_file_upload_size,
'aiowps_block_debug_log_file_access' => $aiowps_block_debug_log_file_access,
'aiowps_disable_index_views' => $aiowps_disable_index_views,
);
}
/**
* Return data for the PHP firewall.
*
* @return array
*/
public function get_php_firewall_data() {
global $aio_wp_security, $aiowps_firewall_config, $aiowps_feature_mgr;
$is_udc_request = AIOS_Helper::is_updraft_central_request();
$block_request_methods = array_map('strtolower', AIOS_Abstracted_Ids::get_firewall_block_request_methods());
$no_firewall_notice = '';
$user_roles = array();
// Load required data from config
if (!empty($aiowps_firewall_config)) {
// firewall config is available
$methods = $aiowps_firewall_config->get_value('aiowps_6g_block_request_methods');
if (empty($methods)) {
$methods = array();
}
$blocked_query = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_query');
$blocked_request = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_request');
$blocked_referrers = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_referrers');
$blocked_agents = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_agents');
if (empty($methods) && (!$blocked_query && !$blocked_request && !$blocked_referrers && !$blocked_agents) && '1' == $aio_wp_security->configs->get_value('aiowps_enable_6g_firewall')) {
$aio_wp_security->configs->set_value('aiowps_enable_6g_firewall', '');
$aio_wp_security->configs->save_config();
$aiowps_feature_mgr->check_feature_status_and_recalculate_points();
}
} else {
if ($is_udc_request) {
ob_start();
}
?>
<div class="notice notice-error">
<p><strong><?php esc_html_e('All-In-One Security', 'all-in-one-wp-security-and-firewall'); ?></strong></p>
<p><?php esc_html_e('We were unable to access the firewall\'s configuration file:', 'all-in-one-wp-security-and-firewall');?></p>
<pre style="max-width: 100%;background-color: #f0f0f0;border: #ccc solid 1px;padding: 10px;white-space: pre-wrap;"><?php echo esc_html(AIOWPSecurity_Utility_Firewall::get_firewall_rules_path() . 'settings.php'); ?></pre>
<p><?php esc_html_e('As a result, the firewall will be unavailable.', 'all-in-one-wp-security-and-firewall');?></p>
<p><?php esc_html_e('Please check your PHP error log for further information.', 'all-in-one-wp-security-and-firewall');?></p>
<p><?php esc_html_e('If you\'re unable to locate your PHP log file, please contact your web hosting company to ask them where it can be found on their setup.', 'all-in-one-wp-security-and-firewall');?></p>
</div>
<?php
if ($is_udc_request) {
$no_firewall_notice .= ob_get_clean();
}
//set default variables
$methods = array();
$blocked_query = false;
$blocked_request = false;
$blocked_referrers = false;
$blocked_agents = false;
}
$aiowps_enable_6g_firewall = $aio_wp_security->configs->get_value('aiowps_enable_6g_firewall');
$advanced_options_disabled = '1' != $aiowps_enable_6g_firewall;
$settings = array_merge(array('methods' => $methods), compact('aiowps_enable_6g_firewall', 'blocked_query', 'blocked_request', 'blocked_referrers', 'blocked_agents', 'block_request_methods', 'aiowps_firewall_config', 'advanced_options_disabled'));
$aiowps_enable_pingback_firewall = $aiowps_firewall_config->get_value('aiowps_enable_pingback_firewall');
$aiowps_disable_xmlrpc_pingback_methods = $aio_wp_security->configs->get_value('aiowps_disable_xmlrpc_pingback_methods');
$aiowps_disable_rss_and_atom_feeds = $aio_wp_security->configs->get_value('aiowps_disable_rss_and_atom_feeds');
$aiowps_forbid_proxy_comments = $aiowps_firewall_config->get_value('aiowps_forbid_proxy_comments');
$aiowps_deny_bad_query_strings = $aiowps_firewall_config->get_value('aiowps_deny_bad_query_strings');
$aiowps_advanced_char_string_filter = $aiowps_firewall_config->get_value('aiowps_advanced_char_string_filter');
$aiowps_disallow_unauthorized_rest_requests = $aio_wp_security->configs->get_value('aiowps_disallow_unauthorized_rest_requests');
$aios_roles_disallowed_rest_requests = $aio_wp_security->configs->get_value('aios_roles_disallowed_rest_requests');
$aios_whitelisted_rest_routes = $aio_wp_security->configs->get_value('aios_whitelisted_rest_routes');
$aiowps_block_fake_googlebots = $aiowps_firewall_config->get_value('aiowps_block_fake_googlebots');
$aiowps_ban_post_blank_headers = $aiowps_firewall_config->get_value('aiowps_ban_post_blank_headers');
$wp_user_roles = AIOWPSecurity_Utility_Permissions::get_user_roles();
foreach ($wp_user_roles as $role => $role_name) {
$user_roles[] = $role;
}
return array(
'aiowps_enable_pingback_firewall' => $aiowps_enable_pingback_firewall,
'aiowps_disable_xmlrpc_pingback_methods' => $aiowps_disable_xmlrpc_pingback_methods,
'aiowps_disable_rss_and_atom_feeds' => $aiowps_disable_rss_and_atom_feeds,
'aiowps_forbid_proxy_comments' => $aiowps_forbid_proxy_comments,
'aiowps_deny_bad_query_strings' => $aiowps_deny_bad_query_strings,
'aiowps_advanced_char_string_filter' => $aiowps_advanced_char_string_filter,
'aiowps_disallow_unauthorized_rest_requests' => $aiowps_disallow_unauthorized_rest_requests,
'aios_roles_disallowed_rest_requests' => $aios_roles_disallowed_rest_requests,
'aios_whitelisted_rest_routes' => $aios_whitelisted_rest_routes,
'user_roles' => $user_roles,
'aiowps_block_fake_googlebots' => $aiowps_block_fake_googlebots,
'aiowps_ban_post_blank_headers' => $aiowps_ban_post_blank_headers,
'ng_settings' => $settings,
'no_firewall' => $no_firewall_notice,
);
}
}
@@ -0,0 +1,133 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
if (trait_exists('AIOWPSecurity_Ip_Commands_Trait')) return;
trait AIOWPSecurity_Ip_Commands_Trait {
/**
* Unlocks an IP.
*
* @param array $data Contains the IP address to be unlocked.
*
* @return array
*/
public function unlock_ip($data) {
if (!isset($data['ip'])) {
return $this->handle_response(false, __('No IP provided.', 'all-in-one-wp-security-and-firewall'));
}
if (!filter_var($data['ip'], FILTER_VALIDATE_IP)) {
return $this->handle_response(false, __('Invalid IP provided.', 'all-in-one-wp-security-and-firewall'));
}
if (!AIOWPSecurity_Utility::unlock_ip($data['ip'])) {
return $this->handle_response(false, __('Failed to unlock the selected IP address.', 'all-in-one-wp-security-and-firewall'));
} else {
return $this->handle_response(true, __('The selected IP address was unlocked successfully.', 'all-in-one-wp-security-and-firewall'));
}
}
/**
* Unblacklists an IP.
*
* @param array $data Contains the IP address to be unblacklisted.
*
* @return array
*/
public function unblacklist_ip($data) {
if (!isset($data['ip'])) {
return $this->handle_response(false, __('No IP provided.', 'all-in-one-wp-security-and-firewall'));
}
if (!filter_var($data['ip'], FILTER_VALIDATE_IP)) {
return $this->handle_response(false, __('Invalid IP provided.', 'all-in-one-wp-security-and-firewall'));
}
if (!AIOWPSecurity_Utility::unblacklist_ip($data['ip'])) {
return $this->handle_response(false, __('Failed to unblacklist the selected IP address.', 'all-in-one-wp-security-and-firewall'));
} else {
return $this->handle_response(true, __('The selected IP address was unblacklisted successfully.', 'all-in-one-wp-security-and-firewall'));
}
}
/**
* Unblocks an IP by permanent block record ID.
*
* @param array $data Contains the ID of the entry in the AIOWPSEC_TBL_PERM_BLOCK table.
*
* @return array
*/
public function blocked_ip_list_unblock_ip($data) {
if (!isset($data['id'])) {
return $this->handle_response(false, __('Invalid blocked IP ID provided.', 'all-in-one-wp-security-and-firewall'));
}
include_once AIO_WP_SECURITY_PATH . '/admin/wp-security-list-permanent-blocked-ip.php'; // For rendering the AIOWPSecurity_List_Table
$blocked_ip_list = new AIOWPSecurity_List_Blocked_IP(); // For rendering the AIOWPSecurity_List_Table
$result = $blocked_ip_list->unblock_ip_address($data['id']);
if (false === $result) {
$message = __('Failed to unblock and delete the selected record(s).', 'all-in-one-wp-security-and-firewall');
} else {
$message = __('Successfully unblocked and deleted the selected record(s).', 'all-in-one-wp-security-and-firewall');
}
return $this->handle_response(true, $message);
}
/**
* Locks an IP.
*
* @param array $data Contains the IP address to be locked.
*
* @return array
*/
public function lock_ip($data) {
if (!isset($data['ip'])) {
return $this->handle_response(false, __('No IP provided.', 'all-in-one-wp-security-and-firewall'));
}
if (!filter_var($data['ip'], FILTER_VALIDATE_IP)) {
return $this->handle_response(false, __('Invalid IP provided.', 'all-in-one-wp-security-and-firewall'));
}
if (!isset($data['lock_reason'])) {
return $this->handle_response(false, __('No lockout reason provided.', 'all-in-one-wp-security-and-firewall'));
}
AIOWPSecurity_Utility::lock_ip($data['ip'], $data['lock_reason']);
return $this->handle_response(true, __('The selected IP address is now temporarily locked.', 'all-in-one-wp-security-and-firewall'));
}
/**
* Blacklists an IP.
*
* @param array $data Contains the IP address to be blacklisted.
*
* @return array
*/
public function blacklist_ip($data) {
if (!isset($data['ip'])) {
return $this->handle_response(false, __('No IP provided.', 'all-in-one-wp-security-and-firewall'));
}
if (!filter_var($data['ip'], FILTER_VALIDATE_IP)) {
return $this->handle_response(false, __('Invalid IP provided.', 'all-in-one-wp-security-and-firewall'));
}
$result = AIOWPSecurity_Utility::blacklist_ip($data['ip']);
if (is_wp_error($result)) {
return $this->handle_response(false, nl2br($result->get_error_message()));
} else {
return $this->handle_response(true, __('The selected IP address has been added to the blacklist.', 'all-in-one-wp-security-and-firewall'));
}
}
}
@@ -0,0 +1,345 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
if (trait_exists('AIOWPSecurity_Log_Commands_Trait')) return;
trait AIOWPSecurity_Log_Commands_Trait {
/**
* Deletes an audit log.
*
* @param array $data Contains the ID of the log to be deleted.
*
* @return array
*/
public function delete_audit_log($data) {
if (!isset($data['id'])) {
return $this->handle_response(false, AIOWPSecurity_Admin_Menu::show_msg_error_st(__('No audit log ID provided.', 'all-in-one-wp-security-and-firewall'), true));
}
include_once AIO_WP_SECURITY_PATH.'/admin/wp-security-list-audit.php';
$audit_log_list = new AIOWPSecurity_List_Audit_Log();
return $this->handle_response(true, $audit_log_list->delete_audit_event_records($data['id']));
}
/**
* Deletes an IP lockout record.
*
* @param array $data Contains the ID of the entry in the AIOWPSEC_TBL_LOGIN_LOCKOUT table.
*
* @return array
*/
public function delete_locked_ip_record($data) {
if (!isset($data['id'])) {
return $this->handle_response(false, AIOWPSecurity_Admin_Menu::show_msg_error_st(__('No locked IP record ID provided.', 'all-in-one-wp-security-and-firewall'), true));
}
include_once AIO_WP_SECURITY_PATH . '/admin/wp-security-list-locked-ip.php';
$locked_ip_list = new AIOWPSecurity_List_Locked_IP();
$result = $locked_ip_list->delete_lockout_records($data['id']);
return $this->handle_response(true, $result);
}
/**
* Clear debug logs
*
* @return array
*/
public function clear_debug_logs() {
global $aio_wp_security;
$ret = $aio_wp_security->debug_logger->clear_logs();
if (is_wp_error($ret)) {
return $this->handle_response(false, AIOWPSecurity_Admin_Menu::show_msg_error_st(esc_html($ret->get_error_message()).'<p>'.esc_html($ret->get_error_data()).'</p>', true));
} else {
return $this->handle_response(true, AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The debug logs have been cleared.', 'all-in-one-wp-security-and-firewall'), true));
}
}
/**
* Renders the audit log tab content.
*
* This function handles the rendering of the audit log tab content based on the
* provided data via AJAX request. The data is used to filter the audit log or perform actions
*
* @access public
* @return void
*/
public function render_audit_log_tab() {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- PCP warning. Nonce checked in previous function.
if (empty($_POST['data'])) return;
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput -- PCP warning. Nonce checked in previous function and sanitization done at later.
$data = wp_unslash($_POST['data']);
// Needed for rendering the audit log table
include_once(AIO_WP_SECURITY_PATH.'/admin/wp-security-list-audit.php');
$audit_log_list = new AIOWPSecurity_List_Audit_Log($data);
$audit_log_list->ajax_response();
}
/**
* Exports the audit logs as a CSV file and sends the data as an AJAX response.
*
* This function retrieves audit log data, prepares it for export, and generates a CSV string.
* The CSV data is then sent back as part of an AJAX response, along with the filename for the CSV file.
*
* @return array
*/
public function export_audit_logs() {
// Needed for rendering the audit log table
include_once(AIO_WP_SECURITY_PATH.'/admin/wp-security-list-audit.php');
$audit_log_list = new AIOWPSecurity_List_Audit_Log();
$audit_log_list->prepare_items(true);
$export_keys = array(
'id' => 'ID',
'created' => __('Date and time', 'all-in-one-wp-security-and-firewall'),
'level' => __('Level', 'all-in-one-wp-security-and-firewall'),
'network_id' => __('Network ID', 'all-in-one-wp-security-and-firewall'),
'site_id' => __('Site ID', 'all-in-one-wp-security-and-firewall'),
'username' => __('Username', 'all-in-one-wp-security-and-firewall'),
'ip' => __('IP', 'all-in-one-wp-security-and-firewall'),
'event_type' => __('Event', 'all-in-one-wp-security-and-firewall'),
'details' => __('Details', 'all-in-one-wp-security-and-firewall'),
'stacktrace' => __('Stack trace', 'all-in-one-wp-security-and-firewall')
);
$title = 'audit_event_logs.csv';
ob_start();
AIOWPSecurity_Admin_Init::aiowps_output_csv($audit_log_list->items, $export_keys, $title);
$data = ob_get_clean();
return array(
'title' => $title,
'data' => $data
);
}
/**
* Initializing the WP List API, since UDC commands do not load all parts of WP.
*
* @return void
*/
private function init_wp_list() {
if (!function_exists('submit_button')) {
require_once(ABSPATH . 'wp-admin/includes/template.php');
}
if (!function_exists('render_screen_reader_content')) {
require_once(ABSPATH . 'wp-admin/includes/class-wp-screen.php');
}
if (!function_exists('get_column_headers')) {
require_once(ABSPATH . 'wp-admin/includes/screen.php');
}
}
/**
* Returns the data for downloading the audit log.
*
* @return array|WP_Error
*/
public function process_audit_log_export() {
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
return new WP_Error(esc_html__('Sorry, you do not have enough privilege to execute the requested action.', 'all-in-one-wp-security-and-firewall'));
}
$this->init_wp_list();
return $this->export_audit_logs();
}
/**
* Returns the HTML for the audit log.
*
* @return array
*/
public function get_audit_log_contents() {
global $aio_wp_security;
$this->init_wp_list();
// Needed for rendering the audit log table
include_once AIO_WP_SECURITY_PATH . '/admin/wp-security-list-audit.php';
$data = array();
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- PCP warning. Processing form data without nonce verification. No nonce.
if (isset($_GET['event-filter'])) $data['event-filter'] = sanitize_text_field(wp_unslash($_GET['event-filter'])); // Failed logins and logins only to show as audit log
$audit_log_list = new AIOWPSecurity_List_Audit_Log($data);
$tab = isset($_GET["tab"]) ? sanitize_text_field(wp_unslash($_GET["tab"])) : '';
$page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
// phpcs:enable WordPress.Security.NonceVerification.Recommended -- PCP warning. Processing form data without nonce verification. No nonce.
$content = $aio_wp_security->include_template('wp-admin/dashboard/audit-logs.php', true, array('audit_log_list' => $audit_log_list, 'page' => $page, 'tab' => $tab));
return array(
'status' => 'success',
'content' => $content,
);
}
/**
* Deletes entry from audit log.
*
* @param array $data Table config data.
*
* @return array|WP_Error
*/
public function do_delete_audit_log($data) {
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
return new WP_Error(esc_html__('Sorry, you do not have enough privilege to execute the requested action.', 'all-in-one-wp-security-and-firewall'));
}
$this->init_wp_list();
if (!class_exists('AIOWPSecurity_Admin_Menu')) {
include_once AIO_WP_SECURITY_PATH . '/admin/wp-security-admin-menu.php';
}
return $this->delete_audit_log($data);
}
/**
* Renders audit log after actions (delete/orderby, block/unblock, etc.)
*
* @param array $data Table config data.
*
* @return array
*/
public function do_render_audit_log_tab($data) {
$this->init_wp_list();
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- PCP warning. Nonce checked in previous function.
if (empty($data)) return array();
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput -- PCP warning. Nonce checked in previous function and sanitization done at later.
$data = wp_unslash($data);
if (!class_exists('AIOWPSecurity_Admin_Menu')) {
include_once AIO_WP_SECURITY_PATH . '/admin/wp-security-admin-menu.php';
}
// Needed for rendering the audit log table
include_once(AIO_WP_SECURITY_PATH.'/admin/wp-security-list-audit.php');
$audit_log_list = new AIOWPSecurity_List_Audit_Log($data);
return $audit_log_list->ajax_response(true);
}
/**
* Parses raw audit log data for human-readable output.
*
* @param AIOWPSecurity_List_Audit_Log $audit_log_list Audit log object.
* @param array $data Raw audit log data.
*
* @return array
*/
private function parse_audit_log_data($audit_log_list, $data) {
$items = array();
foreach ($data as $db_item) {
if (empty($db_item)) {
continue;
}
$item = array();
foreach ($db_item as $key => $value) {
switch ($key) {
case 'created':
$item[$key] = AIOWPSecurity_Utility::convert_timestamp($value);
break;
case 'event_type':
$item[$key] = $audit_log_list->column_event_type($db_item);
break;
case 'details':
$item[$key] = $audit_log_list->column_details($db_item);
break;
case 'stacktrace':
$item[$key] = $audit_log_list->column_stacktrace($db_item);
break;
default:
$item[$key] = $value;
break;
}
}
$items[] = $item;
}
return $items;
}
/**
* Returns the data for the audit log table.
*
* @param array $data Configuration data.
*
* @return array
*/
public function get_audit_log_data($data) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput -- PCP warning. Nonce checked in previous function and sanitization done at later.
$data = isset($data) ? wp_unslash($data) : array();
$data = isset($data['data']) ? $data['data'] : $data;
$this->init_wp_list();
$final_column = array();
// Needed for rendering the audit log table
include_once(AIO_WP_SECURITY_PATH.'/admin/wp-security-list-audit.php');
$audit_log_list = new AIOWPSecurity_List_Audit_Log($data);
$audit_log_list->prepare_items();
list($columns, $hidden) = $audit_log_list->get_column_info();
foreach ($columns as $column_key => $column_display_name) {
if ('cb' !== $column_key) {
if (!in_array($column_key, $hidden, true)) {
$final_column[$column_key] = array('label' => $column_display_name);
}
}
}
$audit_log_items = isset($audit_log_list->items) ? $audit_log_list->items : array();
foreach ($audit_log_items as $key => $item) {
$ip = isset($item['ip']) ? $item['ip'] : '';
if ('' !== $ip) {
$audit_log_items[$key]['is_ip_locked'] = AIOWPSecurity_Utility::check_locked_ip($ip, 'audit-log');
$audit_log_items[$key]['is_ip_blacklisted'] = AIOWPSecurity_Utility::check_blacklist_ip($ip);
}
}
$items = $this->parse_audit_log_data($audit_log_list, $audit_log_items);
$bulk_actions = $audit_log_list->get_bulk_actions();
$paged = !empty($data['paged']) ? (int) $data['paged'] : 1;
AIOWPSecurity_Audit_Events::setup_event_types();
return array(
'audit_log_data' => array(
'bulk_actions' => $bulk_actions,
'event_types' => AIOWPSecurity_Audit_Events::$event_types,
'log_levels' => AIOWPSecurity_Audit_Events::$log_levels,
'columns' => $final_column,
'items' => $items,
'is_multisite' => is_multisite(),
'pagination' => array('page' => $paged, 'pages' => $audit_log_list->get_pagination_arg('total_pages'), 'results' => $audit_log_list->get_pagination_arg('total_items')),
),
);
}
}
@@ -0,0 +1,562 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
if (trait_exists('AIOWPSecurity_Settings_Commands_Trait')) return;
trait AIOWPSecurity_Settings_Commands_Trait {
/**
* Performs the action to disable all security features.
*
* This method disables all security features provided by the AIOWPSecurity_Settings_Tasks class.
*
* @return array An associative array containing the status and messages of the operation.
* - 'status' : (string) The status of the operation, either 'success' or 'error'.
* - 'messages' : (array) An array of messages generated during the operation.
* Each message is represented as a string.
*/
public function perform_disable_all_features() {
$msg = AIOWPSecurity_Settings_Tasks::disable_all_security_features();
$success = true;
$info = array();
$message = '';
if (isset($msg['updated'])) {
$message = $msg['updated'];
}
if (isset($msg['error'])) {
$message = __('Some of the security features could not be disabled.', 'all-in-one-wp-security-and-firewall');
$success = false;
foreach ($msg['error'] as $error_message) {
$info[] = $error_message;
}
}
return $this->handle_response($success, $message, array('info' => $info));
}
/**
* Performs the action to disable all firewall rules.
*
* This method disables all firewall rules provided by the AIOWPSecurity_Settings_Tasks class.
*
* @return array An associative array containing the status and message of the operation.
* - 'status' : (string) The status of the operation, either 'success' or 'error'.
* - 'message' : (string) A message indicating the outcome of the operation.
* If the operation is successful, it contains an update message.
* If there is an error, it contains an error message.
*/
public function perform_disable_all_firewall_rules() {
$msg = AIOWPSecurity_Settings_Tasks::disable_all_firewall_rules();
$success = true;
$message = '';
if (isset($msg['updated'])) {
$message = $msg['updated'];
} elseif (isset($msg['error'])) {
$message = $msg['error'];
$success = false;
}
return $this->handle_response($success, $message);
}
/**
* Performs the action to reset all settings.
*
* This method resets all settings provided by the AIOWPSecurity_Settings_Tasks class to their default values.
*
* @return array An associative array containing the status and message of the operation.
* - 'status' : (string) The status of the operation, either 'success' or 'error'.
* - 'message' : (string) A message indicating the outcome of the operation.
* If the operation is successful, it contains an update message.
* If there is an error, it contains an error message.
*/
public function perform_reset_all_settings() {
$msg = AIOWPSecurity_Settings_Tasks::reset_all_settings();
$success = true;
$message = '';
if (isset($msg['updated'])) {
$message = $msg['updated'];
} elseif (isset($msg['error'])) {
$message = $msg['error'];
$success = false;
}
return $this->handle_response($success, $message);
}
/**
* Performs the action to save debug settings.
*
* This method updates the debug settings in the AIOWPSecurity_Configs instance based on the provided data.
*
* @param array $data An associative array containing the data to update the debug settings.
* - 'aiowps_enable_debug': (bool) Indicates whether debug mode should be enabled.
* @return array An associative array containing the status and message of the operation.
* - 'status' : (string) The status of the operation, which is always 'success'.
* - 'message' : (string) A message indicating that the settings have been updated successfully.
*/
public function perform_save_debug_settings($data) {
global $aio_wp_security;
$aio_wp_security->configs->set_value('aiowps_enable_debug', '1' === $data["aiowps_enable_debug"] ? '1' : '', true);
return $this->handle_response(true);
}
/**
* Performs the action to backup the .htaccess file.
*
* This method creates a backup of the .htaccess file and renames it with a random prefix.
* It also provides a message indicating the outcome of the backup operation.
*
* @global object $aio_wp_security The global instance of the All-in-One WP Security & Firewall plugin.
* @return array An associative array containing the status and message of the backup operation.
* - 'status' : (string) The status of the operation, which can be 'success' or 'error'.
* - 'message' : (string) A message indicating the outcome of the backup operation.
*/
public function perform_backup_htaccess_file() {
global $aio_wp_security;
$home_path = AIOWPSecurity_Utility_File::get_home_path();
$htaccess_path = $home_path . '.htaccess';
$result = AIOWPSecurity_Utility_File::backup_and_rename_htaccess($htaccess_path); //Backup the htaccess file
$extra_args = array();
if ($result) {
$aiowps_backup_dir = WP_CONTENT_DIR.'/'.AIO_WP_SECURITY_BACKUPS_DIR_NAME;
$success = true;
$message = __('Your .htaccess file was successfully backed up.', 'all-in-one-wp-security-and-firewall');
$extra_args['data'] = file_get_contents($aiowps_backup_dir.'/'. $result .'.txt');
$extra_args['title'] = $result;
} else {
$aio_wp_security->debug_logger->log_debug("htaccess - Backup operation failed!", 4);
$success = false;
$message = __('htaccess backup failed.', 'all-in-one-wp-security-and-firewall');
}
return $this->handle_response($success, $message, array('extra_args' => $extra_args));
}
/**
* Performs the action to restore the .htaccess file.
*
* This method restores the .htaccess file using the provided data, which includes the contents of the .htaccess file.
* It also verifies that the file chosen has valid contents relevant to the .htaccess file.
*
* @global object $aio_wp_security The global instance of the All-in-One WP Security & Firewall plugin.
* @param array $data An associative array containing the data needed to restore the .htaccess file.
* - 'aiowps_htaccess_file' : (string) The name of the .htaccess file to restore from.
* - 'aiowps_htaccess_file_contents' : (string) The contents of the .htaccess file to restore.
* @return array An associative array containing the status and message of the restore operation.
* - 'status' : (string) The status of the operation, which can be 'success' or 'error'.
* - 'message' : (string) A message indicating the outcome of the restore operation.
*/
public function perform_restore_htaccess_file($data) {
global $aio_wp_security;
$success = true;
$home_path = AIOWPSecurity_Utility_File::get_home_path();
$htaccess_path = $home_path . '.htaccess';
if (empty($data['aiowps_htaccess_file']) && empty($data['aiowps_htaccess_file_contents'])) {
$message = __('Please choose a valid .htaccess to restore from.', 'all-in-one-wp-security-and-firewall');
$success = false;
} else {
$htaccess_file_contents = trim(stripslashes($data['aiowps_htaccess_file_contents']));
//Verify that file chosen has contents which are relevant to .htaccess file
$is_htaccess = AIOWPSecurity_Utility_Htaccess::check_if_htaccess_contents($htaccess_file_contents);
if (1 == $is_htaccess) {
if (!file_put_contents($htaccess_path, $htaccess_file_contents)) {
//Failed to make a backup copy
$aio_wp_security->debug_logger->log_debug("htaccess - Restore from .htaccess operation failed.", 4);
$message = __('The restoration of the .htaccess file failed; please attempt to restore the .htaccess file manually using FTP.', 'all-in-one-wp-security-and-firewall');
$success = false;
} else {
$message = __('Your .htaccess file has successfully been restored.', 'all-in-one-wp-security-and-firewall');
}
} else {
$aio_wp_security->debug_logger->log_debug("htaccess restore failed - Contents of restore file appear invalid.", 4);
$success = false;
$message = __('The restoration .htaccess file has failed, please check the contents of the file you are trying to restore from.', 'all-in-one-wp-security-and-firewall');
}
}
return $this->handle_response($success, $message);
}
/**
* Performs the action to restore the wp-config.php file.
*
* This method restores the wp-config.php file using the provided data, which includes the contents of the wp-config.php file.
* It also verifies that the file chosen is a valid wp-config.php file.
*
* @global object $aio_wp_security The global instance of the All-in-One WP Security & Firewall plugin.
* @param array $data An associative array containing the data needed to restore the wp-config.php file.
* - 'aiowps_wp_config_file' : (string) The name of the wp-config.php file to restore from.
* - 'aiowps_wp_config_file_contents' : (string) The contents of the wp-config.php file to restore.
* @return array An associative array containing the status and message of the restore operation.
* - 'status' : (string) The status of the operation, which can be 'success' or 'error'.
* - 'message' : (string) A message indicating the outcome of the restore operation.
*/
public function perform_restore_wp_config_file($data) {
global $aio_wp_security;
$success = true;
if (empty($data['aiowps_wp_config_file']) && empty($data['aiowps_wp_config_file_contents'])) {
$message = __('Please choose a wp-config.php file to restore from.', 'all-in-one-wp-security-and-firewall');
$success = false;
} else {
$wp_config_file_contents = trim(stripslashes($data['aiowps_wp_config_file_contents']));
//Verify that file chosen is a wp-config.file
$is_wp_config = AIOWPSecurity_Utility_File::check_if_wp_config_contents($wp_config_file_contents);
if ($is_wp_config) {
$active_root_wp_config = AIOWPSecurity_Utility_File::get_wp_config_file_path();
if (!file_put_contents($active_root_wp_config, $wp_config_file_contents)) {
//Failed to make a backup copy
$aio_wp_security->debug_logger->log_debug("wp-config.php - Restore from backed up wp-config operation failed.", 4);
$message = __('The restoration of the wp-config.php file failed, please attempt to restore this file manually using FTP.', 'all-in-one-wp-security-and-firewall');
$success = false;
} else {
$message =__('Your wp-config.php file has successfully been restored.', 'all-in-one-wp-security-and-firewall');
}
} else {
$aio_wp_security->debug_logger->log_debug("wp-config.php restore failed - Contents of restore file appear invalid.", 4);
$message = __('The restoration of the wp-config.php file failed, please check the contents of the file you are trying to restore from.', 'all-in-one-wp-security-and-firewall');
$success = false;
}
}
return $this->handle_response($success, $message);
}
/**
* Performs the action to delete plugin settings.
*
* This method deletes specific plugin settings based on the provided data.
*
* @param array $data An associative array containing the data needed to delete plugin settings.
* - 'aiowps_on_uninstall_delete_db_tables' : (string) Indicates whether to delete plugin database tables on uninstallation.
* - 'aiowps_on_uninstall_delete_configs' : (string) Indicates whether to delete plugin configuration settings on uninstallation.
* @return array An associative array containing the status and message of the delete operation.
* - 'status' : (string) The status of the operation, which can be 'success'.
* - 'message' : (string) A message indicating that the plugin settings have been successfully deleted.
*/
public function perform_delete_plugin_settings($data) {
$options = array();
//Save settings
$options['aiowps_on_uninstall_delete_db_tables'] = isset($data['aiowps_on_uninstall_delete_db_tables']) ? '1' : '';
$options['aiowps_on_uninstall_delete_configs'] = isset($data['aiowps_on_uninstall_delete_configs']) ? '1' : '';
$this->save_settings($options);
return $this->handle_response(true);
}
/**
* Performs the action to remove WordPress version information settings.
*
* This method sets the option to remove WordPress version information meta tags based on the provided data.
*
* @param array $data An associative array containing the data needed to configure the removal of WordPress version information.
* - 'aiowps_remove_wp_generator_meta_info' : (string) Indicates whether to remove WordPress version information meta tags.
* @return array An associative array containing the status, message, and additional badges related to the removal of WordPress version information.
* - 'status' : (string) The status of the operation, which can be 'success'.
* - 'message' : (string) A message indicating that the settings have been successfully updated.
* - 'badges' : (array) An array containing feature IDs and HTML for additional badges.
*/
public function perform_remove_wp_version_info_settings($data) {
global $aio_wp_security;
$aio_wp_security->configs->set_value('aiowps_remove_wp_generator_meta_info', '1' === $data["aiowps_remove_wp_generator_meta_info"] ? '1' : '', true);
return $this->handle_response(true, '', array('badges' => array('wp-generator-meta-tag')));
}
/**
* Performs the action to restore AIOWPS settings from an imported file.
*
* This method restores AIOWPS settings from the provided data representing an imported file.
*
* @param array $data An associative array containing the data needed to restore AIOWPS settings.
* - 'aiowps_import_settings_file' : (string) The name of the file containing the AIOWPS settings.
* - 'aiowps_import_settings_file_contents': (string) The contents of the file containing the AIOWPS settings.
* @return array An associative array containing the status and messages related to the restoration of AIOWPS settings.
* - 'status' : (string) The status of the operation, which can be 'success' or 'error'.
* - 'messages' : (array) An array of messages indicating the outcome of the restoration process.
* - 'redirect_url' : (string|null) The URL to redirect to after the restoration process, if applicable.
*/
public function perform_restore_aiowps_settings($data) {
global $aio_wp_security, $simba_two_factor_authentication;
$aiowps_firewall_config = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONFIG);
$success = true;
$info = array();
$extra_args = array();
$msg_updated = __('Your AIOS settings were successfully imported.', 'all-in-one-wp-security-and-firewall');
$msg_error = sprintf(__('Could not write to the %s file.', 'all-in-one-wp-security-and-firewall'), AIOWPSecurity_Utility_File::get_home_path().'.htaccess') . ' ' . __('Please check the file permissions.', 'all-in-one-wp-security-and-firewall');
if (empty($data['aiowps_import_settings_file']) && empty($data['aiowps_import_settings_file_contents'])) {
$success = false;
$message = __('Please choose a file to import your settings from.', 'all-in-one-wp-security-and-firewall');
} else {
// Let's get the uploaded import file contents
$import_file_contents = trim($data['aiowps_import_settings_file_contents']); // stripslashes not required wp_unslash applied already AIOWPSecurity_Ajax::set_data
// Verify that file chosen has valid AIOS settings contents
$aiowps_settings_file_contents = AIOWPSecurity_Utility_File::check_if_valid_aiowps_settings_content($import_file_contents);
if ($aiowps_settings_file_contents) {
$is_enabled_cookie_bruteforce_before_import = $aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention');
// Apply the settings
$settings_array = json_decode($import_file_contents, true);
if (array_key_exists('general', $settings_array)) {
$aiowps_settings_applied = update_option('aio_wp_security_configs', $settings_array['general']);
if (!$aiowps_settings_applied && get_option('aio_wp_security_configs') === $settings_array['general']) {
$aiowps_settings_applied = true;
}
if (is_main_site() && is_super_admin()) {
if (array_key_exists('tfa', $settings_array) && true == $simba_two_factor_authentication->is_tfa_integrated) {
$tfa_settings_applied = $simba_two_factor_authentication->set_configs($settings_array['tfa']);
if (!$tfa_settings_applied && $simba_two_factor_authentication->get_configs() !== $settings_array['tfa']) {
$aiowps_settings_applied = false;
}
}
if (array_key_exists('firewall', $settings_array)) {
$aiowps_settings_applied = $aiowps_settings_applied && $aiowps_firewall_config->set_contents($settings_array['firewall']);
}
}
} else {
$aiowps_settings_applied = update_option('aio_wp_security_configs', $settings_array);
if (!$aiowps_settings_applied && get_option('aio_wp_security_configs') === $settings_array) {
$aiowps_settings_applied = true;
}
}
if (!$aiowps_settings_applied) {
// Failed to import settings
$aio_wp_security->debug_logger->log_debug('Import AIOS settings operation failed.', 4);
$success = false;
$message = __('Import AIOS settings operation failed.', 'all-in-one-wp-security-and-firewall');
} else {
$aio_wp_security->configs->load_config(); // Refresh the configs global variable
//Just in case user submits partial config settings
//Run add_option_values to make sure any missing config items are at least set to default
AIOWPSecurity_Configure_Settings::add_option_values();
$res = true;
if (AIOWPSecurity_Utility::allow_to_write_to_htaccess()) $res = AIOWPSecurity_Utility_Htaccess::write_to_htaccess();
// Now let's refresh the .htaccess file with any modified rules if applicable
$is_enabled_cookie_bruteforce = $aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention');
if ($is_enabled_cookie_bruteforce_before_import != $is_enabled_cookie_bruteforce && 1 == $is_enabled_cookie_bruteforce) {
$url = 'admin.php?page='.AIOWPSEC_SETTINGS_MENU_SLUG."&tab=settings-file-operations&success=import_settings";
$url .= empty($aio_wp_security->configs->get_value('aiowps_brute_force_secret_word')) ? '' : '&'.$aio_wp_security->configs->get_value('aiowps_brute_force_secret_word').'=1';
$url .= $res ? '' : '&error=write_htaccess';
$extra_args['redirect_url'] = admin_url(sanitize_url($url));
}
$message = $msg_updated;
if (!$res) {
$info[] = $msg_error;
}
}
} else {
// Invalid settings file
$aio_wp_security->debug_logger->log_debug("The contents of your settings file are invalid.", 4);
$success = false;
$message = __('The contents of your settings file are invalid, please check the contents of the file you are trying to import settings from.', 'all-in-one-wp-security-and-firewall');
}
}
$args = array(
'info' => $info,
'extra_args' => $extra_args
);
return $this->handle_response($success, $message, $args);
}
/**
* Performs the action to save IP settings.
*
* This method saves the IP settings based on the provided data.
*
* @param array $data An associative array containing the data needed to save IP settings.
* - 'aiowps_ip_retrieve_method': (string) The ID of the IP retrieval method.
* @return array An associative array containing the status and message related to saving IP settings.
* - 'status': (string) The status of the operation, which can be 'success' or 'error'.
* - 'message': (string|null) A message indicating the outcome of saving IP settings, or null if no message is provided.
*/
public function perform_save_ip_settings($data) {
global $wpdb, $aio_wp_security;
$aiowps_firewall_config = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONFIG);
$ip_retrieve_method_id = sanitize_text_field($data["aiowps_ip_retrieve_method"]);
$message = false;
if (in_array($ip_retrieve_method_id, array_keys(AIOS_Abstracted_Ids::get_ip_retrieve_methods()))) {
$aio_wp_security->configs->set_value('aiowps_ip_retrieve_method', $ip_retrieve_method_id, true);
$aiowps_firewall_config->set_value('aios_ip_retrieve_method', $ip_retrieve_method_id);
$logged_in_users_table = AIOWPSEC_TBL_LOGGED_IN_USERS;
//Clear logged in list because it might be showing wrong addresses
if (AIOWPSecurity_Utility::is_multisite_install()) {
$current_blog_id = get_current_blog_id();
$wpdb->query($wpdb->prepare("DELETE FROM `{$logged_in_users_table}` WHERE site_id = %d", $current_blog_id));
}
$wpdb->query("DELETE FROM `{$logged_in_users_table}`");
$message = '';
}
return $this->handle_response(true, $message);
}
/**
* Perform saving the wp-config.php file.
*
* This method backs up the wp-config.php file and retrieves its content.
* It returns the status of the operation, the file content, and the backup title.
*
* @return array An array containing the status, file content, and backup title.
*/
public function perform_save_wp_config() {
$wp_config_path = AIOWPSecurity_Utility_File::get_wp_config_file_path();
AIOWPSecurity_Utility_File::backup_and_rename_wp_config($wp_config_path); // Backup the wp_config.php file
$title = "wp-config-backup.txt";
$file_content = file_get_contents($wp_config_path);
$extra_args = array(
'data' => $file_content,
'title' => $title
);
return $this->handle_response(true, false, array('extra_args' => $extra_args));
}
/**
* Perform exporting All-In-One Security settings.
*
* This method exports general settings, firewall settings, and two-factor authentication settings
* if applicable. It then returns the exported data in JSON format along with a title for the export.
*
* @return array An array containing the status, exported data in JSON format, and a title for the export.
*/
public function perform_export_aios_settings() {
global $simba_two_factor_authentication;
$aiowps_firewall_config = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONFIG);
$config_data = array();
$config_data['general'] = get_option('aio_wp_security_configs');
if (is_main_site() && is_super_admin()) {
$config_data['firewall'] = $aiowps_firewall_config->get_contents();
if (true == $simba_two_factor_authentication->is_tfa_integrated) {
$config_data['tfa'] = $simba_two_factor_authentication->get_configs();
}
}
$output = json_encode($config_data);
$extra_args = array(
'data' => $output,
'title' => 'aiowps_' . current_time('Y-m-d_H-i') . '.txt'
);
return $this->handle_response(true, false, array('extra_args' => $extra_args));
}
/**
* Render the Import/Export settings UI for legacy UDC.
*
* @return array
*/
public function get_import_export_contents() {
global $aio_wp_security;
$content = $aio_wp_security->include_template('wp-admin/settings/settings-file-operations.php', true, array());
return array(
'status' => 'success',
'content' => $content,
);
}
/**
* Render the reset settings UI for legacy UDC.
*
* @return array
*/
public function get_reset_contents() {
global $aio_wp_security;
$content = $aio_wp_security->include_template('wp-admin/settings/general-settings.php', true, array());
return array(
'status' => 'success',
'content' => $content,
);
}
/**
* Return ip address detection data for the advanced settings.
*
* @return array
*/
public function get_ip_address_detection_data() {
global $aio_wp_security;
$ip_retrieve_methods_postfixes = array(
'REMOTE_ADDR' => __('Default - if correct, then this is the best option', 'all-in-one-wp-security-and-firewall'),
'HTTP_CF_CONNECTING_IP' => __("Only use if you're using Cloudflare.", 'all-in-one-wp-security-and-firewall'),
);
$ip_retrieve_methods = array();
foreach (AIOS_Abstracted_Ids::get_ip_retrieve_methods() as $id => $ip_method) {
$ip_retrieve_methods[$id]['ip_method'] = $ip_method;
if (isset($_SERVER[$ip_method])) {
/* translators: %s: IP Method */
$ip_retrieve_methods[$id]['ip_method'] .= ' ' . sprintf(__('(current value: %s)', 'all-in-one-wp-security-and-firewall'), sanitize_text_field(wp_unslash($_SERVER[$ip_method])));
$ip_retrieve_methods[$id]['is_enabled'] = true;
} else {
$ip_retrieve_methods[$id]['ip_method'] .= ' (' . __('no value (i.e. empty) on your server', 'all-in-one-wp-security-and-firewall') . ')';
$ip_retrieve_methods[$id]['is_enabled'] = false;
}
if (!empty($ip_retrieve_methods_postfixes[$ip_method])) {
$ip_retrieve_methods[$id]['ip_method'] .= ' (' . $ip_retrieve_methods_postfixes[$ip_method] . ')';
}
}
return array(
'is_localhost' => AIOWPSecurity_Utility::is_localhost(),
'current_ip_retrieve_method' => $aio_wp_security->configs->get_value('aiowps_ip_retrieve_method'),
'ip_retrieve_methods' => $ip_retrieve_methods,
'server_suitable_ip_methods' => AIOWPSecurity_Utility_IP::get_server_suitable_ip_methods()
);
}
}
@@ -0,0 +1,216 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
if (trait_exists('AIOWPSecurity_Tfa_Commands_Trait')) return;
trait AIOWPSecurity_Tfa_Commands_Trait {
/**
* Init TFA for UDC.
*
* @return AIO_WP_Security_Simba_Two_Factor_Authentication_Plugin
*/
private function init_tfa() {
include_once AIO_WP_SECURITY_PATH . '/classes/wp-security-two-factor-login.php';
$tfa = new AIO_WP_Security_Simba_Two_Factor_Authentication_Plugin();
/* Needed to run hook-dependent code in TFA, or there are Divide by Zero errors. */
do_action('plugins_loaded');
return $tfa;
}
/**
* Saves the TFA algorithm setting.
*
* @param array $data Passed arguments.
*
* @return array|WP_Error
*/
public function save_algorithm_setting($data) {
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
return new WP_Error(esc_html__('Sorry, you do not have enough privilege to execute the requested action.', 'all-in-one-wp-security-and-firewall'));
}
global $current_user;
$tfa = $this->init_tfa();
$controller = $tfa->get_controller();
$old_algorithm = $controller->get_user_otp_algorithm($current_user->ID);
if ($old_algorithm != $data['tfa_algorithm_type']) {
$controller->changeUserAlgorithmTo($current_user->ID, $data['tfa_algorithm_type']);
}
return array(
'status' => 'success',
);
}
/**
* Saves the TFA activation setting.
*
* @param array $data Passed arguments.
*
* @return array|WP_Error
*/
public function save_activation_setting($data) {
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
return new WP_Error(esc_html__('Sorry, you do not have enough privilege to execute the requested action.', 'all-in-one-wp-security-and-firewall'));
}
global $current_user;
$tfa = $this->init_tfa();
$tfa->change_tfa_enabled_status($current_user->ID, $data['tfa_enable_tfa']);
return array(
'status' => 'success',
);
}
/**
* Updates the TFA private key.
*
* @return array|WP_Error
*/
public function update_private_key() {
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
return new WP_Error(esc_html__('Sorry, you do not have enough privilege to execute the requested action.', 'all-in-one-wp-security-and-firewall'));
}
global $current_user;
$user_id = $current_user->ID;
delete_user_meta($user_id, 'tfa_priv_key_64');
delete_user_meta($user_id, 'simba_tfa_emergency_codes_64');
return array(
'status' => 'success',
);
}
/**
* Updates the TFA OTP Code.
*
* @param array $data Passed arguments.
*
* @return array|WP_Error
*/
public function update_otp_code($data) {
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
return new WP_Error(esc_html__('Sorry, you do not have enough privilege to execute the requested action.', 'all-in-one-wp-security-and-firewall'));
}
global $current_user;
$tfa = $this->init_tfa();
if ('refreshotp' == $data['subaction']) {
$code = $tfa->get_controller()->get_current_code($current_user->ID);
if (false === $code) {
return array(
'status' => 'error',
'code' => '',
);
}
return array(
'status' => 'success',
'code' => $code,
);
} elseif ('untrust_device' == $data['subaction']) {
global $current_user;
$trusted_devices = $tfa->user_get_trusted_devices();
$trusted_device = $trusted_devices[wp_unslash($data['device_id'])];
if (isset($trusted_device)) {
unset($trusted_device);
}
$current_user_id = $current_user->ID;
$tfa->user_set_trusted_devices($current_user_id, $trusted_devices);
$trusted_list = $tfa->include_template('trusted-devices-inner-box.php', array('trusted_devices' => $tfa->user_get_trusted_devices()), true);
return array(
'status' => 'success',
'trusted_list' => $trusted_list,
);
}
exit;
}
/**
* Renders the TFA UI.
*
* @return array
*/
public function get_tfa_contents() {
if (!function_exists('submit_button')) {
require_once(ABSPATH . 'wp-admin/includes/template.php');
}
$tfa = $this->init_tfa();
$content = $tfa->include_template('user-settings.php', array('simba_tfa' => $tfa), true);
return array(
'status' => 'success',
'content' => $content,
);
}
/**
* Get the TFA settings data for the new UDC theme.
*
* @return array
*/
public function get_tfa_data() {
$tfa = $this->init_tfa();
return array(
'tfa_required_administrator' => $tfa->get_option('tfa_required_administrator'),
'tfa_administrator' => $tfa->get_option('tfa_administrator'),
);
}
/**
* Save the TFA settings data for the new UDC theme.
*
* @param array $data The data to save.
*
* @return array|WP_Error
*/
public function perform_save_tfa($data) {
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
return new WP_Error(esc_html__('Sorry, you do not have enough privilege to execute the requested action.', 'all-in-one-wp-security-and-firewall'));
}
$success = false;
$message = '';
$tfa = $this->init_tfa();
$value = isset($data["tfa_required_administrator"]) ? '1' : '';
if ($tfa->update_option('tfa_required_administrator', $value)) {
$tfa->update_option('tfa_administrator', $value);
$success = true;
}
return $this->handle_response($success, $message);
}
}
@@ -0,0 +1,253 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
if (trait_exists('AIOWPSecurity_Tools_Commands_Trait')) return;
trait AIOWPSecurity_Tools_Commands_Trait {
/**
* Perform a WHOIS lookup for the provided IP address or domain.
*
* @param array $data The data containing the IP address or domain for the WHOIS lookup.
* The data should include the key 'aiowps_whois_ip_or_domain'.
* @return array An array containing the status of the operation and the WHOIS lookup result content.
* The 'status' key indicates whether the operation was successful.
* The 'content' key contains the result of the WHOIS lookup.
*/
public function perform_whois_lookup($data) {
global $aio_wp_security;
$ip_or_domain = trim(stripslashes($data['aiowps_whois_ip_or_domain']));
$invalid_domain = false;
if (empty($ip_or_domain)) {
$invalid_domain = true;
} elseif (version_compare(phpversion(), '5.6', '>')) {
if (!(filter_var($ip_or_domain, FILTER_VALIDATE_IP) || filter_var($ip_or_domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME))) $invalid_domain = true; // phpcs:ignore PHPCompatibility.Constants.NewConstants.filter_validate_domainFound -- This code only runs on php 7.0+ so ignore the warning
}
if ($invalid_domain) {
$result = __('Please enter a valid IP address or domain name to look up.', 'all-in-one-wp-security-and-firewall');
$result .= __('Nothing to show.', 'all-in-one-wp-security-and-firewall');
} else {
$result = $this->whois_lookup($ip_or_domain);
if (is_wp_error($result)) {
$result = htmlspecialchars($result->get_error_message());
$result .= __('Nothing to show.', 'all-in-one-wp-security-and-firewall');
} else {
$result = htmlspecialchars($result);
}
}
$args = array(
'content' => array('aios-who-is-lookup-result-container' => $aio_wp_security->include_template('wp-admin/tools/partials/who-is-lookup-result.php', true, array('result' => $result, 'ip_or_domain' => $ip_or_domain)))
);
return $this->handle_response(true, false, $args);
}
/**
* Store custom .htaccess settings provided by the user.
*
* @param array $data The data containing the custom .htaccess settings.
* It should include keys 'aiowps_enable_custom_rules', 'aiowps_custom_rules',
* and 'aiowps_place_custom_rules_at_top' if applicable.
* @return array An array containing the status of the operation and any relevant messages.
* The 'status' key indicates whether the operation was successful.
* The 'message' key contains any informational or error messages.
*/
public function perform_store_custom_htaccess_settings($data) {
global $aio_wp_security;
$success = true;
$message = '';
$options = array();
// Save settings
if (isset($data["aiowps_enable_custom_rules"]) && empty($data['aiowps_custom_rules'])) {
$message = __('You must enter some .htaccess directives in the text box below', 'all-in-one-wp-security-and-firewall');
return $this->handle_response(false, $message);
} else {
if (!empty($data['aiowps_custom_rules'])) {
// Sanitize textarea shoud not be used as <filesMatch "\.(js|css|html)$"> etc rules gets removed.
// Escape textarea should not be used the & becomes &amp;.
// Here stripslashes as old version 5.3.0 not required, AIOWPSecurity_Ajax::set_data applies wp_unslash for ajax data.
// So the .htacces rule having index\.php backslashes removed if used stripslashes below.
$options['aiowps_custom_rules'] = $data['aiowps_custom_rules'];
} else {
$options['aiowps_custom_rules'] = ''; //Clear the custom rules config value
}
$aiowps_custom_rules = $aio_wp_security->configs->get_value('aiowps_custom_rules');
$aiowps_place_custom_rules_at_top = $aio_wp_security->configs->get_value('aiowps_place_custom_rules_at_top');
$options['aiowps_enable_custom_rules'] = isset($data["aiowps_enable_custom_rules"]) ? '1' : '';
$options['aiowps_place_custom_rules_at_top'] = isset($data["aiowps_place_custom_rules_at_top"]) ? '1' : '';
$this->save_settings($options); // Save the configuration
$write_result = AIOWPSecurity_Utility_Htaccess::write_to_htaccess(); //now let's write to the .htaccess file
if (!$write_result) {
$options['aiowps_enable_custom_rules'] = $aiowps_custom_rules;
$options['aiowps_place_custom_rules_at_top'] = $aiowps_place_custom_rules_at_top;
$this->save_settings($options);
$success = false;
$message = __('The plugin was unable to write to the .htaccess file, please edit file manually.', 'all-in-one-wp-security-and-firewall');
$aio_wp_security->debug_logger->log_debug("Custom Rules feature - The plugin was unable to write to the .htaccess file.");
}
}
return $this->handle_response($success, $message);
}
/**
* Perform the general visitor lockout settings operation.
*
* @param array $data The data containing the general visitor lockout settings.
* It should include keys 'aiowps_site_lockout' and 'aiowps_site_lockout_msg'.
* @return array An array containing the status of the operation and any relevant messages.
* The 'status' key indicates whether the operation was successful.
* The 'message' key contains an informational message about the outcome of the operation.
*/
public function perform_general_visitor_lockout($data) {
$options = array();
// Save settings
$options['aiowps_site_lockout'] = isset($data["aiowps_site_lockout"]) ? '1' : '';
$maint_msg = wp_kses_post(wp_unslash($data['aiowps_site_lockout_msg']));
$options['aiowps_site_lockout_msg'] = $maint_msg; // Text area/msg box
$this->save_settings($options);
do_action('aiowps_site_lockout_settings_saved');
return array(
'status' => 'success',
'message' => __('The settings have been successfully updated.', 'all-in-one-wp-security-and-firewall')
);
}
/**
* Perform the general visitor lockout setting operation for the dashboard widget.
*
* @param array $data The data containing the general visitor lockout setting.
* It should include the 'aiowps_site_lockout' key.
* @return array An array containing the status of the operation and any relevant messages.
* The 'status' key indicates whether the operation was successful.
* The 'message' key contains an informational message about the outcome of the operation.
*/
public function perform_general_visitor_lockout_dashboard_widget($data) {
$options = array();
// Save settings
$options['aiowps_site_lockout'] = isset($data["aiowps_site_lockout"]) ? '1' : '';
$this->save_settings($options);
do_action('aiowps_site_lockout_settings_saved');
return $this->handle_response(true);
}
/**
* Checks a password against the HIBP database.
*
* @param array $data Contains the password to be checked.
*
* @return array
*/
public function hibp_check_password($data) {
return array(
'status' => 'success',
'pwned' => AIOS_HIBP::password_is_pwned($data['password']),
);
}
/**
* Does a WHOIS lookup on an IP address or domain name and then returns the result.
*
* @param String $search - IP address or domain name to do a WHOIS lookup on
* @param Integer $timeout - connection timeout for fsockopen
*
* @return String|WP_Error - returns preformatted WHOIS lookup result or WP_Error
*/
private function whois_lookup($search, $timeout = 10) {
$fp = @fsockopen('whois.iana.org', 43, $errno, $errstr, $timeout);
if (!$fp) {
return new WP_Error('whois_lookup_failed', 'whois.iana.org: Socket Error '.$errno.' - '.$errstr);
}
$queries = sprintf(__('Querying %s: %s', 'all-in-one-wp-security-and-firewall'), 'whois.iana.org', $search)."\n";
fputs($fp, $search."\r\n");
$out = '';
while (!feof($fp)) {
$line = fgets($fp);
if (preg_match('/refer: +(\S+)/', $line, $matches)) {
$referral_server = $matches[1];
$queries .= sprintf(__('Redirected to %s', 'all-in-one-wp-security-and-firewall'), $referral_server)."\n";
break;
}
$out .= $line;
}
fclose($fp);
if (!isset($referral_server) && filter_var($search, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && preg_match('/whois: +(\S+)/', $out, $matches)) {
$referral_server = $matches[1];
$queries .= sprintf(__('Redirected to %s', 'all-in-one-wp-security-and-firewall'), $referral_server)."\n";
}
$referrals = array();
while (isset($referral_server)) {
$referrals[] = $referral_server;
$fp = @fsockopen($referral_server, 43, $errno, $errstr, $timeout);
if (!$fp) {
return new WP_Error('whois_lookup_failed', $referral_server.': Socket Error '.$errno.' - '.$errstr);
}
if ('whois.arin.net' == $referral_server) {
$formatted_search = 'n + '.$search;
} elseif ('whois.denic.de' == $referral_server) {
$formatted_search = '-T dn,ace '.$search;
} elseif ('whois.dk-hostmaster.dk' == $referral_server) {
$formatted_search = '--charset=utf-8 --show-handles '.$search;
} elseif ('whois.nic.ad.jp' == $referral_server || 'whois.jprs.jp' == $referral_server) {
$formatted_search = $search.'/e';
} else {
$formatted_search = $search;
}
$queries .= sprintf(__('Querying %s: %s', 'all-in-one-wp-security-and-firewall'), $referral_server, $formatted_search)."\n";
$referral_server = null;
fputs($fp, $formatted_search."\r\n");
$out = '';
while (!feof($fp)) {
$line = fgets($fp);
if (preg_match('/Registrar WHOIS Server: +(\S+)/', $line, $matches)
|| preg_match('/% referto: +whois -h (\S+)/', $line, $matches)
|| preg_match('/% referto: +(\S+)/', $line, $matches)
|| preg_match('/ReferralServer: +rwhois:\/\/(\S+)/', $line, $matches)
|| preg_match('/ReferralServer: +whois:\/\/(\S+)/', $line, $matches)
) {
if (!in_array($matches[1], $referrals)) {
$referral_server = $matches[1];
$queries .= sprintf(__('Redirected to %s', 'all-in-one-wp-security-and-firewall'), $referral_server)."\n";
break;
}
}
$out .= $line;
}
fclose($fp);
}
return $queries."\n".$out;
}
}
@@ -0,0 +1,716 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
if (trait_exists('AIOWPSecurity_User_Security_Commands_Trait')) return;
trait AIOWPSecurity_User_Security_Commands_Trait {
/**
* Saves user account security settings.
*
* This function updates security settings related to user enumeration prevention
* and strong password enforcement in the AIO WP Security plugin.
*
* @param array $data An associative array containing the security settings:
* - 'aiowps_prevent_users_enumeration' (optional): Set to '1' to prevent user enumeration.
* - 'aiowps_enforce_strong_password' (optional): Set to '1' to enforce strong passwords.
*
* @return array The response array containing:
* - 'badges' (array): A list of applied security badges.
*/
public function perform_save_user_account_settings($data) {
global $aio_wp_security;
// Save settings
$aio_wp_security->configs->set_value('aiowps_prevent_users_enumeration', isset($data["aiowps_prevent_users_enumeration"]) ? '1' : '', true);
$aio_wp_security->configs->set_value('aiowps_enforce_strong_password', isset($data['aiowps_enforce_strong_password']) ? '1' : '', true);
$badges = array('enforce-strong-password', 'disable-users-enumeration');
return $this->handle_response(true, '', array('badges' => $badges));
}
/**
* Performs the action to change the admin username.
*
* @param array $data An array containing the data for changing the admin username.
* The array may contain the following keys:
* - 'aiowps_new_user_name': The new username to be set for the admin.
* @return array Returns an array containing the status of the operation ('success' or 'error'),
* and a message indicating the result of the operation.
* If the operation is successful, it also includes a badge representing the updated feature details.
*/
public function perform_change_admin_username($data) {
global $wpdb, $aio_wp_security;
$response = array(
'status' => 'success',
'content' => array()
);
$error = '';
if (!empty($data['aiowps_new_user_name'])) {
$new_username = sanitize_text_field($data['aiowps_new_user_name']);
if (validate_username($new_username)) {
if (AIOWPSecurity_Utility::check_user_exists($new_username)) {
$response['status'] = 'error';
$error = sprintf(__('Username: %s already exists, please enter another value.', 'all-in-one-wp-security-and-firewall'), $new_username);
} else {
// let's check if currently logged in username is 'admin'
$user = wp_get_current_user();
$user_login = $user->user_login;
if ('admin' == strtolower($user_login)) {
$username_is_admin = true;
} else {
$username_is_admin = false;
}
// Now let's change the username
$sql = $wpdb->prepare("UPDATE `" . $wpdb->users . "` SET user_login = '" . esc_sql($new_username) . "' WHERE user_login=%s", "admin");
$result = $wpdb->query($sql);
if (false === $result) {
// There was an error updating the users table
$user_update_error = __('The database update operation of the user account failed.', 'all-in-one-wp-security-and-firewall');
$response['status'] = 'error';
$response['message'] = $user_update_error;
$aio_wp_security->debug_logger->log_debug($user_update_error . ' ' . $wpdb->last_error, 4);
return $response;
}
// multisite considerations
if (is_multisite()) { // process sitemeta if we're in a multi-site situation
$oldAdmins = $wpdb->get_var("SELECT meta_value FROM `" . $wpdb->sitemeta . "` WHERE meta_key = 'site_admins'");
$newAdmins = str_replace('5:"admin"', strlen($new_username) . ':"' . esc_sql($new_username) . '"', $oldAdmins);
$wpdb->query("UPDATE `" . $wpdb->sitemeta . "` SET meta_value = '" . esc_sql($newAdmins) . "' WHERE meta_key = 'site_admins'");
}
// If user is logged in with username "admin" then log user out and send to login page so they can login again
if ($username_is_admin) {
// Lets logout the user
$aio_wp_security->debug_logger->log_debug("Logging user out with login ".$user_login. " because they changed their username.");
$after_logout_url = AIOWPSecurity_Utility::get_current_page_url();
$after_logout_payload = array('redirect_to' => $after_logout_url, 'msg' => $aio_wp_security->user_login_obj->key_login_msg.'=admin_user_changed');
//Save some of the logout redirect data to a transient
is_multisite() ? set_site_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60) : set_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60);
$logout_url = AIOWPSEC_WP_URL.'?aiowpsec_do_log_out=1';
$logout_url = AIOWPSecurity_Utility::add_query_data_to_url($logout_url, 'al_additional_data', '1');
$response['logout_user'] = true;
$response['logout_url'] = $logout_url;
}
}
} else { // An invalid username was entered
$error = __('You entered an invalid username, please enter another value.', 'all-in-one-wp-security-and-firewall');
}
} else { // No username value was entered
$response['status'] = 'error';
$error = __('Please enter a value for your username.', 'all-in-one-wp-security-and-firewall');
}
if (!empty($error)) { // We have some validation or other error
$response['message'] = $error;
} else {
$response['message'] = __('The username has been successfully changed.', 'all-in-one-wp-security-and-firewall');
$response['badges'] = $this->get_features_id_and_html(array('user-accounts-change-admin-user'));
$response['content']['change-admin-username-content'] = $aio_wp_security->include_template('wp-admin/user-security/partials/wp-username-content.php', true);
}
return $response;
}
/**
* Performs the action to save the login lockout settings.
*
* @param array $data An array containing the data to be saved.
*
* @return array Returns an array containing the status of the operation ('success' or 'error'),
* a message indicating the result of the operation,
* and a badge representing the updated feature details.
*/
public function perform_save_login_lockout_settings($data) {
$response = array(
'status' => 'success',
'values' => array(),
'info' => array()
);
$invalid_fields = array();
$max_login_attempt_val = sanitize_text_field($data['aiowps_max_login_attempts']);
if (!is_numeric($max_login_attempt_val) || 1 > $max_login_attempt_val) {
$invalid_fields[] = 'max login attempts';
$max_login_attempt_val = '3'; // Set it to the default value for this field
}
$login_retry_time_period = sanitize_text_field($data['aiowps_retry_time_period']);
if (!is_numeric($login_retry_time_period) || 1 > $login_retry_time_period) {
$invalid_fields[] = 'login retry time period';
$login_retry_time_period = '5'; // Set it to the default value for this field
}
$lockout_time_length = sanitize_text_field($data['aiowps_lockout_time_length']);
if (!is_numeric($lockout_time_length) || 1 > $lockout_time_length) {
$invalid_fields[] = 'minimum lockout time length';
$lockout_time_length = '5'; // Set it to the default value for this field
}
$max_lockout_time_length = sanitize_text_field($data['aiowps_max_lockout_time_length']);
if (!is_numeric($max_lockout_time_length) || 1 > $max_lockout_time_length) {
$invalid_fields[] = 'maximum lockout time length';
$max_lockout_time_length = '60'; // Set it to the default value for this field
}
if ($lockout_time_length >= $max_lockout_time_length) {
$invalid_fields[] = 'minimum lockout time length';
$lockout_time_length = '5'; // Set it to the default value for this field
$max_lockout_time_length = '60'; // Set it to the default value for this field
}
$email_addresses = isset($data['aiowps_email_address']) ? stripslashes($data['aiowps_email_address']) : get_bloginfo('admin_email');
$email_addresses_trimmed = AIOWPSecurity_Utility::explode_trim_filter_empty($email_addresses, "\n");
// Read into array, sanitize, filter empty and keep only unique usernames.
$email_address_list = array_unique(
array_filter(
array_map(
'sanitize_email',
$email_addresses_trimmed
),
'is_email'
)
);
if (isset($data['aiowps_enable_email_notify']) && 1 == $data['aiowps_enable_email_notify'] && 0 == count($email_addresses_trimmed)) {
$invalid_fields[] = 'email addresses';
} elseif (isset($data['aiowps_enable_email_notify']) && 1 == $data['aiowps_enable_email_notify'] && (0 == count($email_address_list) || count($email_address_list) != count($email_addresses_trimmed))) {
$invalid_fields[] = 'email addresses';
}
if (isset($data['aiowps_enable_email_notify']) && 0 == count($email_address_list)) {
$email_address_list[] = get_bloginfo('admin_email');
}
// Instantly lockout specific usernames
$instantly_lockout_specific_usernames = isset($data['aiowps_instantly_lockout_specific_usernames']) ? $data['aiowps_instantly_lockout_specific_usernames'] : '';
// Read into array, sanitize, filter empty and keep only unique usernames.
$instantly_lockout_specific_usernames = array_unique(
array_filter(
array_map(
'sanitize_user',
AIOWPSecurity_Utility::explode_trim_filter_empty($instantly_lockout_specific_usernames)
),
'strlen'
)
);
$response['message'] = __('The settings have been successfully updated.', 'all-in-one-wp-security-and-firewall');
if (!empty($invalid_fields)) {
$invalid_fields = array_unique($invalid_fields);
$invalid_fields = implode(", ", $invalid_fields);
$response['info'][] = sprintf(__('The following options had invalid values and have been set to the defaults: %s', 'all-in-one-wp-security-and-firewall'), $invalid_fields);
}
$options = array();
// Save all the form values to the options
$random_20_digit_string = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(20); // Generate random 20 char string for use during CAPTCHA encode/decode
$options['aiowps_unlock_request_secret_key'] = $random_20_digit_string;
$options['aiowps_enable_login_lockdown'] = isset($data["aiowps_enable_login_lockdown"]) ? '1' : '';
$options['aiowps_allow_unlock_requests'] = isset($data["aiowps_allow_unlock_requests"]) ? '1' : '';
$options['aiowps_max_login_attempts'] = absint($max_login_attempt_val);
$options['aiowps_retry_time_period'] = absint($login_retry_time_period);
$options['aiowps_lockout_time_length'] = absint($lockout_time_length);
$options['aiowps_max_lockout_time_length'] = absint($max_lockout_time_length);
$options['aiowps_set_generic_login_msg'] = isset($data["aiowps_set_generic_login_msg"]) ? '1' : '';
$options['aiowps_enable_invalid_username_lockdown']= isset($data["aiowps_enable_invalid_username_lockdown"]) ? '1' : '';
$options['aiowps_instantly_lockout_specific_usernames'] = $instantly_lockout_specific_usernames;
$options['aiowps_enable_email_notify'] = isset($data["aiowps_enable_email_notify"]) ? '1' : '';
$options['aiowps_enable_php_backtrace_in_email'] = isset($data['aiowps_enable_php_backtrace_in_email']) ? '1' : '';
$options['aiowps_email_address'] = $email_address_list;
$this->save_settings($options);
$response['values']['aiowps_max_login_attempts'] = absint($max_login_attempt_val);
$response['values']['aiowps_retry_time_period'] = absint($login_retry_time_period);
$response['values']['aiowps_lockout_time_length'] = absint($lockout_time_length);
$response['values']['aiowps_max_lockout_time_length'] = absint($max_lockout_time_length);
$response['values']['aiowps_email_address'] = implode("\n", $email_address_list);
$response['badges'] = $this->get_features_id_and_html(array('user-login-login-lockdown'));
return $response;
}
/**
* Performs the action to save the login lockout whitelist settings.
*
* @param array $data An array containing the data to be saved.
* The array may contain the following keys:
* - 'aiowps_lockdown_enable_whitelisting': A boolean indicating whether whitelisting is enabled.
* - 'aiowps_lockdown_allowed_ip_addresses': The allowed IP addresses for whitelisting.
* @return array Returns an array containing the status of the operation ('success' or 'error'),
* a message indicating the result of the operation,
* and a badge representing the updated feature details.
*/
public function perform_save_login_lockout_whitelist_settings($data) {
global $aio_wp_security;
$response = array(
'status' => 'success'
);
$options = array();
$result = 1;
if (!empty($data['aiowps_lockdown_allowed_ip_addresses'])) {
$ip_addresses = sanitize_textarea_field(wp_unslash($data['aiowps_lockdown_allowed_ip_addresses']));
$ip_list_array = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($ip_addresses);
$validated_ip_list_array = AIOWPSecurity_Utility_IP::validate_ip_list($ip_list_array, 'whitelist');
if (is_wp_error($validated_ip_list_array)) {
$result = -1;
$response['status'] = 'error';
$response['message'] = AIOWPSecurity_Admin_Menu::show_msg_error_st(nl2br($validated_ip_list_array->get_error_message()), true);
} else {
$allowed_ip_data = implode("\n", $validated_ip_list_array);
$options['aiowps_lockdown_allowed_ip_addresses'] = $allowed_ip_data;
}
} else {
$options['aiowps_lockdown_allowed_ip_addresses'] = ''; //Clear the IP address config value
}
if (1 == $result) {
$aio_wp_security->configs->set_value('aiowps_lockdown_enable_whitelisting', isset($data["aiowps_lockdown_enable_whitelisting"]) ? '1' : '', true);
$response['message'] = __('The settings have been successfully updated.', 'all-in-one-wp-security-and-firewall');
$response['badges'] = $this->get_features_id_and_html(array('user-login-lockout-ip-whitelisting'));
}
$this->save_settings($options);
return $response;
}
/**
* Performs the action to force logout users.
*
* @param array $data An array containing the data to be saved.
* The array may contain the following keys:
* - 'aiowps_logout_time_period': The time period (in minutes) for logout.
* - 'aiowps_enable_forced_logout': A boolean indicating whether forced logout is enabled.
* @return array Returns an array containing the status of the operation ('success' or 'error'),
* an array of messages indicating the result of the operation,
* the content representing the logout time period,
* and a badge representing the updated feature details.
*/
public function perform_force_logout($data) {
global $aio_wp_security;
$response = array(
'status' => 'success',
'info' => array(),
'values' => array()
);
$options = array();
$logout_time_period = sanitize_text_field($data['aiowps_logout_time_period']);
if (isset($data["aiowps_enable_forced_logout"]) && (!is_numeric($logout_time_period) || $logout_time_period < 1)) {
$response['info'][] = __('You entered a non numeric or negative value for the logout time period field, it has been set to the default value.', 'all-in-one-wp-security-and-firewall');
$logout_time_period = '60'; // Set it to the default value for this field
}
// Save all the form values to the options
$options['aiowps_logout_time_period'] = absint($logout_time_period);
$options['aiowps_enable_forced_logout'] = isset($data["aiowps_enable_forced_logout"]) ? '1' : '';
$this->save_settings($options);
$response['values']['aiowps_logout_time_period'] = absint($logout_time_period);
$response['badges'] = $this->get_features_id_and_html(array('user-login-force-logout'));
$response['message'] = __('The settings have been successfully updated.', 'all-in-one-wp-security-and-firewall');
if ('1' === $options['aiowps_enable_forced_logout']) {
$response['logout_user'] = $this->check_logout_user();
$response['logout_url'] = $aio_wp_security->user_login_obj->aiowps_force_logout_action_handler(true);
}
return $response;
}
/**
* Performs the action to save the HIBP settings.
*
* @param array $data An array containing the data to be saved.
*
* @return array Returns an array containing the status of the operation ('success' or 'error'),
* a message indicating the result of the operation,
* and a badge representing the updated feature details.
*/
public function perform_save_hibp_settings($data) {
global $aio_wp_security;
$aio_wp_security->configs->set_value('aiowps_hibp_user_profile_update', isset($data['aiowps_hibp_user_profile_update']) ? '1' : '', true);
$aio_wp_security->configs->set_value('aiowps_http_password_reset', isset($data['aiowps_http_password_reset']) ? '1' : '', true);
$aio_wp_security->configs->save_config();
return $this->handle_response(true, false, array('badges' => array('hibp')));
}
/**
* Performs the action to disable application password.
*
* @param array $data An array containing the data to be saved.
* The array may contain the following key:
* - 'aiowps_disable_application_password': A boolean indicating whether application password is disabled.
* @return array Returns an array containing the status of the operation ('success' or 'error'),
* a message indicating the result of the operation,
* and a badge representing the updated feature details.
*/
public function perform_disable_application_password($data) {
global $aio_wp_security;
// Save all the form values to the options
$aio_wp_security->configs->set_value('aiowps_disable_application_password', isset($data['aiowps_disable_application_password']) ? '1' : '', true);
return array(
'status' => 'success',
'message' => __('The settings have been successfully updated.', 'all-in-one-wp-security-and-firewall'),
'badges' => $this->get_features_id_and_html(array('disable-application-password'))
);
}
/**
* Performs the action to add salt postfix.
*
* @param array $data An array containing the data to be saved.
* The array may contain the following key:
* - 'aiowps_enable_salt_postfix': A boolean indicating whether salt postfix is enabled.
* @return array Returns an array containing the status of the operation ('success' or 'error'),
* a message indicating the result of the operation,
* and a badge representing the updated feature details.
*/
public function perform_add_salt_postfix($data) {
global $aio_wp_security;
$response = array(
'status' => 'success'
);
// Save settings
$aiowps_enable_salt_postfix = isset($data['aiowps_enable_salt_postfix']) ? '1' : '';
if ($aiowps_enable_salt_postfix == $aio_wp_security->configs->get_value('aiowps_enable_salt_postfix')) {
$is_setting_changed = true;
} else {
$is_setting_changed = false;
}
$aio_wp_security->configs->set_value('aiowps_enable_salt_postfix', $aiowps_enable_salt_postfix, true);
$ret_schedule = $this->schedule_change_auth_keys_and_salt();
if (is_wp_error($ret_schedule)) {
$aio_wp_security->debug_logger->log_debug($ret_schedule->get_error_message(), 4);
}
if ('1' == $aiowps_enable_salt_postfix && $is_setting_changed) {
AIOWPSecurity_Utility::change_salt_postfixes();
}
$response['message'] = __('The settings have been successfully updated.', 'all-in-one-wp-security-and-firewall');
$response['badges'] = $this->get_features_id_and_html(array('enable-salt-postfix'));
return $response;
}
/**
* Performs actions on logged-in users.
*
* @param array $data An array containing the data for the action to be performed.
* The array may contain the following keys:
* - 'action': The action to be performed on logged-in users (e.g., 'force_user_logout').
* - 'logged_in_id': The ID of the logged-in user on which the action will be performed.
* @return array Returns an array containing the status of the operation ('success' or 'error'),
* and a message indicating the result of the operation.
*/
public function perform_logged_in_user_action($data) {
global $aio_wp_security;
include_once AIO_WP_SECURITY_PATH.'/admin/wp-security-list-logged-in-users.php'; // For rendering the AIOWPSecurity_List_Table
$user_list = new AIOWPSecurity_List_Logged_In_Users();
$response = array(
'status' => 'success'
);
if (empty($data['action']) || !in_array($data['action'], array('force_user_logout'))) { // more actions can be added
return array(
'status' => 'error',
'message' => __('Invalid action provided for logged in user.', 'all-in-one-wp-security-and-firewall')
);
}
if ('force_user_logout' == $data['action']) {
if (empty($data['logged_in_id'])) {
return array(
'status' => 'error',
'message' => __('No user ID was provided', 'all-in-one-wp-security-and-firewall')
);
}
$user_id = strip_tags($data['logged_in_id']);
$error = '';
if (!is_numeric($user_id)) {
$error = __("Invalid user ID provided.", 'all-in-one-wp-security-and-firewall');
} elseif (get_current_user_id() == $user_id) {
$error = __("You cannot log yourself out", 'all-in-one-wp-security-and-firewall');
} elseif (is_super_admin($user_id)) {
$error = __("Super admins cannot be logged out.", 'all-in-one-wp-security-and-firewall');
} elseif (!AIOWPSecurity_Utility::is_user_member_of_blog($user_id)) {
$error = __("You cannot log out a user from a different subsite.", 'all-in-one-wp-security-and-firewall');
}
if ($error) {
return array(
'message' => $error,
'status' => 'error'
);
}
$users = esc_sql($user_id);
$result = $aio_wp_security->user_login_obj->delete_logged_in_user($users);
if ($result) {
$user_list->logout_user($users);
$response['message'] = __('The selected user has been logged out successfully.', 'all-in-one-wp-security-and-firewall');
} else {
$response['message'] = __('Failed to log out the selected user.', 'all-in-one-wp-security-and-firewall');
$response['status'] = 'error';
}
}
return $response;
}
/**
* Performs the action to configure manual registration approval settings.
*
* @param array $data An array containing the data to be saved.
* The array may contain the following key:
* - 'aiowps_enable_manual_registration_approval': A boolean indicating whether manual registration approval is enabled.
* @return array Returns an array containing the status of the operation ('success' or 'error'),
* a message indicating the result of the operation,
* and a badge representing the updated feature details.
*/
public function perform_manual_approval_settings($data) {
global $aio_wp_security;
// Save settings
$aio_wp_security->configs->set_value('aiowps_enable_manual_registration_approval', isset($data["aiowps_enable_manual_registration_approval"]) ? '1' : '', true);
return array(
'status' => 'success',
'message' => __('The settings have been successfully updated.', 'all-in-one-wp-security-and-firewall'),
'badges' => $this->get_features_id_and_html(array('manually-approve-registrations'))
);
}
/**
* Performs actions on manual approval items (e.g., approve account, delete account, block IP).
*
* @param array $data An array containing the data for the action to be performed.
* The array may contain the following keys:
* - 'action': The action to be performed on the manual approval item (e.g., 'approve_acct', 'delete_acct', 'block_ip').
* - 'user_id': The ID of the user for whom the action will be performed (applicable for 'approve_acct' and 'delete_acct' actions).
* - 'ip_address': The IP address to be blocked (applicable for 'block_ip' action).
* @return array Returns an array containing the status of the operation ('success' or 'error'),
* and a message indicating the result of the operation.
*/
public function perform_manual_approval_item_action($data) {
global $aio_wp_security;
include_once AIO_WP_SECURITY_PATH.'/admin/wp-security-list-registered-users.php'; // For rendering the AIOWPSecurity_List_Table
$user_list = new AIOWPSecurity_List_Registered_Users();
$status = 'error';
$valid_actions = array('approve_acct', 'delete_acct', 'block_ip');
if (empty($data['action']) || !in_array($data['action'], $valid_actions)) { // more actions can be added
return array(
'status' => 'error',
'message' => __('Invalid action provided for registered user.', 'all-in-one-wp-security-and-firewall')
);
}
switch ($data['action']) {
case 'approve_acct':
if (empty($data['user_id'])) {
return array(
'status' => 'error',
'message' => __('No valid user ID was provided', 'all-in-one-wp-security-and-firewall')
);
}
$user_id = esc_sql(strip_tags($data['user_id']));
$meta_key = 'aiowps_account_status';
$meta_value = 'approved'; // set account status
// Approve single account
$result = update_user_meta($user_id, $meta_key, $meta_value);
if ($result) {
$user = get_user_by('id', $user_id);
$user_list->send_email_upon_account_activation($user);
$message = __('The selected account was approved successfully.', 'all-in-one-wp-security-and-firewall');
$status = 'success';
} elseif (false === $result) {
$aio_wp_security->debug_logger->log_debug("could not approve account ID: $user_id", 4);
$message = __('The selected account could not be approved.', 'all-in-one-wp-security-and-firewall');
}
break;
case 'delete_acct':
if (empty($data['user_id'])) {
return array(
'status' => 'error',
'message' => __('No valid user ID was provided', 'all-in-one-wp-security-and-firewall')
);
}
$user_id = esc_sql(strip_tags($data['user_id']));
// Delete single account
$result = wp_delete_user($user_id);
if (true === $result) {
$message = __('The selected account was deleted successfully.', 'all-in-one-wp-security-and-firewall');
$status = 'success';
} else {
$aio_wp_security->debug_logger->log_debug("could not delete account ID: $user_id", 4);
$message = __('The selected account could not be deleted.', 'all-in-one-wp-security-and-firewall');
}
break;
case 'block_ip':
if (empty($data['ip_address'])) {
return array(
'status' => 'error',
'message' => __('No valid IP address was provided', 'all-in-one-wp-security-and-firewall')
);
}
$ip = esc_sql(strip_tags($data['ip_address']));
if (AIOWPSecurity_Utility_IP::get_user_ip_address() == $ip) {
$message = __('You cannot block your own IP address:', 'all-in-one-wp-security-and-firewall') . ' ' . $ip;
break;
}
// Block single IP
$result = AIOWPSecurity_Blocking::add_ip_to_block_list($ip, 'registration_spam');
if (true === $result) {
$message = __('The selected IP was successfully added to the permanent block list.', 'all-in-one-wp-security-and-firewall');
$message .= ' <a href="admin.php?page='.AIOWPSEC_MAIN_MENU_SLUG.'&tab=permanent-block" target="_blank">'.__('View Blocked IPs', 'all-in-one-wp-security-and-firewall').'</a>';
$status = 'success';
} else {
$aio_wp_security->debug_logger->log_debug("AIOWPSecurity_List_Registered_Users::block_selected_ips() - could not block IP: $ip", 4);
$message = __('The selected IP could not be added to the permanent block list.', 'all-in-one-wp-security-and-firewall');
}
break;
}
return array(
'status' => $status,
'message' => $message
);
}
/**
* Schedule weekly aios_change_auth_keys_and_salt cron event.
*
* @return Boolean|WP_Error True if event successfully scheduled. False or WP_Error on failure.
*/
private function schedule_change_auth_keys_and_salt() {
$previous_time = wp_next_scheduled('aios_change_auth_keys_and_salt');
if (false !== $previous_time) {
// Clear schedule so that we don't stack up scheduled backups
wp_clear_scheduled_hook('aios_change_auth_keys_and_salt');
}
$gmt_offset_in_seconds = floatval(get_option('gmt_offset')) * 3600;
$first_time = strtotime('next Sunday '.apply_filters('aios_salt_change_schedule_time', '3:00 am')) + $gmt_offset_in_seconds;
return wp_schedule_event($first_time, 'weekly', 'aios_change_auth_keys_and_salt');
}
/**
* Checks if the current user should be automatically logged out based on last login time.
*
* This method compares the current time with the last login time of the user and determines
* if the user should be logged out based on a configured logout time period.
*
* @return bool Returns true if the user should be logged out, false otherwise.
*/
private function check_logout_user() {
global $aio_wp_security;
// Get the current user
$current_user = wp_get_current_user();
$user_id = $current_user->ID;
// Get the current and last login times
$current_time = current_time('mysql', true);
$login_time = $aio_wp_security->user_login_obj->get_wp_user_aiowps_last_login_time($user_id);
// Return false if login time is empty (no last login recorded)
if (empty($login_time)) {
return false;
}
// Calculate the time difference between current time and last login time
$diff = strtotime($current_time) - strtotime($login_time);
// Get the configured logout time period in seconds
$logout_time_interval_value = $aio_wp_security->configs->get_value('aiowps_logout_time_period');
$logout_time_interval_val_seconds = $logout_time_interval_value * 60;
// Return true if the time difference exceeds the logout time interval, indicating the user should be logged out
return $diff > $logout_time_interval_val_seconds;
}
/**
* Whitelists user's IP address
*
* @return array Returns an array containing the status of the operation ('success' or 'error'),
* a message indicating the result of the operation,
* and a badge representing the updated feature details.
*/
public function perform_whitelist_user_ip() {
$response = array(
'status' => 'success'
);
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
$response['status'] = 'error';
$response['message'] = __('You don\'t have enough permissions to whitelist your IP address.', 'all-in-one-wp-security-and-firewall');
return $response;
}
$aiowps_firewall_allow_list = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::ALLOW_LIST);
$whitelisted_ips = $aiowps_firewall_allow_list::get_ips();
$is_whitelisted = $aiowps_firewall_allow_list::is_ip_allowed();
if ($is_whitelisted) {
$response['status'] = 'error';
$response['message'] = __('Your IP address is already whitelisted.', 'all-in-one-wp-security-and-firewall');
return $response;
} else {
$user_ip = AIOWPSecurity_Utility_IP::get_user_ip_address();
if (empty($user_ip)) {
$response['status'] = 'error';
$response['message'] = __('Your IP address could not be detected.', 'all-in-one-wp-security-and-firewall');
return $response;
}
$whitelisted_ips .= (empty($whitelisted_ips) ? '' : "\n") . $user_ip;
if (!$aiowps_firewall_allow_list::add_ips($whitelisted_ips)) {
$response['status'] = 'error';
$response['message'] = __('There was an error whitelisting your IP address, please try again.', 'all-in-one-wp-security-and-firewall');
return $response;
}
$response['message'] = __('Your IP address has been whitelisted successfully.', 'all-in-one-wp-security-and-firewall');
return $response;
}
}
}
@@ -0,0 +1,13 @@
<?php
namespace AIOWPS\Firewall;
/**
* Our list of families
*/
return array(
array('name' => '6G', 'priority' => 10),
array('name' => 'Blacklist', 'priority' => 1),
array('name' => 'Bruteforce', 'priority' => 0),
array('name' => 'General', 'priority' => 20),
array('name' => 'Bots', 'priority' => 2),
);
@@ -0,0 +1,33 @@
<?php
namespace AIOWPS\Firewall;
/**
* Builds all our families
*/
class Family_Builder {
/**
* Get our families sorted by priority
*
* @return array
*/
public static function get_families() {
$family_list = include(AIOWPS_FIREWALL_DIR.'/family/wp-security-firewall-families.php');
//Prioritise the families
usort($family_list, function($member, $member2) {
if ($member['priority'] == $member2['priority']) {
return 0;
}
return ($member['priority'] > $member2['priority']) ? 1 : -1;
});
$families = array();
foreach ($family_list as $member) {
$families[strtolower($member['name'])] = new Family($member['name'], $member['priority']);
}
return $families;
}
}
@@ -0,0 +1,49 @@
<?php
namespace AIOWPS\Firewall;
/**
* Holds all our families
*/
class Family_Collection {
/**
* Holds our families
*
* @var array
*/
protected $families;
/**
* Constructs our family collection object
*
* @param array $families - The sorted families to contain
*/
public function __construct($families = array()) {
$this->families = $families;
}
/**
* Generator method to iterate over the families
*
* @return iterable
*/
public function get_family() {
foreach ($this->families as $family) {
yield $family;
}
}
/**
* Adds a new rule to a family member
*
* @param Rule $rule - an active rule to add to its family
* @return void
*/
public function add_rule_to_member(Rule $rule) {
$key = strtolower($rule->family);
if (array_key_exists($key, $this->families)) {
$this->families[$key]->add_rule($rule);
}
}
}
@@ -0,0 +1,86 @@
<?php
namespace AIOWPS\Firewall;
/**
* Represents a family (a grouping of rules)
*/
class Family {
/**
* Name of the family
*
* @var string
*/
public $name;
/**
* Priority of the family (0 is the highest)
*
* @var int
*/
public $priority;
/**
* List of rules to apply
*
* @var array
*/
protected $rules;
/**
* Builds our family object
*
* @param string $name
* @param integer $priority
*/
public function __construct($name, $priority = 999999) {
$this->name = $name;
$this->priority = $priority;
$this->rules = array();
}
/**
* Adds a rule to the family
*
* @param Rule $rule
* @return void
*/
public function add_rule(Rule $rule) {
$this->rules[] = $rule;
}
/**
* Applies all the rules in the family
*
* @return void
*/
public function apply_all() {
if (empty($this->rules)) {
return;
}
//ensure the rules are ordered by priority
usort($this->rules, function(Rule $rule, Rule $rule2) {
if ($rule->priority == $rule2->priority) {
return 0;
}
return ($rule->priority > $rule2->priority) ? 1 : -1;
});
foreach ($this->rules as $rule) {
$rule->apply();
}
}
/**
* Returns the family name if used as a string
*
* @return string
*/
public function __toString() {
return $this->name;
}
}
@@ -0,0 +1,30 @@
<?php
namespace AIOWPS\Firewall;
trait File_Prefix_Trait {
/**
* Get the file's prefix content. N.B. Some code assumes that this doesn't change, so review all consumers of this method before changing its output.
*
* @return string
*/
public static function get_file_content_prefix() {
$prefix = "<?php __halt_compiler();\n";
$prefix .= "/**\n";
$prefix .= " * This file was created by All In One Security (AIOS) plugin.\n";
$prefix .= self::get_prefix_description();
$prefix .= " */\n";
return $prefix;
}
/**
* Returns the description of the file
* You can override this method for each file that needs a file prefix in order to give it its own description
*
* @return string
*/
public static function get_prefix_description() {
return " * The file is required for storing and retrieving your firewall's settings.\n";
}
}
@@ -0,0 +1,84 @@
<?php
namespace AIOWPS\Firewall;
class Allow_List {
/**
* Include a file prefix when the file is created
*/
use File_Prefix_Trait;
/**
* Holds the path to the allow list
*
* @var string
*/
private static $path;
/**
* Overwrite the prefix description from File_Prefix_Trait
*
* @return string
*/
public static function get_prefix_description() {
return " * The file is required for storing and retrieving your firewall's allow list.\n";
}
/**
* Checks whether the user's IP address is in the allow list
*
* @return bool
*/
public static function is_ip_allowed() {
$ips = self::get_ips();
if (empty($ips)) return false;
$ips = explode("\n", $ips);
return \AIOS_Helper::is_user_ip_address_within_list($ips);
}
/**
* Returns the list of IP addresses in the allow list
*
* @return string
*/
public static function get_ips() {
clearstatcache();
if (!file_exists(self::$path)) return '';
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Cannot use WP API. Firewall is loaded independent of WP.
$contents = file_get_contents(self::$path, false, null, strlen(self::get_file_content_prefix()));
return (false !== $contents ? trim($contents) : '');
}
/**
* Set the path of the allow list
*
* @param string $path
* @return void
*/
public static function set_path($path) {
self::$path = $path;
}
/**
* Add IPs to the allow list
* This overwrites the whole allow list with the given IPs
*
* @param mixed $ips - A string of IPs; one per line or an array of individual IPs
* @return bool
*/
public static function add_ips($ips) {
if (is_array($ips)) $ips = implode("\n", $ips);
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Cannot use WP API. Firewall is loaded independent of WP.
return (false !== file_put_contents(self::$path, self::get_file_content_prefix().$ips));
}
}
@@ -0,0 +1,163 @@
<?php
// phpcs:disable WordPress.WP.AlternativeFunctions -- WP isn't loaded here. WP API is unavailable
namespace AIOWPS\Firewall;
/**
* Gives us access to our firewall's config
*/
class Config {
use File_Prefix_Trait;
/**
* The path to our config file
*
* @var string
*/
protected $path;
/**
* Constructs object
*
* @param string $path
*/
public function __construct($path) {
$this->path = $path;
$this->init_file();
}
/**
* Initialise the file if it doesn't exist
*
* @return void
*/
private function init_file() {
clearstatcache();
if (!file_exists($this->path)) {
$dir = dirname($this->path);
if (!file_exists($dir)) Utility::wp_mkdir_p($dir);
file_put_contents($this->path, self::get_file_content_prefix() . json_encode(array()));
}
}
/**
* Update the config file with the new prefix whenever the prefix changes.
*
* @return void
*/
public function update_prefix() {
$valid_prefix = self::get_file_content_prefix();
$current_prefix = file_get_contents($this->path, false, null, 0, strlen($valid_prefix));
if ($current_prefix === $valid_prefix) return; // prefix is valid
$contents = file_get_contents($this->path);
$matches = array();
if (preg_match('/\{.*\}/', $contents, $matches)) {
//update settings
file_put_contents($this->path, $valid_prefix . $matches[0]);
} else {
//reset settings
file_put_contents($this->path, $valid_prefix . json_encode(array()));
}
}
/**
* Gets the value from the config array
*
* @param string $key
* @return mixed|null
*/
public function get_value($key) {
$contents = $this->get_contents();
if (null === $contents) {
return null;
}
if (!isset($contents[$key])) {
return null;
}
return $contents[$key];
}
/**
* Sets a value in our config array
*
* @param string $key
* @param mixed $value
* @return boolean
*/
public function set_value($key, $value) {
$contents = $this->get_contents();
if (null === $contents) {
return false;
}
$contents[$key] = $value;
return (false !== file_put_contents($this->path, self::get_file_content_prefix() . json_encode($contents), LOCK_EX));
}
/**
* Loads the config array from file
*
* @return string
*/
public function get_contents() {
clearstatcache();
if (!file_exists($this->path)) $this->init_file();
// __COMPILER_HALT_OFFSET__ doesn't define in a few PHP versions. It's a PHP bug.
// https://bugs.php.net/bug.php?id=70164
$contents = file_get_contents($this->path, false, null, strlen(self::get_file_content_prefix()));
if (false === $contents) {
return null;
}
if (empty($contents)) {
return array();
}
return json_decode($contents, true);
}
/**
* Sets entire firewall config from array.
*
* @param Array $contents
*
* @return Boolean
*/
public function set_contents($contents) {
if (null === $contents) {
return false;
}
return (false !== file_put_contents($this->path, self::get_file_content_prefix() . json_encode($contents), LOCK_EX));
}
/**
* Returns the path
*
* @return string
*/
public function __toString() {
return $this->path;
}
}
// phpcs:enable WordPress.WP.AlternativeFunctions -- WP isn't loaded here. WP API is unavailable
@@ -0,0 +1,213 @@
<?php
namespace AIOWPS\Firewall;
/**
* A class for accessing constants (including from wp-config) from the firewall
* Only supports parsing 'defines' that have scalar types: int, float, boolean, string and null
*/
class Constants implements \ArrayAccess, \IteratorAggregate {
/**
* The list of constants parsed
*
* @var array
*/
protected $constants;
/**
* The token part of the token identifier
*
* @see https://www.php.net/manual/en/function.token-get-all#refsect1-function.token-get-all-returnvalues
*/
const TOKEN = 0;
/**
* The string content of the token identifier
*
* @see https://www.php.net/manual/en/function.token-get-all#refsect1-function.token-get-all-returnvalues
*/
const CONTENT = 1;
/**
* The line number of the token identifier
*
* @see https://www.php.net/manual/en/function.token-get-all#refsect1-function.token-get-all-returnvalues
*/
const LINE = 2;
/**
* Offset for define's name [ define(NAME, VALUE); ]
*/
const DEFINE_NAME_OFFSET = 2;
/**
* Offset for define's value [ define(NAME, VALUE); ]
*/
const DEFINE_VALUE_OFFSET = 4;
/**
* Constructs our object
*/
public function __construct() {
$this->constants = array();
$this->populate_constants();
}
/**
* Populates our internal constant array with the defines from wp-config
*
* @return void
*/
protected function populate_constants() {
$wpconfig = Utility::get_wpconfig_path();
clearstatcache();
if (!file_exists($wpconfig)) return;
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- WP isn't loaded. WP_Filesystem cannot be used.
$source = file_get_contents($wpconfig);
if (false === $source) return;
$tokens = token_get_all($source);
//Filter out any unwanted tokens
$tokens = array_values(array_filter($tokens, function($token) {
//All tokens that are not arrays are allowed
if (!is_array($token)) return true;
$unwanted_tokens = array(
'T_COMMENT',
'T_WHITESPACE',
'T_DOC_COMMENT',
);
return (!in_array(token_name($token[self::TOKEN]), $unwanted_tokens));
}));
$token_count = count($tokens);
for ($i = 0; $i < $token_count; $i++) {
$current = $tokens[$i];
if (!is_array($current)) continue;
if ('T_STRING' === token_name($current[self::TOKEN]) && 'define' === strtolower($current[self::CONTENT])) {
// Name of the define without the surrounding quotes
$name = substr($tokens[$i + self::DEFINE_NAME_OFFSET][self::CONTENT], 1, -1);
// Grabs the value of the define
$value = $tokens[$i + self::DEFINE_VALUE_OFFSET];
if (!is_array($value)) continue;
// We need to interpret the data type of the define's value
switch (token_name($value[self::TOKEN])) {
case 'T_CONSTANT_ENCAPSED_STRING':
$this->constants[$name] = substr($value[self::CONTENT], 1, -1);
break;
case 'T_LNUMBER':
$this->constants[$name] = intval($value[self::CONTENT]);
break;
case 'T_DNUMBER':
$this->constants[$name] = floatval($value[self::CONTENT]);
break;
case 'T_STRING':
$this->constants[$name] = filter_var($value[self::CONTENT], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
break;
default:
continue 2;
}
}
}
}
/**
* Access the constants as properties
*
* @param string $name
* @return mixed
*/
public function __get($name) {
return $this[$name];
}
/**
* Iterate over the constants
*
* @return iterable
*/
#[\ReturnTypeWillChange]
public function getIterator() {
foreach ($this->constants as $name => $value) yield $name => $value;
foreach (get_defined_constants() as $name => $value) yield $name => $value;
}
/**
* Gives us array access to the constants
*
* @param mixed $offset
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset) {
if (defined($offset)) {
return constant($offset);
} elseif (isset($this->constants[$offset])) {
return $this->constants[$offset];
} else {
return null;
}
}
/**
* Checks if the constant exists
*
* @param mixed $offset
* @return boolean
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset) {
return defined($offset) || isset($this->constants[$offset]);
}
/**
* Check if constant exists
*
* @param string $name
* @return boolean
*/
public function __isset($name) {
return $this->offsetExists($name);
}
/**
* Sets the constant. This is disabled as we want it read-only
*
* @param mixed $offset
* @param mixed $value
* @return void
* @throws \Exception - Throws an exception if called to ensure it's read-only.
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Needed for ArrayAccess interface but not used by us as we require read-only
throw new \Exception('Constants are read-only.');
}
/**
* Unsets the constant. This is disabled as we want it read-only
*
* @param mixed $offset
* @return void
* @throws \Exception - Throws an exception if called to ensure it's read-only.
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Needed for ArrayAccess interface but not used by us as we require read-only
throw new \Exception('Constants are read-only.');
}
}
@@ -0,0 +1,59 @@
<?php
namespace AIOWPS\Firewall;
/**
* Class to help debug the firewall
*/
class Debug {
/**
* Constructs our object
*/
public function __construct() {
//Capture the events that relate to the firewall's rules
Event::capture('rule_triggered', array($this, 'rule_debug'));
Event::capture('rule_not_triggered', array($this, 'rule_debug'));
Event::capture('rule_active', array($this, 'rule_debug'));
Event::capture('rule_not_active', array($this, 'rule_debug'));
}
/**
* Captures the firewall's events for debugging rules
*
* @global Constants $aiowps_firewall_constants
* @global Message_Store $aiowps_firewall_message_store
*
* @param string $event
* @param Rule $rule
*
* @return void
*/
public function rule_debug($event, Rule $rule) {
global $aiowps_firewall_constants, $aiowps_firewall_message_store;
if (!$aiowps_firewall_constants->AIOS_FIREWALL_DEBUG && 'rule_triggered' !== $event) return;
$details = array(
'name' => $rule->name,
'family' => $rule->family,
'ip' => \AIOS_Helper::get_user_ip_address(),
'time' => time(),
);
// Get any user information
foreach ($_COOKIE as $key => $value) {
if (preg_match('/^wordpress_logged_in_/', $key)) {
$details['potential_user'] = stripslashes($value);
break;
}
}
$details['request'] = $_SERVER;
unset($details['request']['HTTP_COOKIE']);
// Uncomment when the firewall log issues have been resolved
//$aiowps_firewall_message_store->set($event, $details);
// Remove when the firewall log issues have been resolved
$aiowps_firewall_message_store->clear_message_store();
}
}
@@ -0,0 +1,51 @@
<?php
namespace AIOWPS\Firewall;
class Event {
/**
* Stores our events
*
* @var array
*/
private static $events = array();
/**
* Captures an event
*
* @param string $name - Name of the event
* @param callable $callback - Callback to execute when the event is raised
* @return void
*/
public static function capture($name, callable $callback) {
$name = strtolower($name);
if (!isset(self::$events[$name])) {
self::$events[$name] = array();
}
self::$events[$name][] = $callback;
}
/**
* Raises the event
*
* All the callbacks in a given name are executed
*
* @param string $name - Name of the event to raise
* @param array ...$args - Variable list of arguments to pass to the callback
* @return void
*/
public static function raise($name, ...$args) {
$name = strtolower($name);
if (empty(self::$events[$name])) return;
array_unshift($args, $name);
foreach (self::$events[$name] as $event) {
call_user_func_array($event, $args);
}
}
}
@@ -0,0 +1,8 @@
<?php
namespace AIOWPS\Firewall;
/**
* Use this when throwing an exception if you want to also exit the request
*/
class Exit_Exception extends \Exception {
}
@@ -0,0 +1,209 @@
<?php
namespace AIOWPS\Firewall;
class Message_Store {
/**
* Makes this class a singleton
*/
use Singleton_Trait;
/**
* Internal store of the messages
*
* @var array
*/
private $messages;
/**
* Holds the name of the message store's table
*
* @var string
*/
private $table_name;
/**
* A key should only be loaded from the database once per request; this keeps track of them
*
* @var array
*/
private $keys_loaded;
/**
* Constructs our object
*/
private function __construct() {
Event::capture('action_before_exit', array($this, 'dump'));
$this->messages = array();
$this->keys_loaded = array();
$this->table_name = 'aiowps_message_store';
}
/**
* Sets internal message store
*
* @param string $key
* @param mixed $value
* @return void
*/
public function set($key, $value) {
if (!is_string($key)) return;
if (!isset($this->messages[$key])) {
$this->messages[$key] = array();
}
$this->messages[$key][] = $value;
}
/**
* Gets the messages associated with a key
*
* @param string $key
* @return array
*/
public function get($key) {
$is_key_loaded = in_array($key, $this->keys_loaded);
$can_check_database = isset($GLOBALS['wpdb']) && !$is_key_loaded && class_exists('Updraft_Semaphore_3_0');
//Load requested messages from the database
if ($can_check_database) {
$lock = new \Updraft_Semaphore_3_0('aios_message_store_lock_'.$key, 60);
$to_delete = array();
if ($lock->lock()) {
try {
global $wpdb;
$table = $this->get_table();
// If we can't get the table to check the DB, still check our internal store for the key
if (empty($table)) return isset($this->messages[$key]) ? $this->messages[$key] : array();
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$rows = $wpdb->get_results($wpdb->prepare("SELECT id, message_value FROM `{$table}` WHERE message_key = %s", $key));
if (!empty($rows)) {
foreach ($rows as $row) {
$values = json_decode($row->message_value, true);
foreach ($values as $value) $this->set($key, $value);
$to_delete[] = $row->id;
}
$this->keys_loaded[] = $key;
}
} catch (\Exception $e) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- PCP warning. Necessary for AIOS error reporting system.
error_log("AIOS: Error getting database entries for key '{$key}': {$e->getMessage()}");
} catch (\Error $e) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound -- this won't run on PHP 5.6 so we still want to catch it on other versions
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- PCP warning. Necessary for AIOS error reporting system.
error_log("AIOS: Error getting database entries for key '{$key}': {$e->getMessage()}");
} finally {
//Delete IDs of loaded messages
if (!empty($to_delete)) {
$ids = implode(',', $to_delete);
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$wpdb->query("DELETE FROM `{$table}` WHERE id IN ({$ids})");
}
$lock->release();
}
}
}
return isset($this->messages[$key]) ? $this->messages[$key] : array();
}
/**
* Dumps the message store to the database
*
* @return void
*/
public function dump() {
//No point saving if there are no messages
if (empty($this->messages)) return;
if (!Utility::attempt_to_access_wpdb()) throw new Exit_Exception('Unable to save the message store to the database: wpdb is inaccessible.');
global $wpdb;
$table = $this->get_table();
if (empty($table)) throw new Exit_Exception('Unable to save messages store to the database: unable to get the correct table.');
$statement = "INSERT INTO `{$table}` (message_key, message_value, created) VALUES ";
$values = array();
foreach ($this->messages as $key => $value) {
$statement .= '(%s, %s, %s),';
$values[] = $table;
$values[] = $key;
$values[] = json_encode($value); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- This method runs outside the WordPress environment and therefore cannot use WordPress functions.
$values[] = time();
}
$statement = rtrim($statement, ',');
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above.
$wpdb->query($wpdb->prepare($statement, $values));
}
/**
* Returns the table name if it exists
*
* @return string - Table name on success; blank string otherwise
*/
private function get_table() {
global $wpdb;
if (!$wpdb) return '';
$table = $wpdb->get_blog_prefix(0).$this->table_name;
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
if ($table != $wpdb->get_var("SHOW TABLES LIKE '{$table}'")) return '';
return $table;
}
/**
* Clears all the messages from the message store table if it contains data.
*
* @return void
*/
public function clear_message_store() {
global $wpdb;
$table = $this->get_table();
// Check if the table exists and is accessible
if (empty($table)) {
return;
}
//Check if the table has any rows
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Direct query necessary. No caching necessary.
$row_exists = $wpdb->get_var(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- PCP error. Ignore.
$wpdb->prepare("SELECT EXISTS (SELECT 1 FROM `{$table}` LIMIT 1)")
);
// If there are no rows, $row_exists will be 0
if (!$row_exists) {
return;
}
// Clear the table (delete all records)
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
$wpdb->query($wpdb->prepare("DELETE FROM `{$table}`"));
}
}
@@ -0,0 +1,36 @@
<?php
namespace AIOWPS\Firewall;
/**
* A trait with a basic singleton implementation
*/
trait Singleton_Trait {
/**
* Internally stores the class's instance
*
* @var object
*/
private static $instance = null;
/**
* Returns an instance of the class
*
* @return object
*/
public static function instance() {
if (is_null(self::$instance)) self::$instance = new self();
return self::$instance;
}
/**
* We don't want our singleton object to be cloned
*
* @return void
*/
private function __clone() {
}
}
@@ -0,0 +1,18 @@
<?php
namespace AIOWPS\Firewall;
/**
* Trait which exits the current request
*/
trait Action_Exit_Trait {
/**
* Exit when the rule condition is satisfied.
*
* @return void
*/
public function do_action() {
Event::raise('action_before_exit');
exit();
}
}
@@ -0,0 +1,23 @@
<?php
namespace AIOWPS\Firewall;
/**
* Combines the forbid and exit trait
*/
trait Action_Forbid_and_Exit_Trait {
use Action_Forbid_Trait, Action_Exit_Trait {
Action_Forbid_Trait::do_action as protected do_action_forbid;
Action_Exit_Trait::do_action as protected do_action_exit;
}
/**
* Forbid 403 and Exit when the rule condition is satisfied.
*
* @return void
*/
public function do_action() {
$this->do_action_forbid();
$this->do_action_exit();
}
}
@@ -0,0 +1,17 @@
<?php
namespace AIOWPS\Firewall;
/**
* Trait to set the header to forbidden
*/
trait Action_Forbid_Trait {
/**
* Forbid 403 when the rule condition is satisfied.
*
* @return void
*/
public function do_action() {
header('HTTP/1.1 403 Forbidden');
}
}
@@ -0,0 +1,88 @@
<?php
namespace AIOWPS\Firewall;
trait Action_Permblock_and_Exit_Trait {
/**
* Use the forbid and exit trait
*/
use Action_Forbid_and_Exit_Trait {
Action_Forbid_and_Exit_Trait::do_action as protected do_action_forbid_and_exit;
}
/**
* Holds the reason for the perm. block
*
* @var string
*/
private $permblock_reason = '';
/**
* Holds the IP for the perm. block
*
* @var string
*/
private $permblock_ip = '';
/**
* Sets the reason for the perm. block
*
* @param string $reason
* @return void
*/
public function set_perm_block_reason($reason) {
$this->permblock_reason = $reason;
}
/**
* Sets the IP for the perm. block
*
* @param string $ip
* @return void
*/
public function set_perm_block_ip($ip) {
$this->permblock_ip = $ip;
}
/**
* Permanently ban the IP and exit when the rule condition is satisfied.
*
* @return void
*/
public function do_action() {
if (!Utility::attempt_to_access_wpdb()) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- PCP warning. Part of AIOS error reporting system.
error_log('AIOS: Unable to access wpdb to ban IP address.');
$this->do_action_forbid_and_exit();
}
global $wpdb;
$table = $wpdb->prefix.'aiowps_permanent_block';
$ip = empty($this->permblock_ip) ? \AIOS_Helper::get_user_ip_address() : $this->permblock_ip;
$data = array(
'blocked_ip' => $ip,
'block_reason' => empty($this->permblock_reason) ? 'firewall_generic' : $this->permblock_reason,
'blocked_date' => current_time('mysql')
);
// Check if the IP already exists
// phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery -- PCP error. Table name cannot be done via prepare.
$already_exists = $wpdb->get_var($wpdb->prepare("SELECT blocked_ip FROM `{$table}` WHERE blocked_ip = %s", $ip));
// If it does exist, no point adding it again so just forbid and exit
if (!is_null($already_exists)) $this->do_action_forbid_and_exit();
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Table name cannot be done via prepare.
if (false === $wpdb->query($wpdb->prepare("INSERT INTO " .$table." (blocked_ip, block_reason, blocked_date, created) VALUES (%s, %s, %s, UNIX_TIMESTAMP())", $data['blocked_ip'], $data['block_reason'], $data['blocked_date']))) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- PCP warning. Needed for error reporting.
error_log('AIOS: Unable to insert IP address into table.');
}
$this->do_action_forbid_and_exit();
}
}
@@ -0,0 +1,23 @@
<?php
namespace AIOWPS\Firewall;
/**
* Combines the redirect and exit trait
*/
trait Action_Redirect_and_Exit_Trait {
use Action_Redirect_Trait, Action_Exit_Trait {
Action_Redirect_Trait::do_action as protected do_action_redirect;
Action_Exit_Trait::do_action as protected do_action_exit;
}
/**
* Redirect and Exit when the rule condition is satisfied.
*
* @return void
*/
public function do_action() {
$this->do_action_redirect();
$this->do_action_exit();
}
}
@@ -0,0 +1,24 @@
<?php
namespace AIOWPS\Firewall;
/**
* Trait to set the header to redirect
*/
trait Action_Redirect_Trait {
/**
* Redirect to the location.
*
* @var string
*/
public $location = '127.0.0.1';
/**
* Redirect the rule condition is satisfied.
*
* @return void
*/
public function do_action() {
header("Location: $this->location");
}
}
@@ -0,0 +1,62 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks certain kinds of data from the query string
*/
class Rule_Block_Query_Strings_6g extends Rule {
/**
* Implements the action to be taken
*/
use Action_Forbid_and_Exit_Trait;
/**
* Construct our rule
*/
public function __construct() {
// Set the rule's metadata
$this->name = 'Block query strings';
$this->family = '6G';
$this->priority = 0;
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_config;
return (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_query');
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
if (empty($_SERVER['QUERY_STRING'])) return Rule::NOT_SATISFIED;
//Patterns to match against
$patterns = array(
'/[a-z0-9]{2000,}/i',
'/(eval\()/i',
'/(127\.0\.0\.1)/i',
'/(javascript:)(.*)(;)/i',
'/(base64_encode)(.*)(\()/i',
'/(GLOBALS|REQUEST)(=|\[|%)/i',
'/(<|%3C)(.*)script(.*)(>|%3)/i',
'#(\|\.\.\.|\.\./|~|`|<|>|\|)#i',
'#(boot\.ini|etc/passwd|self/environ)#i',
'/(thumbs?(_editor|open)?|tim(thumb)?)\.php/i',
'/(\'|\")(.*)(drop|insert|md5|select|union)/i',
);
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing will interfere with 6g rules.
return Rule_Utils::contains_pattern(rawurldecode($_SERVER['QUERY_STRING']), $patterns);
}
}
@@ -0,0 +1,52 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks certain referrers recommended by 6G
*/
class Rule_Block_Refs_6g extends Rule {
/**
* Implements the action to be taken
*/
use Action_Forbid_and_Exit_Trait;
/**
* Construct our rule
*/
public function __construct() {
// Set the rule's metadata
$this->name = 'Block referrer strings';
$this->family = '6G';
$this->priority = 0;
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_config;
return (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_referrers');
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
if (empty($_SERVER['HTTP_REFERER'])) return Rule::NOT_SATISFIED;
//Patterns to match against
$patterns = array(
'/[a-z0-9]{2000,}/i',
'/(semalt.com|todaperfeita)/i',
);
return Rule_Utils::contains_pattern($_SERVER['HTTP_REFERER'], $patterns); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is not a WordPress context. Also this only evaluates to a boolean.
}
}
@@ -0,0 +1,67 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks certain kinds of data from the request string
*/
class Rule_Block_Request_Strings_6g extends Rule {
/**
* Implements the action to be taken
*/
use Action_Forbid_and_Exit_Trait;
/**
* Construct our rule
*/
public function __construct() {
// Set the rule's metadata
$this->name = 'Block request strings';
$this->family = '6G';
$this->priority = 0;
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_config;
return (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_request');
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
if (empty($_SERVER['REQUEST_URI'])) return Rule::NOT_SATISFIED;
// ensure we get the request uri without the query string
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing will interfere with 6g rules.
$uri = (string) parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ('' == $uri) return Rule::NOT_SATISFIED;
//Patterns to match against
$patterns = array(
'/[a-z0-9]{2000,}/i',
'#(https?|ftp|php):/#i',
'#(base64_encode)(.*)(\()#i',
'#(=\'|=\%27|/\'/?)\.#i',
'#/(\$(\&)?|\*|\"|\.|,|&|&amp;?)/?$#i',
'#(\{0\}|\(/\(|\.\.\.|\+\+\+|\\"\\")#i',
'#(~|`|<|>|:|;|,|%|\|\s|\{|\}|\[|\]|\|)#i',
'#/(=|\$&|_mm|cgi-|etc/passwd|muieblack)#i',
'#(&pws=0|_vti_|\(null\)|\{\$itemURL\}|echo(.*)kae|etc/passwd|eval\(|self/environ)#i',
'#\.(aspx?|bash|bak?|cfg|cgi|dll|exe|git|hg|ini|jsp|log|mdb|out|sql|svn|swp|tar|rar|rdf)$#i',
'#/(^$|(wp-)?config|mobiquo|phpinfo|shell|sqlpatch|thumb|thumb_editor|thumbopen|timthumb|webshell)\.php#i',
);
return Rule_Utils::contains_pattern(rawurldecode($uri), $patterns);
}
}
@@ -0,0 +1,53 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks certain user-agents recommended by 6G
*/
class Rule_Block_User_Agents_6g extends Rule {
/**
* Implements the action to be taken
*/
use Action_Forbid_and_Exit_Trait;
/**
* Construct our rule
*/
public function __construct() {
// Set the rule's metadata
$this->name = 'Block user-agents';
$this->family = '6G';
$this->priority = 0;
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_config;
return (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_agents');
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
if (empty($_SERVER['HTTP_USER_AGENT'])) return Rule::NOT_SATISFIED;
//Patterns to match against
$patterns = array(
'/[a-z0-9]{2000,}/i',
'/(archive.org|binlar|casper|checkpriv|choppy|clshttp|cmsworld|diavol|dotbot|extract|feedfinder|flicky|g00g1e|harvest|heritrix|httrack|kmccrew|loader|miner|nikto|nutch|planetwork|postrank|purebot|pycurl|python|seekerspider|siclab|skygrid|sqlmap|sucker|turnit|vikspider|winhttp|xxxyy|youda|zmeu|zune)/i',
);
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing will interfere with 6g rules.
return Rule_Utils::contains_pattern($_SERVER['HTTP_USER_AGENT'], $patterns);
}
}
@@ -0,0 +1,53 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks certain kinds of HTTP request methods (e.g DEBUG or PUT)
*/
class Rule_Request_Method_6g extends Rule {
/**
* Implements the action to be taken
*/
use Action_Forbid_and_Exit_Trait;
/**
* List of request methods to block
*
* @var array
*/
private $blocked_methods;
/**
* Construct our rule
*/
public function __construct() {
global $aiowps_firewall_config;
// Set the rule's metadata
$this->name = 'Block request methods';
$this->family = '6G';
$this->priority = 0;
$this->blocked_methods = $aiowps_firewall_config->get_value('aiowps_6g_block_request_methods');
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
return !empty($this->blocked_methods);
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
return isset($_SERVER['REQUEST_METHOD']) && in_array(strtoupper($_SERVER['REQUEST_METHOD']), $this->blocked_methods);
}
}
@@ -0,0 +1,65 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks IPs to access.
*/
class Rule_Ips_Blacklist extends Rule {
/**
* Implements the action to be taken
*/
use Action_Forbid_and_Exit_Trait;
/**
* List of IPs / IP range to block
*
* @var array
*/
private $blocked_ips;
/**
* Construct our rule
*
* @global Config $aiowps_firewall_config
*/
public function __construct() {
global $aiowps_firewall_config;
// Set the rule's metadata
$this->name = 'Blocked IPs';
$this->family = 'Blacklist';
$this->priority = 0;
$this->blocked_ips = $aiowps_firewall_config->get_value('aiowps_blacklist_ips');
}
/**
* Determines whether the rule is active
*
* @global Constants $aiowps_firewall_constants
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_constants;
if ($aiowps_firewall_constants->AIOS_DISABLE_BLACKLIST_IP_MANAGER) {
return false;
} else {
return !empty($this->blocked_ips);
}
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
$user_ip_blocked = \AIOS_Helper::is_user_ip_address_within_list($this->blocked_ips);
if (true == $user_ip_blocked) return Rule::SATISFIED;
return Rule::NOT_SATISFIED;
}
}
@@ -0,0 +1,57 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks user agents to access.
*/
class Rule_User_Agent_Blacklist extends Rule {
/**
* Implements the action to be taken
*/
use Action_Forbid_and_Exit_Trait;
/**
* List of user agents to block
*
* @var array
*/
private $blocked_user_agents;
/**
* Construct our rule
*/
public function __construct() {
global $aiowps_firewall_config;
// Set the rule's metadata
$this->name = 'Blocked user agents';
$this->family = 'Blacklist';
$this->priority = 0;
$this->blocked_user_agents = $aiowps_firewall_config->get_value('aiowps_blacklist_user_agents');
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
return !empty($this->blocked_user_agents) && isset($_SERVER['HTTP_USER_AGENT']);
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
foreach ($this->blocked_user_agents as $block_user_agent) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing will interfere with 6g rules.
if (isset($_SERVER['HTTP_USER_AGENT']) && !empty($block_user_agent) && false !== stripos($_SERVER['HTTP_USER_AGENT'], $block_user_agent)) {
return Rule::SATISFIED;
}
}
return Rule::NOT_SATISFIED;
}
}
@@ -0,0 +1,44 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that bans the IP address if the POST request has a blank user-agent and referer
*/
class Rule_Ban_Post_Blank_Headers extends Rule {
/**
* Implements the action to be taken
*/
use Action_Permblock_and_Exit_Trait;
/**
* Construct our rule
*/
public function __construct() {
// Set the rule's metadata
$this->name = 'Ban POST requests with blank user-agent and referer';
$this->family = 'Bots';
$this->priority = 10;
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_config;
return (bool) $aiowps_firewall_config->get_value('aiowps_ban_post_blank_headers');
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
$this->set_perm_block_reason('firewall_post_blank_user_agent_and_referer');
return isset($_SERVER['REQUEST_METHOD']) && (0 === strcasecmp($_SERVER['REQUEST_METHOD'], "POST")) && empty($_SERVER['HTTP_USER_AGENT']) && empty($_SERVER['HTTP_REFERER']); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is not a WordPress context. Also this only evaluates to a boolean.
}
}
@@ -0,0 +1,101 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks fake Googlebots.
*/
class Rule_Block_Fake_Googlebots extends Rule {
/**
* Implements the action to be taken.
*/
use Action_Exit_Trait;
/**
* Construct our rule.
*/
public function __construct() {
// Set the rule's metadata.
$this->name = 'Block fake Googlebots';
$this->family = 'Bots';
$this->priority = 0;
}
/**
* Determines whether the rule is active.
*
* @global Config $aiowps_firewall_config
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_config;
return (bool) $aiowps_firewall_config->get_value('aiowps_block_fake_googlebots');
}
/**
* The condition to be satisfied for the rule to apply.
*
* @global Config $aiowps_firewall_config
*
* @return boolean
*/
public function is_satisfied() {
global $aiowps_firewall_config;
$user_agent = (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
if (preg_match('/Googlebot/i', $user_agent, $matches)) {
// If the user agent says it's a Googlebot, start doing checks.
$ip = \AIOS_Helper::get_user_ip_address();
if (empty($ip)) {
return Rule::NOT_SATISFIED;
}
try {
$name = gethostbyaddr($ip); // Let's get the hostname using the IP address.
if ($name == $ip || false === $name) {
// gethostbyaddr failed.
$googlebot_ips = $aiowps_firewall_config->get_value('aiowps_googlebot_ip_ranges');
if (\AIOS_Helper::is_user_ip_address_within_list($googlebot_ips)) {
return Rule::NOT_SATISFIED;
} else {
return Rule::SATISFIED;
}
}
$host_ip = gethostbyname($name); // Reverse lookup - let's get the IP address using the hostname.
} catch (\Exception $e) {
// gethostbyaddr or gethostbyname not available on site.
$googlebot_ips = $aiowps_firewall_config->get_value('aiowps_googlebot_ip_ranges');
if (\AIOS_Helper::is_user_ip_address_within_list($googlebot_ips)) {
return Rule::NOT_SATISFIED;
} else {
return Rule::SATISFIED;
}
} catch (\Error $e) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound -- this won't run on PHP 5.6 so we still want to catch it on other versions
// gethostbyaddr or gethostbyname not available on site.
$googlebot_ips = $aiowps_firewall_config->get_value('aiowps_googlebot_ip_ranges');
if (\AIOS_Helper::is_user_ip_address_within_list($googlebot_ips)) {
return Rule::NOT_SATISFIED;
} else {
return Rule::SATISFIED;
}
}
if (preg_match('/^(?:.+\.)?googlebot\.com$/i', $name) || preg_match('/^(?:.+\.)?google\.com$/i', $name) || preg_match('/^(?:.+\.)?googleusercontent\.com$/i', $name)) {
if ($host_ip == $ip) {
return Rule::NOT_SATISFIED;
} else {
return Rule::SATISFIED;
}
} else {
return Rule::SATISFIED;
}
}
}
}
@@ -0,0 +1,98 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that uses a cookie to prevent bruteforce attacks.
*/
class Rule_Cookie_Prevent_Bruteforce extends Rule {
/**
* Implements the action to be taken
*/
use Action_Redirect_and_Exit_Trait;
/**
* Construct our rule
*/
public function __construct() {
// Set the rule's metadata
$this->name = 'Cookie based prevent bruteforce';
$this->family = 'Bruteforce';
$this->priority = 0;
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_config, $aiowps_firewall_constants;
if ($aiowps_firewall_constants->AIOS_DISABLE_COOKIE_BRUTE_FORCE_PREVENTION) {
return false;
} else {
return (bool) $aiowps_firewall_config->get_value('aios_enable_brute_force_attack_prevention');
}
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
global $aiowps_firewall_config;
/**
* This rule is not applied at AIOS plugin activation time.
*/
$is_plugins_page = isset($_SERVER['SCRIPT_FILENAME']) && 1 === preg_match('#/wp-admin/(network/)?plugins\.php$#i', $_SERVER['SCRIPT_FILENAME']);
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. A nonce is not available at this point.
$is_activation_action = isset($_GET['action']) && 'activate' === $_GET['action'];
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. A nonce is not available at this point.
$is_target_plugin = isset($_GET['plugin']) && 'all-in-one-wp-security-and-firewall/wp-security.php' === $_GET['plugin'];
if ($is_plugins_page && $is_activation_action && $is_target_plugin) {
return Rule::NOT_SATISFIED;
}
$brute_force_secret_word = $aiowps_firewall_config->get_value('aios_brute_force_secret_word');
$brute_force_secret_cookie_name = $aiowps_firewall_config->get_value('aios_brute_force_secret_cookie_name');
$login_page_slug = $aiowps_firewall_config->get_value('aios_login_page_slug');
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. A nonce is not available at this point.
if (!isset($_GET[$brute_force_secret_word])) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing is not required, as we validate the raw input.
$brute_force_secret_cookie_val = isset($_COOKIE[$brute_force_secret_cookie_name]) ? $_COOKIE[$brute_force_secret_cookie_name] : '';
$pw_protected_exception = $aiowps_firewall_config->get_value('aios_brute_force_attack_prevention_pw_protected_exception');
$prevent_ajax_exception = $aiowps_firewall_config->get_value('aios_brute_force_attack_prevention_ajax_exception');
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing is not required, as we validate the raw input.
if (!empty($_SERVER['REQUEST_URI']) && !hash_equals($brute_force_secret_cookie_val, \AIOS_Helper::get_hash($brute_force_secret_word))) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing is not required, as we validate the raw input.
$request_uri = (string) parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// admin section or login page or login custom slug called
$is_admin_or_login = (false != strpos($request_uri, 'wp-admin') || false != strpos($request_uri, 'wp-login') || ('' != $login_page_slug && false != strpos($request_uri, $login_page_slug))) ? 1 : 0;
// admin side ajax called
$is_admin_ajax_request = ('1' == $prevent_ajax_exception && isset($_SERVER['SCRIPT_NAME']) && ('admin-ajax.php' === basename($_SERVER['SCRIPT_NAME']))) ? 1 : 0;
// password protected page called
$is_password_protected_access = ('1' == $pw_protected_exception && isset($_GET['action']) && 'postpass' == $_GET['action']) ? 1 : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. A nonce is not available at this point.
// logout, set password, reset password action called
$is_logout_resetpassword_action = (isset($_GET['action']) && ('logout' == $_GET['action'] || 'rp' == $_GET['action'] || 'resetpass' == $_GET['action'])) ? 1 : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. A nonce is not available at this point.
// cookie based brute force on and accessing admin without ajax and password protected then redirect
if ($is_admin_or_login && !$is_admin_ajax_request && !$is_password_protected_access && !$is_logout_resetpassword_action) {
$redirect_url = $aiowps_firewall_config->get_value('aios_cookie_based_brute_force_redirect_url');
$this->location = $redirect_url;
return Rule::SATISFIED;
}
}
}
return Rule::NOT_SATISFIED;
}
}
@@ -0,0 +1,156 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks certain kinds of data from the request string
*/
class Rule_Advanced_Character_Filter extends Rule {
/**
* Implements the action to be taken
*/
use Action_Forbid_and_Exit_Trait;
/**
* Construct our rule
*/
public function __construct() {
// Set the rule's metadata
$this->name = 'Advanced character filter';
$this->family = 'General';
$this->priority = 10;
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_config;
return (bool) $aiowps_firewall_config->get_value('aiowps_advanced_char_string_filter');
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
if (empty($_SERVER['REQUEST_URI'])) return Rule::NOT_SATISFIED;
// ensure we get the request uri without the query string
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing will interfere with 6g rules.
$uri = (string) parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
return Rule_Utils::contains_pattern($uri, array_merge($this->get_general_characters(), $this->get_common_patterns(), $this->get_specific_exploits()));
}
/**
* Get the list of 'specific exploits' patterns
*
* @return array
*/
private function get_specific_exploits() {
return array(
'/errors\./i',
'/config\./i',
'/include\./i',
'/display\./i',
'/register\./i',
'/password\./i',
'/maincore\./i',
'/authorize\./i',
'/macromates\./i',
'/head\_auth\./i',
'/submit\_links\./i',
'/change\_action\./i',
'/com\_facileforms\//i',
'/admin\_db\_utilities\./i',
'/admin\.webring\.docs\./i',
'/Table\/Latest\/index\./i',
);
}
/**
* Get the list of common patterns
*
* @return array
*/
private function get_common_patterns() {
return array(
'/\_vpi/i',
'/\.inc/i',
'/xAou6/i',
'/db\_name/i',
'/select\(/i',
'/convert\(/i',
'/\/query\//i',
'/ImpEvData/i',
'/\.XMLHTTP/i',
'/proxydeny/i',
'/function\./i',
'/remoteFile/i',
'/servername/i',
'/\&rptmode\=/i',
'/sys\_cpanel/i',
'/db\_connect/i',
'/doeditconfig/i',
'/check\_proxy/i',
'/system\_user/i',
'/\/\(null\)\//i',
'/clientrequest/i',
'/option\_value/i',
'/ref\.outcontrol/i',
);
}
/**
* Get the list of general characters
*
* @return array
*/
private function get_general_characters() {
return array(
'/\,/i',
'/\:/i',
'/\;/i',
'/\=/i',
'/\[/i',
'/\]/i',
'/\^/i',
'/\`/i',
'/\{/i',
'/\}/i',
'/\~/i',
'/\"/i',
'/\$/i',
'/\</i',
'/\>/i',
'/\|/i',
'/\.\./i',
'/\%0/i',
'/\%A/i',
'/\%B/i',
'/\%C/i',
'/\%D/i',
'/\%E/i',
'/\%F/i',
'/\%22/i',
'/\%27/i',
'/\%28/i',
'/\%29/i',
'/\%3C/i',
'/\%3E/i',
'/\%3F/i',
'/\%5B/i',
'/\%5C/i',
'/\%5D/i',
'/\%7B/i',
'/\%7C/i',
'/\%7D/i',
);
}
}
@@ -0,0 +1,56 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks certain data from the URL's query string
*/
class Rule_Bad_Query_Strings extends Rule {
/**
* Implements the action to be taken
*/
use Action_Forbid_and_Exit_Trait;
/**
* Construct our rule
*/
public function __construct() {
// Set the rule's metadata
$this->name = 'Bad query strings';
$this->family = 'General';
$this->priority = 10;
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_config;
return (bool) $aiowps_firewall_config->get_value('aiowps_deny_bad_query_strings');
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
if (empty($_SERVER['QUERY_STRING'])) return Rule::NOT_SATISFIED;
$patterns = array(
'/ftp:/i',
'/http:/i',
'/https:/i',
'/mosConfig/i',
'/^.*(globals|encode|loopback).*/i',
"/(\;|'|\"|%22).*(request|insert|union|declare|drop)/i",
);
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing will interfere with 6g rules.
return Rule_Utils::contains_pattern($_SERVER['QUERY_STRING'], $patterns);
}
}
@@ -0,0 +1,44 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks access to the xmlrpc.php file
*/
class Rule_Block_Xmlrpc extends Rule {
/**
* Implements the action to be taken
*/
use Action_Forbid_and_Exit_Trait;
/**
* Construct our rule
*/
public function __construct() {
// Set the rule's metadata
$this->name = 'Completely block XMLRPC';
$this->family = 'General';
$this->priority = 10;
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_config;
return (bool) $aiowps_firewall_config->get_value('aiowps_enable_pingback_firewall');
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing will interfere with 6g rules.
return (isset($_SERVER['SCRIPT_FILENAME']) && 1 === preg_match('/\/xmlrpc\.php$/i', $_SERVER['SCRIPT_FILENAME']));
}
}
@@ -0,0 +1,69 @@
<?php
namespace AIOWPS\Firewall;
/**
* Rule that blocks comments being posted if a proxy is detected.
*/
class Rule_Proxy_Comment_Posting extends Rule {
/**
* Implements the action to be taken
*/
use Action_Forbid_and_Exit_Trait;
/**
* Construct our rule
*/
public function __construct() {
// Set the rule's metadata
$this->name = 'Proxy comment posting';
$this->family = 'General';
$this->priority = 10;
}
/**
* Determines whether the rule is active
*
* @return boolean
*/
public function is_active() {
global $aiowps_firewall_config;
return (bool) $aiowps_firewall_config->get_value('aiowps_forbid_proxy_comments');
}
/**
* The condition to be satisfied for the rule to apply
*
* @return boolean
*/
public function is_satisfied() {
//Preconditions for the rule
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing will interfere with 6g rules.
$is_comment_form = (isset($_SERVER['SCRIPT_FILENAME']) && 1 === preg_match('/\/wp-comments-post\.php$/i', $_SERVER['SCRIPT_FILENAME']));
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PCP warning. Sanitizing will interfere with 6g rules.
$is_post = (isset($_SERVER['REQUEST_METHOD']) && 0 === strcasecmp($_SERVER['REQUEST_METHOD'], "POST"));
if (!$is_post || !$is_comment_form) return Rule::NOT_SATISFIED;
//Headers that are present if a proxy is being used
$headers = array(
'HTTP_VIA',
'HTTP_FORWARDED',
'HTTP_USERAGENT_VIA',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED_HOST',
'HTTP_PROXY_CONNECTION',
'HTTP_XPROXY_CONNECTION',
'HTTP_PC_REMOTE_ADDR',
'HTTP_CLIENT_IP',
);
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) return Rule::SATISFIED;
}
return Rule::NOT_SATISFIED;
}
}
@@ -0,0 +1,46 @@
<?php
namespace AIOWPS\Firewall;
/**
* Builds our rules
*/
class Rule_Builder {
/**
* Gets our rule if it's active
*
* @return iterable
*/
public static function get_active_rule() {
foreach (self::get_rule_classname() as $classname) {
$rule = new $classname();
if (!$rule->is_active()) {
Event::raise('rule_not_active', $rule, $classname);
continue;
}
Event::raise('rule_active', $rule, $classname);
yield $rule;
}
}
/**
* Generates the classname for each rule
*
* @return iterable
*/
private static function get_rule_classname() {
$rec_iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(AIOWPS_FIREWALL_DIR.'/rule/rules/', \FilesystemIterator::SKIP_DOTS));
foreach ($rec_iterator as $dir_iterator) {
$matches = array();
if (preg_match('/^rule-(?<rule_name>.*)\.php$/', $dir_iterator->getFilename(), $matches)) {
yield "AIOWPS\Firewall\Rule_".ucwords(str_replace('-', '_', $matches['rule_name']), '_');
}
}
}
}
@@ -0,0 +1,31 @@
<?php
namespace AIOWPS\Firewall;
/**
* Utility methods to help with the rules
*/
class Rule_Utils {
/**
* Check if the subject contains the given pattern or patterns
*
* @param string $subject - The subject we wish to check the pattern or patterns against.
* @param string|array $pattern - Regex pattern. An array for multiple patterns; a string otherwise.
* @return boolean
*/
public static function contains_pattern($subject, $pattern) {
if (empty($subject)) return false;
if (is_string($pattern)) return (1 === preg_match($pattern, $subject));
if (!is_array($pattern)) return false;
foreach ($pattern as $patt) {
if (preg_match($patt, $subject)) return true;
}
return false;
}
}
@@ -0,0 +1,91 @@
<?php
namespace AIOWPS\Firewall;
/**
* Base class for our firewall rules
*/
abstract class Rule {
/**
* Name of the rule
*
* @var string
*/
public $name;
/**
* Name of the family the rule belongs to
*
* @var string
*/
public $family;
/**
* Rule's priority (0 is the highest)
*
* @var int
*/
public $priority;
/**
* An abstraction for when the rule is satisfied
*
* @var boolean
*/
const SATISFIED = true;
/**
* An abstraction for when the rule is not satisfied
*
* @var boolean
*/
const NOT_SATISFIED = false;
/**
* Executes the rule's action
*
* @return void
*/
abstract public function do_action();
/**
* Check if the rule is active
*
* @return boolean
*/
abstract public function is_active();
/**
* Check if the rule has been satisfied
*
* @return boolean
*/
abstract public function is_satisfied();
/**
* Apply the rule and execute the action if satisfied
*
* @return void
*/
public function apply() {
if ($this->is_satisfied()) {
Event::raise('rule_triggered', $this, time());
$this->do_action();
}
Event::raise('rule_not_triggered', $this, time());
}
/**
* Show the rule's name
*
* @return string
*/
public function __toString() {
return $this->name;
}
}
@@ -0,0 +1,104 @@
<?php
namespace AIOWPS\Firewall;
/**
* The firewall can be loaded from several different contexts. This class detects from which context the firewall is loaded.
*/
class Context {
/**
* Possible contexts where the firewall can be loaded
*/
const DIRECTIVE = 'directive';
const PLUGINS_LOADED = 'plugins_loaded';
const WP_CONFIG = 'wp-config';
const MU_PLUGIN = 'mu-plugin';
/**
* Get the current context where the firewall is running
*
* @return string
*/
public static function current() {
$incs = get_included_files();
$index = self::get_bootstrap_index($incs);
$is_setup = (-1 !== $index);
if (!$is_setup) return self::PLUGINS_LOADED;
if (0 === $index) return self::DIRECTIVE;
if (preg_match('/wp-config\.php$/i', $incs[$index-1])) {
return self::WP_CONFIG;
}
if (preg_match('/aios-firewall-loader\.php$/', $incs[$index-1])) {
return self::MU_PLUGIN;
}
return self::DIRECTIVE;
}
/**
* Check if we're in a context safe to run WordPress functions
*
* @return boolean
*/
public static function wordpress_safe() {
return (self::plugins_loaded() || self::mu_plugin());
}
/**
* Check if the current context is `plugins_loaded`
*
* @return boolean
*/
public static function plugins_loaded() {
return (self::PLUGINS_LOADED === self::current());
}
/**
* Check if the current context is `directive` (i.e: auto_prepend_file)
*
* @return boolean
*/
public static function directive() {
return (self::DIRECTIVE === self::current());
}
/**
* Check if the current context is `wp_config`
*
* @return boolean
*/
public static function wp_config() {
return (self::WP_CONFIG === self::current());
}
/**
* Check if the current context is `mu_plugin`
*
* @return boolean
*/
public static function mu_plugin() {
return (self::MU_PLUGIN === self::current());
}
/**
* Locate the bootstrap file's index
*
* @param array $incs
* @return int
*/
private static function get_bootstrap_index(array $incs) {
foreach ($incs as $index => $file) {
if (preg_match('/aios-bootstrap\.php$/', $file)) {
return $index;
}
}
return -1;
}
}
@@ -0,0 +1,214 @@
<?php
namespace AIOWPS\Firewall;
if (!defined('AIOWPS_FIREWALL_DIR')) {
header('HTTP/1.1 403 Forbidden');
exit();
}
/**
* Loads and executes our firewall
*/
class Loader {
/**
* Reference to itself
*
* @var Loader
*/
protected static $instance = null;
/**
* Loads and builds all the necessary files
*
* @return void
*/
public function load_firewall() {
try {
/**
* The preloader file should not be directly accessed.
* It should only be loaded via the bootstrap file or in a WordPress context
*/
if ($this->is_preloader_directly_accessed()) return;
$this->init();
global $aiowps_firewall_constants;
if ($aiowps_firewall_constants->AIOS_NO_FIREWALL) return;
//Allow list for bypassing PHP rules
if (Allow_List::is_ip_allowed()) return;
$families = new Family_Collection(Family_Builder::get_families());
foreach (Rule_Builder::get_active_rule() as $rule) {
$families->add_rule_to_member($rule);
}
foreach ($families->get_family() as $member) {
$member->apply_all();
}
} catch (Exit_Exception $e) {
$this->log_message($e->getMessage());
exit();
} catch (\Exception $e) {
$this->log_message($e->getMessage());
} catch (\Error $e) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound -- this won't run on PHP 5.6 so we still want to catch it on other versions
$this->log_message($e->getMessage());
}
}
/**
* Performs general initialisation
*
* @return void
*/
public function init() {
$this->init_includes();
$this->init_services();
new Debug();
}
/**
* Detects whether the preloader file (wp-security-firewall.php) was directly accessed
*
* @return boolean
*/
public function is_preloader_directly_accessed() {
return (1 === preg_match('/wp-security-firewall\.php$/', get_included_files()[0]));
}
/**
* Log our error messages
*
* @param string $message
* @return void
*/
private function log_message($message) {
if (function_exists('do_action')) {
do_action('aios_firewall_loader_log_error', $message, $this);
}
error_log('AIOS firewall error: ' . $message);
}
/**
* Initialises our services
*
* @return void
*/
private function init_services() {
$workspace = $this->get_firewall_workspace();
if (empty($workspace)) {
throw new \Exception('unable to locate workspace.');
}
$GLOBALS['aiowps_firewall_config'] = new Config($workspace . 'settings.php');
$GLOBALS['aiowps_firewall_message_store'] = Message_Store::instance();
$GLOBALS['aiowps_firewall_constants'] = new Constants();
Allow_List::set_path($workspace.'allowlist.php');
}
/**
* Get our workspace directory, i.e., where we save and load data to and from.
*
* @return string
*/
private function get_firewall_workspace() {
global $aiowps_firewall_rules_path;
$workspace = '';
if (!empty($aiowps_firewall_rules_path)) {
$workspace = $aiowps_firewall_rules_path;
} elseif (Context::wordpress_safe()) {
$workspace = \AIOWPSecurity_Utility_Firewall::get_firewall_rules_path();
}
return $workspace;
}
/**
* Registers the autoloader
*
* @return void
*/
private function init_includes() {
spl_autoload_register(function($class) {
if (0 === strpos($class, "AIOWPS\\Firewall\\")) { //only autoload the firewall's files
$relative_classname = substr($class, strlen("AIOWPS\\Firewall\\"), strlen($class)-1);
$classname = str_replace('_', '-', strtolower($relative_classname));
$file = "wp-security-firewall-{$classname}.php";
$rule = "{$classname}.php";
$paths = array(
AIOWPS_FIREWALL_DIR."/{$file}",
AIOWPS_FIREWALL_DIR."/family/{$file}",
AIOWPS_FIREWALL_DIR."/rule/{$file}",
AIOWPS_FIREWALL_DIR."/rule/actions/{$classname}.php",
AIOWPS_FIREWALL_DIR."/rule/rules/{$rule}",
AIOWPS_FIREWALL_DIR."/rule/rules/6g/{$rule}",
AIOWPS_FIREWALL_DIR."/rule/rules/bruteforce/{$rule}",
AIOWPS_FIREWALL_DIR."/rule/rules/blacklist/{$rule}",
AIOWPS_FIREWALL_DIR."/rule/rules/general/{$rule}",
AIOWPS_FIREWALL_DIR."/rule/rules/bots/{$rule}",
AIOWPS_FIREWALL_DIR."/libs/{$file}",
AIOWPS_FIREWALL_DIR."/libs/traits/{$classname}.php",
);
clearstatcache();
foreach ($paths as $path) {
if (file_exists($path)) {
include_once($path);
break;
}
}
}
});
// Manually include needed files
$classes_dir = dirname(AIOWPS_FIREWALL_DIR);
$manual_files = array(
$classes_dir.'/wp-security-firewall-resource-unavailable.php',
$classes_dir.'/wp-security-firewall-resource.php',
$classes_dir.'/wp-security-helper.php',
);
foreach ($manual_files as $file) {
clearstatcache();
if (file_exists($file)) include_once $file;
}
if (Context::wordpress_safe()) {
include_once("{$classes_dir}/wp-security-utility-file.php");
}
}
/**
* Gets or creates an instance of this object
*
* @return Loader
*/
public static function get_instance() {
if (null === self::$instance) {
return new self();
}
return self::$instance;
}
}
@@ -0,0 +1,218 @@
<?php
namespace AIOWPS\Firewall;
class Utility {
/**
* Returns the directory of where the WordPress files are installed
* This differs from get_root_dir() when WordPress is setup in a subdirectory
*
* @return string
*/
public static function get_wordpress_dir() {
if (Context::wordpress_safe()) {
return wp_normalize_path(ABSPATH);
}
global $aiowps_firewall_data;
return isset($aiowps_firewall_data['ABSPATH']) ? $aiowps_firewall_data['ABSPATH'] : '';
}
/**
* Returns the root directory of the site
* This may be different from where the WordPress files are installed if WordPress is setup in a subdirectory
*
* @return string|null
*/
public static function get_root_dir() {
if (Context::wordpress_safe()) {
return \AIOWPSecurity_Utility_File::get_home_path();
}
// We're in the firewall context here, so get the root directory from the bootstrap file path
$includes = get_included_files();
foreach ($includes as $file) {
if (preg_match('/aios-bootstrap\.php$/', $file)) {
return self::normalize_path(dirname($file).'/');
}
}
return null;
}
/**
* Normalizes the file path
*
* @see https://developer.wordpress.org/reference/functions/wp_normalize_path/
* @param string $path
* @return string
*/
public static function normalize_path($path) {
// Standardize all paths to use '/'.
$path = str_replace('\\', '/', $path);
// Replace multiple slashes down to a singular, allowing for network shares having two slashes.
$path = preg_replace('|(?<=.)/+|', '/', $path);
// Windows paths should uppercase the drive letter.
if (':' === substr($path, 1, 1)) {
$path = ucfirst($path);
}
return $path;
}
/**
* Returns the path to wp-config.php
*
* @param string $root - Where to look for wp-config.php file
* @return string
*/
public static function get_wpconfig_path($root = '') {
if (empty($root)) $root = self::get_wordpress_dir();
$wp_config_file = $root . 'wp-config.php';
if (file_exists($wp_config_file)) {
return $wp_config_file;
} elseif (file_exists(dirname($root) . '/wp-config.php')) {
return dirname($root) . '/wp-config.php';
}
return $wp_config_file;
}
/**
* Recursive directory creation based on full path
*
* @see https://developer.wordpress.org/reference/functions/wp_mkdir_p/
* @param string $target
* @return bool
*/
public static function wp_mkdir_p($target) {
$wrapper = null;
// Strip the protocol.
if (self::wp_is_stream($target)) {
list($wrapper, $target) = explode('://', $target, 2);
}
// From php.net/mkdir user contributed notes.
$target = str_replace('//', '/', $target);
// Put the wrapper back on the target.
if (null !== $wrapper) {
$target = $wrapper . '://' . $target;
}
/*
* Safe mode fails with a trailing slash under certain PHP versions.
* Use rtrim() instead of untrailingslashit to avoid formatting.php dependency.
*/
$target = rtrim($target, '/');
if (empty($target)) {
$target = '/';
}
if (file_exists($target)) {
return @is_dir($target);
}
// Do not allow path traversals.
if (false !== strpos($target, '../') || false !== strpos($target, '..' . DIRECTORY_SEPARATOR)) {
return false;
}
// We need to find the permissions of the parent folder that exists and inherit that.
$target_parent = dirname($target);
while ('.' !== $target_parent && ! is_dir($target_parent) && dirname($target_parent) !== $target_parent) {
$target_parent = dirname($target_parent);
}
// Get the permission bits
$stat = @stat($target_parent);
if ($stat) {
$dir_perms = $stat['mode'] & 0007777;
} else {
$dir_perms = 0777;
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir -- PCP error. WP not loaded. WP API not available.
if (@mkdir($target, $dir_perms, true)) {
/*
* If a umask is set that modifies $dir_perms, we'll have to re-set
* the $dir_perms correctly with chmod()
*/
if (($dir_perms & ~umask()) != $dir_perms) {
$folder_parts = explode('/', substr($target, strlen($target_parent) + 1));
for ($i = 1, $c = count($folder_parts); $i <= $c; $i++) {
// phpcs:ignore WordPress.WP.AlternativeFunctions -- PCP error. WP not loaded. WP API not available.
chmod($target_parent . '/' . implode('/', array_slice($folder_parts, 0, $i)), $dir_perms);
}
}
return true;
}
return false;
}
/**
* Tests if a given path is a stream URL
*
* @see https://developer.wordpress.org/reference/functions/wp_is_stream/
* @param string $path
* @return bool
*/
public static function wp_is_stream($path) {
$scheme_separator = strpos($path, '://');
if (false === $scheme_separator) {
// $path isn't a stream.
return false;
}
$stream = substr($path, 0, $scheme_separator);
return in_array($stream, stream_get_wrappers(), true);
}
/**
* Attempts to give us access to the $wpdb object from the firewall.
* This should only be used when you're sure WordPress will not be loading after the firewall.
*
* @return bool
*/
public static function attempt_to_access_wpdb() {
// wpdb is already accessible
if (isset($GLOBALS['wpdb'])) return true;
$wp_path = self::get_wordpress_dir() . 'wp-load.php';
clearstatcache();
if (!file_exists($wp_path)) return false;
define('SHORTINIT', true);
$included = (bool) include $wp_path;
global $wpdb;
// If $wpdb is inaccessible by this point, it means loading wp-settings didn't complete.
// So we have to manually include the wp-config (which includes wp-settings) for it to complete.
if (empty($wpdb) && $included) include self::get_wpconfig_path();
global $wpdb;
return !empty($wpdb);
}
}
@@ -0,0 +1,21 @@
<?php
/**
* This is the file that will be loaded using auto_prepend_file directive
*/
if (!defined('AIOWPS_FIREWALL_DIR')) {
define('AIOWPS_FIREWALL_DIR', dirname(__FILE__));
define('AIOWPS_FIREWALL_DIR_PARENT', dirname(AIOWPS_FIREWALL_DIR));
}
if (!defined('AIOWPSEC_FIREWALL_DONE')) {
//Gracefully handle if the file is unable to be included. (i.e: ensure the user's site does not crash)
if (!(@include_once AIOWPS_FIREWALL_DIR . '/wp-security-firewall-loader.php')) {
error_log('AIOS firewall error: failed to load the firewall. Unable to include wp-security-firewall-loader.php.');
return;
}
\AIOWPS\Firewall\Loader::get_instance()->load_firewall();
define('AIOWPSEC_FIREWALL_DONE', true);
}
@@ -0,0 +1,800 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_Feature_Item_Manager {
private $feature_list = array();
public $feature_items = array();
private $total_points = 0;
private $total_achievable_points = 0;
private $feature_point_1 = "5";
private $feature_point_2 = "10";
private $feature_point_3 = "15";
private $feature_point_4 = "20";
private $sec_level_basic = "1";
private $sec_level_inter = "2";
private $sec_level_advanced = "3";
private $feature_active = "active";
private $feature_inactive = "inactive";
private $feature_partial = "partial";
/**
* Constructor sets up the feature item manager
*/
public function __construct() {
$this->setup_feature_list();
$this->initialize_features();
}
/**
* This function sets up the feature list
*
* @return void
*/
private function setup_feature_list() {
$feature_list = array(
// Settings menu features
'wp-generator-meta-tag' => array(
'name' => __('Remove WP generator meta tag', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_remove_wp_generator_meta_info'
)
),
// User Accounts menu features
'user-accounts-change-admin-user' => array(
'name' => __('Change admin username', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_3,
'level' => $this->sec_level_basic,
'options' => array(
''
),
'callback' => array($this, 'check_user_accounts_change_admin_user_feature')
),
'user-accounts-display-name' => array(
'name' => __('Change display name', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
''
),
'callback' => array($this, 'check_user_accounts_display_name_feature')
),
// User Login menu features
'user-login-login-lockdown' => array(
'name' => __('Login lockout', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_4,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_login_lockdown'
)
),
'user-login-force-logout' => array(
'name' => __('Force logout', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_forced_logout'
)
),
'hibp' => array(
'name' => __('HIBP', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_hibp_user_profile_update',
'aiowps_http_password_reset',
)
),
'disable-application-password' => array(
'name' => __('Disable application password', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_disable_application_password'
)
),
'user-login-lockout-ip-whitelisting' => array(
'name' => __('Login Lockout IP whitelisting', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_3,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_lockdown_enable_whitelisting'
)
),
// User Registration menu features
'manually-approve-registrations' => array(
'name' => __('Registration approval', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_4,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_manual_registration_approval'
)
),
'user-registration-captcha' => array(
'name' => __('Registration CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_4,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_registration_page_captcha'
)
),
'registration-honeypot' => array(
'name' => __('Enable registration honeypot', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_enable_registration_honeypot'
)
),
'http-authentication-admin-frontend' => array(
'name' => __('HTTP authentication for admin and frontend', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_http_authentication_admin',
'aiowps_http_authentication_frontend',
)
),
// Database Security menu features
'db-security-db-prefix' => array(
'name' => __('Database prefix', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_inter,
'options' => array(
''
),
'callback' => array($this, 'check_db_security_db_prefix_feature')
),
// Filesystem Security menu features
'filesystem-file-permissions' => array(
'name' => __('File permissions', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_4,
'level' => $this->sec_level_basic,
'options' => array(
''
),
'callback' => array($this, 'check_filesystem_permissions_feature')
),
'filesystem-file-editing' => array(
'name' => __('File editing', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_disable_file_editing'
)
),
'auto-delete-wp-files' => array(
'name' => __('WordPress files access', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_auto_delete_default_wp_files'
)
),
// Blacklist Manager menu features
'blacklist-manager-ip-user-agent-blacklisting' => array(
'name' => __('IP and user agent blacklisting', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_3,
'level' => $this->sec_level_advanced,
'options' => array(
'aiowps_enable_blacklisting'
)
),
// Firewall menu features
'firewall-basic-rules' => array(
'name' => __('Enable basic firewall', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_3,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_basic_firewall'
),
'feature_condition_callback' => array('AIOWPSecurity_Utility', 'allow_to_write_to_htaccess')
),
'firewall-pingback-rules' => array(
'name' => __('Enable pingback vulnerability protection', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_3,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_pingback_firewall'
)
),
'firewall-block-debug-file-access' => array(
'name' => __('Block access to debug log file', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_block_debug_log_file_access'
),
'feature_condition_callback' => array('AIOWPSecurity_Utility', 'allow_to_write_to_htaccess')
),
'firewall-disable-index-views' => array(
'name' => __('Disable index views', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_disable_index_views'
),
'feature_condition_callback' => array('AIOWPSecurity_Utility', 'allow_to_write_to_htaccess')
),
'firewall-disable-trace-track' => array(
'name' => __('Disable trace and track', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_advanced,
'options' => array(
'aiowps_disable_trace_and_track'
),
'feature_condition_callback' => array('AIOWPSecurity_Utility', 'allow_to_write_to_htaccess')
),
'firewall-forbid-proxy-comments' => array(
'name' => __('Forbid proxy comments', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_advanced,
'options' => array(
'aiowps_forbid_proxy_comments'
)
),
'firewall-deny-bad-queries' => array(
'name' => __('Deny bad queries', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_3,
'level' => $this->sec_level_advanced,
'options' => array(
'aiowps_deny_bad_query_strings'
)
),
'firewall-advanced-character-string-filter' => array(
'name' => __('Advanced character string filter', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_3,
'level' => $this->sec_level_advanced,
'options' => array(
'aiowps_advanced_char_string_filter'
)
),
'firewall-enable-6g' => array(
'name' => __('6G firewall', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_4,
'level' => $this->sec_level_advanced,
'options' => array(
'aiowps_enable_6g_firewall',
)
),
'firewall-block-fake-googlebots' => array(
'name' => __('Block fake Googlebots', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_advanced,
'options' => array(
'aiowps_block_fake_googlebots'
)
),
'prevent-hotlinking' => array(
'name' => __('Prevent image hotlinking', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_prevent_hotlinking'
)
),
'firewall-enable-404-blocking' => array(
'name' => __('Enable IP blocking for 404 detection', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_enable_404_IP_lockout'
)
),
'firewall-disable-rss-and-atom' => array(
'name' => __('Disable RSS and ATOM feeds', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_disable_rss_and_atom_feeds'
)
),
'upgrade-unsafe-http-calls' => array(
'name' => __('Upgrade unsafe HTTP calls', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_upgrade_unsafe_http_calls'
)
),
// Brute Force menu features
'bf-rename-login-page' => array(
'name' => __('Enable rename login page', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_enable_rename_login_page'
)
),
'firewall-enable-brute-force-attack-prevention' => array(
'name' => __('Enable brute force attack prevention', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_4,
'level' => $this->sec_level_advanced,
'options' => array(
'aiowps_enable_brute_force_attack_prevention'
)
),
'user-login-captcha' => array(
'name' => __('Login CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_4,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_login_captcha'
)
),
'lost-password-captcha' => array(
'name' => __('Lost password CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_lost_password_captcha'
)
),
'custom-login-captcha' => array(
'name' => __('Custom login CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_4,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_custom_login_captcha'
)
),
'password_protected-captcha' => array(
'name' => __('Password-protected CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_password_protected_captcha'
)
),
'whitelist-manager-ip-login-whitelisting' => array(
'name' => __('Login IP whitelisting', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_3,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_enable_whitelisting'
)
),
'login-honeypot' => array(
'name' => __('Enable login honeypot', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_enable_login_honeypot'
)
),
// Spam Prevention menu features
'comment-form-captcha' => array(
'name' => __('Comment CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_4,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_comment_captcha'
)
),
'detect-spambots' => array(
'name' => __('Detect spambots', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_spambot_detecting'
)
),
'auto-block-spam-ips' => array(
'name' => __('Auto block spam ips', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_autoblock_spam_ip'
)
),
// Scanner menu features
'scan-file-change-detection' => array(
'name' => __('File change detection', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_4,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_enable_automated_fcd_scan'
)
),
// Miscellaneous menu features
'enable-copy-protection' => array(
'name' => __('Enable Copy Protection', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_copy_protection'
)
),
'enable-frame-protection' => array(
'name' => __('Enable iFrame protection', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_prevent_site_display_inside_frame'
)
),
'disable-users-enumeration' => array(
'name' => __('Disable users enumeration', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_prevent_users_enumeration'
)
),
'disallow-unauthorised-requests' => array(
'name' => __('Disallow unauthorized REST requests', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_disallow_unauthorized_rest_requests'
)
),
'enable-salt-postfix' => array(
'name' => __('Enable salt postfix', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_3,
'level' => $this->sec_level_advanced,
'options' => array(
'aiowps_enable_salt_postfix'
)
),
// conditional features
'bp-register-captcha' => array(
'name' => __('BuddyPress registration CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_bp_register_captcha'
),
'feature_condition_callback' => array('AIOWPSecurity_Utility', 'is_buddypress_plugin_active'),
),
'bbp-new-topic-captcha' => array(
'name' => __('bbPress new topic CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_bbp_new_topic_captcha'
),
'feature_condition_callback' => array('AIOWPSecurity_Utility', 'is_bbpress_plugin_active'),
),
'woo-login-captcha' => array(
'name' => __('Woo login CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_woo_login_captcha'
),
'feature_condition_callback' => array('AIOWPSecurity_Utility', 'is_woocommerce_plugin_active'),
),
'woo-lostpassword-captcha' => array(
'name' => __('Woo lost password CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_woo_lostpassword_captcha'
),
'feature_condition_callback' => array('AIOWPSecurity_Utility', 'is_woocommerce_plugin_active'),
),
'woo-register-captcha' => array(
'name' => __('Woo register CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_woo_register_captcha'
),
'feature_condition_callback' => array('AIOWPSecurity_Utility', 'is_woocommerce_plugin_active'),
),
'woo-checkout-captcha' => array(
'name' => __('Woo Checkout CAPTCHA', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_woo_checkout_captcha'
),
'feature_condition_callback' => array('AIOWPSecurity_Utility', 'is_woocommerce_plugin_active'),
),
// Ban POST requests with blank user-agent and referer
'firewall-ban-post-blank-headers' => array(
'name' => __('Ban POST requests that have blank user-agent and referer headers', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_2,
'level' => $this->sec_level_inter,
'options' => array(
'aiowps_ban_post_blank_headers'
)
),
'contact-form-7-captcha' => array(
/* translators: %s: Plugin name */
'name' => sprintf(__('%s CAPTCHA', 'all-in-one-wp-security-and-firewall'), 'Contact Form 7'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enable_contact_form_7_captcha'
),
'feature_condition_callback' => array('AIOWPSecurity_Utility', 'is_contact_form_7_plugin_active'),
),
'enforce-strong-password' => array(
'name' => __('Enforce use of strong passwords by users', 'all-in-one-wp-security-and-firewall'),
'points' => $this->feature_point_1,
'level' => $this->sec_level_basic,
'options' => array(
'aiowps_enforce_strong_password'
),
)
);
$feature_list = apply_filters('aiowpsecurity_feature_list', $feature_list);
$this->feature_list = array_filter($feature_list, array($this, 'should_add_item'));
}
/**
* This function will create a feature item object for each feature in the feature list
*
* @return void
*/
private function initialize_features() {
foreach ($this->feature_list as $id => $data) {
$callback = isset($data['callback']) ? $data['callback'] : array($this, 'is_feature_enabled');
$this->feature_items[$id] = new AIOWPSecurity_Feature_Item($id, $data['name'], $data['points'], $data['level'], $data['options'], $callback);
}
}
/**
* This function will return the feature item for the passed in ID
*
* @param string $feature_id - the id of the feature item we want to return
*
* @return boolean|AIOWPSecurity_Feature_Item - returns a feature item or false on coding error
*/
public function get_feature_item_by_id($feature_id) {
if (isset($this->feature_items[$feature_id])) return $this->feature_items[$feature_id];
error_log("AIOS Feature Manager - Feature ID not found (coding error): $feature_id");
return false;
}
/**
* Call the callback function associated with the feature item.
*
* @param mixed $feature_item The feature item object.
*/
private function call_feature_callback($feature_item) {
call_user_func($feature_item->callback, $feature_item);
}
/**
* This function will output the feature details badge HTML
*
* @param string $feature_id - the id of the feature we want to get the badge for
* @param bool $return_instead_of_echo - whether to return the HTML or echo it
*
* @return string|void
*/
public function output_feature_details_badge($feature_id, $return_instead_of_echo = false) {
// Retrieve the feature item by ID
$feature_item = $this->get_feature_item_by_id($feature_id);
if (!$feature_item) return;
$this->call_feature_callback($feature_item);
// Prepare HTML for the feature badge
$max_security_points = $feature_item->item_points;
$current_security_points = $feature_item->is_active() ? $max_security_points : 0;
$security_level = $feature_item->get_security_level_string();
$protection_level = (0 == $current_security_points) ? 'none' : 'full';
$status_icon = (0 == $current_security_points) ? 'dashicons-unlock' : 'dashicons-lock';
$badge_html = '<div class="aiowps_feature_details_badge">';
$badge_html .= '<span class="aiowps_feature_details_badge_difficulty aiowps_feature_protection_'.$protection_level.'" title="'.__('Feature difficulty', 'all-in-one-wp-security-and-firewall').'">';
$badge_html .= '<span class="dashicons '.$status_icon.'"></span>'.$security_level.'</span>';
$badge_html .= '<span class="aiowps_feature_details_badge_points" title="'.__('Security points', 'all-in-one-wp-security-and-firewall').'">';
$badge_html .= $current_security_points.'/'.$max_security_points.'</span>';
$badge_html .= '</div>';
if ($return_instead_of_echo) {
return $badge_html;
} else {
echo $badge_html;
}
}
/**
* This function will calculate the total points for the AJAX save function
*
* @return void
*/
public function calculate_total_feature_points() {
$this->calculate_total_points();
}
/**
* This function will setup the feature status and calculate the total points
*
* @return void
*/
public function check_feature_status_and_recalculate_points() {
$this->check_and_set_feature_status();
$this->calculate_total_points();
}
/**
* This function will calculate the total points for the site
*
* @return void
*/
private function calculate_total_points() {
foreach ($this->feature_items as $item) {
if ($item->is_active()) $this->total_points = $this->total_points + intval($item->item_points);
}
}
/**
* This function will calculate the total achievable points
*
* @return void
*/
private function calculate_total_achievable_points() {
foreach ($this->feature_items as $item) {
$this->total_achievable_points = $this->total_achievable_points + intval($item->item_points);
}
}
/**
* This function will return the total points for the site
*
* @return int - the total points for the site
*/
public function get_total_site_points() {
if (empty($this->total_points)) $this->calculate_total_points();
return $this->total_points;
}
/**
* This function will return the total achievable points
*
* @return int - the total achievable points
*/
public function get_total_achievable_points() {
if (empty($this->total_achievable_points)) $this->calculate_total_achievable_points();
return $this->total_achievable_points;
}
/**
* This function will loop over each feature item, checking if it's enabled and setting it's feature status
*
* @return void
*/
private function check_and_set_feature_status() {
foreach ($this->feature_items as $item) {
call_user_func($item->callback, $item);
}
}
/**
* This function will check if the feature database value is active and set the feature status
*
* @param AIOWPSecurity_Feature_Item $item - the item we want to check is active
*
* @return void
*/
private function is_feature_enabled($item) {
global $aio_wp_security;
$aiowps_firewall_config = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONFIG);
$enabled = false;
foreach ($item->feature_options as $option) {
if ('1' == $aiowps_firewall_config->get_value($option) || '1' == $aio_wp_security->configs->get_value($option)) $enabled = true;
}
if ($enabled) {
$item->set_feature_status($this->feature_active);
} else {
$item->set_feature_status($this->feature_inactive);
}
}
/**
* This function will check if the default admin user exists and set the feature status
*
* @param AIOWPSecurity_Feature_Item $item - the item we want to check is active
*
* @return void
*/
private function check_user_accounts_change_admin_user_feature($item) {
if (AIOWPSecurity_Utility::check_user_exists('admin')) {
$item->set_feature_status($this->feature_inactive);
} else {
$item->set_feature_status($this->feature_active);
}
}
/**
* This function will check if the username and nicknames are identical and set the feature status
*
* @param AIOWPSecurity_Feature_Item $item - the item we want to check is active
*
* @return void
*/
private function check_user_accounts_display_name_feature($item) {
if (AIOWPSecurity_Utility::check_identical_login_and_nick_names()) {
$item->set_feature_status($this->feature_inactive);
} else {
$item->set_feature_status($this->feature_active);
}
}
/**
* This function will check if the default database prefix is in use and set the feature status
*
* @param AIOWPSecurity_Feature_Item $item - the item we want to check is active
*
* @return void
*/
private function check_db_security_db_prefix_feature($item) {
global $wpdb;
$site_id = get_current_blog_id();
$default_prefix = (1 === $site_id) ? 'wp_' : "wp_{$site_id}_";
if ($default_prefix === $wpdb->prefix) {
$item->set_feature_status($this->feature_inactive);
} else {
$item->set_feature_status($this->feature_active);
}
}
/**
* This function will check the filesystem permissions and set the feature status
*
* @param AIOWPSecurity_Feature_Item $item - the item we want to check is active
*
* @return void
*/
private function check_filesystem_permissions_feature($item) {
//TODO
$is_secure = 1;
$files_dirs_to_check = AIOWPSecurity_Utility_File::get_files_and_dirs_to_check();
foreach ($files_dirs_to_check as $file_or_dir) {
$actual_perm = AIOWPSecurity_Utility_File::get_file_permission($file_or_dir['path']);
$is_secure = $is_secure*AIOWPSecurity_Utility_File::is_file_permission_secure($file_or_dir['permissions'], $actual_perm);
}
//Only if all of the files' permissions are deemed secure give this a thumbs up
if (1 == $is_secure) {
$item->set_feature_status($this->feature_active);
} else {
$item->set_feature_status($this->feature_inactive);
}
}
/**
* This function will check if an item should be added to the feature list
*
* @param array $item - the item we want to check if it should be added
* @return bool
*/
public static function should_add_item($item) {
if (empty($item['feature_condition_callback'])) {
return true;
} elseif (is_callable($item['feature_condition_callback'])) {
return call_user_func($item['feature_condition_callback']);
} else {
error_log("Callback function set but not callable (coding error). Feature: " . $item['name']);
return false;
}
}
}
@@ -0,0 +1,78 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class AIOWPSecurity_Feature_Item {
public $feature_id; // Example "user-accounts-tab1-change-admin-user"
public $feature_name;
public $item_points;
public $security_level; // 1, 2, 3
public $feature_status; // active, inactive, partial
public $feature_options; // The database options to check if the feature is enabled or not
public $callback;
/**
* Constructor sets up the feature item
*
* @param string $feature_id - the id of the feature
* @param string $feature_name - the name of the feature
* @param string $item_points - the points this feature is worth
* @param string $security_level - the level of the feature
* @param array $feature_options - the options the feature uses
* @param boolean $callback - callback to set feature active status
*/
public function __construct($feature_id, $feature_name, $item_points, $security_level, $feature_options, $callback) {
$this->feature_id = $feature_id;
$this->feature_name = $feature_name;
$this->item_points = $item_points;
$this->security_level = $security_level;
$this->feature_options = $feature_options;
$this->callback = $callback;
}
/**
* This function will set the status of the feature
*
* @param string $status - the status of the feature
*
* @return void
*/
public function set_feature_status($status) {
$this->feature_status = $status;
}
/**
* This function will return the string version of the level
*
* @return string - the string value of the level
*/
public function get_security_level_string() {
$level_string = "";
if ("1" == $this->security_level) {
$level_string = __('Basic', 'all-in-one-wp-security-and-firewall');
} elseif ("2" == $this->security_level) {
$level_string = __('Intermediate', 'all-in-one-wp-security-and-firewall');
} elseif ("3" == $this->security_level) {
$level_string = __('Advanced', 'all-in-one-wp-security-and-firewall');
}
return $level_string;
}
/**
* This function will return a boolean to indicate if the feature is on or not
*
* @return boolean - true if the feature is on otherwise false
*/
public function is_active() {
return ('active' == $this->feature_status);
}
}
@@ -0,0 +1,4 @@
<?php
/**
* Do not modify the files in this folder.
*/
@@ -0,0 +1,38 @@
<?php
if (!defined('ABSPATH')) die('Access denied.');
/**
* This is a small glue class, which makes available all the commands in AIOWPSecurity_Commands, and translates the response from AIOWPSecurity_Commands (which is either data to return, or a WP_Error) into the format used by UpdraftCentral.
*/
class UpdraftCentral_WP_Security_Commands extends UpdraftCentral_Commands {
private $commands;
/**
* Class constructor
*/
public function __construct() {
$this->commands = new AIOWPSecurity_Commands();
}
/**
* Magic method to pass on the command to AIOWPSecurity_Commands
*
* @param string $name - command name
* @param array $arguments - command parameters
*
* @return array - response
*/
public function __call($name, $arguments) {
if (!is_callable(array($this->commands, $name))) {
return $this->_generic_error_response('aios_no_such_command', $name);
}
$result = call_user_func_array(array($this->commands, $name), $arguments);
if (is_wp_error($result)) {
return $this->_generic_error_response($result->get_error_code(), $result->get_error_data());
} else {
return $this->_response($result);
}
}
}
@@ -0,0 +1,43 @@
<?php
if (!defined('ABSPATH')) die('Access denied.');
/**
* This file is the bootstrapper for UpdraftCentral integration: i.e. it registers what is necessary to deal with commands in the AIOS namespace.
*/
class WP_Security_UpdraftCentral {
public function __construct() {
add_filter('updraftplus_remotecontrol_command_classes', array($this, 'updraftcentral_remotecontrol_command_classes'));
add_filter('updraftcentral_remotecontrol_command_classes', array($this, 'updraftcentral_remotecontrol_command_classes'));
add_action('updraftcentral_command_class_wanted', array($this, 'updraftcentral_command_class_wanted'));
}
/**
* Register our class
*
* @param string $command_classes Passing over an array of command classes.
*/
public function updraftcentral_remotecontrol_command_classes($command_classes) {
if (is_array($command_classes)) $command_classes['aios'] = 'UpdraftCentral_WP_Security_Commands';
return $command_classes;
}
/**
* Load the class when required
*
* @param string $command_php_class Passing over if there are any command classes.
*/
public function updraftcentral_command_class_wanted($command_php_class) {
if ('UpdraftCentral_WP_Security_Commands' == $command_php_class) {
// This fragment is only needed for compatibility with UD < 1.12.30 - thenceforth, the class can be assumed to exist.
if (!class_exists('UpdraftCentral_Commands')) {
include_once(apply_filters('updraftcentral_command_base_class_at', UPDRAFTPLUS_DIR.'/central/commands.php'));
}
include_once(AIO_WP_SECURITY_PATH.'/classes/updraftcentral-wp-security-commands.php');
}
}
}
new WP_Security_UpdraftCentral();
@@ -0,0 +1,121 @@
<?php
if (!defined('ABSPATH') && !defined('AIOWPS_FIREWALL_DIR')) {
exit; //Exit if accessed directly
}
/**
* All ids and static names, array.
*/
if (class_exists('AIOS_Abstracted_Ids')) return;
class AIOS_Abstracted_Ids {
/**
* Get firewall block request methods.
*
* @return array
*/
public static function get_firewall_block_request_methods() {
return array('DEBUG','MOVE', 'PUT', 'TRACK');
}
/**
* Get IP retrieve methods.
*
* @return array
*/
public static function get_ip_retrieve_methods() {
// The keys are merely for maintaining backward compatibility.
return array(
'0' => 'REMOTE_ADDR',
'1' => 'HTTP_CF_CONNECTING_IP',
'2' => 'HTTP_X_FORWARDED_FOR',
'3' => 'HTTP_X_FORWARDED',
'4' => 'HTTP_CLIENT_IP',
'5' => 'HTTP_X_REAL_IP',
'6' => 'HTTP_X_CLUSTER_CLIENT_IP',
);
}
/**
* Get AIOS custom admin notice ids.
*
* @return array
*/
public static function custom_admin_notice_ids() {
return array(
'automated-database-backup',
'ip-retrieval-settings',
'load-firewall-resources-failed',
'end-of-support-php-56',
);
}
/**
* Get notice ids for notices that have transformed HTACESS rules to PHP.
*
* @return array notice ids.
*/
public static function htaccess_to_php_feature_notice_ids() {
return array(
'login-whitelist-disabled-on-upgrade',
'ip-blacklist-settings-on-upgrade',
'upgrade-firewall-tab-rules',
);
}
/**
* Get locale codes that are more than 2 char long supported by Google ReCaptcha.
*
* @return array
*/
public static function get_google_recaptcha_locale_codes() {
/**
* Google reCaptcha accepts 2 char language codes and also more than 2 char language codes.
* Most are 2 chars in length e.g. 'ar' for Arabic.
* Few are more than 2 char in length e.g 'de-AT' for German (Austria)
*
* Below is the list of more than 2 char language codes supported by Google reCaptcha.
* if determine_locale() detects any of the below we return it, otherwise,
* we would return the 2 letter code.
*/
return array(
'zh-HK', // Chinese (Hong Kong).
'zh-CN', // Chinese (Simplified).
'zh-TW', // Chinese (Traditional).
'en-GB', // UK.
'fr-CA', // French (Canadian).
'de-AT', // German (Austria).
'de-CH', // German (Switzerland).
'pt-BR', // Portuguese (Brazil).
'pt-PT', // Portuguese (Portugal).
);
}
/**
* Get IP Lookup services.
*
* @return array
*/
public static function get_ip_lookup_services() {
return array(
'ipify' => 'http://api.ipify.org/',
'ipecho' => 'http://ipecho.net/plain',
'ident' => 'http://ident.me',
'tnedi' => 'http://tnedi.me',
);
}
/**
* Get Reverse IP Lookup services.
*
* @return array
*/
public static function get_reverse_ip_lookup_services() {
return array(
'ip-api' => 'http://ip-api.com/json/%s',
'ipinfo' => 'https://ipinfo.io/%s/json'
);
}
}
@@ -0,0 +1,253 @@
<?php
if (!defined('ABSPATH')) die('Access denied.');
if (!class_exists('AIOWPSecurity_Ajax')) :
class AIOWPSecurity_Ajax {
private $commands_object;
private $nonce;
private $subaction;
private $data;
private $results;
/**
* Constructor
*/
private function __construct() {
if (!class_exists('AIOWPSecurity_Commands')) include_once(AIO_WP_SECURITY_PATH.'/classes/wp-security-commands.php');
$this->commands_object = new AIOWPSecurity_Commands();
add_action('wp_ajax_aios_ajax', array($this, 'handle_ajax_requests'));
add_action('wp_ajax_nopriv_get_antibot_keys', array($this->commands_object, 'get_antibot_keys'));
}
/**
* Return singleton instance
*
* @return AIOWPSecurity_Ajax Returns AIOWPSecurity_Ajax object
*/
public static function get_instance() {
static $instance = null;
if (null === $instance) {
$instance = new self();
}
return $instance;
}
/**
* Handles ajax requests
*
* @return void
*/
public function handle_ajax_requests() {
$this->set_nonce();
$this->set_subaction();
$this->set_data();
$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($this->nonce, 'wp-security-ajax-nonce');
if (is_wp_error($result)) {
wp_send_json(array(
'result' => false,
'error_code' => $result->get_error_code(),
'error_message' => $result->get_error_message(),
));
}
if (is_multisite() && !current_user_can('manage_network_options')) {
if (!$this->is_valid_multisite_command()) {
$this->send_invalid_multisite_command_error_response();
}
}
if ($this->is_invalid_command()) {
$this->add_invalid_command_error_log_entry();
$this->set_invalid_command_error_response();
} else {
$this->execute_command();
$this->set_error_response_on_wp_error();
$this->maybe_set_results_as_null();
}
$this->json_encode_results();
$json_last_error = json_last_error();
if ($json_last_error) {
$this->set_error_response_on_json_encode_error($json_last_error);
}
echo $this->results; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Variable is an array containing escaped data.
die;
}
/**
* Sets nonce property value
*
* @return void
*/
private function set_nonce() {
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput -- It's the actual nonce. It does not need sanitizing.
$this->nonce = empty($_POST['nonce']) ? '' : $_POST['nonce'];
}
/**
* Sets subaction property value
*
* @return void
*/
private function set_subaction() {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce already checked.
$this->subaction = empty($_POST['subaction']) ? '' : sanitize_text_field(wp_unslash($_POST['subaction']));
}
/**
* Sets data property value
*
* @return void
*/
private function set_data() {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce already checked.
$this->data = isset($_POST['data']) ? wp_unslash($_POST['data']) : null;
}
/**
* Checks whether it is multisite setup and command is valid multisite command
*
* @return bool
*/
private function is_valid_multisite_command() {
/**
* Filters the commands allowed to the sub site admins. Other commands are only available to network admin. Only used in a multisite context.
*/
$allowed_commands = apply_filters('aios_multisite_allowed_commands', array(
'delete_audit_log',
'delete_locked_ip_record',
'clear_debug_logs',
'unlock_ip',
'blocked_ip_list_unblock_ip',
'lock_ip',
'dismiss_notice',
));
return in_array($this->subaction, $allowed_commands);
}
/**
* Sends the invalid multisite command error response
*
* @return void
*/
private function send_invalid_multisite_command_error_response() {
wp_send_json(array(
'result' => false,
'error_code' => 'update_failed',
'error_message' => __('Options can only be saved by network admin', 'all-in-one-wp-security-and-firewall')
));
}
/**
* Checks if applied ajax command is an invalid command or not
*
* @return bool Returns true if ajax command is an invalid command, false otherwise
*/
private function is_invalid_command() {
return !is_callable(array($this->commands_object, $this->subaction));
}
/**
* Log an error message for invalid ajax command
*
* @return void
*/
private function add_invalid_command_error_log_entry() {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Part of error reporting.
error_log("AIOS: ajax_handler: no such command (" . $this->subaction . ")");
}
/**
* Set `results` property with error response array for invalid ajax command
*
* @return void
*/
private function set_invalid_command_error_response() {
$this->results = array(
'result' => false,
'error_code' => 'command_not_found',
/* translators: %s: Subaction */
'error_message' => sprintf(__('The command "%s" was not found', 'all-in-one-wp-security-and-firewall'), $this->subaction)
);
}
/**
* Execute the ajax command
*
* @return void
*/
private function execute_command() {
$this->results = call_user_func(array($this->commands_object, $this->subaction), $this->data);
}
/**
* Set `results` property with error message
*
* @return void
*/
private function set_error_response_on_wp_error() {
if (is_wp_error($this->results)) {
$this->results = array(
'result' => false,
'error_code' => $this->results->get_error_code(),
'error_message' => $this->results->get_error_message(),
'error_data' => $this->results->get_error_data(),
);
}
}
/**
* Set `results` property to null, if it is not yet set
*
* @return void
*/
private function maybe_set_results_as_null() {
// if nothing was returned for some reason, set as result null.
if (empty($this->results)) {
$this->results = array(
'result' => null
);
}
}
/**
* Sets `results` property with json encode error
*
* @param int $json_last_error
*
* @return void
*/
private function set_error_response_on_json_encode_error($json_last_error) {
$this->results = array(
'result' => false,
'error_code' => $json_last_error,
'error_message' => 'json_encode error : ' . $json_last_error,
'error_data' => '',
);
$this->results = wp_json_encode($this->results);
}
/**
* Json encode the `results` property value
*
* @return void
*/
private function json_encode_results() {
$this->results = wp_json_encode($this->results);
}
}
endif;
@@ -0,0 +1,159 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
require_once(AIO_WP_SECURITY_PATH.'/classes/wp-security-audit-events.php');
require_once(AIO_WP_SECURITY_PATH.'/classes/wp-security-audit-text-handler.php');
class AIOWPSecurity_Audit_Event_Handler {
protected static $_instance = null;
/**
* This method will create and return the only instance of this class.
*
* @return AIOWPSecurity_Audit_Event_Handler Returns an instance of the class
*/
public static function instance() {
if (empty(self::$_instance)) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Constructor for the class.
*/
private function __construct() {
add_action('aiowps_record_event', array($this, 'record_event'), 10, 4);
add_action('aiowps_bulk_record_events', function($events) {
$this->add_bulk_events($events);
}, 10, 4);
add_action('aiowps_clean_old_events', array($this, 'delete_old_events'), 10);
if (!wp_next_scheduled('aiowps_clean_old_events')) {
wp_schedule_event(time(), 'daily', 'aiowps_clean_old_events');
}
AIOWPSecurity_Audit_Events::add_event_actions();
}
/**
* This function records an event in the audit log
*
* @global AIO_WP_Security $aio_wp_security
*
* @param string $event_type - the event type
* @param array $details - details about the event
* @param string $event_level - the event level
* @param string $username - the username, this is only used if there is no user logged in
*
* @return void
*/
public function record_event($event_type, $details, $event_level = 'info', $username = '') {
if (!function_exists('wp_get_current_user')) {
global $aio_wp_security;
$aio_wp_security->debug_logger->log_debug("AIOWPSecurity_Audit_Event_Handler::record_event() called before plugins_loaded hook has run.", 4);
return;
}
$record_event = apply_filters('aios_audit_log_record_event', true, $event_type, $details, $event_level, $username);
if (!$record_event) return;
$user = wp_get_current_user();
$username = (is_a($user, 'WP_User') && 0 !== $user->ID) ? $user->user_login : $username;
$ip = apply_filters('aios_audit_log_event_user_ip', AIOWPSecurity_Utility_IP::get_user_ip_address());
$data = apply_filters('aios_audit_log_event_user_country_code', array('ip' => $ip));
$country_code = isset($data['country_code']) ? $data['country_code'] : '';
$stacktrace = maybe_serialize(AIOWPSecurity_Utility::normalise_call_stack_args(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS))); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace -- Required for the stacktrace to work.
$network_id = get_current_network_id();
$site_id = get_current_blog_id();
$details = wp_json_encode($details, true);
$this->add_new_event($network_id, $site_id, $username, $ip, $event_level, $event_type, $details, $stacktrace, $country_code);
}
/**
* This function adds the event to the audit log database table
*
* @param integer $network_id - the id of the current network
* @param integer $site_id - the id of the current site
* @param string $username - the username of the user who triggered the event
* @param string $ip - the IP address of the user
* @param string $event_level - the event level
* @param string $event_type - the event type
* @param string $details - details about the event
* @param string $stacktrace - the event stacktrace
* @param string $country_code - the country code
*
* @return void
*/
private function add_new_event($network_id, $site_id, $username, $ip, $event_level, $event_type, $details, $stacktrace, $country_code) {
global $wpdb;
$wpdb->query($wpdb->prepare("INSERT INTO ".AIOWPSEC_TBL_AUDIT_LOG." (network_id, site_id, username, ip, level, event_type, details, stacktrace, created, country_code) VALUES (%d, %d, %s, %s, %s, %s, %s, %s, UNIX_TIMESTAMP(), %s)", $network_id, $site_id, $username, $ip, $event_level, $event_type, $details, $stacktrace, $country_code)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared -- We can't use %i because our plugin supports wordpress < 6.2.
}
/**
* This function adds multiple events to the audit log database table
*
* @param array $events - each event in the array must contain the keys (network_id, site_id, username, ip, level, event_type, details, stacktrace and created)
*
* @return void
*/
private function add_bulk_events($events) {
global $wpdb;
$sql = "INSERT INTO ".AIOWPSEC_TBL_AUDIT_LOG." (network_id, site_id, username, ip, level, event_type, details, stacktrace, created, country_code) VALUES ";
$values = array();
foreach ($events as $event) {
$sql .= "(%d, %d, %s, %s, %s, %s, %s, %s, %d, %s),";
$record_event = apply_filters('aios_audit_log_record_event', true, $event['event_type'], $event['details'], $event['level'], $event['username']);
if (!$record_event) continue;
$event['ip'] = apply_filters('aios_audit_log_event_user_ip', $event['ip']);
$data = apply_filters('aios_audit_log_event_user_country_code', array('ip' => $event['ip']));
$event['country_code'] = isset($data['country_code']) ? $data['country_code'] : '';
$values[] = $event['network_id'];
$values[] = $event['site_id'];
$values[] = $event['username'];
$values[] = $event['ip'];
$values[] = $event['level'];
$values[] = $event['event_type'];
$values[] = $event['details'];
$values[] = $event['stacktrace'];
$values[] = $event['created'];
$values[] = $event['country_code'];
}
// remove last ',' character from query
$sql = rtrim($sql, ',');
$wpdb->query($wpdb->prepare($sql, $values)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared -- The sql query is being dynamically built.
}
/**
* This method will try to delete all audit logs older than 3 months from the database.
*
* @return void
*/
public function delete_old_events() {
global $wpdb;
$after_days = 90;
if (defined('AIOWPSEC_PURGE_AUDIT_LOGS_AFTER_DAYS')) {
$after_days = abs(AIOWPSEC_PURGE_AUDIT_LOGS_AFTER_DAYS);
}
$after_days = empty($after_days) ? 90 : $after_days;
$older_than_date = strtotime("-{$after_days} days", time());
$wpdb->query($wpdb->prepare("DELETE FROM ".AIOWPSEC_TBL_AUDIT_LOG." WHERE created < %s", $older_than_date)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared -- We can't use %i because our plugin supports wordpress < 6.2.
}
}
@@ -0,0 +1,641 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
class AIOWPSecurity_Audit_Events {
public static $log_levels = array(
'info',
'warning',
'fatal',
'error',
'debug',
'trace',
);
public static $event_types = array();
private static $installed_plugin_info = array();
private static $removed_plugin_info = array();
private static $installed_theme_info = array();
private static $removed_theme_info = array();
/**
* This function adds all the event actions we want to capture and record in the audit log
*
* @return void
*/
public static function add_event_actions() {
// Setup event types to display filter dropdown for audit logs list
add_action('init', 'AIOWPSecurity_Audit_Events::setup_event_types');
// Core events
add_action('_core_updated_successfully', 'AIOWPSecurity_Audit_Events::core_updated', 10, 2);
// Plugin events
add_action('upgrader_process_complete', 'AIOWPSecurity_Audit_Events::plugin_installed', 10, 2);
add_action('activated_plugin', 'AIOWPSecurity_Audit_Events::plugin_activated', 10, 2);
add_action('upgrader_process_complete', 'AIOWPSecurity_Audit_Events::plugin_updated', 10, 2);
add_action('deactivated_plugin', 'AIOWPSecurity_Audit_Events::plugin_deactivated', 10, 2);
add_action('delete_plugin', 'AIOWPSecurity_Audit_Events::plugin_delete', 10, 2);
add_action('deleted_plugin', 'AIOWPSecurity_Audit_Events::plugin_deleted', 10, 2);
// Theme events
add_action('upgrader_process_complete', 'AIOWPSecurity_Audit_Events::theme_installed', 10, 2);
add_action('switch_theme', 'AIOWPSecurity_Audit_Events::theme_activated', 10, 1);
add_action('upgrader_process_complete', 'AIOWPSecurity_Audit_Events::theme_updated', 10, 2);
add_action('delete_theme', 'AIOWPSecurity_Audit_Events::theme_delete', 10, 2);
add_action('deleted_theme', 'AIOWPSecurity_Audit_Events::theme_deleted', 10, 2);
// Translation events
add_action('upgrader_process_complete', 'AIOWPSecurity_Audit_Events::translation_updated', 10, 2);
// User events
add_action('password_reset', 'AIOWPSecurity_Audit_Events::password_reset', 10, 1);
add_action('deleted_user', 'AIOWPSecurity_Audit_Events::user_deleted', 10, 3);
add_action('remove_user_from_blog', 'AIOWPSecurity_Audit_Events::user_removed_from_blog', 10, 3);
// Rule events
add_action('plugins_loaded', 'AIOWPSecurity_Audit_Events::rule_event', 10, 2);
// Attach an URL to the details to show as a link for configuring rules
add_filter('aios_audit_filter_details', function($details, $event_type) {
// Ensure we only process rules from the firewall
if (!preg_match('/^rule_/', $event_type)) return $details;
$key = "{$details['firewall_event']['rule_name']}::{$details['firewall_event']['rule_family']}";
// Get the URL for the corresponding rule
$location = AIOS_Helper::get_firewall_rule_location($key);
$can_show_configure = !empty($location);
// Only the super admin on the main site can configure the firewall, so only show the configure link to them
if (is_multisite()) $can_show_configure = $can_show_configure && is_main_site() && is_super_admin();
if ($can_show_configure) $details['firewall_event']['location'] = admin_url("admin.php?{$location}");
return $details;
}, 10, 2);
}
/**
* This function removes event actions that need to be removed when we are removing the plugin
*
* @return void
*/
public static function remove_event_actions() {
remove_action('delete_plugin', 'AIOWPSecurity_Audit_Events::plugin_delete');
remove_action('deleted_plugin', 'AIOWPSecurity_Audit_Events::plugin_deleted');
}
/**
* Populates the event_types array
*
* @return void
*/
public static function setup_event_types() {
self::$event_types = array(
'core_updated' => __('Core updated', 'all-in-one-wp-security-and-firewall'),
'plugin_installed' => __('Plugin installed', 'all-in-one-wp-security-and-firewall'),
'plugin_activated' => __('Plugin activated', 'all-in-one-wp-security-and-firewall'),
'plugin_updated' => __('Plugin updated', 'all-in-one-wp-security-and-firewall'),
'plugin_deactivated' => __('Plugin deactivated', 'all-in-one-wp-security-and-firewall'),
'plugin_deleted' => __('Plugin deleted', 'all-in-one-wp-security-and-firewall'),
'theme_installed' => __('Theme installed', 'all-in-one-wp-security-and-firewall'),
'theme_activated' => __('Theme activated', 'all-in-one-wp-security-and-firewall'),
'theme_updated' => __('Theme updated', 'all-in-one-wp-security-and-firewall'),
'theme_deleted' => __('Theme deleted', 'all-in-one-wp-security-and-firewall'),
'translation_updated' => __('Translation updated', 'all-in-one-wp-security-and-firewall'),
'entity_changed' => __('Entity changed', 'all-in-one-wp-security-and-firewall'),
'successful_login' => __('Successful login', 'all-in-one-wp-security-and-firewall'),
'successful_logout' => __('Successful logout', 'all-in-one-wp-security-and-firewall'),
'failed_login' => __('Failed login', 'all-in-one-wp-security-and-firewall'),
'user_registration' => __('User registration', 'all-in-one-wp-security-and-firewall'),
'user_deleted' => __('User deleted', 'all-in-one-wp-security-and-firewall'),
'user_removed' => __('User removed from blog', 'all-in-one-wp-security-and-firewall'),
'table_migration' => __('Table migration', 'all-in-one-wp-security-and-firewall'),
'rule_triggered' => __('Rule triggered', 'all-in-one-wp-security-and-firewall'),
'rule_not_triggered' => __('Rule not triggered', 'all-in-one-wp-security-and-firewall'),
'rule_active' => __('Rule active', 'all-in-one-wp-security-and-firewall'),
'rule_not_active' => __('Rule not active', 'all-in-one-wp-security-and-firewall'),
'password_reset' => __('Password reset', 'all-in-one-wp-security-and-firewall'),
);
}
/**
* Adds a core updated event to the audit log
*
* @param string $new_version - the wp version we updated to
*
* @return void
*/
public static function core_updated($new_version) {
global $wp_version;
$details = array(
'core_updated' => array(
'old_version' => $wp_version,
'new_version' => $new_version
)
);
do_action('aiowps_record_event', 'core_updated', $details, 'info');
}
/**
* Adds a plugin installed event to the audit log
*
* @param WP_Upgrader $upgrader - WP_Upgrader instance
* @param array $hook_extra - Array of bulk item update data
*
* @return void
*/
public static function plugin_installed($upgrader, $hook_extra) {
// If this is empty then we have no way to know if this is a plugin/theme install/update so create an entity changed event
if (empty($hook_extra)) {
self::event_entity_changed($upgrader);
return;
}
if ('plugin' !== $hook_extra['type'] || 'install' !== $hook_extra['action']) return;
self::$installed_plugin_info = $upgrader->new_plugin_data;
self::event_plugin_changed('installed', '', '');
}
/**
* Adds a plugin activated event to the audit log
*
* @param string $plugin - Path to the plugin file relative to the plugins directory
* @param boolean $network_activation - Whether to enable the plugin for all sites in the network or just the current site
*
* @return void
*/
public static function plugin_activated($plugin, $network_activation) {
$network = $network_activation ? 'network' : '';
self::event_plugin_changed('activated', $plugin, $network);
}
/**
* Adds a plugin updated event to the audit log
*
* @param WP_Upgrader $upgrader - WP_Upgrader instance
* @param array $hook_extra - Array of bulk item update data
*
* @return void
*/
public static function plugin_updated($upgrader, $hook_extra) {
// If this is empty then we have no way to know if this is a plugin/theme install/update so return as we catch this in plugin_installed()
if (empty($hook_extra)) return;
if ('plugin' !== $hook_extra['type'] || 'update' !== $hook_extra['action']) return;
if (isset($hook_extra['plugin'])) {
$plugin = $hook_extra['plugin'];
self::event_plugin_changed('updated', $plugin, '');
} elseif (isset($hook_extra['plugins'])) {
foreach ($hook_extra['plugins'] as $plugin) {
self::event_plugin_changed('updated', $plugin, '');
}
}
}
/**
* Adds a plugin deactivated event to the audit log
*
* @param string $plugin - Path to the plugin file relative to the plugins directory
* @param boolean $network_deactivation - Whether to disable the plugin for all sites in the network or just the current site
*
* @return void
*/
public static function plugin_deactivated($plugin, $network_deactivation) {
$network = $network_deactivation ? 'network' : '';
self::event_plugin_changed('deactivated', $plugin, $network, 'warning');
}
/**
* Records the plugin info of the plugin that is about to be deleted
*
* @param string $plugin - Path to the plugin file relative to the plugins directory
*
* @return void
*/
public static function plugin_delete($plugin) {
$filename = WP_PLUGIN_DIR . '/' . $plugin;
if (!file_exists($filename)) return;
self::$removed_plugin_info = get_plugin_data($filename);
}
/**
* Adds a plugin deleted event to the audit log
*
* @param string $plugin - Path to the plugin file relative to the plugins directory
* @param boolean $deleted - Whether the plugin deletion was successful
*
* @return void
*/
public static function plugin_deleted($plugin, $deleted) {
if ($deleted) self::event_plugin_changed('deleted', $plugin, '', 'warning');
}
/**
* This function will construct the event details and send it to be recorded
*
* @param string $action - the action taken (activated, deactivated)
* @param string $plugin - Path to the plugin file relative to the plugins directory
* @param string $network - status of if the plugin was network activated/deactivated
* @param string $level - the log level
*
* @return void
*/
private static function event_plugin_changed($action, $plugin, $network, $level = 'info') {
if ('deleted' == $action) {
$info = self::$removed_plugin_info;
} elseif ('installed' == $action) {
$info = self::$installed_plugin_info;
} else {
$filename = WP_PLUGIN_DIR . '/' . $plugin;
if (!file_exists($filename)) return;
$info = get_plugin_data($filename);
}
$name = empty($info['Name']) ? 'Unknown' : $info['Name'];
$version = empty($info['Version']) ? '0.0.0' : $info['Version'];
$details = array(
'plugin' => array(
'name' => $name,
'version' => $version,
'action' => $action,
'network' => $network
)
);
do_action('aiowps_record_event', 'plugin_' . $action, $details, $level);
}
/**
* Adds a theme installed event to the audit log
*
* @param WP_Upgrader $upgrader - WP_Upgrader instance
* @param array $hook_extra - Array of bulk item update data
*
* @return void
*/
public static function theme_installed($upgrader, $hook_extra) {
// If this is empty then we have no way to know if this is a plugin/theme install/update so return as we catch this in plugin_installed()
if (empty($hook_extra)) return;
if ('theme' !== $hook_extra['type'] || 'install' !== $hook_extra['action']) return;
self::$installed_theme_info = $upgrader->new_theme_data;
self::event_theme_changed('installed', '', '');
}
/**
* Adds a theme activated event to the audit log
*
* @param string $new_name - Name of the new active theme
*
* @return void
*/
public static function theme_activated($new_name) {
$details = array(
'theme' => array(
'name' => $new_name,
'action' => 'activated',
)
);
do_action('aiowps_record_event', 'theme_activated', $details);
}
/**
* Adds a theme updated event to the audit log
*
* @param WP_Upgrader $upgrader - WP_Upgrader instance
* @param array $hook_extra - Array of bulk item update data
*
* @return void
*/
public static function theme_updated($upgrader, $hook_extra) {
// If this is empty then we have no way to know if this is a plugin/theme install/update so return as we catch this in plugin_installed()
if (empty($hook_extra)) return;
if ('theme' !== $hook_extra['type'] || 'update' !== $hook_extra['action']) return;
if (isset($hook_extra['theme'])) {
$theme = $hook_extra['theme'];
self::event_theme_changed('updated', $theme, '');
} elseif (isset($hook_extra['themes'])) {
foreach ($hook_extra['themes'] as $theme) {
self::event_theme_changed('updated', $theme, '');
}
}
}
/**
* Records the theme info of the plugin that is about to be deleted
*
* @param string $theme - Path to the theme file relative to the themes directory
*
* @return void
*/
public static function theme_delete($theme) {
$info_object = wp_get_theme($theme);
$info = array(
'Name' => $info_object->get('Name'),
'Version' => $info_object->get('Version'),
);
self::$removed_theme_info = $info;
}
/**
* Adds a theme deleted event to the audit log
*
* @param string $theme - Path to the theme file relative to the themes directory
* @param boolean $deleted - Whether the theme deletion was successful
*
* @return void
*/
public static function theme_deleted($theme, $deleted) {
if ($deleted) self::event_theme_changed('deleted', $theme, '', 'warning');
}
/**
* This function will construct the event details and send it to be recorded
*
* @param string $action - the action taken (activated, deactivated)
* @param string $theme - Path to the theme file relative to the themes directory
* @param string $network - status of if the theme was network activated/deactivated
* @param string $level - the log level
*
* @return void
*/
private static function event_theme_changed($action, $theme, $network, $level = 'info') {
if ('deleted' == $action) {
$info = self::$removed_theme_info;
} elseif ('installed' == $action) {
$info = self::$installed_theme_info;
} else {
$info_object = wp_get_theme($theme);
$info = array(
'Name' => $info_object->get('Name'),
'Version' => $info_object->get('Version'),
);
}
$name = empty($info['Name']) ? 'Unknown' : $info['Name'];
$version = empty($info['Version']) ? '0.0.0' : $info['Version'];
$details = array(
'theme' => array(
'name' => $name,
'version' => $version,
'action' => $action,
'network' => $network
)
);
do_action('aiowps_record_event', 'theme_' . $action, $details, $level);
}
/**
* Adds a translation updated event to the audit log
*
* @param WP_Upgrader $upgrader - WP_Upgrader instance
* @param array $hook_extra - Array of bulk item update data
*
* @return void
*/
public static function translation_updated($upgrader, $hook_extra) {
// If this is empty then we have no way to know if this is a plugin/theme/translation install/update so return as we catch this in plugin_installed()
if (empty($hook_extra)) return;
if ('translation' !== $hook_extra['type'] || 'update' !== $hook_extra['action']) return;
if (!isset($hook_extra['translations']) || empty($hook_extra['translations'])) return;
foreach ($hook_extra['translations'] as $info) {
$details = array(
'translation_updated' => $info
);
do_action('aiowps_record_event', 'translation_updated', $details, 'info');
}
}
/**
* Adds a entity changed event to the audit log
*
* @param WP_Upgrader $upgrader - WP_Upgrader instance
*
* @return void
*/
public static function event_entity_changed($upgrader) {
$entity = (isset($upgrader->result) && isset($upgrader->result['destination_name'])) ? $upgrader->result['destination_name'] : false;
$details = array(
'entity_changed' => array(
'entity' => $entity,
)
);
do_action('aiowps_record_event', 'entity_changed', $details, 'warning');
}
/**
* Adds all the firewall rule events to the audit log
*
* @return void
*/
public static function rule_event() {
$aiowps_firewall_message_store = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::MESSAGE_STORE);
$events = array();
foreach (array('active', 'not_active', 'triggered', 'not_triggered') as $event) {
$data = $aiowps_firewall_message_store->get('rule_'.$event);
if (empty($data)) continue;
foreach ($data as $rule) {
$details = array(
'firewall_event' => array(
'event' => $event,
'rule_name' => $rule['name'],
'rule_family' => $rule['family'],
)
);
$blog_id = AIOWPSecurity_Utility::get_blog_id_from_request($rule['request']);
$rule['request'] = apply_filters('aios_audit_filter_request', $rule['request'], $event);
$events[] = array(
'network_id' => get_current_network_id(),
'site_id' => $blog_id,
'username' => (isset($rule['potential_user']) ? AIOWPSecurity_Utility::verify_username($rule['potential_user']) : false),
'ip' => $rule['ip'],
'level' => 'triggered' === $event ? 'warning' : 'info',
'event_type' => 'rule_'.$event,
'details' => wp_json_encode($details, true),
'stacktrace' => (isset($rule['request']) ? print_r($rule['request'], true) : ''), // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- PCP warning. Part of AOIS error reporting system.
'created' => $rule['time']
);
}
}
if (empty($events)) return;
do_action('aiowps_bulk_record_events', $events);
}
/**
* Adds a password reset event to the audit log
*
* @param object $user_data - Object containing user's data
*/
public static function password_reset($user_data) {
$user_login = (false === $user_data) ? 'unknown' : $user_data->user_login;
$details = array(
'password_reset' => array(
'user_login' => $user_login
)
);
do_action('aiowps_record_event', 'password_reset', $details, 'warning');
}
/**
* Adds a user deleted event to the audit log
*
* @param int $user_id - the id of the deleted user
* @param int|null $reassign - the id of the user to reassign data to or null
* @param object $user_data - Object containing user's data
*
* @return void
*/
public static function user_deleted($user_id, $reassign, $user_data) {
$user_login = (false === $user_data) ? 'unknown' : $user_data->user_login;
$details = array(
'user_deleted' => array(
'user_id' => $user_id,
'reassign' => $reassign,
'user_login' => $user_login
)
);
do_action('aiowps_record_event', 'user_deleted', $details, 'warning');
}
/**
* Adds a user removed event to the audit log
*
* @param int $user_id - the id of the removed user
* @param int $blog_id - the id of the blog the user was removed from
* @param int $reassign - the id of the user to reassign data to or null
*
* @return void
*/
public static function user_removed_from_blog($user_id, $blog_id, $reassign) {
$user_data = get_user_by('ID', $user_id);
$user_login = is_a($user_data, 'WP_User') && 0 !== $user_data->ID ? $user_data->user_login : 'unknown';
$details = array(
'user_removed' => array(
'user_id' => $user_id,
'blog_id' => $blog_id,
'reassign' => $reassign,
'user_login' => $user_login
)
);
do_action('aiowps_record_event', 'user_removed', $details, 'warning');
}
/**
* Adds a failed login event to the audit log
*
* @param string $username - the username for the failed login attempt
*
* @return void
*/
public static function event_failed_login($username) {
$user = is_email($username) ? get_user_by('email', $username) : get_user_by('login', $username);
$details = array(
'failed_login' => array(
'imported' => false,
'username' => $username,
'known' => true,
)
);
if (is_a($user, 'WP_User')) {
do_action('aiowps_record_event', 'failed_login', $details, 'warning', $username);
} else {
$details['failed_login']['known'] = false;
do_action('aiowps_record_event', 'failed_login', $details, 'warning', $username);
}
}
/**
* Adds a user registration event to the audit log
*
* @param integer $user_id - the user ID of the newly registered user
* @param string $type - the type of registration valid values (admin, pending, registered)
*
* @return void
*/
public static function event_user_registration($user_id, $type) {
$registered_user = get_user_by('ID', $user_id);
$registered_username = is_a($registered_user, 'WP_User') && 0 !== $registered_user->ID ? $registered_user->user_login : '';
$details = array(
'user_registration' => array(
'registered_username' => $registered_username,
'type' => $type,
)
);
if ('admin' == $type) {
$admin_user = wp_get_current_user();
$admin_username = is_a($admin_user, 'WP_User') ? $admin_user->user_login : '';
$details['user_registration']['admin_username'] = $admin_username;
do_action('aiowps_record_event', 'user_registration', $details, 'info');
} elseif ('pending' == $type) {
do_action('aiowps_record_event', 'user_registration', $details, 'info', $registered_username);
} elseif ('registered' == $type) {
do_action('aiowps_record_event', 'user_registration', $details, 'info', $registered_username);
}
}
/**
* Adds a successful login event to the audit log
*
* @param string $username - the username for the successful login
*
* @return void
*/
public static function event_successful_login($username) {
$details = array(
'successful_login' => array(
'username' => $username,
)
);
do_action('aiowps_record_event', 'successful_login', $details, 'info', $username);
}
/**
* Adds a successful logout event to the audit log
*
* @param string $username - the username for the successful logout
* @param boolean $force_logout - if the logout was a force logout
*
* @return void
*/
public static function event_successful_logout($username, $force_logout = false) {
$details = array(
'successful_logout' => array(
'username' => $username,
'force_logout' => $force_logout ? __('(force logout)', 'all-in-one-wp-security-and-firewall') : ''
)
);
do_action('aiowps_record_event', 'successful_logout', $details, 'info', $username);
}
}
@@ -0,0 +1,238 @@
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
/**
* Handles converting the audit log's details column to a text representation
*/
class AIOWPSecurity_Audit_Text_Handler {
/**
* Return the text version of 'successful_login' event
*
* @param array $info
* @return string
*/
public static function successful_login_to_text($info) {
/* translators: %s: User name */
return sprintf(__('Successful login with username: %s', 'all-in-one-wp-security-and-firewall'), $info['username']);
}
/**
* Return the text version of 'successful_logout' event
*
* @param array $info - contains info used to generate the returned string
*
* @return string - the text to be shown for details on audit log table
*/
public static function successful_logout_to_text($info) {
return __('Successful logout with username:', 'all-in-one-wp-security-and-firewall') . ' ' . $info['username'] . ' ' . $info['force_logout'];
}
/**
* Return the text version of 'core_updated' event
*
* @param array $info
* @return string
*/
public static function core_updated_to_text($info) {
/* translators: 1: Old version, 2: New version */
return sprintf(__('WordPress updated from version %1$s to %2$s', 'all-in-one-wp-security-and-firewall'), $info['old_version'], $info['new_version']);
}
/**
* Return the text version of 'plugin' event
*
* @param array $info
* @return string
*/
public static function plugin_to_text($info) {
return sprintf(__('Plugin', 'all-in-one-wp-security-and-firewall').': %s %s %s (v%s)', $info['name'], $info['network'], $info['action'], $info['version']);
}
/**
* Return the text version of 'theme' event
*
* @param array $info
* @return string
*/
public static function theme_to_text($info) {
if ('activated' == $info['action']) {
return sprintf(__('Theme', 'all-in-one-wp-security-and-firewall').': %s %s', $info['name'], $info['action']);
} else {
return sprintf(__('Theme', 'all-in-one-wp-security-and-firewall').': %s %s %s (v%s)', $info['name'], $info['network'], $info['action'], $info['version']);
}
}
/**
* Return the text version of 'entity_changed' event
*
* @param array $info
* @return string
*/
public static function entity_changed_to_text($info) {
if ($info['entity']) {
/* translators: %s: Entity name */
return sprintf(__('Entity: "%s" has changed, please check the stacktrace for more details', 'all-in-one-wp-security-and-firewall'), $info['entity']);
} else {
return __('An unknown entity has changed, please check the stacktrace for more details', 'all-in-one-wp-security-and-firewall');
}
}
/**
* Return the text version of 'translation_updated' event
*
* @param array $info
* @return string
*/
public static function translation_updated_to_text($info) {
if ('core' == $info['type']) {
/* translators: 1: Language name, 2: Version */
return sprintf(__('Core %1$s translations updated to version %2$s', 'all-in-one-wp-security-and-firewall'), $info['language'], $info['version']);
} elseif ('plugin' == $info['type']) {
/* translators: 1: Slug, 2: Language, 3: Version */
return sprintf(__('Plugin "%1$s" %2$s translations updated to version %3$s', 'all-in-one-wp-security-and-firewall'), $info['slug'], $info['language'], $info['version']);
} elseif ('theme' == $info['type']) {
/* translators: 1: Slug, 2: Language name, 3: Version */
return sprintf(__('Theme "%1$s" %2$s translations updated to version %3$s', 'all-in-one-wp-security-and-firewall'), $info['slug'], $info['language'], $info['version']);
}
}
/**
* Return the text version of 'failed_login' event
*
* @param array $info
* @return string
*/
public static function failed_login_to_text($info) {
if ($info['imported']) {
return __('Event imported from the failed logins table', 'all-in-one-wp-security-and-firewall');
} elseif ($info['known']) {
/* translators: %s: User name */
return sprintf(__('Failed login attempt with a known username: %s', 'all-in-one-wp-security-and-firewall'), $info['username']);
} else {
/* translators: %s: User name */
return sprintf(__('Failed login attempt with a unknown username: %s', 'all-in-one-wp-security-and-firewall'), $info['username']);
}
}
/**
* Return the text version of 'user_registration' event
*
* @param array $info
* @return string
*/
public static function user_registration_to_text($info) {
if ('admin' == $info['type']) {
/* translators: 1: Admin User name, 2: Registered User name */
return sprintf(__('Admin %1$s registered new user: %2$s', 'all-in-one-wp-security-and-firewall'), $info['admin_username'], $info['registered_username']);
} elseif ('pending' == $info['type']) {
/* translators: %s: Registered User name */
return sprintf(__('User %s registered and set to pending', 'all-in-one-wp-security-and-firewall'), $info['registered_username']);
} elseif ('registered' == $info['type']) {
/* translators: %s: Registered User name */
return sprintf(__('User %s registered', 'all-in-one-wp-security-and-firewall'), $info['registered_username']);
}
}
/**
* Return the text version of 'table_migration' event
*
* @param array $info
* @return string
*/
public static function table_migration_to_text($info) {
if ($info['success']) {
/* translators: 1: From table, 2: To table */
return sprintf(__('Successfully migrated the `%1$s` table data to the `%2$s` table', 'all-in-one-wp-security-and-firewall'), $info['from_table'], $info['to_table']);
} else {
/* translators: 1: From table, 2: To table */
return sprintf(__('Failed to migrate the `%1$s` table data to the `%2$s` table', 'all-in-one-wp-security-and-firewall'), $info['from_table'], $info['to_table']);
}
}
/**
* Return the text version of 'firewall_event' event
*
* @param array $info
* @return string
*/
public static function firewall_event_to_text($info) {
$output = '';
switch ($info['event']) {
case 'triggered':
/* translators: 1: Rule name, 2: Rule family */
$output = sprintf(__('"%1$s [%2$s]" rule has been triggered.', 'all-in-one-wp-security-and-firewall'), $info['rule_name'], $info['rule_family']);
break;
case 'not_triggered':
/* translators: 1: Rule name, 2: Rule family */
$output = sprintf(__('"%1$s [%2$s]" rule was not triggered.', 'all-in-one-wp-security-and-firewall'), $info['rule_name'], $info['rule_family']);
break;
case 'active':
/* translators: 1: Rule name, 2: Rule family */
$output = sprintf(__('"%1$s [%2$s]" rule is active.', 'all-in-one-wp-security-and-firewall'), $info['rule_name'], $info['rule_family']);
break;
case 'not_active':
/* translators: 1: Rule name, 2: Rule family */
$output = sprintf(__('"%1$s [%2$s]" rule is not active.', 'all-in-one-wp-security-and-firewall'), $info['rule_name'], $info['rule_family']);
break;
default:
$output = array(
'status' => $info['event'],
'rule_name' => $info['rule_name'],
'rule_family' => $info['rule_family'],
);
break;
}
// Add a link to the rule, if present
if (isset($info['location'])) {
$output .= "<br><br><a href='{$info['location']}'target='_blank'>".__('Configure this rule', 'all-in-one-wp-security-and-firewall').'</a>';
}
return is_array($output) ? wp_json_encode($output) : $output;
}
/**
* Return the text version of 'password_reset' event
*
* @param array $info
* @return string
*/
public static function password_reset_to_text($info) {
/* translators: %s: User login */
return sprintf(__('Password for user account: `%s` successfully changed', 'all-in-one-wp-security-and-firewall'), $info['user_login']);
}
/**
* Return the text version of 'user_deleted' event
*
* @param array $info
* @return string
*/
public static function user_deleted_to_text($info) {
if (empty($info['reassign'])) {
/* translators: 1: User login, 2: User ID */
return sprintf(__('User account: %1$s with ID: `%2$s` has been deleted', 'all-in-one-wp-security-and-firewall'), $info['user_login'], $info['user_id']);
} else {
/* translators: 1: User login, 2: User ID, 3: Reassign */
return sprintf(__('User account: `%1$s` with ID: `%2$s` has been deleted and all content has been reassigned to user with ID: `%3$s`', 'all-in-one-wp-security-and-firewall'), $info['user_login'], $info['user_id'], $info['reassign']);
}
}
/**
* Return the text version of 'user_removed' event
*
* @param array $info
* @return string
*/
public static function user_removed_to_text($info) {
if (empty($info['reassign'])) {
/* translators: 1: User login, 2: User ID, 3: Blog ID */
return sprintf(__('User account: %1$s with ID: `%2$s` has been removed from the blog with ID: `%3$s`', 'all-in-one-wp-security-and-firewall'), $info['user_login'], $info['user_id'], $info['blog_id']);
} else {
/* translators: 1: User login, 2: User ID, 3: Blog ID, 4: Reassign */
return sprintf(__('User account: `%1$s` with ID: `%2$s` has been removed from the blog with ID: `%3$s` and all content has been reassigned to user with ID: `%4$s`', 'all-in-one-wp-security-and-firewall'), $info['user_login'], $info['user_id'], $info['blog_id'], $info['reassign']);
}
}
}
@@ -0,0 +1,43 @@
<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly.
}
abstract class AIOWPSecurity_Base_Tasks {
/**
* Runs intended various tasks
* Handles single and multi-site (NW activation) cases
*
* @global type $wpdb
*/
public static function run() {
if (is_multisite()) {
global $wpdb;
// check if it is a network activation
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
$blog_ids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
foreach ($blog_ids as $blog_id) {
switch_to_blog($blog_id);
static::run_for_a_site();
restore_current_blog();
}
} else {
static::run_for_a_site();
}
}
/**
* Run uninstallation task for a single site.
*
* This method must be implemented in child classes.
* Since static abstract methods are not allowed in PHP, we enforce it at runtime.
*
* @throws Exception If not overridden in a child class.
* @return void
*/
protected static function run_for_a_site() {
throw new Exception(
sprintf('%s : Child classes must implement run_for_a_site() method.', get_called_class())
);
}
}
@@ -0,0 +1,207 @@
<?php
if (!defined('ABSPATH')) {
exit; //Exit if accessed directly
}
/**
* Firewall bootstrap file content block.
*/
class AIOWPSecurity_Block_Bootstrap extends AIOWPSecurity_Block_File {
/**
* Keeps track of our bootstrap file version
*
* @var string
*/
protected $version = '1.0.2';
/**
* Inserts our code into our bootstrap file.
*
* @return boolean|WP_Error
*/
public function insert_contents() {
$info = pathinfo($this->file_path);
if (!isset($info['dirname'])) {
return new WP_Error(
'file_no_directory',
'No directory has been set',
$this->file_path
);
}
// phpcs:disable WordPress.WP.AlternativeFunctions -- wp_filesystem not recommended for firewall.
if (!is_writable($info['dirname'])) {
return new WP_Error(
'file_directory_not_writable',
'The directory has incorrect write permissions. Please double check its permissions and try again.',
$info['dirname']
);
}
return (false !== @file_put_contents($this->file_path, $this->get_contents())); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
// phpcs:enable WordPress.WP.AlternativeFunctions -- wp_filesystem not recommended for firewall.
}
/**
* Checks whether the bootstrap contents are valid
*
* @param string $contents
* @return boolean
*/
protected function is_content_valid($contents) {
//Ensure we're using the correct version of the file
$version = $this->get_bootstrap_version();
if (false === $version) return false;
if ($version['full_version'] !== $this->version) return false;
//Ensure the required paths are valid
$regexes = array('/file_exists\((?<file_path>\'.*\')\)/isU', '/include_once\((?<file_path>\'.*\')\)/isU');
$firewall_path_str = $this->get_firewall_path_str();
foreach ($regexes as $regex) {
if (preg_match($regex, $contents, $matches)) {
if ($firewall_path_str !== $matches['file_path']) return false;
}
}
return true;
}
/**
* Returns the bootstrap version from file
*
* @return array|boolean Array with the version information; false otherwise.
*/
protected function get_bootstrap_version() {
$contents = @file_get_contents($this->file_path); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
if (false === $contents) return false;
if (preg_match('/@version (?<full_version>(?<major>\d{1,})\.(?<minor>\d{1,})\.(?<patch>\d{1,}))$/m', $contents, $matches)) {
return $matches;
}
return false;
}
/**
* Get the firewall path string that contains "__DIR__" for home dir, if plugin dir isn't a symbolic link..
*
* @return string The firewall path string.
*/
private function get_firewall_path_str() {
$firewall_path = AIOWPSecurity_Utility_Firewall::get_firewall_path();
$firewall_path_str = $this->get_path_str_for_given_absolute_path($firewall_path);
return $firewall_path_str;
}
/**
* Get path string to write bootstrap file from given path.
*
* @param string $path a path that we want to write to the bootstrap file.
* @return string The path that can be written in the bootstrap file.
*/
private function get_path_str_for_given_absolute_path($path) {
$home_path = AIOWPSecurity_Utility_File::get_home_path();
// If the plugin is symbolic linked, then the plugin's firewall path is not started with home_path.
$path_str = (0 === strpos($path, $home_path)) ? "__DIR__.'/".substr($path, strlen($home_path))."'" : "'".$path."'";
return $path_str;
}
/**
* Get the firewall rules path string that contains "__DIR__" for home dir, if plugin dir isn't a symbolic link.
*
* @return string The firewall rule path string.
*/
private function get_firewall_rules_path_str() {
$firewall_rules_path = AIOWPSecurity_Utility_Firewall::get_firewall_rules_path();
$firewall_rules_path_str = $this->get_path_str_for_given_absolute_path($firewall_rules_path);
return $firewall_rules_path_str;
}
/**
* The regex pattern that demarcates our contents
*
* @return string
*/
protected function get_regex_pattern() {
return '#// Begin AIOWPSEC Firewall(.*)// End AIOWPSEC Firewall#isU';
}
/**
* Bootstrap file contents to insert
*
* @return string
*/
public function get_contents() {
$firewall_path_str = $this->get_firewall_path_str();
$firewall_rules_path_str = $this->get_firewall_rules_path_str();
// Any extra data we want to have accessible to the firewall
$data = array(
'ABSPATH' => wp_normalize_path(ABSPATH)
);
$code = "<?php\n";
$code .= $this->get_warning_message();
$directive = AIOWPSecurity_Utility_Firewall::get_already_set_directive();
if (!empty($directive) && $directive !== $this->file_path) {
$directive = wp_normalize_path($directive);
$code .= "// Previously set auto_prepend_file\n";
$code .= "if (file_exists('{$directive}')) {\n";
$code .= "\tinclude_once('{$directive}');\n";
$code .= "}\n";
}
$code .= '$GLOBALS[\'aiowps_firewall_rules_path\'] = '.$firewall_rules_path_str.";\n\n";
// write any other data we need
$code .= "\$GLOBALS['aiowps_firewall_data'] = array(\n";
foreach ($data as $name => $value) {
$code .= "\t'{$name}' => '{$value}',\n";
}
$code .= ");\n\n"; //close data array
$code .= "// Begin AIOWPSEC Firewall\n";
$code .= "if (file_exists({$firewall_path_str})) {\n";
$code .= "\tinclude_once({$firewall_path_str});\n";
$code .= "}\n";
$code .= "// End AIOWPSEC Firewall\n";
return $code;
}
/**
* Gets our warning message for users
*
* @return string
*/
protected function get_warning_message() {
$warning = "/** \n";
$warning .= " * @version {$this->version}\n";
$warning .= " * WARNING: Please do not delete this file.\n";
$warning .= " * \n";
$warning .= " * This will cause PHP to throw a fatal error and render your site unusable.\n";
$warning .= " * \n";
$warning .= " * To safely delete this file, please check both your .user.ini file and your php.ini file and ensure this file is not set in the auto_prepend_file directive.\n";
$warning .= " * \n";
$warning .= " * Please ask your web hosting provider if you need guidance with executing the aforementioned steps.\n";
$warning .= " */\n";
return $warning;
}
}
@@ -0,0 +1,202 @@
<?php
if (!defined('ABSPATH')) {
exit; //Exit if accessed directly
}
abstract class AIOWPSecurity_Block_File {
/**
* Full path to the file we're managing
*
* @var string
*/
protected $file_path;
/**
* Receives the full file path
*
* @param string $file_path
*/
public function __construct($file_path) {
$this->file_path = $file_path;
}
/**
* Insert the contents to the managed file
*
* @return boolean|WP_Error true if success; false if failed
*/
abstract public function insert_contents();
/**
* Returns the contents to be inserted into the managed file
*
* @return string
*/
abstract public function get_contents();
/**
* Returns the regex pattern that separates our contents from others the file may contain
*
* @return string
*/
abstract protected function get_regex_pattern();
/**
* Checks whether the file's contents are valid
*
* @param string $contents
* @return boolean
*/
abstract protected function is_content_valid($contents);
/**
* Updates the contents of the managed file
*
* @return boolean|WP_Error true if updated; false if not updated
*/
public function update_contents() {
// phpcs:ignore WordPress.WP.AlternativeFunctions -- wp_filesystem not recommended.
if (!is_readable($this->file_path) || !is_writable($this->file_path)) {
return new WP_Error(
'file_wrong_permissions',
'The file has incorrect read or write permissions. Please double check its permissions and try again.',
$this->file_path
);
}
$contents = @file_get_contents($this->file_path); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
if (false === $contents) {
return new WP_Error(
'file_unable_to_read',
'Unable to read the file. Please double check its permissions and try again.',
$this->file_path
);
}
$matches = array();
$match_count = preg_match_all($this->get_regex_pattern(), $contents, $matches);
if (empty($matches[1]) || false === $match_count) {
return false;
}
//checks whether an update is required
$requires_update = false;
$match = '';
foreach ($matches[1] as $match) {
$requires_update = !$this->is_content_valid($match);
if (true === $requires_update) {
break;
}
}
//perform the update
if ($requires_update) {
$block_removed = $this->remove_contents();
$block_inserted = $this->insert_contents();
return (true === $block_removed && true === $block_inserted);
}
return false;
}
/**
* Checks whether the file contains our contents
*
* @return boolean|WP_Error true if found; false if not found
*/
public function contains_contents() {
clearstatcache();
if (!file_exists($this->file_path)) {
return new WP_Error(
'file_does_not_exist',
'The file does not exist.',
$this->file_path
);
}
if (!is_readable($this->file_path)) {
return new WP_Error(
'file_wrong_permissions',
'The file has incorrect read permissions. Please double check its permissions and try again.',
$this->file_path
);
}
$contents = @file_get_contents($this->file_path); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
if (false === $contents) {
return new WP_Error(
'file_unable_to_read',
'Unable to read the file. Please double check its permissions and try again.',
$this->file_path
);
}
return (1 === preg_match($this->get_regex_pattern(), $contents));
}
/**
* Removes our contents from the file
*
* @return boolean|WP_Error
*/
public function remove_contents() {
if (!is_readable($this->file_path) || !is_writable($this->file_path)) {
return new WP_Error(
'file_wrong_permissions',
'The file has incorrect read or write permissions. Please double check its permissions and try again.',
$this->file_path
);
}
$contents = @file_get_contents($this->file_path); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
if (false === $contents) {
return new WP_Error(
'file_unable_to_read',
'Unable to read the file. Please double check its permissions and try again.',
$this->file_path
);
}
$removed = 0;
$contents = preg_replace($this->get_regex_pattern(), "", $contents, -1, $removed);
if (null === $contents) {
return new WP_Error(
'file_unable_to_alter',
'Unable to alter the file.',
$this->file_path
);
}
if (false === @file_put_contents($this->file_path, $contents, LOCK_EX)) { // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
return new WP_Error(
'file_unable_to_write',
'Unable to write to the file. Please double check its permissions and try again.',
$this->file_path
);
}
return $removed > 0;
}
/**
* By default returns the full path to the file being managed
*
* @return string
*/
public function __toString() {
return $this->file_path;
}
} //end of class
@@ -0,0 +1,91 @@
<?php
if (!defined('ABSPATH')) {
exit; //Exit if accessed directly
}
class AIOWPSecurity_Block_Htaccess extends AIOWPSecurity_Block_File {
/**
* Attempts to insert our apache directives into the htaccess file
*
* @return boolean|WP_Error true if success; false if failed
*/
public function insert_contents() {
$home_path = AIOWPSecurity_Utility_File::get_home_path();
// phpcs:ignore WordPress.WP.AlternativeFunctions -- wp_filesystem not recommended.
if (!is_writable($home_path)) {
return new WP_Error(
'file_wrong_permissions',
'The file has incorrect write permissions. Please double check its permissions and try again.',
$this->file_path
);
}
return (false !== @file_put_contents($this->file_path, $this->get_contents(), FILE_APPEND)); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
}
/**
* Checks whether the file's contents are valid
*
* @param string $contents
* @return boolean
*/
protected function is_content_valid($contents) {
$regex = '/php_value auto_prepend_file \'(.*)\'/isU';
$bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
$matches = array();
if (preg_match_all($regex, $contents, $matches)) {
$match = '';
foreach ($matches[1] as $match) {
if ($bootstrap_path !== $match) {
return false;
}
}
} else {
return false;
}
return true;
}
/**
* The regex pattern that demarcates our contents
*
* @return string
*/
protected function get_regex_pattern() {
return '/\r?\n?# Begin AIOWPSEC Firewall(.*?)# End AIOWPSEC Firewall/is';
}
/**
* Our contents; the required apache directives for auto prepending a file
*
* @return string
*/
public function get_contents() {
$bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
$directives = "\n# Begin AIOWPSEC Firewall\n";
$directives .= "\t<IfModule mod_php5.c>\n";
$directives .= "\t\tphp_value auto_prepend_file '{$bootstrap_path}'\n";
$directives .= "\t</IfModule>\n";
$directives .= "\t<IfModule mod_php7.c>\n";
$directives .= "\t\tphp_value auto_prepend_file '{$bootstrap_path}'\n";
$directives .= "\t</IfModule>\n";
$directives .= "\t<IfModule mod_php.c>\n";
$directives .= "\t\tphp_value auto_prepend_file '{$bootstrap_path}'\n";
$directives .= "\t</IfModule>\n";
$directives .= "# End AIOWPSEC Firewall";
return $directives;
}
} //end of class
@@ -0,0 +1,29 @@
<?php
if (!defined('ABSPATH')) {
exit; //Exit if accessed directly
}
class AIOWPSecurity_Block_Litespeed extends AIOWPSecurity_Block_Htaccess {
/**
* Get the directives needed for litespeed server
*
* @return string
*/
public function get_contents() {
$bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
$directives = "\n# Begin AIOWPSEC Firewall\n";
$directives .= "\t<IfModule LiteSpeed>\n";
$directives .= "\t\tphp_value auto_prepend_file '{$bootstrap_path}'\n";
$directives .= "\t</IfModule>\n";
$directives .= "\t<IfModule lsapi_module>\n";
$directives .= "\t\tphp_value auto_prepend_file '{$bootstrap_path}'\n";
$directives .= "\t</IfModule>\n";
$directives .= "# End AIOWPSEC Firewall";
return $directives;
}
}
@@ -0,0 +1,93 @@
<?php
if (!defined('ABSPATH')) {
exit; //Exit if accessed directly
}
class AIOWPSecurity_Block_Muplugin extends AIOWPSecurity_Block_File {
/**
* Inserts our code into our mu-plugin.
*
* The mu-plugin and the mu-plugin directory will be created if they don't already exists
*
* @return boolean|WP_Error
*/
public function insert_contents() {
$info = pathinfo($this->file_path);
if (!isset($info['dirname'])) {
return new WP_Error(
'file_no_directory',
'No directory has been set',
$this->file_path
);
}
if (false === wp_mkdir_p($info['dirname'])) {
return new WP_Error(
'file_no_directory_created',
'Unable to create the directory',
$info['dirname']
);
}
return (false !== @file_put_contents($this->file_path, $this->get_contents())); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- WP_Filesystem is not appropriate here.
}
/**
* Checks whether the mu-plugin contents are valid
*
* @param string $contents
* @return boolean
*/
protected function is_content_valid($contents) {
//The regexes we extract the paths from
$regexes = array('/file_exists\(\'(.*)\'\)/isU', '/include_once\(\'(.*)\'\)/isU');
$regex = '';
$bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
foreach ($regexes as $regex) {
$matches = array();
$result = preg_match($regex, $contents, $matches);
if (empty($matches[1]) || false === $result) {
continue;
}
if ($bootstrap_path !== $matches[1]) {
return false;
}
}
return true;
}
/**
* The regex pattern that demarcates our contents
*
* @return string
*/
protected function get_regex_pattern() {
return '#// Begin AIOWPSEC Firewall(.*)// End AIOWPSEC Firewall#isU';
}
/**
* Our firewall code to insert
*
* @return string
*/
public function get_contents() {
$bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
$code = "<?php\n";
$code .= "// Begin AIOWPSEC Firewall\n";
$code .= "if (file_exists('{$bootstrap_path}')) {\n";
$code .= "\tinclude_once('{$bootstrap_path}');\n";
$code .= "}\n";
$code .= "// End AIOWPSEC Firewall\n";
return $code;
}
}
@@ -0,0 +1,98 @@
<?php
if (!defined('ABSPATH')) {
exit; //Exit if accessed directly
}
class AIOWPSecurity_Block_Userini extends AIOWPSecurity_Block_File {
/**
* Inserts our directive into the user.ini file
*
* @return boolean|WP_Error true if inserted; false if failed
*/
public function insert_contents() {
$home_path = AIOWPSecurity_Utility_File::get_home_path();
// phpcs:ignore WordPress.WP.AlternativeFunctions -- wp_filesystem not recommended.
if (!is_writable($home_path)) {
return new WP_Error(
'file_directory_not_writable',
'The directory has incorrect write permissions. Please double check its permissions and try again.',
$home_path
);
}
return (false !== @file_put_contents($this->file_path, $this->get_contents(), FILE_APPEND)); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
}
/**
* Checks whether the user.ini file contents are valid
*
* @param string $contents
* @return boolean
*/
protected function is_content_valid($contents) {
$regex = '/auto_prepend_file=\'(.*)\'/isU';
$bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
$match = array();
$result = preg_match($regex, $contents, $match);
if (empty($match[1]) || false === $result) {
return false;
}
if ($bootstrap_path !== $match[1]) {
return false;
}
return true;
}
/**
* Our regex pattern that demarcates our contents
*
* @return string
*/
protected function get_regex_pattern() {
return '/\r?\n?# Begin AIOWPSEC Firewall(.*?)# End AIOWPSEC Firewall/is';
}
/**
* Directives inserted into user.ini
*
* @return string
*/
public function get_contents() {
$bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
$directive = "\n# Begin AIOWPSEC Firewall\n";
$directive .= "auto_prepend_file='{$bootstrap_path}'\n";
$directive .= "# End AIOWPSEC Firewall";
return $directive;
}
/**
* Extends the contains_contents function to check for already set directives
*
* @return boolean|WP_Error
*/
public function contains_contents() {
$contains = parent::contains_contents();
if (false === $contains) {
$directive_userini = AIOWPSecurity_Utility_Firewall::get_already_set_directive($this->file_path);
$directive = AIOWPSecurity_Utility_Firewall::get_already_set_directive();
if ((AIOWPSecurity_Utility_Firewall::get_bootstrap_path() === $directive) || (AIOWPSecurity_Utility_Firewall::get_bootstrap_path() === $directive_userini)) {
return true;
}
}
return $contains;
}
}

Some files were not shown because too many files have changed in this diff Show More