Changeset 3300103
- Timestamp:
- 05/25/2025 06:56:50 AM (10 months ago)
- Location:
- iron-security
- Files:
-
- 2 added
- 10 edited
- 45 copied
-
tags/2.3.3 (copied) (copied from iron-security/trunk)
-
tags/2.3.3/LICENSE.txt (copied) (copied from iron-security/trunk/LICENSE.txt)
-
tags/2.3.3/README.txt (copied) (copied from iron-security/trunk/README.txt) (4 diffs)
-
tags/2.3.3/admin (copied) (copied from iron-security/trunk/admin)
-
tags/2.3.3/admin/class-iron-security-admin.php (copied) (copied from iron-security/trunk/admin/class-iron-security-admin.php) (148 diffs)
-
tags/2.3.3/admin/classes/general-security.php (copied) (copied from iron-security/trunk/admin/classes/general-security.php)
-
tags/2.3.3/admin/classes/login-logout-functionality.php (copied) (copied from iron-security/trunk/admin/classes/login-logout-functionality.php)
-
tags/2.3.3/admin/classes/tracking-access.php (copied) (copied from iron-security/trunk/admin/classes/tracking-access.php)
-
tags/2.3.3/admin/css/dashboard.css (copied) (copied from iron-security/trunk/admin/css/dashboard.css)
-
tags/2.3.3/admin/css/iron-security-admin.css (copied) (copied from iron-security/trunk/admin/css/iron-security-admin.css)
-
tags/2.3.3/admin/css/transitions.css (copied) (copied from iron-security/trunk/admin/css/transitions.css)
-
tags/2.3.3/admin/js/2fa.js (copied) (copied from iron-security/trunk/admin/js/2fa.js)
-
tags/2.3.3/admin/js/components/Dashboard.jsx (copied) (copied from iron-security/trunk/admin/js/components/Dashboard.jsx) (1 diff)
-
tags/2.3.3/admin/js/components/FileDirectoryProectionSettings.jsx (copied) (copied from iron-security/trunk/admin/js/components/FileDirectoryProectionSettings.jsx) (1 diff)
-
tags/2.3.3/admin/js/components/GeneralSettings.jsx (copied) (copied from iron-security/trunk/admin/js/components/GeneralSettings.jsx) (2 diffs)
-
tags/2.3.3/admin/js/components/HttpSecurityHeadersSettings.jsx (copied) (copied from iron-security/trunk/admin/js/components/HttpSecurityHeadersSettings.jsx) (1 diff)
-
tags/2.3.3/admin/js/components/LoginLogoutSettings.jsx (copied) (copied from iron-security/trunk/admin/js/components/LoginLogoutSettings.jsx) (7 diffs)
-
tags/2.3.3/admin/js/components/SecurityLogs.jsx (copied) (copied from iron-security/trunk/admin/js/components/SecurityLogs.jsx)
-
tags/2.3.3/admin/js/dashboard.js (copied) (copied from iron-security/trunk/admin/js/dashboard.js)
-
tags/2.3.3/admin/js/dist/dashboard.409171eb914877d3afe7.js (added)
-
tags/2.3.3/admin/js/dist/dashboard.bundle.js (copied) (copied from iron-security/trunk/admin/js/dist/dashboard.bundle.js)
-
tags/2.3.3/admin/js/dist/dashboard.d4938437720f6eface82.js (copied) (copied from iron-security/trunk/admin/js/dist/dashboard.d4938437720f6eface82.js)
-
tags/2.3.3/admin/js/dist/manifest.json (copied) (copied from iron-security/trunk/admin/js/dist/manifest.json) (1 diff)
-
tags/2.3.3/admin/js/dist/vendors.6faccb462d3791bbb518.js (copied) (copied from iron-security/trunk/admin/js/dist/vendors.6faccb462d3791bbb518.js)
-
tags/2.3.3/admin/js/dist/vendors.6faccb462d3791bbb518.js.LICENSE.txt (copied) (copied from iron-security/trunk/admin/js/dist/vendors.6faccb462d3791bbb518.js.LICENSE.txt)
-
tags/2.3.3/admin/js/dist/vendors.91782840b9e9337a9a5f.js (copied) (copied from iron-security/trunk/admin/js/dist/vendors.91782840b9e9337a9a5f.js)
-
tags/2.3.3/admin/js/dist/vendors.91782840b9e9337a9a5f.js.LICENSE.txt (copied) (copied from iron-security/trunk/admin/js/dist/vendors.91782840b9e9337a9a5f.js.LICENSE.txt)
-
tags/2.3.3/admin/js/iron-security-admin.js (copied) (copied from iron-security/trunk/admin/js/iron-security-admin.js)
-
tags/2.3.3/build (copied) (copied from iron-security/trunk/build)
-
tags/2.3.3/composer.json (copied) (copied from iron-security/trunk/composer.json)
-
tags/2.3.3/composer.lock (copied) (copied from iron-security/trunk/composer.lock)
-
tags/2.3.3/includes (copied) (copied from iron-security/trunk/includes)
-
tags/2.3.3/includes/class-iron-security-deactivator.php (copied) (copied from iron-security/trunk/includes/class-iron-security-deactivator.php)
-
tags/2.3.3/includes/class-iron-security-logger.php (copied) (copied from iron-security/trunk/includes/class-iron-security-logger.php)
-
tags/2.3.3/includes/class-iron-security.php (copied) (copied from iron-security/trunk/includes/class-iron-security.php) (12 diffs)
-
tags/2.3.3/includes/ss.json (copied) (copied from iron-security/trunk/includes/ss.json)
-
tags/2.3.3/index.php (copied) (copied from iron-security/trunk/index.php)
-
tags/2.3.3/iron-security.php (copied) (copied from iron-security/trunk/iron-security.php) (2 diffs)
-
tags/2.3.3/languages (copied) (copied from iron-security/trunk/languages)
-
tags/2.3.3/package.json (copied) (copied from iron-security/trunk/package.json)
-
tags/2.3.3/pnpm-lock.yaml (copied) (copied from iron-security/trunk/pnpm-lock.yaml)
-
tags/2.3.3/public (copied) (copied from iron-security/trunk/public)
-
tags/2.3.3/public/class-iron-security-public.php (copied) (copied from iron-security/trunk/public/class-iron-security-public.php)
-
tags/2.3.3/uninstall.php (copied) (copied from iron-security/trunk/uninstall.php)
-
tags/2.3.3/vendor (copied) (copied from iron-security/trunk/vendor)
-
tags/2.3.3/webpack.config.js (copied) (copied from iron-security/trunk/webpack.config.js)
-
trunk/README.txt (modified) (4 diffs)
-
trunk/admin/class-iron-security-admin.php (modified) (148 diffs)
-
trunk/admin/js/components/Dashboard.jsx (modified) (1 diff)
-
trunk/admin/js/components/FileDirectoryProectionSettings.jsx (modified) (1 diff)
-
trunk/admin/js/components/GeneralSettings.jsx (modified) (2 diffs)
-
trunk/admin/js/components/HttpSecurityHeadersSettings.jsx (modified) (1 diff)
-
trunk/admin/js/components/LoginLogoutSettings.jsx (modified) (7 diffs)
-
trunk/admin/js/dist/dashboard.409171eb914877d3afe7.js (added)
-
trunk/admin/js/dist/manifest.json (modified) (1 diff)
-
trunk/includes/class-iron-security.php (modified) (12 diffs)
-
trunk/iron-security.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
iron-security/tags/2.3.3/README.txt
r3296251 r3300103 5 5 Requires at least: 4.7 6 6 Tested up to: 6.8 7 Stable tag: 2.3. 27 Stable tag: 2.3.3 8 8 Requires PHP: 7.0 9 9 License: GPLv2 or later … … 35 35 - Change default Admin ID 36 36 - Block user enumeration 37 - Change Default Admin Username 37 38 38 39 **Files & Directory Protection** … … 65 66 66 67 = What makes Iron Security different from other WordPress security plugins? = 67 Iron Security is designed to be lightweight, fast, and focused on practical features that matter most for securing your WordPress site.68 Iron Security is lightweight, fast, and focused on the most effective hardening techniques. Instead of bloated features, it delivers practical tools that directly protect your WordPress site from real threats. 68 69 69 70 = Is Iron Security suitable for beginners? = 70 Yes! Iron Security comes with an intuitive dashboard and clear explanations for each option. Whether you're a WordPress beginner or an experienced developer, you'll find it easy to use and configure.71 72 = How does the custom login URL helpprotect my site? =73 Changing the default `/wp-admin` or `/wp-login.php` URL makes it harder for bots and attackers to find your login page, reducing brute force attempts. You can set your own unique login slug in a few clicks from the plugin settings.74 75 = What happens when a userexceeds the allowed login attempts? =76 If a user exceeds the allowed number of login attempts, their IP will be temporarily blocked based on your configured lockout settings. You can customize the number of allowed attempts, lockout duration, and view attempt logs.77 78 = How does theAdmin ID protection work? =79 By default, WordPress assigns user ID 1 to the first admin account — a known vulnerability targeted by bots. Iron Security lets you assign a different ID to your admin account, making it harder to guess and exploit.80 81 = Does Iron Security block XML-RPC and REST API? Why? =82 Yes , you can optionally disable XML-RPC and REST API — two common attack vectors. XML-RPC is often used in DDoS and brute force attacks, while REST API may expose user data. Disabling them improves security, especially if you don’t use them.83 84 = What are HTTP security headers and why should I enable them? =85 HTTP security headers like X-Frame-Options, Content-Security-Policy, and Strict-Transport-Security provide an extra layer of browser-based protection. They help prevent XSS, clickjacking, and other code injection attacks. Iron Security lets you enable them easily from the dashboard.71 Absolutely. Iron Security features an intuitive dashboard with clear explanations for each setting. Whether you’re new to WordPress or a seasoned developer, you’ll find it easy to set up and use. 72 73 = How does changing the login URL protect my site? = 74 By replacing the default /wp-admin or /wp-login.php URLs, you reduce the risk of automated brute force attacks. Bots and attackers can’t easily locate your login page, adding an extra layer of protection. 75 76 = What happens if someone exceeds the allowed login attempts? = 77 Their IP will be temporarily blocked based on your configured settings. You can set how many failed attempts are allowed, how long the lockout lasts, and monitor the attempt logs directly from the plugin dashboard. 78 79 = How does Admin ID protection work? = 80 WordPress assigns the first admin user an ID of 1 — a known target for attacks. Iron Security helps you avoid this by allowing you to assign a different user ID to your admin account, reducing exposure to targeted exploits. 81 82 = Can Iron Security block XML-RPC and REST API access? = 83 Yes. These features can be optionally disabled. XML-RPC and REST API are common targets for attacks like brute force or user data enumeration. If you don’t use them, disabling them can significantly reduce your attack surface. 84 85 = What are HTTP security headers, and why should I enable them? = 86 HTTP security headers such as X-Frame-Options, Content-Security-Policy, and Strict-Transport-Security enhance browser-level security. They help protect your site against XSS, clickjacking, and other injection-based attacks. Iron Security lets you enable these headers with just a few clicks. 86 87 87 88 = Will Iron Security slow down my website? = 88 No t at all. The plugin is built to be lightweight and uses efficient code practices. It doesn’t run background scans or heavy processes, so your site’s performance remains unaffected.89 90 = Can I use Iron Security on WooCommerce stores? =91 Absolutely. Iron Security is fully compatible with WooCommerce and protects your login area, admin panel, and core files without affecting your store’s functionality.92 93 = Wherecan I get support or report a bug? =94 You can submit issues or ask for help via the [support forum on WordPress.org](https://wordpress.org/support/plugin/iron-security/) or by contacting us directly at [https://wpiron.com](https://wpiron.com).89 No. Iron Security is performance-focused and doesn't include heavy scans or background processes. It’s designed to secure your site without impacting loading speed or user experience. 90 91 = Is Iron Security compatible with WooCommerce? = 92 Yes, Iron Security works seamlessly with WooCommerce. It protects your admin area, login forms, and critical files without interfering with your store's functionality or customer experience. 93 94 = How can I get support or report a bug? = 95 You can reach out via the WordPress.org support forum: https://wordpress.org/support/plugin/iron-security/ or contact our team directly at https://wpiron.com. 95 96 96 97 = How often is Iron Security updated? = 97 We actively maintain and improve Iron Security. You can expect regular updates for new features, security patches, and WordPress compatibility improvements. 98 We actively maintain Iron Security to ensure compatibility with the latest WordPress releases. Expect regular updates with new features, security improvements, and bug fixes. 99 100 = What is the benefit of disabling file editing in WordPress? = 101 Disabling the file editor in the admin panel prevents attackers from injecting malicious code directly into theme or plugin files if they gain admin access. This is a simple but effective hardening step. 102 103 = What does hiding the WordPress version do? = 104 Iron Security removes the WordPress version from your website’s source code to reduce the risk of automated attacks targeting specific versions with known vulnerabilities. 105 106 = How does Iron Security block AI crawlers? = 107 The plugin adds specific rules to your robots.txt file to block AI and data scraping bots, helping protect your content from unauthorized use and training. 108 109 = What does "Limit number of administrators" mean? = 110 Limiting admin users helps reduce the number of high-privilege accounts on your site. The plugin notifies you if more than one admin account exists, encouraging better access control and role management. 111 112 = How does session timeout protect my site? = 113 If a logged-in user is inactive for a certain period, they are automatically logged out. This prevents unauthorized access from unattended sessions on shared or public devices. 114 115 = Can I block user enumeration? = 116 Yes. Iron Security prevents bots from discovering usernames through the `?author=` query parameter, a common tactic used in brute force and targeted attacks. 117 118 = What does “Change default admin username” do? = 119 If your admin account uses "admin" as the username, it becomes an easy target. Iron Security helps you safely change this default to something unique and secure. 120 121 = What does "Prevent direct file access" mean? = 122 The plugin restricts access to sensitive files like PHP templates and system files that should not be accessed directly, protecting your site from unauthorized requests. 123 124 = Can I block PHP file uploads? = 125 Yes. Iron Security allows you to block PHP file uploads in forms and media to prevent malicious scripts from being uploaded to your site. 126 127 = What are plugin and core auto-updates, and why enable them? = 128 Keeping your WordPress core and plugins updated is critical to staying secure. Iron Security allows you to enable automatic updates, so your site is always protected with the latest patches. 98 129 99 130 … … 111 142 == Changelog == 112 143 144 = 2.3.3 = 145 * Add functionality to change default admin username 146 113 147 = 2.3.2 = 114 148 * Gutenberg some blocks were disabled - fixed it. -
iron-security/tags/2.3.3/admin/class-iron-security-admin.php
r3291538 r3300103 47 47 plugin_dir_url( __FILE__ ) . 'css/admin.css', 48 48 array(), 49 time(),49 $this->version, 50 50 'all' ); 51 51 wp_enqueue_style( $this->plugin_name . '-dashboard', … … 57 57 plugin_dir_url( __FILE__ ) . 'css/transitions.css', 58 58 array(), 59 time(),59 $this->version, 60 60 'all' ); 61 61 } … … 81 81 plugin_dir_url( __FILE__ ) . 'js/iron-security-admin.js', 82 82 array( 'jquery', 'wp-element', 'wp-components' ), 83 //$this->version,84 time(),83 $this->version, 84 // time(), 85 85 false 86 86 ); … … 92 92 plugin_dir_url( __FILE__ ) . 'js/session-timeout.js', 93 93 array( 'jquery' ), 94 //$this->version,95 time(),94 $this->version, 95 // time(), 96 96 true 97 97 ); … … 114 114 array( 'wp-element', 'wp-components', 'wp-i18n' ), 115 115 $this->version, 116 // time(), 116 117 true 117 118 ); … … 122 123 array( 'wp-element', 'wp-components', 'wp-i18n' ), 123 124 $this->version, 125 // time(), 124 126 true 125 127 ); … … 171 173 // Your content for login/logout tab 172 174 echo '<h3>Login/Logout Settings</h3>'; 173 // add your logic here 174 } 175 // Add other tabs content conditions 175 176 } 177 176 178 ?> 177 179 </div> … … 223 225 $sanitized_input = array(); 224 226 foreach ( $input as $key => $value ) { 225 // Special handling for checkbox/toggle fields 227 226 228 if ( in_array( $key, array( 227 229 'wpironis_disable_xmlrpc', … … 346 348 $secret = get_user_meta( $user->ID, 'google_authenticator_secret', true ); 347 349 if ( ! $secret ) { 348 return $user; // No 2FA setup for the user350 return $user; 349 351 } 350 352 … … 478 480 } 479 481 480 // When enabled is true, we want to disable XML-RPC 482 481 483 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 482 484 $options = get_option( 'wpironis_options', array() ); 483 485 484 // Set to 1 to disable XML-RPC when toggle is enabled 486 485 487 $options['wpironis_disable_xmlrpc'] = $enabled ? 1 : 0; 486 488 … … 494 496 ) ); 495 497 } else { 496 // Check if the value was actually the same (no update needed) 498 497 499 $current_options = get_option( 'wpironis_options' ); 498 500 if ( isset( $current_options['wpironis_disable_xmlrpc'] ) && … … 520 522 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 521 523 522 // Get current options or initialize array 524 523 525 $options = get_option( 'wpironis_options', array() ); 524 526 525 // Update the WordPress version hiding setting 527 526 528 $options['wpironis_hide_wp_version'] = $enabled ? 1 : 0; 527 529 528 // Save the updated options 530 529 531 $updated = update_option( 'wpironis_options', $options ); 530 532 … … 536 538 ) ); 537 539 } else { 538 // Check if the value was actually the same (no update needed) 540 539 541 $current_options = get_option( 'wpironis_options' ); 540 542 if ( isset( $current_options['wpironis_hide_wp_version'] ) && … … 562 564 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 563 565 564 // Get current options or initialize array 566 565 567 $options = get_option( 'wpironis_options', array() ); 566 568 567 // Update the security headers setting 569 568 570 $options['wpironis_security_headers'] = $enabled ? 1 : 0; 569 571 570 // Save the updated options 572 571 573 $updated = update_option( 'wpironis_options', $options ); 572 574 … … 578 580 ) ); 579 581 } else { 580 // Check if the value was actually the same (no update needed) 582 581 583 $current_options = get_option( 'wpironis_options' ); 582 584 if ( isset( $current_options['wpironis_security_headers'] ) && … … 604 606 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 605 607 606 // Get current options or initialize array 608 607 609 $options = get_option( 'wpironis_options', array() ); 608 610 609 // Update the PHP uploads blocking setting 611 610 612 $options['wpironis_block_php_uploads'] = $enabled ? 1 : 0; 611 613 612 // Save the updated options 614 613 615 $updated = update_option( 'wpironis_options', $options ); 614 616 615 // Handle .htaccess rules 617 616 618 if ( $enabled ) { 617 619 $this->wpironis_create_upload_protection(); … … 627 629 ) ); 628 630 } else { 629 // Check if the value was actually the same (no update needed) 631 630 632 $current_options = get_option( 'wpironis_options' ); 631 633 if ( isset( $current_options['wpironis_block_php_uploads'] ) && … … 646 648 $htaccess_path = $upload_dir['basedir'] . '/.htaccess'; 647 649 648 // Define the rules 650 649 651 $rules = " 650 652 # Iron Security … … 675 677 "; 676 678 677 // Create or update .htaccess file 679 678 680 if ( ! file_exists( $htaccess_path ) || is_writable( $htaccess_path ) ) { 679 681 file_put_contents( $htaccess_path, $rules, LOCK_EX ); 680 682 } 681 683 682 // Also create an index.php file to prevent directory listing 684 683 685 $index_path = $upload_dir['basedir'] . '/index.php'; 684 686 if ( ! file_exists( $index_path ) ) { … … 697 699 698 700 public function wpironis_prevent_php_upload( $file ) { 699 // Get the current options 701 700 702 $options = get_option( 'wpironis_options', array() ); 701 703 702 // Check if PHP upload blocking is enabled 704 703 705 if ( ! isset( $options['wpironis_block_php_uploads'] ) || $options['wpironis_block_php_uploads'] !== 1 ) { 704 706 return $file; 705 707 } 706 708 707 // Ensure the file name is set and is a string 709 708 710 if ( ! isset( $file['name'] ) || empty( $file['name'] ) || ! is_string( $file['name'] ) ) { 709 711 $file['error'] = __( 'Invalid file name detected. Upload blocked.', 'iron-security' ); … … 712 714 } 713 715 714 // List of blocked extensions 716 715 717 $blocked_extensions = array( 716 718 'php', … … 734 736 $extension = strtolower( pathinfo( $file_path, PATHINFO_EXTENSION ) ?: '' ); 735 737 736 // Check if the file extension is in the blocked list 738 737 739 if ( in_array( $extension, $blocked_extensions, true ) ) { 738 740 $file['error'] = sprintf( … … 745 747 } 746 748 747 // Check for double extensions (e.g., file.jpg.php) 749 748 750 $all_extensions = explode( '.', $file_path ); 749 751 if ( count( $all_extensions ) > 2 ) { … … 789 791 ) ); 790 792 } else { 791 // Check if the value was actually the same (no update needed) 793 792 794 $current_options = get_option( 'wpironis_options' ); 793 795 if ( isset( $current_options['wpironis_prevent_direct_access'] ) && … … 842 844 "; 843 845 844 // Create or update root .htaccess file 846 845 847 if ( ! file_exists( $root_htaccess_path ) || is_writable( $root_htaccess_path ) ) { 846 // If .htaccess exists, read its content 848 847 849 $existing_content = file_exists( $root_htaccess_path ) ? file_get_contents( $root_htaccess_path ) : ''; 848 850 849 // Check if our rules are already present 851 850 852 if ( strpos( $existing_content, 'Iron Security - Prevent Direct File Access' ) === false ) { 851 // Add our rules after WordPress rules if they exist 853 852 854 if ( strpos( $existing_content, '# END WordPress' ) !== false ) { 853 855 $existing_content = str_replace( '# END WordPress', … … 862 864 } 863 865 864 // Also protect wp-content directory 866 865 867 $content_htaccess_path = WP_CONTENT_DIR . '/.htaccess'; 866 868 $content_rules = " … … 918 920 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 919 921 920 // Get current options or initialize array 922 921 923 $options = get_option( 'wpironis_options', array() ); 922 924 923 // Update the file editor setting 925 924 926 $options['wpironis_disable_file_editor'] = $enabled ? 1 : 0; 925 927 926 // Save the updated options 928 927 929 $updated = update_option( 'wpironis_options', $options ); 928 930 929 // Update wp-config.php to disable file editor 931 930 932 if ( $enabled ) { 931 933 $this->wpironis_disable_file_editor_in_config(); … … 941 943 ) ); 942 944 } else { 943 // Check if the value was actually the same (no update needed) 945 944 946 $current_options = get_option( 'wpironis_options' ); 945 947 if ( isset( $current_options['wpironis_disable_file_editor'] ) && … … 965 967 $config_content = file_get_contents( $wp_config_path ); 966 968 967 // Check if the constant is already defined 969 968 970 if ( strpos( $config_content, 'DISALLOW_FILE_EDIT' ) === false ) { 969 // Find the line where we should add our constant 971 970 972 $insert_point = strpos( $config_content, "/* That's all, stop editing!" ); 971 973 … … 981 983 } 982 984 } else { 983 // Update existing constant if it's set to false 985 984 986 $config_content = preg_replace( 985 987 "/define\(\s*'DISALLOW_FILE_EDIT'\s*,\s*false\s*\);/", … … 1000 1002 $config_content = file_get_contents( $wp_config_path ); 1001 1003 1002 // Remove the constant definition if it exists 1004 1003 1005 $config_content = preg_replace( 1004 1006 "/define\(\s*'DISALLOW_FILE_EDIT'\s*,\s*true\s*\);(\r\n|\n|\r)?/", … … 1021 1023 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1022 1024 1023 // Get current options or initialize array 1025 1024 1026 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1025 1027 1026 // Update the custom URL setting 1028 1027 1029 $options['enable_slug_change'] = $enabled ? '1' : '0'; 1028 1030 1029 // Save the updated options 1031 1030 1032 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1031 1033 … … 1037 1039 ) ); 1038 1040 } else { 1039 // Check if the value was actually the same (no update needed) 1041 1040 1042 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1041 1043 if ( isset( $current_options['enable_slug_change'] ) && … … 1067 1069 } 1068 1070 1069 $url = sanitize_title( $_POST['url'] ); // Sanitize and convert spaces to hyphens1071 $url = sanitize_title( $_POST['url'] ); 1070 1072 1071 1073 if ( empty( $url ) ) { … … 1075 1077 } 1076 1078 1077 // Get current options 1079 1078 1080 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1079 1081 1080 // Update the custom URL 1082 1081 1083 $options['wpironis_custom_login_slug'] = $url; 1082 1084 1083 // Save the updated options 1085 1084 1086 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1085 1087 … … 1091 1093 ) ); 1092 1094 } else { 1093 // Check if the value was actually the same (no update needed) 1095 1094 1096 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1095 1097 if ( isset( $current_options['wpironis_custom_login_slug'] ) && … … 1117 1119 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1118 1120 1119 // Get current options or initialize array 1121 1120 1122 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1121 1123 1122 // Update the session timeout setting 1124 1123 1125 $options['enable_session_timeout'] = $enabled ? '1' : '0'; 1124 1126 1125 // Save the updated options 1127 1126 1128 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1127 1129 … … 1133 1135 ) ); 1134 1136 } else { 1135 // Check if the value was actually the same (no update needed) 1137 1136 1138 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1137 1139 if ( isset( $current_options['enable_session_timeout'] ) && … … 1172 1174 } 1173 1175 1174 // Get current options 1176 1175 1177 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1176 1178 1177 // Update the timeout value 1179 1178 1180 $options['session_timeout_value'] = $timeout; 1179 1181 1180 // Save the updated options 1182 1181 1183 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1182 1184 … … 1188 1190 ) ); 1189 1191 } else { 1190 // Check if the value was actually the same (no update needed) 1192 1191 1193 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1192 1194 if ( isset( $current_options['session_timeout_value'] ) && … … 1214 1216 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1215 1217 1216 // Get current options 1218 1217 1219 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1218 1220 1219 // Update the limit login attempts setting 1221 1220 1222 $options['enable_limit_login_attempts'] = $enabled ? '1' : '0'; 1221 1223 1222 // Save the updated options 1224 1223 1225 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1224 1226 … … 1230 1232 ) ); 1231 1233 } else { 1232 // Check if the value was actually the same (no update needed) 1234 1233 1235 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1234 1236 if ( isset( $current_options['enable_limit_login_attempts'] ) && … … 1277 1279 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1278 1280 1279 // Update the settings 1281 1280 1282 $options['wpironis_limit_login_attempts'] = (string) $attempts; 1281 1283 $options['wpironis_lockout_duration'] = (string) $duration; 1282 1284 1283 // Save the updated options 1285 1284 1286 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1285 1287 … … 1292 1294 ) ); 1293 1295 } else { 1294 // Check if the values were actually the same (no update needed) 1296 1295 1297 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1296 1298 if ( isset( $current_options['wpironis_limit_login_attempts'] ) && … … 1321 1323 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1322 1324 1323 // Get current options 1325 1324 1326 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1325 1327 1326 // Update the user enumeration protection setting 1328 1327 1329 $options['enable_user_enumeration'] = $enabled ? '1' : '0'; 1328 1330 1329 // Save the updated options 1331 1330 1332 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1331 1333 … … 1337 1339 ) ); 1338 1340 } else { 1339 // Check if the value was actually the same (no update needed) 1341 1340 1342 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1341 1343 if ( isset( $current_options['enable_user_enumeration'] ) && … … 1375 1377 } 1376 1378 1377 // Get current options 1379 1378 1380 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1379 1381 1380 // Update the custom error message 1382 1381 1383 $options['user_enumeration_message'] = $message; 1382 1384 1383 // Save the updated options 1385 1384 1386 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1385 1387 … … 1391 1393 ) ); 1392 1394 } else { 1393 // Check if the value was actually the same (no update needed) 1395 1394 1396 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1395 1397 if ( isset( $current_options['user_enumeration_message'] ) && … … 1407 1409 1408 1410 public function wpironis_modify_login_errors( $error ) { 1409 // Get the options 1411 1410 1412 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1411 1413 1412 // Check if user enumeration protection is enabled 1414 1413 1415 if ( ! empty( $options['enable_user_enumeration'] ) && $options['enable_user_enumeration'] === '1' ) { 1414 // Get the custom message or use default 1416 1415 1417 $custom_message = ! empty( $options['user_enumeration_message'] ) 1416 1418 ? $options['user_enumeration_message'] 1417 1419 : 'Wrong Login credentials'; 1418 1420 1419 // Check if this is a password-related error for an existing username 1421 1420 1422 if ( strpos( $error, 'password you entered for' ) !== false ) { 1421 1423 return __( $custom_message, 'iron-security' ); … … 1427 1429 1428 1430 public function wpironis_block_user_enumeration() { 1429 // Get the options 1431 1430 1432 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1431 1433 1432 // Check if user enumeration protection is enabled 1434 1433 1435 if ( ! empty( $options['enable_user_enumeration'] ) && $options['enable_user_enumeration'] === '1' ) { 1434 // Block access to author pages 1436 1435 1437 if ( is_author() ) { 1436 1438 wp_redirect( home_url(), 301 ); … … 1438 1440 } 1439 1441 1440 // Block user enumeration through REST API 1442 1441 1443 add_filter( 'rest_endpoints', function ( $endpoints ) { 1442 1444 if ( isset( $endpoints['/wp/v2/users'] ) ) { … … 1450 1452 } ); 1451 1453 1452 // Block ?author=N queries 1454 1453 1455 if ( isset( $_GET['author'] ) && is_numeric( $_GET['author'] ) ) { 1454 1456 wp_redirect( home_url(), 301 ); … … 1469 1471 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1470 1472 1471 // Get current options or initialize array 1473 1472 1474 $options = get_option( 'wpironis_options', array() ); 1473 1475 1474 // Update the REST API setting 1476 1475 1477 $options['wpironis_disable_rest_api'] = $enabled ? 1 : 0; 1476 1478 1477 // Save the updated options 1479 1478 1480 $updated = update_option( 'wpironis_options', $options ); 1479 1481 … … 1485 1487 ) ); 1486 1488 } else { 1487 // Check if the value was actually the same (no update needed) 1489 1488 1490 $current_options = get_option( 'wpironis_options' ); 1489 1491 if ( isset( $current_options['wpironis_disable_rest_api'] ) && … … 1511 1513 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1512 1514 1513 // Get current options or initialize array 1515 1514 1516 $options = get_option( 'wpironis_options', array() ); 1515 1517 1516 // Update the plugin auto-update setting 1518 1517 1519 $options['wpironis_enable_plugin_autoupdate'] = $enabled ? 1 : 0; 1518 1520 1519 // Save the updated options 1521 1520 1522 $updated = update_option( 'wpironis_options', $options ); 1521 1523 1522 // Configure plugin auto-updates 1524 1523 1525 $this->wpironis_configure_plugin_autoupdates( $enabled ); 1524 1526 … … 1530 1532 ) ); 1531 1533 } else { 1532 // Check if the value was actually the same (no update needed) 1534 1533 1535 $current_options = get_option( 'wpironis_options' ); 1534 1536 if ( isset( $current_options['wpironis_enable_plugin_autoupdate'] ) && … … 1556 1558 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1557 1559 1558 // Get current options or initialize array 1560 1559 1561 $options = get_option( 'wpironis_options', array() ); 1560 1562 1561 // Update the core auto-update setting 1563 1562 1564 $options['wpironis_enable_core_autoupdate'] = $enabled ? 1 : 0; 1563 1565 1564 // Save the updated options 1566 1565 1567 $updated = update_option( 'wpironis_options', $options ); 1566 1568 1567 // Configure core auto-updates 1569 1568 1570 $this->wpironis_configure_core_autoupdates( $enabled ); 1569 1571 … … 1575 1577 ) ); 1576 1578 } else { 1577 // Check if the value was actually the same (no update needed) 1579 1578 1580 $current_options = get_option( 'wpironis_options' ); 1579 1581 if ( isset( $current_options['wpironis_enable_core_autoupdate'] ) && … … 1592 1594 private function wpironis_configure_plugin_autoupdates( $enabled ) { 1593 1595 if ( $enabled ) { 1594 // Enable auto-updates for all plugins using filter 1596 1595 1597 add_filter( 'auto_update_plugin', '__return_true' ); 1596 1598 1597 // Also set the auto_update_plugins option 1599 1598 1600 $all_plugins = array_keys( get_plugins() ); 1599 1601 update_site_option( 'auto_update_plugins', $all_plugins ); 1600 1602 } else { 1601 // Disable auto-updates for all plugins 1603 1602 1604 add_filter( 'auto_update_plugin', '__return_false' ); 1603 1605 1604 // Clear the auto_update_plugins option 1606 1605 1607 update_site_option( 'auto_update_plugins', array() ); 1606 1608 } … … 1609 1611 private function wpironis_configure_core_autoupdates( $enabled ) { 1610 1612 if ( $enabled ) { 1611 // Enable core auto-updates for all update types 1613 1612 1614 add_filter( 'allow_major_auto_core_updates', '__return_true' ); 1613 1615 add_filter( 'allow_minor_auto_core_updates', '__return_true' ); … … 1615 1617 add_filter( 'auto_update_translation', '__return_true' ); 1616 1618 1617 // Update the core update settings 1619 1618 1620 update_site_option( 'auto_update_core_major', 'enabled' ); 1619 1621 update_site_option( 'auto_update_core_minor', 'enabled' ); 1620 1622 update_site_option( 'auto_update_core_dev', 'enabled' ); 1621 1623 } else { 1622 // Disable core auto-updates 1624 1623 1625 add_filter( 'allow_major_auto_core_updates', '__return_false' ); 1624 1626 add_filter( 'allow_minor_auto_core_updates', '__return_false' ); … … 1626 1628 add_filter( 'auto_update_translation', '__return_false' ); 1627 1629 1628 // Update the core update settings 1630 1629 1631 update_site_option( 'auto_update_core_major', 'disabled' ); 1630 1632 update_site_option( 'auto_update_core_minor', 'disabled' ); … … 1660 1662 } 1661 1663 1662 // Update last activity time 1664 1663 1665 update_user_meta( get_current_user_id(), 'iron_security_last_activity', time() ); 1664 1666 } … … 1704 1706 } 1705 1707 1706 // Get filter parameters 1708 1707 1709 $log_type = isset( $_POST['log_type'] ) ? sanitize_text_field( $_POST['log_type'] ) : ''; 1708 1710 $status = isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : ''; … … 1714 1716 $per_page = isset( $_POST['per_page'] ) ? intval( $_POST['per_page'] ) : 50; 1715 1717 1716 // Build filter arguments 1718 1717 1719 $args = array( 1718 1720 'log_type' => $log_type, … … 1726 1728 ); 1727 1729 1728 // Get logs with the specified filters 1730 1729 1731 $logs_data = Iron_Security_Logger::get_logs( $args ); 1730 1732 1731 // Get log summary 1733 1732 1734 $summary = $this->get_logs_summary(); 1733 1735 … … 1749 1751 $table_name = $wpdb->prefix . 'iron_security_logs'; 1750 1752 1751 // Get total count 1753 1752 1754 $total = $wpdb->get_var( "SELECT COUNT(*) FROM $table_name" ); 1753 1755 1754 // Get count by status 1756 1755 1757 $success_count = $wpdb->get_var( "SELECT COUNT(*) FROM $table_name WHERE status = 'success'" ); 1756 1758 $failure_count = $wpdb->get_var( "SELECT COUNT(*) FROM $table_name WHERE status = 'failure'" ); … … 1777 1779 } 1778 1780 1779 // Get filter parameters 1781 1780 1782 $log_type = isset( $_POST['log_type'] ) ? sanitize_text_field( $_POST['log_type'] ) : ''; 1781 1783 $status = isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : ''; … … 1785 1787 $date_to = isset( $_POST['date_to'] ) ? sanitize_text_field( $_POST['date_to'] ) : ''; 1786 1788 1787 // Build filter arguments (with large per_page for export) 1789 1788 1790 $args = array( 1789 1791 'log_type' => $log_type, … … 1793 1795 'date_from' => $date_from, 1794 1796 'date_to' => $date_to, 1795 'per_page' => 10000, // Large number to get most/all logs1797 'per_page' => 10000, 1796 1798 'page' => 1 1797 1799 ); 1798 1800 1799 // Get all logs with the specified filters 1801 1800 1802 $logs_data = Iron_Security_Logger::get_logs( $args ); 1801 1803 $logs = $logs_data['logs']; 1802 1804 1803 // Set headers for CSV download 1805 1804 1806 header( 'Content-Type: text/csv' ); 1805 1807 header( 'Content-Disposition: attachment; filename="iron-security-logs-' . date( 'Y-m-d' ) . '.csv"' ); … … 1807 1809 header( 'Expires: 0' ); 1808 1810 1809 // Create a file pointer connected to the output stream 1811 1810 1812 $output = fopen( 'php://output', 'w' ); 1811 1813 1812 // Output the column headings 1814 1813 1815 fputcsv( $output, 1814 1816 array( 'ID', 'Date', 'Type', 'Username', 'User ID', 'IP Address', 'Message', 'Status', 'Extra Data' ) ); 1815 1817 1816 // Output each row of the data 1818 1817 1819 foreach ( $logs as $log ) { 1818 1820 $log_type_labels = Iron_Security_Logger::get_log_types(); 1819 1821 $log_type_label = isset( $log_type_labels[ $log['log_type'] ] ) ? $log_type_labels[ $log['log_type'] ] : $log['log_type']; 1820 1822 1821 // Format extra data for CSV 1823 1822 1824 $extra_data = ''; 1823 1825 if ( ! empty( $log['extra_data'] ) ) { … … 1842 1844 } 1843 1845 1844 // Close the file pointer 1846 1845 1847 fclose( $output ); 1846 1848 exit; … … 1895 1897 } 1896 1898 1897 // Map the JavaScript camelCase to the database option name 1899 1898 1900 $option_map = [ 1899 1901 'contentTypeOptions' => 'wpironis_security_header_content_type_options', … … 1914 1916 $option_name = $option_map[ $header_name ]; 1915 1917 1916 // Get current options 1918 1917 1919 $options = get_option( 'wpironis_options', array() ); 1918 1920 1919 // Update the specific security header setting 1921 1920 1922 $options[ $option_name ] = $enabled ? 1 : 0; 1921 1923 1922 // Save the updated options 1924 1923 1925 $updated = update_option( 'wpironis_options', $options ); 1924 1926 1925 // Generate a friendly name for the header for use in messages 1927 1926 1928 $header_friendly_names = [ 1927 1929 'contentTypeOptions' => 'X-Content-Type-Options', … … 1945 1947 ) ); 1946 1948 } else { 1947 // Check if the value was actually the same (no update needed) 1949 1948 1950 $current_options = get_option( 'wpironis_options' ); 1949 1951 if ( isset( $current_options[ $option_name ] ) && $current_options[ $option_name ] === ( $enabled ? 1 : 0 ) ) { … … 1975 1977 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1976 1978 1977 // Get current settings 1979 1978 1980 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1979 1981 1980 // Update the limit admins setting 1982 1981 1983 $options['enable_limit_admins'] = $enabled ? '1' : '0'; 1982 1984 1983 // Save settings 1985 1984 1986 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1985 1987 … … 1990 1992 ); 1991 1993 1992 // If the feature is enabled, include admin count and users list 1994 1993 1995 if ( $enabled ) { 1994 1996 $admin_users = get_users( array( … … 2014 2016 wp_send_json_success( $response_data ); 2015 2017 } else { 2016 // Check if the value was actually the same (no update needed) 2018 2017 2019 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 2018 2020 if ( isset( $current_options['enable_limit_admins'] ) && … … 2023 2025 ); 2024 2026 2025 // If the feature is enabled, include admin count and users list 2027 2026 2028 if ( $enabled ) { 2027 2029 $admin_users = get_users( array( … … 2064 2066 } 2065 2067 2066 // Validate and sanitize inputs 2068 2067 2069 $max_admins = isset( $_POST['max_admins'] ) ? absint( $_POST['max_admins'] ) : 1; 2068 2070 $fallback_role = isset( $_POST['fallback_role'] ) ? sanitize_text_field( $_POST['fallback_role'] ) : 'editor'; 2069 2071 2070 // Ensure max_admins is at least 1 2072 2071 2073 if ( $max_admins < 1 ) { 2072 2074 $max_admins = 1; 2073 2075 } 2074 2076 2075 // Validate fallback role is a valid WordPress role 2077 2076 2078 $valid_roles = array( 'editor', 'author', 'contributor', 'subscriber' ); 2077 2079 if ( ! in_array( $fallback_role, $valid_roles ) ) { … … 2079 2081 } 2080 2082 2081 // Get current settings 2083 2082 2084 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 2083 2085 2084 // Update the limit admins settings 2086 2085 2087 $options['wpironis_max_admins'] = (string) $max_admins; 2086 2088 $options['wpironis_admin_fallback_role'] = $fallback_role; 2087 2089 2088 // Save settings 2090 2089 2091 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 2090 2092 2091 // Get admin users for the response 2093 2092 2094 $admin_users = get_users( array( 2093 2095 'role' => 'administrator', 2094 2096 ) ); 2095 2097 2096 // Count admins 2098 2097 2099 $admin_count = count( $admin_users ); 2098 2100 2099 // Prepare the user data to return 2101 2100 2102 $users = array(); 2101 2103 foreach ( $admin_users as $user ) { … … 2109 2111 2110 2112 if ( $updated ) { 2111 // Enforce the limit now by checking current admin count 2113 2112 2114 $this->wpironis_enforce_admin_limit_now(); 2113 2115 2114 // Get updated admin count after enforcement 2116 2115 2117 $updated_admin_users = get_users( array( 2116 2118 'role' => 'administrator', … … 2118 2120 $updated_admin_count = count( $updated_admin_users ); 2119 2121 2120 // Prepare updated user list 2122 2121 2123 $updated_users = array(); 2122 2124 foreach ( $updated_admin_users as $user ) { … … 2138 2140 ) ); 2139 2141 } else { 2140 // Check if the value was actually the same (no update needed) 2142 2141 2143 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 2142 2144 if ( isset( $current_options['wpironis_max_admins'] ) && … … 2165 2167 */ 2166 2168 public function wpironis_enforce_admin_limit( $user_id, $role, $old_roles ) { 2167 // Check if the new role is administrator 2169 2168 2170 if ( $role !== 'administrator' ) { 2169 2171 return; 2170 2172 } 2171 2173 2172 // Get the settings 2174 2173 2175 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 2174 2176 2175 // Check if the feature is enabled 2177 2176 2178 if ( ! isset( $options['enable_limit_admins'] ) || $options['enable_limit_admins'] !== '1' ) { 2177 2179 return; 2178 2180 } 2179 2181 2180 // Get maximum allowed admins 2182 2181 2183 $max_admins = isset( $options['wpironis_max_admins'] ) ? absint( $options['wpironis_max_admins'] ) : 1; 2182 2184 2183 // Get the fallback role 2185 2184 2186 $fallback_role = isset( $options['wpironis_admin_fallback_role'] ) ? $options['wpironis_admin_fallback_role'] : 'editor'; 2185 2187 2186 // Count current admins 2188 2187 2189 $admin_count = $this->wpironis_count_admins(); 2188 2190 2189 // If the max has been reached, change this user's role to the fallback 2191 2190 2192 if ( $admin_count > $max_admins ) { 2191 // Skip the current hook to avoid infinite loop 2193 2192 2194 remove_action( 'set_user_role', array( $this, 'wpironis_enforce_admin_limit' ), 10 ); 2193 2195 2194 // Downgrade the user to the fallback role 2196 2195 2197 $user = new WP_User( $user_id ); 2196 2198 $user->set_role( $fallback_role ); 2197 2199 2198 // Log the event safely 2200 2199 2201 $this->safe_log_security_event( 2200 2202 'admin_limit_enforced', … … 2207 2209 ); 2208 2210 2209 // Add admin notice 2211 2210 2212 add_action( 'admin_notices', function () use ( $user, $fallback_role, $max_admins ) { 2211 2213 echo '<div class="notice notice-warning is-dismissible">'; … … 2220 2222 } ); 2221 2223 2222 // Re-add the hook 2224 2223 2225 add_action( 'set_user_role', array( $this, 'wpironis_enforce_admin_limit' ), 10, 3 ); 2224 2226 } … … 2231 2233 */ 2232 2234 public function wpironis_check_admin_limit_on_register( $user_id ) { 2233 // Get the user object 2235 2234 2236 $user = new WP_User( $user_id ); 2235 2237 2236 // Check if the user is being registered as an admin 2238 2237 2239 if ( ! in_array( 'administrator', $user->roles ) ) { 2238 2240 return; 2239 2241 } 2240 2242 2241 // Get the settings 2243 2242 2244 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 2243 2245 2244 // Check if the feature is enabled 2246 2245 2247 if ( ! isset( $options['enable_limit_admins'] ) || $options['enable_limit_admins'] !== '1' ) { 2246 2248 return; 2247 2249 } 2248 2250 2249 // Get maximum allowed admins 2251 2250 2252 $max_admins = isset( $options['wpironis_max_admins'] ) ? absint( $options['wpironis_max_admins'] ) : 1; 2251 2253 2252 // Get the fallback role 2254 2253 2255 $fallback_role = isset( $options['wpironis_admin_fallback_role'] ) ? $options['wpironis_admin_fallback_role'] : 'editor'; 2254 2256 2255 // Count current admins 2257 2256 2258 $admin_count = $this->wpironis_count_admins(); 2257 2259 2258 // If the max has been reached, change this user's role to the fallback 2260 2259 2261 if ( $admin_count > $max_admins ) { 2260 // Downgrade the user to the fallback role 2262 2261 2263 $user->set_role( $fallback_role ); 2262 2264 2263 // Log the event safely 2265 2264 2266 $this->safe_log_security_event( 2265 2267 'admin_limit_enforced', … … 2281 2283 */ 2282 2284 private function safe_log_security_event( $event_type, $message ) { 2283 // Try to use the Logger class if it exists and has the method 2285 2284 2286 if ( class_exists( 'Iron_Security_Logger' ) && method_exists( 'Iron_Security_Logger', 'log_security_event' ) ) { 2285 2287 Iron_Security_Logger::log_security_event( $event_type, $message ); 2286 2288 } else { 2287 // Fallback logging to the WordPress error log 2289 2288 2290 error_log( sprintf( '[Iron Security] %s: %s', $event_type, $message ) ); 2289 2291 2290 // Attempt to log to a custom database table if it exists 2292 2291 2293 global $wpdb; 2292 2294 $table_name = $wpdb->prefix . 'iron_security_logs'; 2293 2295 2294 // Check if the table exists before attempting to insert 2296 2295 2297 if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) === $table_name ) { 2296 2298 $wpdb->insert( … … 2330 2332 */ 2331 2333 private function wpironis_enforce_admin_limit_now() { 2332 // Get the settings 2334 2333 2335 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 2334 2336 2335 // Check if the feature is enabled 2337 2336 2338 if ( ! isset( $options['enable_limit_admins'] ) || $options['enable_limit_admins'] !== '1' ) { 2337 2339 return; 2338 2340 } 2339 2341 2340 // Get maximum allowed admins 2342 2341 2343 $max_admins = isset( $options['wpironis_max_admins'] ) ? absint( $options['wpironis_max_admins'] ) : 1; 2342 2344 2343 // Get the fallback role 2345 2344 2346 $fallback_role = isset( $options['wpironis_admin_fallback_role'] ) ? $options['wpironis_admin_fallback_role'] : 'editor'; 2345 2347 2346 // Get all admins 2348 2347 2349 $admin_users = get_users( array( 2348 2350 'role' => 'administrator', … … 2351 2353 ) ); 2352 2354 2353 // Skip if we have fewer admins than the limit 2355 2354 2356 if ( count( $admin_users ) <= $max_admins ) { 2355 2357 return; 2356 2358 } 2357 2359 2358 // Keep track of how many admins we've processed 2360 2359 2361 $admin_count = 0; 2360 2362 2361 // Process each admin user 2363 2362 2364 foreach ( $admin_users as $admin_user ) { 2363 2365 $admin_count ++; 2364 2366 2365 // Skip the first max_admins administrators (ordered by ID) 2367 2366 2368 if ( $admin_count <= $max_admins ) { 2367 2369 continue; 2368 2370 } 2369 2371 2370 // Downgrade the user to the fallback role 2372 2371 2373 $admin_user->set_role( $fallback_role ); 2372 2374 2373 // Log the event safely 2375 2374 2376 $this->safe_log_security_event( 2375 2377 'admin_limit_enforced', … … 2415 2417 } 2416 2418 2417 // Check if this is a user create/update request 2419 2418 2420 $route = $request->get_route(); 2419 2421 if ( strpos( $route, '/wp/v2/users' ) === false ) { … … 2421 2423 } 2422 2424 2423 // Check if operation involves setting a role to administrator 2425 2424 2426 $params = $request->get_params(); 2425 2427 if ( ! isset( $params['roles'] ) || ! in_array( 'administrator', (array) $params['roles'] ) ) { … … 2427 2429 } 2428 2430 2429 // Get the settings 2431 2430 2432 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 2431 2433 2432 // Check if the feature is enabled 2434 2433 2435 if ( ! isset( $options['enable_limit_admins'] ) || $options['enable_limit_admins'] !== '1' ) { 2434 2436 return $response; 2435 2437 } 2436 2438 2437 // Get maximum allowed admins 2439 2438 2440 $max_admins = isset( $options['wpironis_max_admins'] ) ? absint( $options['wpironis_max_admins'] ) : 1; 2439 2441 2440 // Get the fallback role 2442 2441 2443 $fallback_role = isset( $options['wpironis_admin_fallback_role'] ) ? $options['wpironis_admin_fallback_role'] : 'editor'; 2442 2444 2443 // Count current admins 2445 2444 2446 $admin_count = $this->wpironis_count_admins(); 2445 2447 2446 // Get the user ID from the request 2448 2447 2449 $user_id = isset( $params['id'] ) ? $params['id'] : null; 2448 2450 2449 // For user creation, the ID will be in the response 2451 2450 2452 if ( ! $user_id && isset( $response->data['id'] ) ) { 2451 2453 $user_id = $response->data['id']; 2452 2454 } 2453 2455 2454 // Get the user to check if they were already an admin 2456 2455 2457 $user = get_user_by( 'id', $user_id ); 2456 2458 $was_admin = $user && in_array( 'administrator', $user->roles ); 2457 2459 2458 // If the user was not already an admin and the limit has been reached 2460 2459 2461 if ( ! $was_admin && $admin_count >= $max_admins ) { 2460 // Update the user to the fallback role 2462 2461 2463 $user = new WP_User( $user_id ); 2462 2464 $user->set_role( $fallback_role ); 2463 2465 2464 // Log the event safely 2466 2465 2467 $this->safe_log_security_event( 2466 2468 'admin_limit_enforced_api', … … 2473 2475 ); 2474 2476 2475 // Update the response to reflect the actual role 2477 2476 2478 if ( isset( $response->data['roles'] ) ) { 2477 2479 $response->data['roles'] = array( $fallback_role ); 2478 2480 } 2479 2481 2480 // Add a message to the response 2482 2481 2483 if ( ! isset( $response->data['iron_security_message'] ) ) { 2482 2484 $response->data['iron_security_message'] = sprintf( … … 2496 2498 */ 2497 2499 public function wpironsec_get_admin_info() { 2498 // Verify nonce 2500 2499 2501 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'iron_security_nonce' ) ) { 2500 2502 wp_send_json_error( 'Invalid security token sent.' ); … … 2502 2504 } 2503 2505 2504 // Check if user is authorized 2506 2505 2507 if ( ! current_user_can( 'manage_options' ) ) { 2506 2508 wp_send_json_error( 'You do not have permission to perform this action.' ); … … 2508 2510 } 2509 2511 2510 // Get administrator users 2512 2511 2513 $admin_users = get_users( array( 2512 2514 'role' => 'administrator', 2513 2515 ) ); 2514 2516 2515 // Prepare the user data to return 2517 2516 2518 $users = array(); 2517 2519 foreach ( $admin_users as $user ) { … … 2524 2526 } 2525 2527 2526 // Count 2528 2527 2529 $count = count( $admin_users ); 2528 2530 2529 // Send the response 2531 2530 2532 wp_send_json_success( array( 2531 2533 'count' => $count, … … 2567 2569 } 2568 2570 2569 // Get the differences to determine what was updated 2571 2570 2572 $changes = array(); 2571 2573 2572 // Check email change 2574 2573 2575 if ( $old_user_data->user_email !== $user->user_email ) { 2574 2576 $changes['email'] = array( … … 2578 2580 } 2579 2581 2580 // Check display name change 2582 2581 2583 if ( $old_user_data->display_name !== $user->display_name ) { 2582 2584 $changes['display_name'] = array( … … 2586 2588 } 2587 2589 2588 // Check URL change 2590 2589 2591 if ( $old_user_data->user_url !== $user->user_url ) { 2590 2592 $changes['url'] = array( … … 2595 2597 2596 2598 if ( empty( $changes ) ) { 2597 // Log general profile update if no specific changes detected 2599 2598 2600 $message = sprintf( 'User "%s" profile was updated', $user->user_login ); 2599 2601 } else { 2600 // Log specific changes 2602 2601 2603 $fields = array_keys( $changes ); 2602 2604 $message = sprintf( 'User "%s" profile was updated. Changed fields: %s', … … 2822 2824 global $wp_version; 2823 2825 2824 // Only log this occasionally to avoid excessive logs 2826 2825 2827 $last_check = get_option( 'wpironis_last_version_check_log', 0 ); 2826 if ( time() - $last_check < 86400 ) { // Once per day max2828 if ( time() - $last_check < 86400 ) { 2827 2829 return; 2828 2830 } … … 2855 2857 global $pagenow; 2856 2858 2857 // Only log certain admin actions to avoid excessive logging 2859 2858 2860 if ( ! is_admin() || ! current_user_can( 'edit_posts' ) ) { 2859 2861 return; … … 2862 2864 $action = isset( $_REQUEST['action'] ) ? sanitize_text_field( $_REQUEST['action'] ) : ''; 2863 2865 2864 // Skip logging for common, routine actions 2866 2865 2867 $skip_actions = array( 'heartbeat', 'wp-refresh-post-lock', 'ajax-tag-search' ); 2866 2868 if ( in_array( $action, $skip_actions ) ) { … … 2868 2870 } 2869 2871 2870 // Log specific high-value actions only 2872 2871 2873 $important_actions = array( 2872 2874 'update' => 'Update', … … 2893 2895 $details = array( 2894 2896 'page' => $pagenow, 2895 'query_args' => $_GET // Including all query parameters for context2897 'query_args' => $_GET 2896 2898 ); 2897 2899 … … 2899 2901 } 2900 2902 2901 // Log access to sensitive admin pages 2903 2902 2904 $sensitive_pages = array( 2903 2905 'options.php' => 'Settings Update', … … 2922 2924 2923 2925 foreach ( $sensitive_pages as $page => $description ) { 2924 // Check if we're on this page or if page with query matches 2926 2925 2927 if ( $pagenow === $page || ( strpos( $page, '?' ) !== false && strpos( $_SERVER['REQUEST_URI'], 2926 2928 $page ) !== false ) ) { … … 2992 2994 */ 2993 2995 public function detect_frame_embedding() { 2994 // Only run this occasionally to avoid performance issues 2996 2995 2997 if ( ! is_admin() || mt_rand( 1, 10 ) !== 1 ) { 2996 2998 return; … … 3002 3004 $home_url = home_url(); 3003 3005 3004 // Check if the referer is from a different domain 3006 3005 3007 if ( strpos( $referer, $site_url ) !== 0 && strpos( $referer, $home_url ) !== 0 ) { 3006 // This could potentially be a clickjacking attempt 3008 3007 3009 $referer_host = parse_url( $referer, PHP_URL_HOST ); 3008 3010 $site_host = parse_url( $site_url, PHP_URL_HOST ); … … 3020 3022 */ 3021 3023 public function detect_suspicious_requests() { 3022 // Only run this occasionally to avoid performance issues 3024 3023 3025 if ( mt_rand( 1, 10 ) !== 1 ) { 3024 3026 return; 3025 3027 } 3026 3028 3027 // Detect common attack patterns in URLs 3029 3028 3030 $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : ''; 3029 3031 $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : ''; 3030 3032 3031 // List of suspicious URL patterns with attack type and whether it's a regex 3033 3032 3034 $suspicious_patterns = array( 3033 3035 'eval\(' => array( 'type' => 'PHP Injection', 'is_regex' => true ), … … 3077 3079 } 3078 3080 3079 // Check for known malicious or scanning user agents 3081 3080 3082 $malicious_agents = array( 3081 3083 'nikto' => 'Security Scanner', … … 3102 3104 sprintf( 'Detected %s user agent: %s', $agent_type, $agent ) 3103 3105 ); 3104 break; // Only log once per request3106 break; 3105 3107 } 3106 3108 } … … 3111 3113 */ 3112 3114 public function monitor_request_methods() { 3113 // Only monitor in admin area or for login/register pages 3115 3114 3116 if ( ! is_admin() && ! in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ) ) ) { 3115 3117 return; … … 3118 3120 $method = isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : ''; 3119 3121 3120 // Unusual request methods for WordPress admin 3122 3121 3123 $unusual_methods = array( 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH' ); 3122 3124 … … 3156 3158 */ 3157 3159 public function setup_rest_logging() { 3158 // Add a filter to log API requests 3160 3159 3161 add_filter( 'rest_pre_dispatch', array( $this, 'log_rest_api_request' ), 10, 3 ); 3160 3162 } … … 3172 3174 $route = $request->get_route(); 3173 3175 3174 // Skip some common, noisy routes 3176 3175 3177 $skip_routes = array( 3176 3178 '/wp/v2/users/me', … … 3182 3184 } 3183 3185 3184 // Sensitive routes that should always be logged 3186 3185 3187 $sensitive_routes = array( 3186 3188 '/wp/v2/users', … … 3201 3203 } 3202 3204 3203 // Always log writes (POST, PUT, DELETE) or sensitive routes 3205 3204 3206 $method = $request->get_method(); 3205 3207 if ( $is_sensitive || in_array( $method, array( 'POST', 'PUT', 'DELETE', 'PATCH' ) ) ) { … … 3243 3245 */ 3244 3246 public function log_http_api_errors( $response, $context, $class, $parsed_args, $url ) { 3245 // Only log errors 3247 3246 3248 if ( ! is_wp_error( $response ) ) { 3247 3249 return; … … 3251 3253 $error_message = $response->get_error_message(); 3252 3254 3253 // Don't log common timeout errors which may be normal 3255 3254 3256 if ( strpos( $error_code, 'timeout' ) !== false ) { 3255 3257 return; … … 3263 3265 */ 3264 3266 public function detect_security_scanners() { 3265 // Only run this check occasionally to avoid performance impact 3267 3266 3268 if ( mt_rand( 1, 20 ) !== 1 ) { 3267 3269 return; 3268 3270 } 3269 3271 3270 // Check for rapid-fire requests from the same IP 3272 3271 3273 $ip = $this->get_ip_address(); 3272 3274 3273 // Get the last time we recorded this IP 3275 3274 3276 $last_checked = get_transient( 'wpironis_ip_check_' . md5( $ip ) ); 3275 3277 $now = time(); 3276 3278 3277 3279 if ( $last_checked ) { 3278 // If this IP made a request very recently (within 1 second) 3280 3279 3281 if ( $now - $last_checked < 1 ) { 3280 // Increment the counter for this IP 3282 3281 3283 $count = get_transient( 'wpironis_ip_count_' . md5( $ip ) ); 3282 3284 $count = $count ? $count + 1 : 1; 3283 set_transient( 'wpironis_ip_count_' . md5( $ip ), $count, 300 ); // Keep count for 5 minutes3284 3285 // If we've seen too many rapid requests, log it as a scanner 3285 set_transient( 'wpironis_ip_count_' . md5( $ip ), $count, 300 ); 3286 3287 3286 3288 if ( $count > 10 ) { 3287 3289 Iron_Security_Logger::log_suspicious_behavior( … … 3290 3292 ); 3291 3293 3292 // Reset counter to avoid logging repeatedly 3294 3293 3295 set_transient( 'wpironis_ip_count_' . md5( $ip ), 0, 300 ); 3294 3296 } … … 3296 3298 } 3297 3299 3298 // Update the last checked time for this IP 3299 set_transient( 'wpironis_ip_check_' . md5( $ip ), $now, 300 ); // Keep for 5 minutes3300 3301 set_transient( 'wpironis_ip_check_' . md5( $ip ), $now, 300 ); 3300 3302 } 3301 3303 … … 3305 3307 */ 3306 3308 public function wpironis_handle_admin_id_protection_toggle() { 3307 // Verify nonce 3309 3308 3310 if ( ! check_ajax_referer( 'iron_security_nonce', 'nonce', false ) ) { 3309 3311 wp_send_json_error( 'Invalid security token. Please refresh the page and try again.' ); … … 3320 3322 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 3321 3323 3322 // Get current settings 3324 3323 3325 $loginlogout_settings = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 3324 3326 3325 // Update the settings 3327 3326 3328 $loginlogout_settings['enable_admin_id_protection'] = $enabled ? '1' : '0'; 3327 3329 3328 $admin_id = '1'; // Default admin ID3329 3330 // If enabling the feature 3330 $admin_id = '1'; 3331 3332 3331 3333 if ( $enabled ) { 3332 // Check if we already have a stored admin ID in settings 3334 3333 3335 if ( ! empty( $loginlogout_settings['current_admin_id'] ) ) { 3334 3336 $admin_id = $loginlogout_settings['current_admin_id']; 3335 3337 $message = 'Admin ID protection enabled. Using current administrator ID: ' . $admin_id; 3336 3338 3337 // Log this security event 3339 3338 3340 $this->safe_log_security_event( 3339 3341 'settings_change', … … 3342 3344 'success' 3343 3345 ); 3344 } // Check if admin with ID 1 still exists3346 } 3345 3347 elseif ( ! get_userdata( 1 ) ) { 3346 // Admin ID 1 doesn't exist, try to find the first admin user 3348 3347 3349 $admins = get_users( array( 'role' => 'administrator', 'number' => 1 ) ); 3348 3350 if ( ! empty( $admins ) ) { … … 3351 3353 $message = 'Admin ID protection enabled. Using existing administrator ID: ' . $admin_id; 3352 3354 3353 // Log this security event 3355 3354 3356 $this->safe_log_security_event( 3355 3357 'settings_change', … … 3359 3361 ); 3360 3362 } else { 3361 // No admin users found - this is unlikely but handle it 3363 3362 3364 $loginlogout_settings['enable_admin_id_protection'] = '0'; 3363 3365 wp_send_json_error( 'No administrator accounts found.' ); … … 3365 3367 return; 3366 3368 } 3367 } // If admin ID 1 exists and needs to be changed3369 } 3368 3370 else { 3369 3371 try { … … 3380 3382 $message = 'Admin ID protection enabled. Administrator ID changed from 1 to ' . $admin_id; 3381 3383 } else { 3382 // If we couldn't change the ID, disable the feature 3384 3383 3385 $loginlogout_settings['enable_admin_id_protection'] = '0'; 3384 3386 wp_send_json_error( 'Failed to change admin ID. Please try again.' ); … … 3394 3396 } 3395 3397 } else { 3396 // When disabling, just update the setting but don't change the ID back 3397 // since that would be complex and potentially disruptive 3398 3399 3398 3400 $admin_id = ! empty( $loginlogout_settings['current_admin_id'] ) ? $loginlogout_settings['current_admin_id'] : '1'; 3399 3401 $message = 'Admin ID protection disabled. Note that the admin ID will remain changed.'; 3400 3402 3401 // Log this security event 3403 3402 3404 $this->safe_log_security_event( 3403 3405 'settings_change', … … 3408 3410 } 3409 3411 3410 // Save updated settings 3412 3411 3413 update_option( 'wpironis_plugin_settings_loginlogout', $loginlogout_settings ); 3412 3414 3413 // Return success with the admin ID 3415 3414 3416 wp_send_json_success( array( 3415 3417 'message' => $message, … … 3426 3428 global $wpdb; 3427 3429 3428 // First check if user ID 1 exists 3430 3429 3431 $user = get_userdata( 1 ); 3430 3432 if ( ! $user ) { … … 3434 3436 } 3435 3437 3436 // Check if user is an administrator 3438 3437 3439 if ( ! in_array( 'administrator', $user->roles ) ) { 3438 3440 error_log( 'Iron Security: Cannot change admin ID - User ID 1 is not an administrator' ); … … 3441 3443 } 3442 3444 3443 // Start a transaction 3445 3444 3446 $wpdb->query( 'START TRANSACTION' ); 3445 3447 3446 3448 try { 3447 // Get the highest user ID in the database 3449 3448 3450 $highest_id = $wpdb->get_var( "SELECT MAX(ID) FROM {$wpdb->users}" ); 3449 3451 3450 // New ID will be highest + a random offset between 100-999 3452 3451 3453 $new_id = (int) $highest_id + 1; 3452 3454 3453 // Update user ID in users table 3455 3454 3456 $result = $wpdb->query( $wpdb->prepare( 3455 3457 "UPDATE {$wpdb->users} SET ID = %d WHERE ID = 1", … … 3461 3463 } 3462 3464 3463 // Update user ID in usermeta table 3465 3464 3466 $result = $wpdb->query( $wpdb->prepare( 3465 3467 "UPDATE {$wpdb->usermeta} SET user_id = %d WHERE user_id = 1", … … 3471 3473 } 3472 3474 3473 // Update user ID in posts table (for post author) 3475 3474 3476 $result = $wpdb->query( $wpdb->prepare( 3475 3477 "UPDATE {$wpdb->posts} SET post_author = %d WHERE post_author = 1", … … 3481 3483 } 3482 3484 3483 // Update user ID in comments table 3485 3484 3486 $result = $wpdb->query( $wpdb->prepare( 3485 3487 "UPDATE {$wpdb->comments} SET user_id = %d WHERE user_id = 1", … … 3491 3493 } 3492 3494 3493 // Additional tables to check for user_id references 3494 // WooCommerce orders if present 3495 3496 3495 3497 if ( $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}woocommerce_orders'" ) ) { 3496 3498 $wpdb->query( $wpdb->prepare( … … 3500 3502 } 3501 3503 3502 // If using BuddyPress 3504 3503 3505 if ( $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}bp_activity'" ) ) { 3504 3506 $wpdb->query( $wpdb->prepare( … … 3508 3510 } 3509 3511 3510 // If we're here, everything succeeded 3512 3511 3513 $wpdb->query( 'COMMIT' ); 3512 3514 3513 // Clear caches 3515 3514 3516 wp_cache_delete( 1, 'users' ); 3515 3517 wp_cache_delete( 1, 'user_meta' ); … … 3517 3519 wp_cache_delete( $new_id, 'user_meta' ); 3518 3520 3519 // Return the new ID 3521 3520 3522 return (string) $new_id; 3521 3523 } catch ( Exception $e ) { 3522 // Something went wrong, rollback 3524 3523 3525 $wpdb->query( 'ROLLBACK' ); 3524 3526 3525 // Log the error 3527 3526 3528 error_log( 'Iron Security: Failed to change admin ID: ' . $e->getMessage() ); 3527 3529 3528 // Rethrow the exception 3530 3529 3531 throw $e; 3530 3532 } … … 3578 3580 } 3579 3581 3580 // Correctly interpret "true" or "false" string from JS 3582 3581 3583 $enabled = isset( $_POST['enabled'] ) && $_POST['enabled'] === 'true'; 3582 3584 3583 // Save proper value to DB 3585 3584 3586 $general_settings = get_option( 'wpironis_options', array() ); 3585 3587 $general_settings['wpironis_block_ai_bots'] = $enabled ? 1 : 0; 3586 3588 update_option( 'wpironis_options', $general_settings ); 3587 3589 3588 // Modify .htaccess 3590 3589 3591 $this->general_security->modifyHtaccessForAiBots( $enabled ); 3590 3592 3591 // Prepare response message 3593 3592 3594 $message = $enabled 3593 3595 ? __( 'AI bot blocking has been enabled. Your site is now protected from AI bot crawlers.', … … 3616 3618 } 3617 3619 3618 // Fallback to glob if manifest doesn't exist or entry not found 3620 3619 3621 $dist_path = plugin_dir_path( __FILE__ ) . 'js/dist/'; 3620 3622 $files = glob( $dist_path . $base_name . '.*.js' ); … … 3626 3628 } 3627 3629 3628 // Final fallback to non-hashed filename 3630 3629 3631 return $base_name . '.bundle.js'; 3630 3632 } 3631 3633 3634 /** 3635 * Handle toggle for changing admin username 3636 */ 3637 public function wpironis_handle_change_admin_username_toggle() { 3638 3639 if ( ! check_ajax_referer( 'iron_security_nonce', 'nonce', false ) ) { 3640 wp_send_json_error( 'Invalid security token. Please refresh the page and try again.' ); 3641 return; 3642 } 3643 3644 if ( ! current_user_can( 'manage_options' ) ) { 3645 wp_send_json_error( 'You do not have permission to change this setting.' ); 3646 return; 3647 } 3648 3649 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 3650 3651 3652 $loginlogout_settings = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 3653 3654 3655 $loginlogout_settings['enable_change_admin_username'] = $enabled ? '1' : '0'; 3656 3657 3658 if ( $enabled && empty( $loginlogout_settings['wpironis_new_admin_username'] ) ) { 3659 $loginlogout_settings['wpironis_new_admin_username'] = $this->wpironis_generate_random_username(); 3660 } 3661 3662 3663 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $loginlogout_settings ); 3664 3665 3666 $admin_usernames = $this->wpironis_get_admin_usernames(); 3667 3668 if ( $updated ) { 3669 $message = 'Admin username protection setting updated successfully'; 3670 $status = 'success'; 3671 3672 if ( $enabled && !empty( $admin_usernames ) ) { 3673 $message = 'Admin username protection enabled. Insecure usernames detected: ' . implode(', ', $admin_usernames); 3674 $status = 'warning'; 3675 } 3676 3677 wp_send_json_success( array( 3678 'message' => $message, 3679 'status' => $status, 3680 'admin_usernames' => $admin_usernames 3681 ) ); 3682 } else { 3683 wp_send_json_error( 'Failed to update admin username protection setting' ); 3684 } 3685 } 3686 3687 /** 3688 * Handle saving new admin username 3689 */ 3690 public function wpironis_handle_change_admin_username_save() { 3691 3692 if ( ! check_ajax_referer( 'iron_security_nonce', 'nonce', false ) ) { 3693 wp_send_json_error( 'Invalid security token. Please refresh the page and try again.' ); 3694 return; 3695 } 3696 3697 if ( ! current_user_can( 'manage_options' ) ) { 3698 wp_send_json_error( 'You do not have permission to change admin username.' ); 3699 return; 3700 } 3701 3702 3703 $new_username = isset( $_POST['new_username'] ) ? sanitize_user( $_POST['new_username'] ) : ''; 3704 3705 if ( empty( $new_username ) ) { 3706 wp_send_json_error( 'Username cannot be empty.' ); 3707 return; 3708 } 3709 3710 3711 if ( !validate_username( $new_username ) ) { 3712 wp_send_json_error( 'Invalid username. Please use only letters.' ); 3713 return; 3714 } 3715 3716 3717 if ( username_exists( $new_username ) ) { 3718 wp_send_json_error( 'Username already exists. Please choose another username.' ); 3719 return; 3720 } 3721 3722 3723 $loginlogout_settings = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 3724 3725 3726 $loginlogout_settings['wpironis_new_admin_username'] = $new_username; 3727 update_option( 'wpironis_plugin_settings_loginlogout', $loginlogout_settings ); 3728 3729 3730 $admin_usernames = $this->wpironis_get_admin_usernames(); 3731 3732 3733 $changed_users = array(); 3734 3735 if ( !empty( $admin_usernames ) ) { 3736 foreach ( $admin_usernames as $username ) { 3737 $user = get_user_by( 'login', $username ); 3738 if ( $user ) { 3739 3740 $unique_username = $this->wpironis_get_unique_username( $new_username ); 3741 3742 3743 global $wpdb; 3744 $wpdb->update( 3745 $wpdb->users, 3746 array( 'user_login' => $unique_username ), 3747 array( 'ID' => $user->ID ) 3748 ); 3749 3750 3751 $this->safe_log_security_event( 'username_change', sprintf( 3752 'Changed username from %s to %s for user ID %d', 3753 $username, 3754 $unique_username, 3755 $user->ID 3756 )); 3757 3758 3759 clean_user_cache( $user->ID ); 3760 3761 $changed_users[] = array( 3762 'old_username' => $username, 3763 'new_username' => $unique_username, 3764 'display_name' => $user->display_name 3765 ); 3766 } 3767 } 3768 } 3769 3770 if ( !empty( $changed_users ) ) { 3771 wp_send_json_success( array( 3772 'message' => count( $changed_users ) . ' admin username(s) changed successfully.', 3773 'changed_users' => $changed_users 3774 ) ); 3775 } else { 3776 wp_send_json_success( array( 3777 'message' => 'New admin username saved. No insecure admin usernames found to change.' 3778 ) ); 3779 } 3780 } 3781 3782 /** 3783 * Generate a random username with letters only 3784 */ 3785 private function wpironis_generate_random_username() { 3786 $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 3787 $username = ''; 3788 $length = rand(8, 12); 3789 3790 for ( $i = 0; $i < $length; $i++ ) { 3791 $username .= $chars[rand(0, strlen($chars) - 1)]; 3792 } 3793 3794 3795 return $this->wpironis_get_unique_username( $username ); 3796 } 3797 3798 /** 3799 * Get a unique username by adding a number suffix if needed 3800 */ 3801 private function wpironis_get_unique_username( $username ) { 3802 $original_username = $username; 3803 $suffix = 1; 3804 3805 while ( username_exists( $username ) ) { 3806 $username = $original_username . $suffix; 3807 $suffix++; 3808 } 3809 3810 return $username; 3811 } 3812 3813 /** 3814 * Get usernames of admin users with common/default usernames 3815 */ 3816 private function wpironis_get_admin_usernames() { 3817 $insecure_usernames = array( 'admin', 'administrator', 'wordpress' ); 3818 $found_usernames = array(); 3819 3820 foreach ( $insecure_usernames as $username ) { 3821 $user = get_user_by( 'login', $username ); 3822 if ( $user && in_array( 'administrator', $user->roles ) ) { 3823 $found_usernames[] = $username; 3824 } 3825 } 3826 3827 return $found_usernames; 3828 } 3829 3632 3830 } -
iron-security/tags/2.3.3/admin/js/components/Dashboard.jsx
r3288628 r3300103 135 135 ); 136 136 137 console.log(settings)138 137 139 138 if (isLoading || !settings) { -
iron-security/tags/2.3.3/admin/js/components/FileDirectoryProectionSettings.jsx
r3288628 r3300103 12 12 13 13 useEffect(() => { 14 // Update settings when they change 14 15 15 setIsPhpUploadBlocked(settings.wpironis_options?.wpironis_block_php_uploads === 1); 16 16 setIsDirectAccessPrevented(settings.wpironis_options?.wpironis_prevent_direct_access === 1); -
iron-security/tags/2.3.3/admin/js/components/GeneralSettings.jsx
r3288628 r3300103 15 15 16 16 useEffect(() => { 17 // Update all state values when settings change 17 18 18 setIsXmlrpcEnabled(settings.wpironis_options?.wpironis_disable_xmlrpc === 1); 19 19 setIsWpVersionHidden(settings.wpironis_options?.wpironis_hide_wp_version === 1); … … 57 57 58 58 const data = await response.json(); 59 console.log('Response:', data); // Debug log60 59 61 60 if (data.success) { -
iron-security/tags/2.3.3/admin/js/components/HttpSecurityHeadersSettings.jsx
r3288628 r3300103 19 19 20 20 useEffect(() => { 21 // Update header settings when settings change 21 22 22 setHeaderSettings({ 23 23 contentTypeOptions: settings.wpironis_options?.wpironis_security_header_content_type_options === 1, -
iron-security/tags/2.3.3/admin/js/components/LoginLogoutSettings.jsx
r3288628 r3300103 72 72 73 73 const [isLoadingAdminInfo, setIsLoadingAdminInfo] = useState(false); 74 75 const [isChangeAdminUsernameEnabled, setIsChangeAdminUsernameEnabled] = useState( 76 loginLogoutSettings.enable_change_admin_username === '1' 77 ); 78 79 const [newAdminUsername, setNewAdminUsername] = useState( 80 loginLogoutSettings.wpironis_new_admin_username || '' 81 ); 82 83 const [adminUsernameStatus, setAdminUsernameStatus] = useState(null); 74 84 75 85 const getAdminCountStatus = () => { … … 103 113 useEffect(() => { 104 114 const loginLogoutSettings = settings?.login_logout || {}; 105 // Update all state values when settings change 115 106 116 setIsCustomUrlEnabled(loginLogoutSettings.enable_slug_change === '1'); 107 117 setCustomUrl(loginLogoutSettings.wpironis_custom_login_slug || 'wp-admin'); … … 129 139 setIsAdminIdProtectionEnabled(true); 130 140 } 141 142 if (loginLogoutSettings.enable_change_admin_username === '1') { 143 setIsChangeAdminUsernameEnabled(true); 144 } 145 146 if (loginLogoutSettings.wpironis_new_admin_username) { 147 setNewAdminUsername(loginLogoutSettings.wpironis_new_admin_username); 148 } 131 149 }, [settings]); 132 150 … … 221 239 console.error('Error:', error); 222 240 showNotification(error.message || 'Failed to update custom URL setting', 'error'); 223 setIsCustomUrlEnabled(!enabled); // Revert the toggle241 setIsCustomUrlEnabled(!enabled); 224 242 } finally { 225 243 setIsSaving(false); … … 282 300 console.error('Error:', error); 283 301 showNotification(error.message || 'Failed to update session timeout setting', 'error'); 284 setIsSessionTimeoutEnabled(!enabled); // Revert the toggle302 setIsSessionTimeoutEnabled(!enabled); 285 303 } finally { 286 304 setIsSaving(false); … … 546 564 setIsSaving(false); 547 565 } 566 }; 567 568 const handleChangeAdminUsernameToggle = async (enabled) => { 569 setIsSaving(true); 570 try { 571 const formData = new FormData(); 572 formData.append('action', 'iron_security_toggle_change_admin_username'); 573 formData.append('enabled', enabled); 574 formData.append('nonce', settings.nonce); 575 576 const response = await fetch(ajaxurl, { 577 method: 'POST', 578 body: formData, 579 credentials: 'same-origin' 580 }); 581 582 const data = await response.json(); 583 584 if (data.success) { 585 setIsChangeAdminUsernameEnabled(enabled); 586 587 if (enabled && data.data.admin_usernames && data.data.admin_usernames.length > 0) { 588 setAdminUsernameStatus({ 589 message: `Insecure admin usernames detected: ${data.data.admin_usernames.join(', ')}. Change them to improve security.`, 590 type: 'warning' 591 }); 592 593 594 if (!newAdminUsername) { 595 generateRandomUsername(); 596 } 597 } else if (enabled) { 598 setAdminUsernameStatus({ 599 message: 'No insecure admin usernames detected.', 600 type: 'info' 601 }); 602 } else { 603 setAdminUsernameStatus(null); 604 } 605 606 showNotification(data.data.message || 'Change admin username setting updated successfully'); 607 } else { 608 throw new Error(data.data || 'Failed to update setting'); 609 } 610 } catch (error) { 611 console.error('Error:', error); 612 showNotification(error.message || 'Failed to update change admin username setting', 'error'); 613 setIsChangeAdminUsernameEnabled(!enabled); 614 } finally { 615 setIsSaving(false); 616 } 617 }; 618 619 const handleSaveAdminUsername = async () => { 620 setIsSaving(true); 621 try { 622 const formData = new FormData(); 623 formData.append('action', 'iron_security_save_change_admin_username'); 624 formData.append('new_username', newAdminUsername); 625 formData.append('nonce', settings.nonce); 626 627 const response = await fetch(ajaxurl, { 628 method: 'POST', 629 body: formData, 630 credentials: 'same-origin' 631 }); 632 633 const data = await response.json(); 634 635 if (data.success) { 636 if (data.data.changed_users && data.data.changed_users.length > 0) { 637 const changedUsers = data.data.changed_users; 638 let message = 'The following admin usernames were changed:\n'; 639 640 changedUsers.forEach(user => { 641 message += `• ${user.old_username} → ${user.new_username}`; 642 if (user.display_name) { 643 message += ` (${user.display_name})`; 644 } 645 message += '\n'; 646 }); 647 648 setAdminUsernameStatus({ 649 message: 'Admin usernames successfully changed.', 650 type: 'info' 651 }); 652 653 showNotification(data.data.message || 'Admin usernames changed successfully'); 654 setTimeout(() => { 655 alert(message); 656 }, 500); 657 } else { 658 setAdminUsernameStatus({ 659 message: 'New admin username saved. No insecure admin usernames found to change.', 660 type: 'info' 661 }); 662 showNotification(data.data.message || 'New admin username saved successfully'); 663 } 664 } else { 665 throw new Error(data.data || 'Failed to save new admin username'); 666 } 667 } catch (error) { 668 console.error('Error:', error); 669 showNotification(error.message || 'Failed to save new admin username', 'error'); 670 } finally { 671 setIsSaving(false); 672 } 673 }; 674 675 const generateRandomUsername = () => { 676 const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 677 let result = ''; 678 const length = 8 + Math.floor(Math.random() * 8); 679 for (let i = 0; i < length; i++) { 680 result += chars.charAt(Math.floor(Math.random() * chars.length)); 681 } 682 setNewAdminUsername(result); 548 683 }; 549 684 … … 831 966 </div> 832 967 )} 968 969 <SettingToggle 970 label="Change Default Admin Username" 971 description="Change admin username if you have accounts with usernames like 'admin' or 'administrator' to prevent targeted attacks." 972 checked={isChangeAdminUsernameEnabled} 973 onChange={handleChangeAdminUsernameToggle} 974 disabled={isSaving} 975 /> 976 977 {isChangeAdminUsernameEnabled && ( 978 <div className="wpironis-custom-url-input"> 979 <div className="admin-username-info"> 980 <p className="description"> 981 <strong>Default admin usernames like 'admin' or 'administrator' are frequently targeted in brute force attacks.</strong> 982 Change them to enhance your site's security. 983 </p> 984 {adminUsernameStatus && ( 985 <p className={`description ${adminUsernameStatus.type}`}> 986 <span className={`dashicons dashicons-${adminUsernameStatus.type === 'warning' ? 'warning' : 'info'}`}></span> 987 {adminUsernameStatus.message} 988 </p> 989 )} 990 <div className="wpironis-input-group"> 991 <label htmlFor="new-admin-username">New Username:</label> 992 <input 993 type="text" 994 id="new-admin-username" 995 value={newAdminUsername} 996 onChange={(e) => setNewAdminUsername(e.target.value.replace(/[^a-zA-Z]/g, ''))} 997 placeholder="Enter new username (letters only)" 998 disabled={isSaving} 999 /> 1000 <button 1001 className="button button-secondary" 1002 onClick={generateRandomUsername} 1003 disabled={isSaving} 1004 style={{ marginLeft: '5px' }} 1005 > 1006 Generate Random 1007 </button> 1008 <button 1009 className="button button-primary" 1010 onClick={handleSaveAdminUsername} 1011 disabled={isSaving || !newAdminUsername} 1012 style={{ marginLeft: '5px' }} 1013 > 1014 {isSaving ? 'Saving...' : 'Save Username'} 1015 </button> 1016 </div> 1017 </div> 1018 </div> 1019 )} 833 1020 </div> 834 1021 </div> -
iron-security/tags/2.3.3/admin/js/dist/manifest.json
r3296251 r3300103 1 1 { 2 "dashboard.js": "dashboard. d4938437720f6eface82.js",2 "dashboard.js": "dashboard.409171eb914877d3afe7.js", 3 3 "vendors.js": "vendors.91782840b9e9337a9a5f.js" 4 4 } -
iron-security/tags/2.3.3/includes/class-iron-security.php
r3291538 r3300103 84 84 $this->loader->add_action( 'admin_menu', $plugin_admin, 'wpironis_plugin_menu' ); 85 85 86 // Login Logout 86 87 87 $this->loader->add_action( 'init', $plugin_admin, 'wpironis_redirect_login' ); 88 88 $this->loader->add_action( 'admin_init', $plugin_admin, 'wpironis_plugin_settings_init' ); … … 116 116 $this->loader->add_filter( 'wp_handle_upload_prefilter', $plugin_admin, 'wpironis_prevent_php_upload' ); 117 117 118 // AJAX handlers 118 119 119 $this->loader->add_action( 'wp_ajax_iron_security_toggle_xmlrpc', 120 120 $plugin_admin, … … 149 149 'wpironis_handle_core_autoupdate_toggle' ); 150 150 151 // Custom admin URL AJAX handlers 151 152 152 $this->loader->add_action( 'wp_ajax_iron_security_toggle_custom_url', 153 153 $plugin_admin, … … 157 157 'wpironis_handle_custom_url_save' ); 158 158 159 // Session timeout AJAX handlers 159 160 160 $this->loader->add_action( 'wp_ajax_iron_security_toggle_session_timeout', 161 161 $plugin_admin, … … 165 165 'wpironis_handle_session_timeout_save' ); 166 166 167 // Limit login attempts AJAX handlers 167 168 168 $this->loader->add_action( 'wp_ajax_iron_security_toggle_limit_login', 169 169 $plugin_admin, … … 173 173 'wpironis_handle_limit_login_save' ); 174 174 175 // Limit admins AJAX handlers 175 176 176 $this->loader->add_action( 'wp_ajax_iron_security_toggle_limit_admins', 177 177 $plugin_admin, … … 181 181 'wpironis_handle_limit_admins_save' ); 182 182 183 // Admin ID protection AJAX handler 183 184 184 $this->loader->add_action( 'wp_ajax_iron_security_toggle_admin_id_protection', 185 185 $plugin_admin, 186 186 'wpironis_handle_admin_id_protection_toggle' ); 187 187 188 // Admin role limitation hook 188 $this->loader->add_action( 'wp_ajax_iron_security_toggle_change_admin_username', 189 $plugin_admin, 190 'wpironis_handle_change_admin_username_toggle' ); 191 $this->loader->add_action( 'wp_ajax_iron_security_save_change_admin_username', 192 $plugin_admin, 193 'wpironis_handle_change_admin_username_save' ); 194 189 195 $this->loader->add_action( 'set_user_role', 190 196 $plugin_admin, … … 192 198 10, 193 199 3 ); 194 // Hook into user creation/update for admin limit 200 195 201 $this->loader->add_action( 'user_register', 196 202 $plugin_admin, … … 199 205 1 ); 200 206 201 // REST API filter for admin limit202 207 $this->loader->add_filter( 'rest_request_after_callbacks', 203 208 $plugin_admin, … … 216 221 'wpironis_handle_user_enum_message_save' ); 217 222 218 // Also add filters for auto-updates on plugin initialization 223 219 224 $options = get_option( 'wpironis_options', array() ); 220 225 221 // Configure plugin auto-updates based on saved setting 226 222 227 if ( ! empty( $options['wpironis_enable_plugin_autoupdate'] ) && $options['wpironis_enable_plugin_autoupdate'] === 1 ) { 223 228 add_filter( 'auto_update_plugin', '__return_true' ); … … 226 231 } 227 232 228 // Configure core auto-updates based on saved setting 233 229 234 if ( ! empty( $options['wpironis_enable_core_autoupdate'] ) && $options['wpironis_enable_core_autoupdate'] === 1 ) { 230 235 add_filter( 'allow_major_auto_core_updates', '__return_true' ); … … 302 307 $this->loader->add_action( 'init', $plugin_public, 'wpironis_init' ); 303 308 304 // Add REST API restriction hooks 309 305 310 $this->loader->add_filter( 'rest_authentication_errors', $plugin_public, 'wpironis_restrict_rest_api' ); 306 311 $this->loader->add_filter( 'rest_endpoints', $plugin_public, 'wpironis_disable_rest_endpoints' ); -
iron-security/tags/2.3.3/iron-security.php
r3296251 r3300103 17 17 * Plugin URI: https://wpiron.com 18 18 * Description: Iron Security is a powerful WordPress security plugin to protect your site from common threats. Lock down your site with login protection, file security, and HTTP headers — all in one lightweight plugin. 19 * Version: 2.3. 219 * Version: 2.3.3 20 20 * Author: wpiron 21 21 * Author URI: https://wpiron.com/ … … 31 31 } 32 32 33 define( 'IRON_SECURITY_VERSION', '2.3. 2' );33 define( 'IRON_SECURITY_VERSION', '2.3.3' ); 34 34 35 35 function wpiisec_activate_iron_security() { -
iron-security/trunk/README.txt
r3296251 r3300103 5 5 Requires at least: 4.7 6 6 Tested up to: 6.8 7 Stable tag: 2.3. 27 Stable tag: 2.3.3 8 8 Requires PHP: 7.0 9 9 License: GPLv2 or later … … 35 35 - Change default Admin ID 36 36 - Block user enumeration 37 - Change Default Admin Username 37 38 38 39 **Files & Directory Protection** … … 65 66 66 67 = What makes Iron Security different from other WordPress security plugins? = 67 Iron Security is designed to be lightweight, fast, and focused on practical features that matter most for securing your WordPress site.68 Iron Security is lightweight, fast, and focused on the most effective hardening techniques. Instead of bloated features, it delivers practical tools that directly protect your WordPress site from real threats. 68 69 69 70 = Is Iron Security suitable for beginners? = 70 Yes! Iron Security comes with an intuitive dashboard and clear explanations for each option. Whether you're a WordPress beginner or an experienced developer, you'll find it easy to use and configure.71 72 = How does the custom login URL helpprotect my site? =73 Changing the default `/wp-admin` or `/wp-login.php` URL makes it harder for bots and attackers to find your login page, reducing brute force attempts. You can set your own unique login slug in a few clicks from the plugin settings.74 75 = What happens when a userexceeds the allowed login attempts? =76 If a user exceeds the allowed number of login attempts, their IP will be temporarily blocked based on your configured lockout settings. You can customize the number of allowed attempts, lockout duration, and view attempt logs.77 78 = How does theAdmin ID protection work? =79 By default, WordPress assigns user ID 1 to the first admin account — a known vulnerability targeted by bots. Iron Security lets you assign a different ID to your admin account, making it harder to guess and exploit.80 81 = Does Iron Security block XML-RPC and REST API? Why? =82 Yes , you can optionally disable XML-RPC and REST API — two common attack vectors. XML-RPC is often used in DDoS and brute force attacks, while REST API may expose user data. Disabling them improves security, especially if you don’t use them.83 84 = What are HTTP security headers and why should I enable them? =85 HTTP security headers like X-Frame-Options, Content-Security-Policy, and Strict-Transport-Security provide an extra layer of browser-based protection. They help prevent XSS, clickjacking, and other code injection attacks. Iron Security lets you enable them easily from the dashboard.71 Absolutely. Iron Security features an intuitive dashboard with clear explanations for each setting. Whether you’re new to WordPress or a seasoned developer, you’ll find it easy to set up and use. 72 73 = How does changing the login URL protect my site? = 74 By replacing the default /wp-admin or /wp-login.php URLs, you reduce the risk of automated brute force attacks. Bots and attackers can’t easily locate your login page, adding an extra layer of protection. 75 76 = What happens if someone exceeds the allowed login attempts? = 77 Their IP will be temporarily blocked based on your configured settings. You can set how many failed attempts are allowed, how long the lockout lasts, and monitor the attempt logs directly from the plugin dashboard. 78 79 = How does Admin ID protection work? = 80 WordPress assigns the first admin user an ID of 1 — a known target for attacks. Iron Security helps you avoid this by allowing you to assign a different user ID to your admin account, reducing exposure to targeted exploits. 81 82 = Can Iron Security block XML-RPC and REST API access? = 83 Yes. These features can be optionally disabled. XML-RPC and REST API are common targets for attacks like brute force or user data enumeration. If you don’t use them, disabling them can significantly reduce your attack surface. 84 85 = What are HTTP security headers, and why should I enable them? = 86 HTTP security headers such as X-Frame-Options, Content-Security-Policy, and Strict-Transport-Security enhance browser-level security. They help protect your site against XSS, clickjacking, and other injection-based attacks. Iron Security lets you enable these headers with just a few clicks. 86 87 87 88 = Will Iron Security slow down my website? = 88 No t at all. The plugin is built to be lightweight and uses efficient code practices. It doesn’t run background scans or heavy processes, so your site’s performance remains unaffected.89 90 = Can I use Iron Security on WooCommerce stores? =91 Absolutely. Iron Security is fully compatible with WooCommerce and protects your login area, admin panel, and core files without affecting your store’s functionality.92 93 = Wherecan I get support or report a bug? =94 You can submit issues or ask for help via the [support forum on WordPress.org](https://wordpress.org/support/plugin/iron-security/) or by contacting us directly at [https://wpiron.com](https://wpiron.com).89 No. Iron Security is performance-focused and doesn't include heavy scans or background processes. It’s designed to secure your site without impacting loading speed or user experience. 90 91 = Is Iron Security compatible with WooCommerce? = 92 Yes, Iron Security works seamlessly with WooCommerce. It protects your admin area, login forms, and critical files without interfering with your store's functionality or customer experience. 93 94 = How can I get support or report a bug? = 95 You can reach out via the WordPress.org support forum: https://wordpress.org/support/plugin/iron-security/ or contact our team directly at https://wpiron.com. 95 96 96 97 = How often is Iron Security updated? = 97 We actively maintain and improve Iron Security. You can expect regular updates for new features, security patches, and WordPress compatibility improvements. 98 We actively maintain Iron Security to ensure compatibility with the latest WordPress releases. Expect regular updates with new features, security improvements, and bug fixes. 99 100 = What is the benefit of disabling file editing in WordPress? = 101 Disabling the file editor in the admin panel prevents attackers from injecting malicious code directly into theme or plugin files if they gain admin access. This is a simple but effective hardening step. 102 103 = What does hiding the WordPress version do? = 104 Iron Security removes the WordPress version from your website’s source code to reduce the risk of automated attacks targeting specific versions with known vulnerabilities. 105 106 = How does Iron Security block AI crawlers? = 107 The plugin adds specific rules to your robots.txt file to block AI and data scraping bots, helping protect your content from unauthorized use and training. 108 109 = What does "Limit number of administrators" mean? = 110 Limiting admin users helps reduce the number of high-privilege accounts on your site. The plugin notifies you if more than one admin account exists, encouraging better access control and role management. 111 112 = How does session timeout protect my site? = 113 If a logged-in user is inactive for a certain period, they are automatically logged out. This prevents unauthorized access from unattended sessions on shared or public devices. 114 115 = Can I block user enumeration? = 116 Yes. Iron Security prevents bots from discovering usernames through the `?author=` query parameter, a common tactic used in brute force and targeted attacks. 117 118 = What does “Change default admin username” do? = 119 If your admin account uses "admin" as the username, it becomes an easy target. Iron Security helps you safely change this default to something unique and secure. 120 121 = What does "Prevent direct file access" mean? = 122 The plugin restricts access to sensitive files like PHP templates and system files that should not be accessed directly, protecting your site from unauthorized requests. 123 124 = Can I block PHP file uploads? = 125 Yes. Iron Security allows you to block PHP file uploads in forms and media to prevent malicious scripts from being uploaded to your site. 126 127 = What are plugin and core auto-updates, and why enable them? = 128 Keeping your WordPress core and plugins updated is critical to staying secure. Iron Security allows you to enable automatic updates, so your site is always protected with the latest patches. 98 129 99 130 … … 111 142 == Changelog == 112 143 144 = 2.3.3 = 145 * Add functionality to change default admin username 146 113 147 = 2.3.2 = 114 148 * Gutenberg some blocks were disabled - fixed it. -
iron-security/trunk/admin/class-iron-security-admin.php
r3291538 r3300103 47 47 plugin_dir_url( __FILE__ ) . 'css/admin.css', 48 48 array(), 49 time(),49 $this->version, 50 50 'all' ); 51 51 wp_enqueue_style( $this->plugin_name . '-dashboard', … … 57 57 plugin_dir_url( __FILE__ ) . 'css/transitions.css', 58 58 array(), 59 time(),59 $this->version, 60 60 'all' ); 61 61 } … … 81 81 plugin_dir_url( __FILE__ ) . 'js/iron-security-admin.js', 82 82 array( 'jquery', 'wp-element', 'wp-components' ), 83 //$this->version,84 time(),83 $this->version, 84 // time(), 85 85 false 86 86 ); … … 92 92 plugin_dir_url( __FILE__ ) . 'js/session-timeout.js', 93 93 array( 'jquery' ), 94 //$this->version,95 time(),94 $this->version, 95 // time(), 96 96 true 97 97 ); … … 114 114 array( 'wp-element', 'wp-components', 'wp-i18n' ), 115 115 $this->version, 116 // time(), 116 117 true 117 118 ); … … 122 123 array( 'wp-element', 'wp-components', 'wp-i18n' ), 123 124 $this->version, 125 // time(), 124 126 true 125 127 ); … … 171 173 // Your content for login/logout tab 172 174 echo '<h3>Login/Logout Settings</h3>'; 173 // add your logic here 174 } 175 // Add other tabs content conditions 175 176 } 177 176 178 ?> 177 179 </div> … … 223 225 $sanitized_input = array(); 224 226 foreach ( $input as $key => $value ) { 225 // Special handling for checkbox/toggle fields 227 226 228 if ( in_array( $key, array( 227 229 'wpironis_disable_xmlrpc', … … 346 348 $secret = get_user_meta( $user->ID, 'google_authenticator_secret', true ); 347 349 if ( ! $secret ) { 348 return $user; // No 2FA setup for the user350 return $user; 349 351 } 350 352 … … 478 480 } 479 481 480 // When enabled is true, we want to disable XML-RPC 482 481 483 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 482 484 $options = get_option( 'wpironis_options', array() ); 483 485 484 // Set to 1 to disable XML-RPC when toggle is enabled 486 485 487 $options['wpironis_disable_xmlrpc'] = $enabled ? 1 : 0; 486 488 … … 494 496 ) ); 495 497 } else { 496 // Check if the value was actually the same (no update needed) 498 497 499 $current_options = get_option( 'wpironis_options' ); 498 500 if ( isset( $current_options['wpironis_disable_xmlrpc'] ) && … … 520 522 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 521 523 522 // Get current options or initialize array 524 523 525 $options = get_option( 'wpironis_options', array() ); 524 526 525 // Update the WordPress version hiding setting 527 526 528 $options['wpironis_hide_wp_version'] = $enabled ? 1 : 0; 527 529 528 // Save the updated options 530 529 531 $updated = update_option( 'wpironis_options', $options ); 530 532 … … 536 538 ) ); 537 539 } else { 538 // Check if the value was actually the same (no update needed) 540 539 541 $current_options = get_option( 'wpironis_options' ); 540 542 if ( isset( $current_options['wpironis_hide_wp_version'] ) && … … 562 564 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 563 565 564 // Get current options or initialize array 566 565 567 $options = get_option( 'wpironis_options', array() ); 566 568 567 // Update the security headers setting 569 568 570 $options['wpironis_security_headers'] = $enabled ? 1 : 0; 569 571 570 // Save the updated options 572 571 573 $updated = update_option( 'wpironis_options', $options ); 572 574 … … 578 580 ) ); 579 581 } else { 580 // Check if the value was actually the same (no update needed) 582 581 583 $current_options = get_option( 'wpironis_options' ); 582 584 if ( isset( $current_options['wpironis_security_headers'] ) && … … 604 606 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 605 607 606 // Get current options or initialize array 608 607 609 $options = get_option( 'wpironis_options', array() ); 608 610 609 // Update the PHP uploads blocking setting 611 610 612 $options['wpironis_block_php_uploads'] = $enabled ? 1 : 0; 611 613 612 // Save the updated options 614 613 615 $updated = update_option( 'wpironis_options', $options ); 614 616 615 // Handle .htaccess rules 617 616 618 if ( $enabled ) { 617 619 $this->wpironis_create_upload_protection(); … … 627 629 ) ); 628 630 } else { 629 // Check if the value was actually the same (no update needed) 631 630 632 $current_options = get_option( 'wpironis_options' ); 631 633 if ( isset( $current_options['wpironis_block_php_uploads'] ) && … … 646 648 $htaccess_path = $upload_dir['basedir'] . '/.htaccess'; 647 649 648 // Define the rules 650 649 651 $rules = " 650 652 # Iron Security … … 675 677 "; 676 678 677 // Create or update .htaccess file 679 678 680 if ( ! file_exists( $htaccess_path ) || is_writable( $htaccess_path ) ) { 679 681 file_put_contents( $htaccess_path, $rules, LOCK_EX ); 680 682 } 681 683 682 // Also create an index.php file to prevent directory listing 684 683 685 $index_path = $upload_dir['basedir'] . '/index.php'; 684 686 if ( ! file_exists( $index_path ) ) { … … 697 699 698 700 public function wpironis_prevent_php_upload( $file ) { 699 // Get the current options 701 700 702 $options = get_option( 'wpironis_options', array() ); 701 703 702 // Check if PHP upload blocking is enabled 704 703 705 if ( ! isset( $options['wpironis_block_php_uploads'] ) || $options['wpironis_block_php_uploads'] !== 1 ) { 704 706 return $file; 705 707 } 706 708 707 // Ensure the file name is set and is a string 709 708 710 if ( ! isset( $file['name'] ) || empty( $file['name'] ) || ! is_string( $file['name'] ) ) { 709 711 $file['error'] = __( 'Invalid file name detected. Upload blocked.', 'iron-security' ); … … 712 714 } 713 715 714 // List of blocked extensions 716 715 717 $blocked_extensions = array( 716 718 'php', … … 734 736 $extension = strtolower( pathinfo( $file_path, PATHINFO_EXTENSION ) ?: '' ); 735 737 736 // Check if the file extension is in the blocked list 738 737 739 if ( in_array( $extension, $blocked_extensions, true ) ) { 738 740 $file['error'] = sprintf( … … 745 747 } 746 748 747 // Check for double extensions (e.g., file.jpg.php) 749 748 750 $all_extensions = explode( '.', $file_path ); 749 751 if ( count( $all_extensions ) > 2 ) { … … 789 791 ) ); 790 792 } else { 791 // Check if the value was actually the same (no update needed) 793 792 794 $current_options = get_option( 'wpironis_options' ); 793 795 if ( isset( $current_options['wpironis_prevent_direct_access'] ) && … … 842 844 "; 843 845 844 // Create or update root .htaccess file 846 845 847 if ( ! file_exists( $root_htaccess_path ) || is_writable( $root_htaccess_path ) ) { 846 // If .htaccess exists, read its content 848 847 849 $existing_content = file_exists( $root_htaccess_path ) ? file_get_contents( $root_htaccess_path ) : ''; 848 850 849 // Check if our rules are already present 851 850 852 if ( strpos( $existing_content, 'Iron Security - Prevent Direct File Access' ) === false ) { 851 // Add our rules after WordPress rules if they exist 853 852 854 if ( strpos( $existing_content, '# END WordPress' ) !== false ) { 853 855 $existing_content = str_replace( '# END WordPress', … … 862 864 } 863 865 864 // Also protect wp-content directory 866 865 867 $content_htaccess_path = WP_CONTENT_DIR . '/.htaccess'; 866 868 $content_rules = " … … 918 920 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 919 921 920 // Get current options or initialize array 922 921 923 $options = get_option( 'wpironis_options', array() ); 922 924 923 // Update the file editor setting 925 924 926 $options['wpironis_disable_file_editor'] = $enabled ? 1 : 0; 925 927 926 // Save the updated options 928 927 929 $updated = update_option( 'wpironis_options', $options ); 928 930 929 // Update wp-config.php to disable file editor 931 930 932 if ( $enabled ) { 931 933 $this->wpironis_disable_file_editor_in_config(); … … 941 943 ) ); 942 944 } else { 943 // Check if the value was actually the same (no update needed) 945 944 946 $current_options = get_option( 'wpironis_options' ); 945 947 if ( isset( $current_options['wpironis_disable_file_editor'] ) && … … 965 967 $config_content = file_get_contents( $wp_config_path ); 966 968 967 // Check if the constant is already defined 969 968 970 if ( strpos( $config_content, 'DISALLOW_FILE_EDIT' ) === false ) { 969 // Find the line where we should add our constant 971 970 972 $insert_point = strpos( $config_content, "/* That's all, stop editing!" ); 971 973 … … 981 983 } 982 984 } else { 983 // Update existing constant if it's set to false 985 984 986 $config_content = preg_replace( 985 987 "/define\(\s*'DISALLOW_FILE_EDIT'\s*,\s*false\s*\);/", … … 1000 1002 $config_content = file_get_contents( $wp_config_path ); 1001 1003 1002 // Remove the constant definition if it exists 1004 1003 1005 $config_content = preg_replace( 1004 1006 "/define\(\s*'DISALLOW_FILE_EDIT'\s*,\s*true\s*\);(\r\n|\n|\r)?/", … … 1021 1023 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1022 1024 1023 // Get current options or initialize array 1025 1024 1026 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1025 1027 1026 // Update the custom URL setting 1028 1027 1029 $options['enable_slug_change'] = $enabled ? '1' : '0'; 1028 1030 1029 // Save the updated options 1031 1030 1032 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1031 1033 … … 1037 1039 ) ); 1038 1040 } else { 1039 // Check if the value was actually the same (no update needed) 1041 1040 1042 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1041 1043 if ( isset( $current_options['enable_slug_change'] ) && … … 1067 1069 } 1068 1070 1069 $url = sanitize_title( $_POST['url'] ); // Sanitize and convert spaces to hyphens1071 $url = sanitize_title( $_POST['url'] ); 1070 1072 1071 1073 if ( empty( $url ) ) { … … 1075 1077 } 1076 1078 1077 // Get current options 1079 1078 1080 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1079 1081 1080 // Update the custom URL 1082 1081 1083 $options['wpironis_custom_login_slug'] = $url; 1082 1084 1083 // Save the updated options 1085 1084 1086 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1085 1087 … … 1091 1093 ) ); 1092 1094 } else { 1093 // Check if the value was actually the same (no update needed) 1095 1094 1096 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1095 1097 if ( isset( $current_options['wpironis_custom_login_slug'] ) && … … 1117 1119 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1118 1120 1119 // Get current options or initialize array 1121 1120 1122 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1121 1123 1122 // Update the session timeout setting 1124 1123 1125 $options['enable_session_timeout'] = $enabled ? '1' : '0'; 1124 1126 1125 // Save the updated options 1127 1126 1128 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1127 1129 … … 1133 1135 ) ); 1134 1136 } else { 1135 // Check if the value was actually the same (no update needed) 1137 1136 1138 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1137 1139 if ( isset( $current_options['enable_session_timeout'] ) && … … 1172 1174 } 1173 1175 1174 // Get current options 1176 1175 1177 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1176 1178 1177 // Update the timeout value 1179 1178 1180 $options['session_timeout_value'] = $timeout; 1179 1181 1180 // Save the updated options 1182 1181 1183 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1182 1184 … … 1188 1190 ) ); 1189 1191 } else { 1190 // Check if the value was actually the same (no update needed) 1192 1191 1193 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1192 1194 if ( isset( $current_options['session_timeout_value'] ) && … … 1214 1216 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1215 1217 1216 // Get current options 1218 1217 1219 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1218 1220 1219 // Update the limit login attempts setting 1221 1220 1222 $options['enable_limit_login_attempts'] = $enabled ? '1' : '0'; 1221 1223 1222 // Save the updated options 1224 1223 1225 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1224 1226 … … 1230 1232 ) ); 1231 1233 } else { 1232 // Check if the value was actually the same (no update needed) 1234 1233 1235 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1234 1236 if ( isset( $current_options['enable_limit_login_attempts'] ) && … … 1277 1279 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1278 1280 1279 // Update the settings 1281 1280 1282 $options['wpironis_limit_login_attempts'] = (string) $attempts; 1281 1283 $options['wpironis_lockout_duration'] = (string) $duration; 1282 1284 1283 // Save the updated options 1285 1284 1286 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1285 1287 … … 1292 1294 ) ); 1293 1295 } else { 1294 // Check if the values were actually the same (no update needed) 1296 1295 1297 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1296 1298 if ( isset( $current_options['wpironis_limit_login_attempts'] ) && … … 1321 1323 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1322 1324 1323 // Get current options 1325 1324 1326 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1325 1327 1326 // Update the user enumeration protection setting 1328 1327 1329 $options['enable_user_enumeration'] = $enabled ? '1' : '0'; 1328 1330 1329 // Save the updated options 1331 1330 1332 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1331 1333 … … 1337 1339 ) ); 1338 1340 } else { 1339 // Check if the value was actually the same (no update needed) 1341 1340 1342 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1341 1343 if ( isset( $current_options['enable_user_enumeration'] ) && … … 1375 1377 } 1376 1378 1377 // Get current options 1379 1378 1380 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1379 1381 1380 // Update the custom error message 1382 1381 1383 $options['user_enumeration_message'] = $message; 1382 1384 1383 // Save the updated options 1385 1384 1386 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1385 1387 … … 1391 1393 ) ); 1392 1394 } else { 1393 // Check if the value was actually the same (no update needed) 1395 1394 1396 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 1395 1397 if ( isset( $current_options['user_enumeration_message'] ) && … … 1407 1409 1408 1410 public function wpironis_modify_login_errors( $error ) { 1409 // Get the options 1411 1410 1412 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1411 1413 1412 // Check if user enumeration protection is enabled 1414 1413 1415 if ( ! empty( $options['enable_user_enumeration'] ) && $options['enable_user_enumeration'] === '1' ) { 1414 // Get the custom message or use default 1416 1415 1417 $custom_message = ! empty( $options['user_enumeration_message'] ) 1416 1418 ? $options['user_enumeration_message'] 1417 1419 : 'Wrong Login credentials'; 1418 1420 1419 // Check if this is a password-related error for an existing username 1421 1420 1422 if ( strpos( $error, 'password you entered for' ) !== false ) { 1421 1423 return __( $custom_message, 'iron-security' ); … … 1427 1429 1428 1430 public function wpironis_block_user_enumeration() { 1429 // Get the options 1431 1430 1432 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1431 1433 1432 // Check if user enumeration protection is enabled 1434 1433 1435 if ( ! empty( $options['enable_user_enumeration'] ) && $options['enable_user_enumeration'] === '1' ) { 1434 // Block access to author pages 1436 1435 1437 if ( is_author() ) { 1436 1438 wp_redirect( home_url(), 301 ); … … 1438 1440 } 1439 1441 1440 // Block user enumeration through REST API 1442 1441 1443 add_filter( 'rest_endpoints', function ( $endpoints ) { 1442 1444 if ( isset( $endpoints['/wp/v2/users'] ) ) { … … 1450 1452 } ); 1451 1453 1452 // Block ?author=N queries 1454 1453 1455 if ( isset( $_GET['author'] ) && is_numeric( $_GET['author'] ) ) { 1454 1456 wp_redirect( home_url(), 301 ); … … 1469 1471 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1470 1472 1471 // Get current options or initialize array 1473 1472 1474 $options = get_option( 'wpironis_options', array() ); 1473 1475 1474 // Update the REST API setting 1476 1475 1477 $options['wpironis_disable_rest_api'] = $enabled ? 1 : 0; 1476 1478 1477 // Save the updated options 1479 1478 1480 $updated = update_option( 'wpironis_options', $options ); 1479 1481 … … 1485 1487 ) ); 1486 1488 } else { 1487 // Check if the value was actually the same (no update needed) 1489 1488 1490 $current_options = get_option( 'wpironis_options' ); 1489 1491 if ( isset( $current_options['wpironis_disable_rest_api'] ) && … … 1511 1513 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1512 1514 1513 // Get current options or initialize array 1515 1514 1516 $options = get_option( 'wpironis_options', array() ); 1515 1517 1516 // Update the plugin auto-update setting 1518 1517 1519 $options['wpironis_enable_plugin_autoupdate'] = $enabled ? 1 : 0; 1518 1520 1519 // Save the updated options 1521 1520 1522 $updated = update_option( 'wpironis_options', $options ); 1521 1523 1522 // Configure plugin auto-updates 1524 1523 1525 $this->wpironis_configure_plugin_autoupdates( $enabled ); 1524 1526 … … 1530 1532 ) ); 1531 1533 } else { 1532 // Check if the value was actually the same (no update needed) 1534 1533 1535 $current_options = get_option( 'wpironis_options' ); 1534 1536 if ( isset( $current_options['wpironis_enable_plugin_autoupdate'] ) && … … 1556 1558 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1557 1559 1558 // Get current options or initialize array 1560 1559 1561 $options = get_option( 'wpironis_options', array() ); 1560 1562 1561 // Update the core auto-update setting 1563 1562 1564 $options['wpironis_enable_core_autoupdate'] = $enabled ? 1 : 0; 1563 1565 1564 // Save the updated options 1566 1565 1567 $updated = update_option( 'wpironis_options', $options ); 1566 1568 1567 // Configure core auto-updates 1569 1568 1570 $this->wpironis_configure_core_autoupdates( $enabled ); 1569 1571 … … 1575 1577 ) ); 1576 1578 } else { 1577 // Check if the value was actually the same (no update needed) 1579 1578 1580 $current_options = get_option( 'wpironis_options' ); 1579 1581 if ( isset( $current_options['wpironis_enable_core_autoupdate'] ) && … … 1592 1594 private function wpironis_configure_plugin_autoupdates( $enabled ) { 1593 1595 if ( $enabled ) { 1594 // Enable auto-updates for all plugins using filter 1596 1595 1597 add_filter( 'auto_update_plugin', '__return_true' ); 1596 1598 1597 // Also set the auto_update_plugins option 1599 1598 1600 $all_plugins = array_keys( get_plugins() ); 1599 1601 update_site_option( 'auto_update_plugins', $all_plugins ); 1600 1602 } else { 1601 // Disable auto-updates for all plugins 1603 1602 1604 add_filter( 'auto_update_plugin', '__return_false' ); 1603 1605 1604 // Clear the auto_update_plugins option 1606 1605 1607 update_site_option( 'auto_update_plugins', array() ); 1606 1608 } … … 1609 1611 private function wpironis_configure_core_autoupdates( $enabled ) { 1610 1612 if ( $enabled ) { 1611 // Enable core auto-updates for all update types 1613 1612 1614 add_filter( 'allow_major_auto_core_updates', '__return_true' ); 1613 1615 add_filter( 'allow_minor_auto_core_updates', '__return_true' ); … … 1615 1617 add_filter( 'auto_update_translation', '__return_true' ); 1616 1618 1617 // Update the core update settings 1619 1618 1620 update_site_option( 'auto_update_core_major', 'enabled' ); 1619 1621 update_site_option( 'auto_update_core_minor', 'enabled' ); 1620 1622 update_site_option( 'auto_update_core_dev', 'enabled' ); 1621 1623 } else { 1622 // Disable core auto-updates 1624 1623 1625 add_filter( 'allow_major_auto_core_updates', '__return_false' ); 1624 1626 add_filter( 'allow_minor_auto_core_updates', '__return_false' ); … … 1626 1628 add_filter( 'auto_update_translation', '__return_false' ); 1627 1629 1628 // Update the core update settings 1630 1629 1631 update_site_option( 'auto_update_core_major', 'disabled' ); 1630 1632 update_site_option( 'auto_update_core_minor', 'disabled' ); … … 1660 1662 } 1661 1663 1662 // Update last activity time 1664 1663 1665 update_user_meta( get_current_user_id(), 'iron_security_last_activity', time() ); 1664 1666 } … … 1704 1706 } 1705 1707 1706 // Get filter parameters 1708 1707 1709 $log_type = isset( $_POST['log_type'] ) ? sanitize_text_field( $_POST['log_type'] ) : ''; 1708 1710 $status = isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : ''; … … 1714 1716 $per_page = isset( $_POST['per_page'] ) ? intval( $_POST['per_page'] ) : 50; 1715 1717 1716 // Build filter arguments 1718 1717 1719 $args = array( 1718 1720 'log_type' => $log_type, … … 1726 1728 ); 1727 1729 1728 // Get logs with the specified filters 1730 1729 1731 $logs_data = Iron_Security_Logger::get_logs( $args ); 1730 1732 1731 // Get log summary 1733 1732 1734 $summary = $this->get_logs_summary(); 1733 1735 … … 1749 1751 $table_name = $wpdb->prefix . 'iron_security_logs'; 1750 1752 1751 // Get total count 1753 1752 1754 $total = $wpdb->get_var( "SELECT COUNT(*) FROM $table_name" ); 1753 1755 1754 // Get count by status 1756 1755 1757 $success_count = $wpdb->get_var( "SELECT COUNT(*) FROM $table_name WHERE status = 'success'" ); 1756 1758 $failure_count = $wpdb->get_var( "SELECT COUNT(*) FROM $table_name WHERE status = 'failure'" ); … … 1777 1779 } 1778 1780 1779 // Get filter parameters 1781 1780 1782 $log_type = isset( $_POST['log_type'] ) ? sanitize_text_field( $_POST['log_type'] ) : ''; 1781 1783 $status = isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : ''; … … 1785 1787 $date_to = isset( $_POST['date_to'] ) ? sanitize_text_field( $_POST['date_to'] ) : ''; 1786 1788 1787 // Build filter arguments (with large per_page for export) 1789 1788 1790 $args = array( 1789 1791 'log_type' => $log_type, … … 1793 1795 'date_from' => $date_from, 1794 1796 'date_to' => $date_to, 1795 'per_page' => 10000, // Large number to get most/all logs1797 'per_page' => 10000, 1796 1798 'page' => 1 1797 1799 ); 1798 1800 1799 // Get all logs with the specified filters 1801 1800 1802 $logs_data = Iron_Security_Logger::get_logs( $args ); 1801 1803 $logs = $logs_data['logs']; 1802 1804 1803 // Set headers for CSV download 1805 1804 1806 header( 'Content-Type: text/csv' ); 1805 1807 header( 'Content-Disposition: attachment; filename="iron-security-logs-' . date( 'Y-m-d' ) . '.csv"' ); … … 1807 1809 header( 'Expires: 0' ); 1808 1810 1809 // Create a file pointer connected to the output stream 1811 1810 1812 $output = fopen( 'php://output', 'w' ); 1811 1813 1812 // Output the column headings 1814 1813 1815 fputcsv( $output, 1814 1816 array( 'ID', 'Date', 'Type', 'Username', 'User ID', 'IP Address', 'Message', 'Status', 'Extra Data' ) ); 1815 1817 1816 // Output each row of the data 1818 1817 1819 foreach ( $logs as $log ) { 1818 1820 $log_type_labels = Iron_Security_Logger::get_log_types(); 1819 1821 $log_type_label = isset( $log_type_labels[ $log['log_type'] ] ) ? $log_type_labels[ $log['log_type'] ] : $log['log_type']; 1820 1822 1821 // Format extra data for CSV 1823 1822 1824 $extra_data = ''; 1823 1825 if ( ! empty( $log['extra_data'] ) ) { … … 1842 1844 } 1843 1845 1844 // Close the file pointer 1846 1845 1847 fclose( $output ); 1846 1848 exit; … … 1895 1897 } 1896 1898 1897 // Map the JavaScript camelCase to the database option name 1899 1898 1900 $option_map = [ 1899 1901 'contentTypeOptions' => 'wpironis_security_header_content_type_options', … … 1914 1916 $option_name = $option_map[ $header_name ]; 1915 1917 1916 // Get current options 1918 1917 1919 $options = get_option( 'wpironis_options', array() ); 1918 1920 1919 // Update the specific security header setting 1921 1920 1922 $options[ $option_name ] = $enabled ? 1 : 0; 1921 1923 1922 // Save the updated options 1924 1923 1925 $updated = update_option( 'wpironis_options', $options ); 1924 1926 1925 // Generate a friendly name for the header for use in messages 1927 1926 1928 $header_friendly_names = [ 1927 1929 'contentTypeOptions' => 'X-Content-Type-Options', … … 1945 1947 ) ); 1946 1948 } else { 1947 // Check if the value was actually the same (no update needed) 1949 1948 1950 $current_options = get_option( 'wpironis_options' ); 1949 1951 if ( isset( $current_options[ $option_name ] ) && $current_options[ $option_name ] === ( $enabled ? 1 : 0 ) ) { … … 1975 1977 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 1976 1978 1977 // Get current settings 1979 1978 1980 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 1979 1981 1980 // Update the limit admins setting 1982 1981 1983 $options['enable_limit_admins'] = $enabled ? '1' : '0'; 1982 1984 1983 // Save settings 1985 1984 1986 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 1985 1987 … … 1990 1992 ); 1991 1993 1992 // If the feature is enabled, include admin count and users list 1994 1993 1995 if ( $enabled ) { 1994 1996 $admin_users = get_users( array( … … 2014 2016 wp_send_json_success( $response_data ); 2015 2017 } else { 2016 // Check if the value was actually the same (no update needed) 2018 2017 2019 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 2018 2020 if ( isset( $current_options['enable_limit_admins'] ) && … … 2023 2025 ); 2024 2026 2025 // If the feature is enabled, include admin count and users list 2027 2026 2028 if ( $enabled ) { 2027 2029 $admin_users = get_users( array( … … 2064 2066 } 2065 2067 2066 // Validate and sanitize inputs 2068 2067 2069 $max_admins = isset( $_POST['max_admins'] ) ? absint( $_POST['max_admins'] ) : 1; 2068 2070 $fallback_role = isset( $_POST['fallback_role'] ) ? sanitize_text_field( $_POST['fallback_role'] ) : 'editor'; 2069 2071 2070 // Ensure max_admins is at least 1 2072 2071 2073 if ( $max_admins < 1 ) { 2072 2074 $max_admins = 1; 2073 2075 } 2074 2076 2075 // Validate fallback role is a valid WordPress role 2077 2076 2078 $valid_roles = array( 'editor', 'author', 'contributor', 'subscriber' ); 2077 2079 if ( ! in_array( $fallback_role, $valid_roles ) ) { … … 2079 2081 } 2080 2082 2081 // Get current settings 2083 2082 2084 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 2083 2085 2084 // Update the limit admins settings 2086 2085 2087 $options['wpironis_max_admins'] = (string) $max_admins; 2086 2088 $options['wpironis_admin_fallback_role'] = $fallback_role; 2087 2089 2088 // Save settings 2090 2089 2091 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $options ); 2090 2092 2091 // Get admin users for the response 2093 2092 2094 $admin_users = get_users( array( 2093 2095 'role' => 'administrator', 2094 2096 ) ); 2095 2097 2096 // Count admins 2098 2097 2099 $admin_count = count( $admin_users ); 2098 2100 2099 // Prepare the user data to return 2101 2100 2102 $users = array(); 2101 2103 foreach ( $admin_users as $user ) { … … 2109 2111 2110 2112 if ( $updated ) { 2111 // Enforce the limit now by checking current admin count 2113 2112 2114 $this->wpironis_enforce_admin_limit_now(); 2113 2115 2114 // Get updated admin count after enforcement 2116 2115 2117 $updated_admin_users = get_users( array( 2116 2118 'role' => 'administrator', … … 2118 2120 $updated_admin_count = count( $updated_admin_users ); 2119 2121 2120 // Prepare updated user list 2122 2121 2123 $updated_users = array(); 2122 2124 foreach ( $updated_admin_users as $user ) { … … 2138 2140 ) ); 2139 2141 } else { 2140 // Check if the value was actually the same (no update needed) 2142 2141 2143 $current_options = get_option( 'wpironis_plugin_settings_loginlogout' ); 2142 2144 if ( isset( $current_options['wpironis_max_admins'] ) && … … 2165 2167 */ 2166 2168 public function wpironis_enforce_admin_limit( $user_id, $role, $old_roles ) { 2167 // Check if the new role is administrator 2169 2168 2170 if ( $role !== 'administrator' ) { 2169 2171 return; 2170 2172 } 2171 2173 2172 // Get the settings 2174 2173 2175 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 2174 2176 2175 // Check if the feature is enabled 2177 2176 2178 if ( ! isset( $options['enable_limit_admins'] ) || $options['enable_limit_admins'] !== '1' ) { 2177 2179 return; 2178 2180 } 2179 2181 2180 // Get maximum allowed admins 2182 2181 2183 $max_admins = isset( $options['wpironis_max_admins'] ) ? absint( $options['wpironis_max_admins'] ) : 1; 2182 2184 2183 // Get the fallback role 2185 2184 2186 $fallback_role = isset( $options['wpironis_admin_fallback_role'] ) ? $options['wpironis_admin_fallback_role'] : 'editor'; 2185 2187 2186 // Count current admins 2188 2187 2189 $admin_count = $this->wpironis_count_admins(); 2188 2190 2189 // If the max has been reached, change this user's role to the fallback 2191 2190 2192 if ( $admin_count > $max_admins ) { 2191 // Skip the current hook to avoid infinite loop 2193 2192 2194 remove_action( 'set_user_role', array( $this, 'wpironis_enforce_admin_limit' ), 10 ); 2193 2195 2194 // Downgrade the user to the fallback role 2196 2195 2197 $user = new WP_User( $user_id ); 2196 2198 $user->set_role( $fallback_role ); 2197 2199 2198 // Log the event safely 2200 2199 2201 $this->safe_log_security_event( 2200 2202 'admin_limit_enforced', … … 2207 2209 ); 2208 2210 2209 // Add admin notice 2211 2210 2212 add_action( 'admin_notices', function () use ( $user, $fallback_role, $max_admins ) { 2211 2213 echo '<div class="notice notice-warning is-dismissible">'; … … 2220 2222 } ); 2221 2223 2222 // Re-add the hook 2224 2223 2225 add_action( 'set_user_role', array( $this, 'wpironis_enforce_admin_limit' ), 10, 3 ); 2224 2226 } … … 2231 2233 */ 2232 2234 public function wpironis_check_admin_limit_on_register( $user_id ) { 2233 // Get the user object 2235 2234 2236 $user = new WP_User( $user_id ); 2235 2237 2236 // Check if the user is being registered as an admin 2238 2237 2239 if ( ! in_array( 'administrator', $user->roles ) ) { 2238 2240 return; 2239 2241 } 2240 2242 2241 // Get the settings 2243 2242 2244 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 2243 2245 2244 // Check if the feature is enabled 2246 2245 2247 if ( ! isset( $options['enable_limit_admins'] ) || $options['enable_limit_admins'] !== '1' ) { 2246 2248 return; 2247 2249 } 2248 2250 2249 // Get maximum allowed admins 2251 2250 2252 $max_admins = isset( $options['wpironis_max_admins'] ) ? absint( $options['wpironis_max_admins'] ) : 1; 2251 2253 2252 // Get the fallback role 2254 2253 2255 $fallback_role = isset( $options['wpironis_admin_fallback_role'] ) ? $options['wpironis_admin_fallback_role'] : 'editor'; 2254 2256 2255 // Count current admins 2257 2256 2258 $admin_count = $this->wpironis_count_admins(); 2257 2259 2258 // If the max has been reached, change this user's role to the fallback 2260 2259 2261 if ( $admin_count > $max_admins ) { 2260 // Downgrade the user to the fallback role 2262 2261 2263 $user->set_role( $fallback_role ); 2262 2264 2263 // Log the event safely 2265 2264 2266 $this->safe_log_security_event( 2265 2267 'admin_limit_enforced', … … 2281 2283 */ 2282 2284 private function safe_log_security_event( $event_type, $message ) { 2283 // Try to use the Logger class if it exists and has the method 2285 2284 2286 if ( class_exists( 'Iron_Security_Logger' ) && method_exists( 'Iron_Security_Logger', 'log_security_event' ) ) { 2285 2287 Iron_Security_Logger::log_security_event( $event_type, $message ); 2286 2288 } else { 2287 // Fallback logging to the WordPress error log 2289 2288 2290 error_log( sprintf( '[Iron Security] %s: %s', $event_type, $message ) ); 2289 2291 2290 // Attempt to log to a custom database table if it exists 2292 2291 2293 global $wpdb; 2292 2294 $table_name = $wpdb->prefix . 'iron_security_logs'; 2293 2295 2294 // Check if the table exists before attempting to insert 2296 2295 2297 if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) === $table_name ) { 2296 2298 $wpdb->insert( … … 2330 2332 */ 2331 2333 private function wpironis_enforce_admin_limit_now() { 2332 // Get the settings 2334 2333 2335 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 2334 2336 2335 // Check if the feature is enabled 2337 2336 2338 if ( ! isset( $options['enable_limit_admins'] ) || $options['enable_limit_admins'] !== '1' ) { 2337 2339 return; 2338 2340 } 2339 2341 2340 // Get maximum allowed admins 2342 2341 2343 $max_admins = isset( $options['wpironis_max_admins'] ) ? absint( $options['wpironis_max_admins'] ) : 1; 2342 2344 2343 // Get the fallback role 2345 2344 2346 $fallback_role = isset( $options['wpironis_admin_fallback_role'] ) ? $options['wpironis_admin_fallback_role'] : 'editor'; 2345 2347 2346 // Get all admins 2348 2347 2349 $admin_users = get_users( array( 2348 2350 'role' => 'administrator', … … 2351 2353 ) ); 2352 2354 2353 // Skip if we have fewer admins than the limit 2355 2354 2356 if ( count( $admin_users ) <= $max_admins ) { 2355 2357 return; 2356 2358 } 2357 2359 2358 // Keep track of how many admins we've processed 2360 2359 2361 $admin_count = 0; 2360 2362 2361 // Process each admin user 2363 2362 2364 foreach ( $admin_users as $admin_user ) { 2363 2365 $admin_count ++; 2364 2366 2365 // Skip the first max_admins administrators (ordered by ID) 2367 2366 2368 if ( $admin_count <= $max_admins ) { 2367 2369 continue; 2368 2370 } 2369 2371 2370 // Downgrade the user to the fallback role 2372 2371 2373 $admin_user->set_role( $fallback_role ); 2372 2374 2373 // Log the event safely 2375 2374 2376 $this->safe_log_security_event( 2375 2377 'admin_limit_enforced', … … 2415 2417 } 2416 2418 2417 // Check if this is a user create/update request 2419 2418 2420 $route = $request->get_route(); 2419 2421 if ( strpos( $route, '/wp/v2/users' ) === false ) { … … 2421 2423 } 2422 2424 2423 // Check if operation involves setting a role to administrator 2425 2424 2426 $params = $request->get_params(); 2425 2427 if ( ! isset( $params['roles'] ) || ! in_array( 'administrator', (array) $params['roles'] ) ) { … … 2427 2429 } 2428 2430 2429 // Get the settings 2431 2430 2432 $options = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 2431 2433 2432 // Check if the feature is enabled 2434 2433 2435 if ( ! isset( $options['enable_limit_admins'] ) || $options['enable_limit_admins'] !== '1' ) { 2434 2436 return $response; 2435 2437 } 2436 2438 2437 // Get maximum allowed admins 2439 2438 2440 $max_admins = isset( $options['wpironis_max_admins'] ) ? absint( $options['wpironis_max_admins'] ) : 1; 2439 2441 2440 // Get the fallback role 2442 2441 2443 $fallback_role = isset( $options['wpironis_admin_fallback_role'] ) ? $options['wpironis_admin_fallback_role'] : 'editor'; 2442 2444 2443 // Count current admins 2445 2444 2446 $admin_count = $this->wpironis_count_admins(); 2445 2447 2446 // Get the user ID from the request 2448 2447 2449 $user_id = isset( $params['id'] ) ? $params['id'] : null; 2448 2450 2449 // For user creation, the ID will be in the response 2451 2450 2452 if ( ! $user_id && isset( $response->data['id'] ) ) { 2451 2453 $user_id = $response->data['id']; 2452 2454 } 2453 2455 2454 // Get the user to check if they were already an admin 2456 2455 2457 $user = get_user_by( 'id', $user_id ); 2456 2458 $was_admin = $user && in_array( 'administrator', $user->roles ); 2457 2459 2458 // If the user was not already an admin and the limit has been reached 2460 2459 2461 if ( ! $was_admin && $admin_count >= $max_admins ) { 2460 // Update the user to the fallback role 2462 2461 2463 $user = new WP_User( $user_id ); 2462 2464 $user->set_role( $fallback_role ); 2463 2465 2464 // Log the event safely 2466 2465 2467 $this->safe_log_security_event( 2466 2468 'admin_limit_enforced_api', … … 2473 2475 ); 2474 2476 2475 // Update the response to reflect the actual role 2477 2476 2478 if ( isset( $response->data['roles'] ) ) { 2477 2479 $response->data['roles'] = array( $fallback_role ); 2478 2480 } 2479 2481 2480 // Add a message to the response 2482 2481 2483 if ( ! isset( $response->data['iron_security_message'] ) ) { 2482 2484 $response->data['iron_security_message'] = sprintf( … … 2496 2498 */ 2497 2499 public function wpironsec_get_admin_info() { 2498 // Verify nonce 2500 2499 2501 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'iron_security_nonce' ) ) { 2500 2502 wp_send_json_error( 'Invalid security token sent.' ); … … 2502 2504 } 2503 2505 2504 // Check if user is authorized 2506 2505 2507 if ( ! current_user_can( 'manage_options' ) ) { 2506 2508 wp_send_json_error( 'You do not have permission to perform this action.' ); … … 2508 2510 } 2509 2511 2510 // Get administrator users 2512 2511 2513 $admin_users = get_users( array( 2512 2514 'role' => 'administrator', 2513 2515 ) ); 2514 2516 2515 // Prepare the user data to return 2517 2516 2518 $users = array(); 2517 2519 foreach ( $admin_users as $user ) { … … 2524 2526 } 2525 2527 2526 // Count 2528 2527 2529 $count = count( $admin_users ); 2528 2530 2529 // Send the response 2531 2530 2532 wp_send_json_success( array( 2531 2533 'count' => $count, … … 2567 2569 } 2568 2570 2569 // Get the differences to determine what was updated 2571 2570 2572 $changes = array(); 2571 2573 2572 // Check email change 2574 2573 2575 if ( $old_user_data->user_email !== $user->user_email ) { 2574 2576 $changes['email'] = array( … … 2578 2580 } 2579 2581 2580 // Check display name change 2582 2581 2583 if ( $old_user_data->display_name !== $user->display_name ) { 2582 2584 $changes['display_name'] = array( … … 2586 2588 } 2587 2589 2588 // Check URL change 2590 2589 2591 if ( $old_user_data->user_url !== $user->user_url ) { 2590 2592 $changes['url'] = array( … … 2595 2597 2596 2598 if ( empty( $changes ) ) { 2597 // Log general profile update if no specific changes detected 2599 2598 2600 $message = sprintf( 'User "%s" profile was updated', $user->user_login ); 2599 2601 } else { 2600 // Log specific changes 2602 2601 2603 $fields = array_keys( $changes ); 2602 2604 $message = sprintf( 'User "%s" profile was updated. Changed fields: %s', … … 2822 2824 global $wp_version; 2823 2825 2824 // Only log this occasionally to avoid excessive logs 2826 2825 2827 $last_check = get_option( 'wpironis_last_version_check_log', 0 ); 2826 if ( time() - $last_check < 86400 ) { // Once per day max2828 if ( time() - $last_check < 86400 ) { 2827 2829 return; 2828 2830 } … … 2855 2857 global $pagenow; 2856 2858 2857 // Only log certain admin actions to avoid excessive logging 2859 2858 2860 if ( ! is_admin() || ! current_user_can( 'edit_posts' ) ) { 2859 2861 return; … … 2862 2864 $action = isset( $_REQUEST['action'] ) ? sanitize_text_field( $_REQUEST['action'] ) : ''; 2863 2865 2864 // Skip logging for common, routine actions 2866 2865 2867 $skip_actions = array( 'heartbeat', 'wp-refresh-post-lock', 'ajax-tag-search' ); 2866 2868 if ( in_array( $action, $skip_actions ) ) { … … 2868 2870 } 2869 2871 2870 // Log specific high-value actions only 2872 2871 2873 $important_actions = array( 2872 2874 'update' => 'Update', … … 2893 2895 $details = array( 2894 2896 'page' => $pagenow, 2895 'query_args' => $_GET // Including all query parameters for context2897 'query_args' => $_GET 2896 2898 ); 2897 2899 … … 2899 2901 } 2900 2902 2901 // Log access to sensitive admin pages 2903 2902 2904 $sensitive_pages = array( 2903 2905 'options.php' => 'Settings Update', … … 2922 2924 2923 2925 foreach ( $sensitive_pages as $page => $description ) { 2924 // Check if we're on this page or if page with query matches 2926 2925 2927 if ( $pagenow === $page || ( strpos( $page, '?' ) !== false && strpos( $_SERVER['REQUEST_URI'], 2926 2928 $page ) !== false ) ) { … … 2992 2994 */ 2993 2995 public function detect_frame_embedding() { 2994 // Only run this occasionally to avoid performance issues 2996 2995 2997 if ( ! is_admin() || mt_rand( 1, 10 ) !== 1 ) { 2996 2998 return; … … 3002 3004 $home_url = home_url(); 3003 3005 3004 // Check if the referer is from a different domain 3006 3005 3007 if ( strpos( $referer, $site_url ) !== 0 && strpos( $referer, $home_url ) !== 0 ) { 3006 // This could potentially be a clickjacking attempt 3008 3007 3009 $referer_host = parse_url( $referer, PHP_URL_HOST ); 3008 3010 $site_host = parse_url( $site_url, PHP_URL_HOST ); … … 3020 3022 */ 3021 3023 public function detect_suspicious_requests() { 3022 // Only run this occasionally to avoid performance issues 3024 3023 3025 if ( mt_rand( 1, 10 ) !== 1 ) { 3024 3026 return; 3025 3027 } 3026 3028 3027 // Detect common attack patterns in URLs 3029 3028 3030 $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : ''; 3029 3031 $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : ''; 3030 3032 3031 // List of suspicious URL patterns with attack type and whether it's a regex 3033 3032 3034 $suspicious_patterns = array( 3033 3035 'eval\(' => array( 'type' => 'PHP Injection', 'is_regex' => true ), … … 3077 3079 } 3078 3080 3079 // Check for known malicious or scanning user agents 3081 3080 3082 $malicious_agents = array( 3081 3083 'nikto' => 'Security Scanner', … … 3102 3104 sprintf( 'Detected %s user agent: %s', $agent_type, $agent ) 3103 3105 ); 3104 break; // Only log once per request3106 break; 3105 3107 } 3106 3108 } … … 3111 3113 */ 3112 3114 public function monitor_request_methods() { 3113 // Only monitor in admin area or for login/register pages 3115 3114 3116 if ( ! is_admin() && ! in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ) ) ) { 3115 3117 return; … … 3118 3120 $method = isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : ''; 3119 3121 3120 // Unusual request methods for WordPress admin 3122 3121 3123 $unusual_methods = array( 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH' ); 3122 3124 … … 3156 3158 */ 3157 3159 public function setup_rest_logging() { 3158 // Add a filter to log API requests 3160 3159 3161 add_filter( 'rest_pre_dispatch', array( $this, 'log_rest_api_request' ), 10, 3 ); 3160 3162 } … … 3172 3174 $route = $request->get_route(); 3173 3175 3174 // Skip some common, noisy routes 3176 3175 3177 $skip_routes = array( 3176 3178 '/wp/v2/users/me', … … 3182 3184 } 3183 3185 3184 // Sensitive routes that should always be logged 3186 3185 3187 $sensitive_routes = array( 3186 3188 '/wp/v2/users', … … 3201 3203 } 3202 3204 3203 // Always log writes (POST, PUT, DELETE) or sensitive routes 3205 3204 3206 $method = $request->get_method(); 3205 3207 if ( $is_sensitive || in_array( $method, array( 'POST', 'PUT', 'DELETE', 'PATCH' ) ) ) { … … 3243 3245 */ 3244 3246 public function log_http_api_errors( $response, $context, $class, $parsed_args, $url ) { 3245 // Only log errors 3247 3246 3248 if ( ! is_wp_error( $response ) ) { 3247 3249 return; … … 3251 3253 $error_message = $response->get_error_message(); 3252 3254 3253 // Don't log common timeout errors which may be normal 3255 3254 3256 if ( strpos( $error_code, 'timeout' ) !== false ) { 3255 3257 return; … … 3263 3265 */ 3264 3266 public function detect_security_scanners() { 3265 // Only run this check occasionally to avoid performance impact 3267 3266 3268 if ( mt_rand( 1, 20 ) !== 1 ) { 3267 3269 return; 3268 3270 } 3269 3271 3270 // Check for rapid-fire requests from the same IP 3272 3271 3273 $ip = $this->get_ip_address(); 3272 3274 3273 // Get the last time we recorded this IP 3275 3274 3276 $last_checked = get_transient( 'wpironis_ip_check_' . md5( $ip ) ); 3275 3277 $now = time(); 3276 3278 3277 3279 if ( $last_checked ) { 3278 // If this IP made a request very recently (within 1 second) 3280 3279 3281 if ( $now - $last_checked < 1 ) { 3280 // Increment the counter for this IP 3282 3281 3283 $count = get_transient( 'wpironis_ip_count_' . md5( $ip ) ); 3282 3284 $count = $count ? $count + 1 : 1; 3283 set_transient( 'wpironis_ip_count_' . md5( $ip ), $count, 300 ); // Keep count for 5 minutes3284 3285 // If we've seen too many rapid requests, log it as a scanner 3285 set_transient( 'wpironis_ip_count_' . md5( $ip ), $count, 300 ); 3286 3287 3286 3288 if ( $count > 10 ) { 3287 3289 Iron_Security_Logger::log_suspicious_behavior( … … 3290 3292 ); 3291 3293 3292 // Reset counter to avoid logging repeatedly 3294 3293 3295 set_transient( 'wpironis_ip_count_' . md5( $ip ), 0, 300 ); 3294 3296 } … … 3296 3298 } 3297 3299 3298 // Update the last checked time for this IP 3299 set_transient( 'wpironis_ip_check_' . md5( $ip ), $now, 300 ); // Keep for 5 minutes3300 3301 set_transient( 'wpironis_ip_check_' . md5( $ip ), $now, 300 ); 3300 3302 } 3301 3303 … … 3305 3307 */ 3306 3308 public function wpironis_handle_admin_id_protection_toggle() { 3307 // Verify nonce 3309 3308 3310 if ( ! check_ajax_referer( 'iron_security_nonce', 'nonce', false ) ) { 3309 3311 wp_send_json_error( 'Invalid security token. Please refresh the page and try again.' ); … … 3320 3322 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 3321 3323 3322 // Get current settings 3324 3323 3325 $loginlogout_settings = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 3324 3326 3325 // Update the settings 3327 3326 3328 $loginlogout_settings['enable_admin_id_protection'] = $enabled ? '1' : '0'; 3327 3329 3328 $admin_id = '1'; // Default admin ID3329 3330 // If enabling the feature 3330 $admin_id = '1'; 3331 3332 3331 3333 if ( $enabled ) { 3332 // Check if we already have a stored admin ID in settings 3334 3333 3335 if ( ! empty( $loginlogout_settings['current_admin_id'] ) ) { 3334 3336 $admin_id = $loginlogout_settings['current_admin_id']; 3335 3337 $message = 'Admin ID protection enabled. Using current administrator ID: ' . $admin_id; 3336 3338 3337 // Log this security event 3339 3338 3340 $this->safe_log_security_event( 3339 3341 'settings_change', … … 3342 3344 'success' 3343 3345 ); 3344 } // Check if admin with ID 1 still exists3346 } 3345 3347 elseif ( ! get_userdata( 1 ) ) { 3346 // Admin ID 1 doesn't exist, try to find the first admin user 3348 3347 3349 $admins = get_users( array( 'role' => 'administrator', 'number' => 1 ) ); 3348 3350 if ( ! empty( $admins ) ) { … … 3351 3353 $message = 'Admin ID protection enabled. Using existing administrator ID: ' . $admin_id; 3352 3354 3353 // Log this security event 3355 3354 3356 $this->safe_log_security_event( 3355 3357 'settings_change', … … 3359 3361 ); 3360 3362 } else { 3361 // No admin users found - this is unlikely but handle it 3363 3362 3364 $loginlogout_settings['enable_admin_id_protection'] = '0'; 3363 3365 wp_send_json_error( 'No administrator accounts found.' ); … … 3365 3367 return; 3366 3368 } 3367 } // If admin ID 1 exists and needs to be changed3369 } 3368 3370 else { 3369 3371 try { … … 3380 3382 $message = 'Admin ID protection enabled. Administrator ID changed from 1 to ' . $admin_id; 3381 3383 } else { 3382 // If we couldn't change the ID, disable the feature 3384 3383 3385 $loginlogout_settings['enable_admin_id_protection'] = '0'; 3384 3386 wp_send_json_error( 'Failed to change admin ID. Please try again.' ); … … 3394 3396 } 3395 3397 } else { 3396 // When disabling, just update the setting but don't change the ID back 3397 // since that would be complex and potentially disruptive 3398 3399 3398 3400 $admin_id = ! empty( $loginlogout_settings['current_admin_id'] ) ? $loginlogout_settings['current_admin_id'] : '1'; 3399 3401 $message = 'Admin ID protection disabled. Note that the admin ID will remain changed.'; 3400 3402 3401 // Log this security event 3403 3402 3404 $this->safe_log_security_event( 3403 3405 'settings_change', … … 3408 3410 } 3409 3411 3410 // Save updated settings 3412 3411 3413 update_option( 'wpironis_plugin_settings_loginlogout', $loginlogout_settings ); 3412 3414 3413 // Return success with the admin ID 3415 3414 3416 wp_send_json_success( array( 3415 3417 'message' => $message, … … 3426 3428 global $wpdb; 3427 3429 3428 // First check if user ID 1 exists 3430 3429 3431 $user = get_userdata( 1 ); 3430 3432 if ( ! $user ) { … … 3434 3436 } 3435 3437 3436 // Check if user is an administrator 3438 3437 3439 if ( ! in_array( 'administrator', $user->roles ) ) { 3438 3440 error_log( 'Iron Security: Cannot change admin ID - User ID 1 is not an administrator' ); … … 3441 3443 } 3442 3444 3443 // Start a transaction 3445 3444 3446 $wpdb->query( 'START TRANSACTION' ); 3445 3447 3446 3448 try { 3447 // Get the highest user ID in the database 3449 3448 3450 $highest_id = $wpdb->get_var( "SELECT MAX(ID) FROM {$wpdb->users}" ); 3449 3451 3450 // New ID will be highest + a random offset between 100-999 3452 3451 3453 $new_id = (int) $highest_id + 1; 3452 3454 3453 // Update user ID in users table 3455 3454 3456 $result = $wpdb->query( $wpdb->prepare( 3455 3457 "UPDATE {$wpdb->users} SET ID = %d WHERE ID = 1", … … 3461 3463 } 3462 3464 3463 // Update user ID in usermeta table 3465 3464 3466 $result = $wpdb->query( $wpdb->prepare( 3465 3467 "UPDATE {$wpdb->usermeta} SET user_id = %d WHERE user_id = 1", … … 3471 3473 } 3472 3474 3473 // Update user ID in posts table (for post author) 3475 3474 3476 $result = $wpdb->query( $wpdb->prepare( 3475 3477 "UPDATE {$wpdb->posts} SET post_author = %d WHERE post_author = 1", … … 3481 3483 } 3482 3484 3483 // Update user ID in comments table 3485 3484 3486 $result = $wpdb->query( $wpdb->prepare( 3485 3487 "UPDATE {$wpdb->comments} SET user_id = %d WHERE user_id = 1", … … 3491 3493 } 3492 3494 3493 // Additional tables to check for user_id references 3494 // WooCommerce orders if present 3495 3496 3495 3497 if ( $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}woocommerce_orders'" ) ) { 3496 3498 $wpdb->query( $wpdb->prepare( … … 3500 3502 } 3501 3503 3502 // If using BuddyPress 3504 3503 3505 if ( $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}bp_activity'" ) ) { 3504 3506 $wpdb->query( $wpdb->prepare( … … 3508 3510 } 3509 3511 3510 // If we're here, everything succeeded 3512 3511 3513 $wpdb->query( 'COMMIT' ); 3512 3514 3513 // Clear caches 3515 3514 3516 wp_cache_delete( 1, 'users' ); 3515 3517 wp_cache_delete( 1, 'user_meta' ); … … 3517 3519 wp_cache_delete( $new_id, 'user_meta' ); 3518 3520 3519 // Return the new ID 3521 3520 3522 return (string) $new_id; 3521 3523 } catch ( Exception $e ) { 3522 // Something went wrong, rollback 3524 3523 3525 $wpdb->query( 'ROLLBACK' ); 3524 3526 3525 // Log the error 3527 3526 3528 error_log( 'Iron Security: Failed to change admin ID: ' . $e->getMessage() ); 3527 3529 3528 // Rethrow the exception 3530 3529 3531 throw $e; 3530 3532 } … … 3578 3580 } 3579 3581 3580 // Correctly interpret "true" or "false" string from JS 3582 3581 3583 $enabled = isset( $_POST['enabled'] ) && $_POST['enabled'] === 'true'; 3582 3584 3583 // Save proper value to DB 3585 3584 3586 $general_settings = get_option( 'wpironis_options', array() ); 3585 3587 $general_settings['wpironis_block_ai_bots'] = $enabled ? 1 : 0; 3586 3588 update_option( 'wpironis_options', $general_settings ); 3587 3589 3588 // Modify .htaccess 3590 3589 3591 $this->general_security->modifyHtaccessForAiBots( $enabled ); 3590 3592 3591 // Prepare response message 3593 3592 3594 $message = $enabled 3593 3595 ? __( 'AI bot blocking has been enabled. Your site is now protected from AI bot crawlers.', … … 3616 3618 } 3617 3619 3618 // Fallback to glob if manifest doesn't exist or entry not found 3620 3619 3621 $dist_path = plugin_dir_path( __FILE__ ) . 'js/dist/'; 3620 3622 $files = glob( $dist_path . $base_name . '.*.js' ); … … 3626 3628 } 3627 3629 3628 // Final fallback to non-hashed filename 3630 3629 3631 return $base_name . '.bundle.js'; 3630 3632 } 3631 3633 3634 /** 3635 * Handle toggle for changing admin username 3636 */ 3637 public function wpironis_handle_change_admin_username_toggle() { 3638 3639 if ( ! check_ajax_referer( 'iron_security_nonce', 'nonce', false ) ) { 3640 wp_send_json_error( 'Invalid security token. Please refresh the page and try again.' ); 3641 return; 3642 } 3643 3644 if ( ! current_user_can( 'manage_options' ) ) { 3645 wp_send_json_error( 'You do not have permission to change this setting.' ); 3646 return; 3647 } 3648 3649 $enabled = isset( $_POST['enabled'] ) ? filter_var( $_POST['enabled'], FILTER_VALIDATE_BOOLEAN ) : false; 3650 3651 3652 $loginlogout_settings = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 3653 3654 3655 $loginlogout_settings['enable_change_admin_username'] = $enabled ? '1' : '0'; 3656 3657 3658 if ( $enabled && empty( $loginlogout_settings['wpironis_new_admin_username'] ) ) { 3659 $loginlogout_settings['wpironis_new_admin_username'] = $this->wpironis_generate_random_username(); 3660 } 3661 3662 3663 $updated = update_option( 'wpironis_plugin_settings_loginlogout', $loginlogout_settings ); 3664 3665 3666 $admin_usernames = $this->wpironis_get_admin_usernames(); 3667 3668 if ( $updated ) { 3669 $message = 'Admin username protection setting updated successfully'; 3670 $status = 'success'; 3671 3672 if ( $enabled && !empty( $admin_usernames ) ) { 3673 $message = 'Admin username protection enabled. Insecure usernames detected: ' . implode(', ', $admin_usernames); 3674 $status = 'warning'; 3675 } 3676 3677 wp_send_json_success( array( 3678 'message' => $message, 3679 'status' => $status, 3680 'admin_usernames' => $admin_usernames 3681 ) ); 3682 } else { 3683 wp_send_json_error( 'Failed to update admin username protection setting' ); 3684 } 3685 } 3686 3687 /** 3688 * Handle saving new admin username 3689 */ 3690 public function wpironis_handle_change_admin_username_save() { 3691 3692 if ( ! check_ajax_referer( 'iron_security_nonce', 'nonce', false ) ) { 3693 wp_send_json_error( 'Invalid security token. Please refresh the page and try again.' ); 3694 return; 3695 } 3696 3697 if ( ! current_user_can( 'manage_options' ) ) { 3698 wp_send_json_error( 'You do not have permission to change admin username.' ); 3699 return; 3700 } 3701 3702 3703 $new_username = isset( $_POST['new_username'] ) ? sanitize_user( $_POST['new_username'] ) : ''; 3704 3705 if ( empty( $new_username ) ) { 3706 wp_send_json_error( 'Username cannot be empty.' ); 3707 return; 3708 } 3709 3710 3711 if ( !validate_username( $new_username ) ) { 3712 wp_send_json_error( 'Invalid username. Please use only letters.' ); 3713 return; 3714 } 3715 3716 3717 if ( username_exists( $new_username ) ) { 3718 wp_send_json_error( 'Username already exists. Please choose another username.' ); 3719 return; 3720 } 3721 3722 3723 $loginlogout_settings = get_option( 'wpironis_plugin_settings_loginlogout', array() ); 3724 3725 3726 $loginlogout_settings['wpironis_new_admin_username'] = $new_username; 3727 update_option( 'wpironis_plugin_settings_loginlogout', $loginlogout_settings ); 3728 3729 3730 $admin_usernames = $this->wpironis_get_admin_usernames(); 3731 3732 3733 $changed_users = array(); 3734 3735 if ( !empty( $admin_usernames ) ) { 3736 foreach ( $admin_usernames as $username ) { 3737 $user = get_user_by( 'login', $username ); 3738 if ( $user ) { 3739 3740 $unique_username = $this->wpironis_get_unique_username( $new_username ); 3741 3742 3743 global $wpdb; 3744 $wpdb->update( 3745 $wpdb->users, 3746 array( 'user_login' => $unique_username ), 3747 array( 'ID' => $user->ID ) 3748 ); 3749 3750 3751 $this->safe_log_security_event( 'username_change', sprintf( 3752 'Changed username from %s to %s for user ID %d', 3753 $username, 3754 $unique_username, 3755 $user->ID 3756 )); 3757 3758 3759 clean_user_cache( $user->ID ); 3760 3761 $changed_users[] = array( 3762 'old_username' => $username, 3763 'new_username' => $unique_username, 3764 'display_name' => $user->display_name 3765 ); 3766 } 3767 } 3768 } 3769 3770 if ( !empty( $changed_users ) ) { 3771 wp_send_json_success( array( 3772 'message' => count( $changed_users ) . ' admin username(s) changed successfully.', 3773 'changed_users' => $changed_users 3774 ) ); 3775 } else { 3776 wp_send_json_success( array( 3777 'message' => 'New admin username saved. No insecure admin usernames found to change.' 3778 ) ); 3779 } 3780 } 3781 3782 /** 3783 * Generate a random username with letters only 3784 */ 3785 private function wpironis_generate_random_username() { 3786 $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 3787 $username = ''; 3788 $length = rand(8, 12); 3789 3790 for ( $i = 0; $i < $length; $i++ ) { 3791 $username .= $chars[rand(0, strlen($chars) - 1)]; 3792 } 3793 3794 3795 return $this->wpironis_get_unique_username( $username ); 3796 } 3797 3798 /** 3799 * Get a unique username by adding a number suffix if needed 3800 */ 3801 private function wpironis_get_unique_username( $username ) { 3802 $original_username = $username; 3803 $suffix = 1; 3804 3805 while ( username_exists( $username ) ) { 3806 $username = $original_username . $suffix; 3807 $suffix++; 3808 } 3809 3810 return $username; 3811 } 3812 3813 /** 3814 * Get usernames of admin users with common/default usernames 3815 */ 3816 private function wpironis_get_admin_usernames() { 3817 $insecure_usernames = array( 'admin', 'administrator', 'wordpress' ); 3818 $found_usernames = array(); 3819 3820 foreach ( $insecure_usernames as $username ) { 3821 $user = get_user_by( 'login', $username ); 3822 if ( $user && in_array( 'administrator', $user->roles ) ) { 3823 $found_usernames[] = $username; 3824 } 3825 } 3826 3827 return $found_usernames; 3828 } 3829 3632 3830 } -
iron-security/trunk/admin/js/components/Dashboard.jsx
r3288628 r3300103 135 135 ); 136 136 137 console.log(settings)138 137 139 138 if (isLoading || !settings) { -
iron-security/trunk/admin/js/components/FileDirectoryProectionSettings.jsx
r3288628 r3300103 12 12 13 13 useEffect(() => { 14 // Update settings when they change 14 15 15 setIsPhpUploadBlocked(settings.wpironis_options?.wpironis_block_php_uploads === 1); 16 16 setIsDirectAccessPrevented(settings.wpironis_options?.wpironis_prevent_direct_access === 1); -
iron-security/trunk/admin/js/components/GeneralSettings.jsx
r3288628 r3300103 15 15 16 16 useEffect(() => { 17 // Update all state values when settings change 17 18 18 setIsXmlrpcEnabled(settings.wpironis_options?.wpironis_disable_xmlrpc === 1); 19 19 setIsWpVersionHidden(settings.wpironis_options?.wpironis_hide_wp_version === 1); … … 57 57 58 58 const data = await response.json(); 59 console.log('Response:', data); // Debug log60 59 61 60 if (data.success) { -
iron-security/trunk/admin/js/components/HttpSecurityHeadersSettings.jsx
r3288628 r3300103 19 19 20 20 useEffect(() => { 21 // Update header settings when settings change 21 22 22 setHeaderSettings({ 23 23 contentTypeOptions: settings.wpironis_options?.wpironis_security_header_content_type_options === 1, -
iron-security/trunk/admin/js/components/LoginLogoutSettings.jsx
r3288628 r3300103 72 72 73 73 const [isLoadingAdminInfo, setIsLoadingAdminInfo] = useState(false); 74 75 const [isChangeAdminUsernameEnabled, setIsChangeAdminUsernameEnabled] = useState( 76 loginLogoutSettings.enable_change_admin_username === '1' 77 ); 78 79 const [newAdminUsername, setNewAdminUsername] = useState( 80 loginLogoutSettings.wpironis_new_admin_username || '' 81 ); 82 83 const [adminUsernameStatus, setAdminUsernameStatus] = useState(null); 74 84 75 85 const getAdminCountStatus = () => { … … 103 113 useEffect(() => { 104 114 const loginLogoutSettings = settings?.login_logout || {}; 105 // Update all state values when settings change 115 106 116 setIsCustomUrlEnabled(loginLogoutSettings.enable_slug_change === '1'); 107 117 setCustomUrl(loginLogoutSettings.wpironis_custom_login_slug || 'wp-admin'); … … 129 139 setIsAdminIdProtectionEnabled(true); 130 140 } 141 142 if (loginLogoutSettings.enable_change_admin_username === '1') { 143 setIsChangeAdminUsernameEnabled(true); 144 } 145 146 if (loginLogoutSettings.wpironis_new_admin_username) { 147 setNewAdminUsername(loginLogoutSettings.wpironis_new_admin_username); 148 } 131 149 }, [settings]); 132 150 … … 221 239 console.error('Error:', error); 222 240 showNotification(error.message || 'Failed to update custom URL setting', 'error'); 223 setIsCustomUrlEnabled(!enabled); // Revert the toggle241 setIsCustomUrlEnabled(!enabled); 224 242 } finally { 225 243 setIsSaving(false); … … 282 300 console.error('Error:', error); 283 301 showNotification(error.message || 'Failed to update session timeout setting', 'error'); 284 setIsSessionTimeoutEnabled(!enabled); // Revert the toggle302 setIsSessionTimeoutEnabled(!enabled); 285 303 } finally { 286 304 setIsSaving(false); … … 546 564 setIsSaving(false); 547 565 } 566 }; 567 568 const handleChangeAdminUsernameToggle = async (enabled) => { 569 setIsSaving(true); 570 try { 571 const formData = new FormData(); 572 formData.append('action', 'iron_security_toggle_change_admin_username'); 573 formData.append('enabled', enabled); 574 formData.append('nonce', settings.nonce); 575 576 const response = await fetch(ajaxurl, { 577 method: 'POST', 578 body: formData, 579 credentials: 'same-origin' 580 }); 581 582 const data = await response.json(); 583 584 if (data.success) { 585 setIsChangeAdminUsernameEnabled(enabled); 586 587 if (enabled && data.data.admin_usernames && data.data.admin_usernames.length > 0) { 588 setAdminUsernameStatus({ 589 message: `Insecure admin usernames detected: ${data.data.admin_usernames.join(', ')}. Change them to improve security.`, 590 type: 'warning' 591 }); 592 593 594 if (!newAdminUsername) { 595 generateRandomUsername(); 596 } 597 } else if (enabled) { 598 setAdminUsernameStatus({ 599 message: 'No insecure admin usernames detected.', 600 type: 'info' 601 }); 602 } else { 603 setAdminUsernameStatus(null); 604 } 605 606 showNotification(data.data.message || 'Change admin username setting updated successfully'); 607 } else { 608 throw new Error(data.data || 'Failed to update setting'); 609 } 610 } catch (error) { 611 console.error('Error:', error); 612 showNotification(error.message || 'Failed to update change admin username setting', 'error'); 613 setIsChangeAdminUsernameEnabled(!enabled); 614 } finally { 615 setIsSaving(false); 616 } 617 }; 618 619 const handleSaveAdminUsername = async () => { 620 setIsSaving(true); 621 try { 622 const formData = new FormData(); 623 formData.append('action', 'iron_security_save_change_admin_username'); 624 formData.append('new_username', newAdminUsername); 625 formData.append('nonce', settings.nonce); 626 627 const response = await fetch(ajaxurl, { 628 method: 'POST', 629 body: formData, 630 credentials: 'same-origin' 631 }); 632 633 const data = await response.json(); 634 635 if (data.success) { 636 if (data.data.changed_users && data.data.changed_users.length > 0) { 637 const changedUsers = data.data.changed_users; 638 let message = 'The following admin usernames were changed:\n'; 639 640 changedUsers.forEach(user => { 641 message += `• ${user.old_username} → ${user.new_username}`; 642 if (user.display_name) { 643 message += ` (${user.display_name})`; 644 } 645 message += '\n'; 646 }); 647 648 setAdminUsernameStatus({ 649 message: 'Admin usernames successfully changed.', 650 type: 'info' 651 }); 652 653 showNotification(data.data.message || 'Admin usernames changed successfully'); 654 setTimeout(() => { 655 alert(message); 656 }, 500); 657 } else { 658 setAdminUsernameStatus({ 659 message: 'New admin username saved. No insecure admin usernames found to change.', 660 type: 'info' 661 }); 662 showNotification(data.data.message || 'New admin username saved successfully'); 663 } 664 } else { 665 throw new Error(data.data || 'Failed to save new admin username'); 666 } 667 } catch (error) { 668 console.error('Error:', error); 669 showNotification(error.message || 'Failed to save new admin username', 'error'); 670 } finally { 671 setIsSaving(false); 672 } 673 }; 674 675 const generateRandomUsername = () => { 676 const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 677 let result = ''; 678 const length = 8 + Math.floor(Math.random() * 8); 679 for (let i = 0; i < length; i++) { 680 result += chars.charAt(Math.floor(Math.random() * chars.length)); 681 } 682 setNewAdminUsername(result); 548 683 }; 549 684 … … 831 966 </div> 832 967 )} 968 969 <SettingToggle 970 label="Change Default Admin Username" 971 description="Change admin username if you have accounts with usernames like 'admin' or 'administrator' to prevent targeted attacks." 972 checked={isChangeAdminUsernameEnabled} 973 onChange={handleChangeAdminUsernameToggle} 974 disabled={isSaving} 975 /> 976 977 {isChangeAdminUsernameEnabled && ( 978 <div className="wpironis-custom-url-input"> 979 <div className="admin-username-info"> 980 <p className="description"> 981 <strong>Default admin usernames like 'admin' or 'administrator' are frequently targeted in brute force attacks.</strong> 982 Change them to enhance your site's security. 983 </p> 984 {adminUsernameStatus && ( 985 <p className={`description ${adminUsernameStatus.type}`}> 986 <span className={`dashicons dashicons-${adminUsernameStatus.type === 'warning' ? 'warning' : 'info'}`}></span> 987 {adminUsernameStatus.message} 988 </p> 989 )} 990 <div className="wpironis-input-group"> 991 <label htmlFor="new-admin-username">New Username:</label> 992 <input 993 type="text" 994 id="new-admin-username" 995 value={newAdminUsername} 996 onChange={(e) => setNewAdminUsername(e.target.value.replace(/[^a-zA-Z]/g, ''))} 997 placeholder="Enter new username (letters only)" 998 disabled={isSaving} 999 /> 1000 <button 1001 className="button button-secondary" 1002 onClick={generateRandomUsername} 1003 disabled={isSaving} 1004 style={{ marginLeft: '5px' }} 1005 > 1006 Generate Random 1007 </button> 1008 <button 1009 className="button button-primary" 1010 onClick={handleSaveAdminUsername} 1011 disabled={isSaving || !newAdminUsername} 1012 style={{ marginLeft: '5px' }} 1013 > 1014 {isSaving ? 'Saving...' : 'Save Username'} 1015 </button> 1016 </div> 1017 </div> 1018 </div> 1019 )} 833 1020 </div> 834 1021 </div> -
iron-security/trunk/admin/js/dist/manifest.json
r3296251 r3300103 1 1 { 2 "dashboard.js": "dashboard. d4938437720f6eface82.js",2 "dashboard.js": "dashboard.409171eb914877d3afe7.js", 3 3 "vendors.js": "vendors.91782840b9e9337a9a5f.js" 4 4 } -
iron-security/trunk/includes/class-iron-security.php
r3291538 r3300103 84 84 $this->loader->add_action( 'admin_menu', $plugin_admin, 'wpironis_plugin_menu' ); 85 85 86 // Login Logout 86 87 87 $this->loader->add_action( 'init', $plugin_admin, 'wpironis_redirect_login' ); 88 88 $this->loader->add_action( 'admin_init', $plugin_admin, 'wpironis_plugin_settings_init' ); … … 116 116 $this->loader->add_filter( 'wp_handle_upload_prefilter', $plugin_admin, 'wpironis_prevent_php_upload' ); 117 117 118 // AJAX handlers 118 119 119 $this->loader->add_action( 'wp_ajax_iron_security_toggle_xmlrpc', 120 120 $plugin_admin, … … 149 149 'wpironis_handle_core_autoupdate_toggle' ); 150 150 151 // Custom admin URL AJAX handlers 151 152 152 $this->loader->add_action( 'wp_ajax_iron_security_toggle_custom_url', 153 153 $plugin_admin, … … 157 157 'wpironis_handle_custom_url_save' ); 158 158 159 // Session timeout AJAX handlers 159 160 160 $this->loader->add_action( 'wp_ajax_iron_security_toggle_session_timeout', 161 161 $plugin_admin, … … 165 165 'wpironis_handle_session_timeout_save' ); 166 166 167 // Limit login attempts AJAX handlers 167 168 168 $this->loader->add_action( 'wp_ajax_iron_security_toggle_limit_login', 169 169 $plugin_admin, … … 173 173 'wpironis_handle_limit_login_save' ); 174 174 175 // Limit admins AJAX handlers 175 176 176 $this->loader->add_action( 'wp_ajax_iron_security_toggle_limit_admins', 177 177 $plugin_admin, … … 181 181 'wpironis_handle_limit_admins_save' ); 182 182 183 // Admin ID protection AJAX handler 183 184 184 $this->loader->add_action( 'wp_ajax_iron_security_toggle_admin_id_protection', 185 185 $plugin_admin, 186 186 'wpironis_handle_admin_id_protection_toggle' ); 187 187 188 // Admin role limitation hook 188 $this->loader->add_action( 'wp_ajax_iron_security_toggle_change_admin_username', 189 $plugin_admin, 190 'wpironis_handle_change_admin_username_toggle' ); 191 $this->loader->add_action( 'wp_ajax_iron_security_save_change_admin_username', 192 $plugin_admin, 193 'wpironis_handle_change_admin_username_save' ); 194 189 195 $this->loader->add_action( 'set_user_role', 190 196 $plugin_admin, … … 192 198 10, 193 199 3 ); 194 // Hook into user creation/update for admin limit 200 195 201 $this->loader->add_action( 'user_register', 196 202 $plugin_admin, … … 199 205 1 ); 200 206 201 // REST API filter for admin limit202 207 $this->loader->add_filter( 'rest_request_after_callbacks', 203 208 $plugin_admin, … … 216 221 'wpironis_handle_user_enum_message_save' ); 217 222 218 // Also add filters for auto-updates on plugin initialization 223 219 224 $options = get_option( 'wpironis_options', array() ); 220 225 221 // Configure plugin auto-updates based on saved setting 226 222 227 if ( ! empty( $options['wpironis_enable_plugin_autoupdate'] ) && $options['wpironis_enable_plugin_autoupdate'] === 1 ) { 223 228 add_filter( 'auto_update_plugin', '__return_true' ); … … 226 231 } 227 232 228 // Configure core auto-updates based on saved setting 233 229 234 if ( ! empty( $options['wpironis_enable_core_autoupdate'] ) && $options['wpironis_enable_core_autoupdate'] === 1 ) { 230 235 add_filter( 'allow_major_auto_core_updates', '__return_true' ); … … 302 307 $this->loader->add_action( 'init', $plugin_public, 'wpironis_init' ); 303 308 304 // Add REST API restriction hooks 309 305 310 $this->loader->add_filter( 'rest_authentication_errors', $plugin_public, 'wpironis_restrict_rest_api' ); 306 311 $this->loader->add_filter( 'rest_endpoints', $plugin_public, 'wpironis_disable_rest_endpoints' ); -
iron-security/trunk/iron-security.php
r3296251 r3300103 17 17 * Plugin URI: https://wpiron.com 18 18 * Description: Iron Security is a powerful WordPress security plugin to protect your site from common threats. Lock down your site with login protection, file security, and HTTP headers — all in one lightweight plugin. 19 * Version: 2.3. 219 * Version: 2.3.3 20 20 * Author: wpiron 21 21 * Author URI: https://wpiron.com/ … … 31 31 } 32 32 33 define( 'IRON_SECURITY_VERSION', '2.3. 2' );33 define( 'IRON_SECURITY_VERSION', '2.3.3' ); 34 34 35 35 function wpiisec_activate_iron_security() {
Note: See TracChangeset
for help on using the changeset viewer.