Changeset 3334658
- Timestamp:
- 07/26/2025 10:47:22 PM (7 months ago)
- Location:
- cloudaware-security-audit/trunk
- Files:
-
- 2 edited
-
cloudaware-security-audit.php (modified) (18 diffs)
-
readme.txt (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
cloudaware-security-audit/trunk/cloudaware-security-audit.php
r3331590 r3334658 4 4 Plugin URI: https://www.cloudaware.eu 5 5 Description: Plugin to monitor and audit security aspects of your Wordpress installation 6 Version: 1.0. 86 Version: 1.0.9 7 7 Author: Jeroen Hermans 8 8 License: GPLv2 … … 11 11 12 12 defined( 'ABSPATH' ) || die( 'No script kiddies please!' ); 13 define("REQUESTHEADERS", array( 14 'timeout' => 10, 15 'User-Agent' => 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0' 16 )); 13 17 14 18 function cloudseca_make_data() { … … 17 21 //This include is needed to get all updates for plugins in the same way as: 18 22 //https://wordpress.org/plugins/wpvulnerability/ 19 if ( ! function_exists( 'get_plugin_updates' ) ) {20 require_once ABSPATH . 'wp-admin/includes/update.php';21 }23 if ( ! function_exists( 'get_plugin_updates' ) ) { 24 require_once ABSPATH . 'wp-admin/includes/update.php'; 25 } 22 26 $plugin_updates = get_plugin_updates(); 23 27 $theme_updates = get_theme_updates(); … … 26 30 //This include is needed to get all plugins installed on the system in the same way as: 27 31 //https://wordpress.org/plugins/wpvulnerability/ 28 if ( ! function_exists( 'get_plugins' ) ) {29 require_once ABSPATH . 'wp-admin/includes/plugin.php';30 }31 $plugins = get_plugins();32 $themes = wp_get_themes();32 if ( ! function_exists( 'get_plugins' ) ) { 33 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 34 } 35 $plugins = get_plugins(); 36 $themes = wp_get_themes(); 33 37 34 38 //Global … … 47 51 //Optionally include wpvulnerability in order to get data about vulnerabilities 48 52 $wpvulnerabilities = array(); 49 if ( ! function_exists( 'wpvulnerability_plugin_get_vulnerabilities' ) ) {53 if ( ! function_exists( 'wpvulnerability_plugin_get_vulnerabilities' ) ) { 50 54 if (defined('WPVULNERABILITY_PLUGIN_PATH')) { 51 $file_path = WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-plugins.php';52 if ( file_exists($file_path) ) {53 require_once $file_path;55 $file_path = WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-plugins.php'; 56 if ( file_exists($file_path) ) { 57 require_once $file_path; 54 58 } 55 59 } 56 60 $wpvulnerabilities = wpvulnerability_plugin_get_vulnerabilities(); 57 }61 } 58 62 59 63 $data = array('global_autoupdates' => array('themes' => $global_theme_autoupdate, 'plugins' => $global_plugin_autoupdate), … … 63 67 'url' => get_option( 'siteurl' ), 64 68 'time' => time(), 65 'config' => array() 69 'config' => cloudseca_get_config($plugins), 70 'themehashes' => hashFoldersInDirectory(ABSPATH, 'wp-content/themes'), 71 'pluginhashes' => hashFoldersInDirectory(ABSPATH, 'wp-content/plugins') 66 72 ); 67 73 … … 93 99 //This include is needed to get information about installed plugins on the system in the same way as: 94 100 //https://wordpress.org/plugins/wpvulnerability/ 95 if ( ! function_exists( 'plugins_api' ) ) {96 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';97 }101 if ( ! function_exists( 'plugins_api' ) ) { 102 require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; 103 } 98 104 $call_api = plugins_api( 'plugin_information', $args ); 99 105 $plugindata['Active_installs'] = $call_api->active_installs; … … 105 111 if($name == 'revslider/revslider.php') { 106 112 $url = 'https://www.sliderrevolution.com/documentation/changelog/'; 107 $res = wp_remote_get($url );113 $res = wp_remote_get($url, REQUESTHEADERS); 108 114 $html = wp_remote_retrieve_body($res); 109 115 $dom = new DOMDocument; … … 114 120 preg_match_all($re, $nodevalue, $matches, PREG_SET_ORDER, 0); 115 121 116 $plugindata[' Version_latest'] = $matches[0][1];117 $plugindata[' Version_latest_date'] = date_parse($matches[0][2]);118 $plugindata[' Version_latest_date'] = $plugindata['Version_latest_date']['year'].'-'.$plugindata['Version_latest_date']['month'].'-'.$plugindata['Version_latest_date']['day'];122 $plugindata['version_latest'] = $matches[0][1]; 123 $plugindata['version_latest_date'] = date_parse($matches[0][2]); 124 $plugindata['version_latest_date'] = $plugindata['Version_latest_date']['year'].'-'.$plugindata['Version_latest_date']['month'].'-'.$plugindata['Version_latest_date']['day']; 119 125 } else { 120 126 if( array_key_exists($name, $plugin_updates) ) { 121 $plugindata[' Version_latest'] = $plugin_updates[$name]->update->new_version;127 $plugindata['version_latest'] = $plugin_updates[$name]->update->new_version; 122 128 } else { 123 $plugindata[' Version_latest'] = $plugindata['Version'];129 $plugindata['version_latest'] = $plugindata['Version']; 124 130 } 125 131 } … … 128 134 $active_theme = wp_get_theme()->get_stylesheet(); 129 135 foreach($themes as $name => &$themedata) { 130 if( in_array($name, $auto_update_themes) ) { 131 $data['themes'][$name]['Autoupdate'] = true; 132 } else { 133 $data['themes'][$name]['Autoupdate'] = false; 134 } 135 136 if($active_theme == $name) { 137 $data['themes'][$name]['Active'] = true; 138 } else { 139 $data['themes'][$name]['Active'] = false; 140 } 136 $data['themes'][$name]['autoupdate'] = in_array($name, $auto_update_themes); 137 $data['themes'][$name]['active'] = ($active_theme == $name); 141 138 142 139 $themedetails = wp_get_theme($name); 143 140 $data['themes'][$name]['Update'] = $themedata->update; 144 141 $data['themes'][$name]['Name'] = $themedetails->get('Name'); 145 $data['themes'][$name][' Version'] = $themedetails->get('Version');142 $data['themes'][$name]['version'] = $themedetails->get('version'); 146 143 147 144 if( array_key_exists($name, $theme_updates) ) { 148 $data['themes'][$name][' Version_latest'] = $theme_updates[$name]->update['new_version'];145 $data['themes'][$name]['version_latest'] = $theme_updates[$name]->update['new_version']; 149 146 } else { 150 $data['themes'][$name][' Version_latest'] = $data['themes'][$name]['Version'];147 $data['themes'][$name]['version_latest'] = $data['themes'][$name]['version']; 151 148 } 152 149 } … … 162 159 163 160 $url = 'https://api.github.com/repos/ImageMagick/ImageMagick/releases'; 164 $res = wp_remote_get($url, array( 165 'timeout' => 10, 166 'User-Agent' => 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0' 167 )); 161 $res = wp_remote_get($url, REQUESTHEADERS); 168 162 $json = wp_remote_retrieve_body($res); 169 163 … … 171 165 $latest_version = $releases[0]['name']; 172 166 173 $data['imagemagick'][' Version'] = $current_version;174 $data['imagemagick'][' Version_latest'] = $latest_version;167 $data['imagemagick']['version'] = $current_version; 168 $data['imagemagick']['version_latest'] = $latest_version; 175 169 } catch(Exception $e) {} 176 170 … … 178 172 $data['curl'] = array(); 179 173 } 180 $data['curl'][' Version'] = curl_version()['version'];174 $data['curl']['version'] = curl_version()['version']; 181 175 182 176 $url = 'https://api.github.com/repos/curl/curl/releases'; 183 $res = wp_remote_get($url, array( 184 'timeout' => 10, 185 'User-Agent' => 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0' 186 )); 177 $res = wp_remote_get($url, REQUESTHEADERS); 187 178 $json = wp_remote_retrieve_body($res); 188 179 189 180 $obj = json_decode($json, true); 190 $data['curl']['Version_latest'] = $obj[0]['name']; 191 192 193 #2FA 194 $data['config']['2fa_enabled'] = false; 195 if ( 196 array_key_exists('wordfence/wordfence.php', $data['plugins']) && 197 function_exists('is_plugin_active') && 198 is_plugin_active('wordfence/wordfence.php') 199 ) { #Wordfence is installed 200 // Define the roles to check 201 $target_roles = ['administrator', 'contributor', 'editor']; 202 203 // Get all defined roles 204 if (!function_exists('get_editable_roles')) { 205 // needed for get_editable_roles() 206 require_once ABSPATH . 'wp-admin/includes/user.php'; 207 } 208 $all_roles = get_editable_roles(); 209 210 // Filter roles that exist 211 $existing_roles = array_filter($target_roles, function($role) use ($all_roles) { 212 return array_key_exists($role, $all_roles); 213 }); 214 215 if (!empty($existing_roles)) { 216 // Prepare setting keys for those roles 217 $setting_keys = array_map(function($role) { 218 return "required-2fa-role.$role"; 219 }, $existing_roles); 220 221 // Try to get cached results 222 $cache_key = 'wordfence_2fa_roles_settings'; 223 $settings = wp_cache_get($cache_key, 'wordfence'); 224 if ($settings === false) { 225 $placeholders = implode(', ', array_fill(0, count($setting_keys), '%s')); 226 $table_name = $wpdb->prefix . 'wfls_settings'; 227 228 // Fetch settings in a single query 229 $query = "SELECT name, value FROM $table_name WHERE name IN ($placeholders)"; 230 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery 231 $results = $wpdb->get_results($wpdb->prepare($query, ...$setting_keys), OBJECT_K); 232 // Cache the results 233 $settings = []; 234 foreach ($results as $row) { 235 $settings[$row->name] = $row->value; 236 } 237 238 wp_cache_set($cache_key, $settings, 'wordfence', 300); // Cache for 5 minutes 239 } 240 241 $all_roles_have_2fa = true; 242 foreach ($existing_roles as $role) { 243 $key = "required-2fa-role.$role"; 244 if (!isset($settings[$key]) || intval($settings[$key]) <= 0) { 245 $all_roles_have_2fa = false; 246 break; 247 } 248 } 249 250 if ($all_roles_have_2fa) { 251 $data['config']['2fa_enabled'] = true; 252 } 253 } 254 } 255 256 #Configuration 257 if ( username_exists( 'admin' ) ) { 258 $data['config']['admin_user_found'] = true; 259 } else { 260 $data['config']['admin_user_found'] = false; 261 } 262 if (defined('DISALLOW_FILE_EDIT')) { 263 $data['config']['disallow_file_edit'] = true; 264 } else { 265 $data['config']['disallow_file_edit'] = false; 266 } 267 if (defined('WP_DEBUG') && WP_DEBUG) { 268 $data['config']['debug'] = true; 269 } else { 270 $data['config']['debug'] = false; 271 } 272 if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) { 273 $data['config']['debug_log'] = true; 274 } else { 275 $data['config']['debug_log'] = false; 276 } 277 if (defined('WP_DEBUG_DISPLAY') && WP_DEBUG_DISPLAY) { 278 $data['config']['debug_display'] = true; 279 } else { 280 $data['config']['debug_display'] = false; 281 } 282 if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) { 283 $data['config']['script_debug'] = true; 284 } else { 285 $data['config']['script_debug'] = false; 286 } 287 288 289 if (defined('WP_HOME') && strpos(WP_HOME, 'https://') === 0) { 290 $data['config']['home_https'] = true; 291 } else { 292 $data['config']['home_https'] = false; 293 } 294 if (defined('WP_SITEURL') && strpos(WP_SITEURL, 'https://') === 0) { 295 $data['config']['siteurl_https'] = true; 296 } else { 297 $data['config']['siteurl_https'] = false; 298 } 299 if (defined('FORCE_SSL_ADMIN') && strpos(FORCE_SSL_ADMIN, 'https://') === 0) { 300 $data['config']['force_ssl_admin'] = true; 301 } else { 302 $data['config']['force_ssl_admin'] = false; 303 } 304 305 if (defined('AUTOSAVE_INTERVAL')) { 306 $data['config']['autosave_interval'] = AUTOSAVE_INTERVAL; 307 } else { 308 $data['config']['autosave_interval'] = null; 309 } 310 if (defined('WP_POST_REVISIONS')) { 311 $data['config']['post_revisions'] = WP_POST_REVISIONS; 312 } else { 313 $data['config']['post_revisions'] = null; 314 } 315 if (defined('EMPTY_TRASH_DAYS')) { 316 $data['config']['empty_trash_days'] = EMPTY_TRASH_DAYS; 317 } else { 318 $data['config']['empty_trash_days'] = null; 319 } 320 if (defined('WP_MEMORY_LIMIT')) { 321 $data['config']['memory_limit'] = WP_MEMORY_LIMIT; 322 } else { 323 $data['config']['memory_limit'] = null; 324 } 325 326 327 $url = rtrim(get_option( 'siteurl' ), "/"); 328 $url .= '/xmlrpc.php'; 329 $res = wp_remote_get($url, array( 330 'timeout' => 10, 331 'User-Agent' => 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0' 332 )); 333 if (wp_remote_retrieve_response_code($res) == 200 ) { 334 $data['config']['xmlrpc_enabled'] = true; 335 } else { 336 $data['config']['xmlrpc_enabled'] = false; 337 } 338 $data['config']['table_prefix'] = $wpdb->prefix; 181 $data['curl']['version_latest'] = $obj[0]['name']; 339 182 340 183 return $data; 341 184 } 342 185 343 // see wp-admin/update-core.php186 //rest api endpoint function 344 187 function cloudseca_security_status (WP_REST_Request $request) { 345 #if ( ! function_exists( 'core_auto_updates_settings' ) ) {346 # require_once ABSPATH . 'wp-admin/update-core.php';347 #}348 349 #if(function_exists('needed_function')){350 # needed_function();351 #}352 353 #$wp_version = wp_get_wp_version();354 #$cur_wp_version = preg_replace( '/-.*$/', '', $wp_version );355 //$data = array('name'=>$request->get_param( 'naam' ));356 188 $data = cloudseca_make_data(); 357 189 return new WP_REST_Response( $data ); … … 378 210 379 211 function cloudseca_menu() { 380 add_options_page( 'CloudAware', 'CloudAware Security', 'manage_options', 'cloudseca-admin-menu', 'cloudseca_options' );212 add_options_page( 'CloudAware', 'CloudAware Security', 'manage_options', 'cloudseca-admin-menu', 'cloudseca_options' ); 381 213 } 382 214 383 215 function cloudseca_options() { 384 if ( !current_user_can( 'manage_options' ) ) {385 wp_die( 'You do not have sufficient permissions to access this page.' );386 }216 if ( !current_user_can( 'manage_options' ) ) { 217 wp_die( 'You do not have sufficient permissions to access this page.' ); 218 } 387 219 echo "<h2>".esc_html("Cloudaware Security Settings")."</h2>\n"; 388 220 echo "<form action=\"".esc_url("options.php")."\" method=\"post\">\n"; 389 221 settings_fields( 'cloudseca_plugin_options' ); 390 222 do_settings_sections( 'cloudseca_plugin' ); 391 echo "<input name=\"submit\" class=\"button button-primary\" type=\"submit\" value=\"". esc_attr( 'Save' ). "\" />\n"; 223 #echo "<input name=\"submit\" class=\"button button-primary\" type=\"submit\" value=\"". esc_attr( 'Save' ). "\" />\n"; 224 submit_button('Save Settings'); 392 225 echo "</form>\n"; 226 227 echo "<button id=\"cloudseca_activate_btn\" class=\"button button-secondary\">Create role and user</button>\n"; 228 echo "<div id=\"cloudseca_modal\" style=\"display:none;\">\n"; 229 echo " <p><span class=\"dashicons dashicons-warning\" style=\"color: #d63638; font-size: 18px; vertical-align: middle; margin-right: 6px;\"></span>\n"; 230 echo " A new user <strong>cloudaware</strong> will be created with minimal access (role <code>cloudseca_api</code>).<br>\n"; 231 echo " If a cloudaware.eu callback url has been defined, a secure application password will be generated and sent to CloudAware’s secure callback URL for monitoring. If the callback url is not in the cloudaware.eu domain, it will be shown to you once and not send anywhere else.</p>\n"; 232 echo " <button id=\"cloudseca_confirm_btn\" class=\"button button-primary\" style=\"background-color: #28a745; border-color: #28a745;\">Confirm</button>\n"; 233 echo " <button id=\"cloudseca_cancel_btn\" class=\"button\" style=\"background-color: #dc3545; border-color: #dc3545; color: white;\">Cancel</button>\n"; 234 echo "</div>\n"; 235 echo "<div id=\"cloudseca_response\"></div>\n"; 236 237 echo "<script>\n"; 238 echo "document.addEventListener('DOMContentLoaded', function () {\n"; 239 echo " const activateBtn = document.getElementById('cloudseca_activate_btn');\n"; 240 echo " const modal = document.getElementById('cloudseca_modal');\n"; 241 echo " const confirmBtn = document.getElementById('cloudseca_confirm_btn');\n"; 242 echo " const cancelBtn = document.getElementById('cloudseca_cancel_btn');\n"; 243 echo " const response = document.getElementById('cloudseca_response');\n\n"; 244 245 echo " activateBtn.addEventListener('click', function(e) {\n"; 246 echo " e.preventDefault();\n"; 247 echo " modal.style.display = 'block';\n"; 248 echo " });\n\n"; 249 250 echo " cancelBtn.addEventListener('click', function() {\n"; 251 echo " modal.style.display = 'none';\n"; 252 echo " });\n\n"; 253 254 echo " confirmBtn.addEventListener('click', function() {\n"; 255 echo " modal.style.display = 'none';\n"; 256 echo " response.innerHTML = 'Activating...';\n"; 257 echo " fetch(ajaxurl, {\n"; 258 echo " method: 'POST',\n"; 259 echo " headers: {'Content-Type': 'application/x-www-form-urlencoded'},\n"; 260 echo " body: 'action=cloudseca_activate_api_user&_wpnonce=".esc_js(wp_create_nonce("cloudseca_nonce")) ."'\n"; 261 echo " })\n"; 262 echo " .then(res => res.json())\n"; 263 echo " .then(data => {\n"; 264 echo " response.innerHTML = data.success ? '<strong>Success:</strong> ' + data.data.message : '<strong>Error:</strong> ' + data.data.message;\n"; 265 echo " })\n"; 266 echo " .catch(err => {\n"; 267 echo " response.innerHTML = '<strong>Error:</strong> Could not connect.';\n"; 268 echo " });\n"; 269 echo " });\n"; 270 echo "});\n"; 271 echo "</script>\n"; 393 272 } 394 273 395 274 function cloudseca_register_settings() { 275 // phpcs:ignore PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic 396 276 register_setting( 'cloudseca_plugin_options', //settings group name 397 277 'cloudseca_plugin_options', //name of option … … 414 294 } 415 295 add_action( 'admin_init', 'cloudseca_register_settings' ); 416 296 add_action('wp_ajax_cloudseca_activate_api_user', 'cloudseca_handle_api_user_creation'); 417 297 418 298 function cloudseca_plugin_options_validate( $input ) { 419 #var_dump($input);420 299 $newinput['callback_url'] = trim( $input['callback_url'] ); 421 300 #if ( ! preg_match( '/^[a-z0-9]{32}$/i', $newinput['api_key'] ) ) { … … 438 317 //get_option('dbi_example_plugin_options')[api_key] 439 318 440 441 319 function cloudseca_handle_api_user_creation() { 320 check_ajax_referer('cloudseca_nonce'); 321 322 $role_name = 'cloudseca_api'; 323 $role_label = 'Cloudseca API'; 324 $username = 'cloudaware'; 325 $email = 'wordpresssecurity@cloudaware.eu'; 326 // The exact permissions this role should have 327 $desired_perms = [ 328 'activate_plugins' => true, 329 'list_users' => true, 330 'read' => true, 331 'switch_themes' => true, 332 'view_site_health_checks' => true, 333 ]; 334 335 // 1. Create role if needed 336 $roles = wp_roles(); 337 if (!$roles->is_role($role_name)) { 338 // Role does not exist — create it 339 add_role($role_name, $role_label, $desired_perms); 340 } else { 341 // Role exists — ensure it has only the desired capabilities 342 $role = get_role($role_name); 343 if ($role) { 344 // First, remove all existing caps 345 foreach ($role->capabilities as $cap => $value) { 346 $role->remove_cap($cap); 347 } 348 349 // Then, add the desired capabilities 350 foreach ($desired_perms as $perm => $value) { 351 $role->add_cap($perm, $value); 352 } 353 } 354 } 355 356 // 2. Create user if needed 357 $user_id = username_exists($username); 358 if (!$user_id && !email_exists($email)) { 359 $password = wp_generate_password(24, true); 360 $user_id = wp_create_user($username, $password, $email); 361 if (is_wp_error($user_id)) { 362 wp_send_json_error(['message' => 'Failed to create user.']); 363 } 364 $user = get_user_by('id', $user_id); 365 $user->set_role($role_name); 366 } else { 367 // User exists — check and update role if necessary 368 $user = get_user_by('id', $user_id); 369 if ($user && $user->role !== $role_name) { 370 $user->set_role($role_name); 371 } 372 } 373 374 if (!$user_id) { 375 wp_send_json_error(['message' => 'User exists but could not retrieve ID.']); 376 } 377 378 // 3. Create application password 379 if (!class_exists('WP_Application_Passwords')) { 380 require_once ABSPATH . 'wp-includes/class-wp-application-passwords.php'; 381 } 382 383 $app_exists = WP_Application_Passwords::application_name_exists_for_user($user_id, 'cloudaware'); 384 if (!$app_exists) { 385 $app_pass = WP_Application_Passwords::create_new_application_password($user_id, ['name' => 'cloudaware']); 386 if (is_wp_error($app_pass)) { 387 wp_send_json_error(['message' => 'Failed to create application password.']); 388 } 389 390 // 4. Send app pass to callback URL 391 $options = get_option('cloudseca_plugin_options'); 392 #$callback = get_option('cloudseca_plugin_options')['callback_url']; 393 $callback = isset($options['callback_url']) ? trim($options['callback_url']) : ''; 394 #if (!$callback || !filter_var($callback, FILTER_VALIDATE_URL)) { 395 # wp_send_json_error(['message' => 'Invalid or missing callback URL.']); 396 #} 397 398 if ($callback && stripos($callback, 'cloudaware.eu') !== false && filter_var($callback, FILTER_VALIDATE_URL)) { 399 $send = wp_remote_post($callback, [ 400 'headers' => ['Content-Type' => 'application/json; charset=utf-8'], 401 'body' => json_encode([ 402 'app_pass' => $app_pass[0], 403 'url' => get_option('siteurl'), 404 ]), 405 'method' => 'POST', 406 'data_format' => 'body', 407 'timeout' => 10, 408 ]); 409 410 $code = wp_remote_retrieve_response_code($send); 411 if ($code >= 200 && $code < 300) { 412 wp_send_json_success(['message' => 'API user created and app password sent to CloudAware.']); 413 } else { 414 wp_send_json_error(['message' => 'App password created but failed to notify CloudAware.']); 415 } 416 } else { 417 // Show password to user 418 wp_send_json_success([ 419 'message' => 'API user created. Please copy the application password now — it will not be shown again: <code>'.$app_pass[0].'</code>' 420 ]); } 421 } else { 422 wp_send_json_success(['message' => 'Application password already exists.']); 423 } 424 } 425 426 function cloudseca_get_config($plugins){ 427 global $wpdb; 428 $config = array(); 429 430 #2FA 431 $config['2fa_enabled'] = false; 432 if ( 433 array_key_exists('wordfence/wordfence.php', $plugins) && 434 function_exists('is_plugin_active') && 435 is_plugin_active('wordfence/wordfence.php') 436 ) { #Wordfence is installed 437 // Define the roles to check 438 $target_roles = ['administrator', 'contributor', 'editor']; 439 440 // Get all defined roles 441 if (!function_exists('get_editable_roles')) { 442 // needed for get_editable_roles() 443 require_once ABSPATH . 'wp-admin/includes/user.php'; 444 } 445 $all_roles = get_editable_roles(); 446 447 // Filter roles that exist 448 $existing_roles = array_filter($target_roles, function($role) use ($all_roles) { 449 return array_key_exists($role, $all_roles); 450 }); 451 452 if (!empty($existing_roles)) { 453 // Prepare setting keys for those roles 454 $setting_keys = array_map(function($role) { 455 return "required-2fa-role.$role"; 456 }, $existing_roles); 457 458 // Try to get cached results 459 $cache_key = 'wordfence_2fa_roles_settings'; 460 $settings = wp_cache_get($cache_key, 'wordfence'); 461 if ($settings === false) { 462 $placeholders = implode(', ', array_fill(0, count($setting_keys), '%s')); 463 $table_name = $wpdb->prefix . 'wfls_settings'; 464 465 // Fetch settings in a single query 466 $query = "SELECT name, value FROM esc_sql($table_name) WHERE name IN ($placeholders)"; 467 // This IS a prepared statement using splat notation 468 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery 469 $results = $wpdb->get_results($wpdb->prepare($query, ...$setting_keys), OBJECT_K); 470 // Cache the results 471 $settings = []; 472 foreach ($results as $row) { 473 $settings[$row->name] = $row->value; 474 } 475 476 wp_cache_set($cache_key, $settings, 'wordfence', 300); // Cache for 5 minutes 477 } 478 479 $all_roles_have_2fa = true; 480 foreach ($existing_roles as $role) { 481 $key = "required-2fa-role.$role"; 482 if (!isset($settings[$key]) || intval($settings[$key]) <= 0) { 483 $all_roles_have_2fa = false; 484 break; 485 } 486 } 487 488 if ($all_roles_have_2fa) { 489 $config['2fa_enabled'] = true; 490 } 491 } 492 } 493 494 #Configuration 495 $config['admin_user_found'] = username_exists( 'admin' ); 496 $config['disallow_file_edit'] = defined('DISALLOW_FILE_EDIT'); 497 $config['debug'] = (defined('WP_DEBUG') && WP_DEBUG); 498 $config['debug_log'] = (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG); 499 $config['debug_display'] = defined('WP_DEBUG_DISPLAY') && WP_DEBUG_DISPLAY; 500 $config['script_debug'] = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG; 501 $config['home_https'] = (defined('WP_HOME') && strpos(WP_HOME, 'https://') === 0) ; 502 $config['siteurl_https'] = (defined('WP_SITEURL') && strpos(WP_SITEURL, 'https://') === 0); 503 $config['force_ssl_admin'] = (defined('FORCE_SSL_ADMIN') && strpos(FORCE_SSL_ADMIN, 'https://') === 0); 504 $config['autosave_interval'] = defined('AUTOSAVE_INTERVAL')?AUTOSAVE_INTERVAL:null; 505 $config['post_revisions'] = defined('WP_POST_REVISIONS')?WP_POST_REVISIONS:null; 506 $config['empty_trash_days'] = defined('EMPTY_TRASH_DAYS')?EMPTY_TRASH_DAYS:null; 507 $config['memory_limit'] = defined('WP_MEMORY_LIMIT')?WP_MEMORY_LIMIT:null; 508 509 $url = rtrim(get_option( 'siteurl' ), "/"); 510 $url .= '/xmlrpc.php'; 511 $res = wp_remote_get($url, REQUESTHEADERS); 512 $config['xmlrpc_enabled'] = (wp_remote_retrieve_response_code($res) == 200); 513 $config['table_prefix'] = $wpdb->prefix; 514 515 return $config; 516 } 517 518 function getFolderHash($folderPath) { 519 $fileHashes = []; 520 521 $iterator = new RecursiveIteratorIterator( 522 new RecursiveDirectoryIterator($folderPath, FilesystemIterator::SKIP_DOTS) 523 ); 524 525 foreach ($iterator as $file) { 526 if ($file->isFile()) { 527 $relativePath = str_replace('\\', '/', substr($file->getPathname(), strlen($folderPath))); 528 $contentHash = md5_file($file->getPathname()); 529 $fileHashes[$relativePath] = $contentHash; 530 } 531 } 532 533 // Sort by path to ensure consistent order 534 ksort($fileHashes); 535 536 // Combine all file hashes into a single string 537 $combined = ''; 538 foreach ($fileHashes as $path => $hash) { 539 $combined .= $path . ':' . $hash . "\n"; 540 } 541 542 // Final folder-level hash 543 return md5($combined); 544 } 545 546 function hashFoldersInDirectory($baseDir, $subPath) { 547 $result = []; 548 $fullPath = rtrim($baseDir, '/') . '/' . trim($subPath, '/'); 549 550 if (!is_dir($fullPath)) { 551 return $result; 552 } 553 554 foreach (scandir($fullPath) as $item) { 555 if ($item === '.' || $item === '..') continue; 556 $itemPath = $fullPath . '/' . $item; 557 if (is_dir($itemPath)) { 558 $relative = $subPath . '/' . $item; 559 $result[$relative] = getFolderHash($itemPath); 560 } 561 } 562 563 return $result; 564 } 442 565 443 566 … … 446 569 ####### https://blazzdev.com/scheduled-tasks-cron-wordpress-plugin-boilerplate/ 447 570 ############################################################################ 448 449 #####Initialise450 register_activation_hook( __FILE__, 'cloudseca_activate_plugin' );451 function cloudseca_activate_plugin() { // runs on plugin activation452 #require_once( ABSPATH . 'wp-admin/includes/user.php' ); //wp_create_user()453 #require_once( ABSPATH . 'wp-includes/pluggable.php' ); //get_user_by()454 #require_once( ABSPATH . 'wp-includes/capabilities.php' ); //add_role()455 #require_once( ABSPATH . 'wp-includes/class-wp-application-passwords.php' );456 457 if ( get_option( 'cloudseca_plugin_options' ) === false ||458 get_option( 'cloudseca_plugin_options' )['callback_url'] === false ||459 get_option( 'cloudseca_plugin_options' )['callback_url'] == ''460 ) {461 #add_option( 'cloudseca_plugin_options', array('callback_url' => 'https://app.cloudaware.eu/callbacks') );462 add_option( 'cloudseca_plugin_options', array('callback_url' => '') );463 }464 if ( ! wp_next_scheduled( 'cloudseca_cron_security_check' ) ) {465 wp_schedule_event( time(), 'daily', 'cloudseca_cron_security_check' ); // cloudseca_cron_security_check is a hook466 }467 468 #if ( ! wp_roles()->is_role( 'cloudseca_api' ) ) {469 # add_role('cloudseca_api', 'cloudseca_api', array(470 # 'activate_plugins' => true,471 # 'list_users' => true,472 # 'read' => true,473 # 'switch_themes' => true,474 # 'view_site_health_checks' => true,475 # ));476 #}477 #$username = 'cloudaware';478 #$email = 'wordpresssecurity@cloudaware.eu';479 #$password = wp_generate_password(32, true);480 481 #$user_id = username_exists( $username );482 #if ( !$user_id && email_exists($email) == false ) {483 # $user_id = wp_create_user( $username, $password, $email );484 # if( !is_wp_error($user_id) ) {485 # $user = get_user_by( 'id', $user_id );486 # $user->set_role( 'cloudseca_api' );487 # }488 #}489 #Future use from configuration page to send an app password to cloudaware as a sort of activation490 #This will always be with consent of the admin491 #$app_exists = WP_Application_Passwords::application_name_exists_for_user( $user_id, 'cloudaware' );492 #if ( ! $app_exists ) {493 # $app_pass = WP_Application_Passwords::create_new_application_password( $user_id, array( 'name' => 'cloudaware' ) );494 # $res = wp_remote_post(get_option('cloudseca_plugin_options')['callback_url'], array(495 # 'headers' => array('Content-Type' => 'application/json; charset=utf-8'),496 # 'body' => json_encode(array('app_pass' => $app_pass, 'url' => get_option( 'siteurl' ))),497 # 'method' => 'POST',498 # 'data_format' => 'body',499 # ));500 #}501 };502 503 571 add_action( 'cloudseca_cron_security_check', 'cloudseca_plugin_cron_daily' ); 504 572 function cloudseca_plugin_cron_daily() { … … 514 582 } 515 583 584 #####Initialise 585 register_activation_hook( __FILE__, 'cloudseca_activate_plugin' ); 586 function cloudseca_activate_plugin() { // runs on plugin activation 587 if ( get_option( 'cloudseca_plugin_options' ) === false || 588 get_option( 'cloudseca_plugin_options' )['callback_url'] === false || 589 get_option( 'cloudseca_plugin_options' )['callback_url'] == '' 590 ) { 591 add_option( 'cloudseca_plugin_options', array('callback_url' => '') ); 592 } 593 }; 594 595 add_action('init', 'cloudseca_init_plugin'); 596 function cloudseca_init_plugin() { 597 if ( ! wp_next_scheduled( 'cloudseca_cron_security_check' ) ) { 598 wp_schedule_event( time(), 'daily', 'cloudseca_cron_security_check' ); // cloudseca_cron_security_check is a hook 599 } 600 } 516 601 517 602 #Deinitialise 518 603 register_deactivation_hook( __FILE__, 'cloudseca_deactivate_plugin' ); 519 604 function cloudseca_deactivate_plugin() { 520 #require_once( ABSPATH . 'wp-admin/includes/user.php' ); //wp_delete_user()521 #require_once( ABSPATH . 'wp-includes/pluggable.php' ); //get_user_by()522 #require_once( ABSPATH . 'wp-includes/capabilities.php' ); //remove_role()523 524 605 $timestamp = wp_next_scheduled( 'cloudseca_cron_security_check' ); 525 606 wp_unschedule_event( $timestamp, 'cloudseca_cron_security_check' ); 526 527 delete_option('cloudseca_plugin_options'); 528 529 #$user = get_user_by( 'login', 'cloudaware' ); 530 #wp_delete_user( $user->ID ); 531 #remove_role( 'cloudseca_api' ); 532 } 607 } 608 609 //Deinstall 610 register_uninstall_hook(__FILE__, 'cloudseca_plugin_uninstall'); 611 function cloudseca_plugin_uninstall() { 612 // Delete user 613 $user = get_user_by('login', 'cloudaware'); 614 if ($user) { 615 wp_delete_user($user->ID); 616 } 617 618 // Remove custom role 619 remove_role('cloudseca_api'); 620 621 // Delete plugin options 622 delete_option('cloudseca_plugin_options'); 623 } -
cloudaware-security-audit/trunk/readme.txt
r3331590 r3334658 1 1 === CloudAware Security Audit === 2 2 3 Contributors: cloudaware 3 Contributors: cloudaware, underdarknl 4 4 Tags: security, audit 5 5 Requires at least: 6.0 6 6 Tested up to: 6.8 7 Stable tag: 1.0. 87 Stable tag: 1.0.9 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 24 24 - see what themes are installed 25 25 - check if 2FA is enabled 26 - see MD5 hashes of all theme and plugin folders 27 26 28 For installations where the RESTAPI is disabled, the plugin can also push this information to an endpoint. 27 29 This will work for installations that are behind a geoblock or have no RESTAPI. To disable this, remove the … … 32 34 Install the plugin via the Wordpress "Plugins" menu in Wordpress and then 33 35 activate using the blue "Activate" button. 36 You can add a new user with restrictive role to your Wordpress installation from within the plugin settings page 37 by clicking on a button. 34 38 35 39 == Frequently Asked Questions == … … 66 70 Specifically no version information is transmitted to external services. 67 71 72 If you fill out an external url in the callback URL field in the settings, a Wordpress cronjob will send a POST request 73 with the audit data to this URL daily. 74 68 75 == Changelog == 76 77 = v1.0.9 = 78 * Code cleanup 79 * Add hashing of theme and plugin folders 80 * Add button to setting to add new user and role to system 81 * Cleaner initialisation, deinitialisation 69 82 70 83 = v1.0.8 =
Note: See TracChangeset
for help on using the changeset viewer.