license_is_active() || $this->get_option( 'patchstack_license_free', 0 ) == 1 ) { return; } // Disallowed modification of the theme files? if ( ! defined( 'DISALLOW_FILE_EDIT' ) && $this->get_option( 'patchstack_pluginedit', true ) ) { define( 'DISALLOW_FILE_EDIT', 1 ); } // Set security headers add_filter( 'wp_headers', [ $this, 'set_security_headers' ], 10, 1 ); // When country blocking is set. if ( $this->get_option( 'patchstack_geo_block_enabled', false ) && ! empty( $this->get_option( 'patchstack_geo_block_countries', [] ) ) ) { add_action( 'init', array( $this, 'geo_block_check' ), ~PHP_INT_MAX ); } // Apply comment captcha? if ( $this->get_option( 'patchstack_captcha_on_comments', 0 ) && ! is_user_logged_in() ) { add_action( 'comment_form_after_fields', [ $this, 'captcha_display' ] ); add_filter( 'preprocess_comment', [ $this, 'verify_recaptcha' ] ); } // Disable the application passwords feature? if ( $this->get_option( 'patchstack_application_passwords_disabled', false ) == true ) { add_filter( 'wp_is_application_passwords_available', '__return_false' ); } // Block unauthorized XML-RPC requests? if ( $this->get_option( 'patchstack_xmlrpc_is_disabled', false ) == true ) { add_filter( 'xmlrpc_enabled', '__return_false' ); } // Block unauthorized wp-json requests? if ( $this->get_option( 'patchstack_json_is_disabled', false ) ) { add_filter( 'rest_authentication_errors', [ $this, 'disable_wpjson' ] ); } // Prevent user enumeration? if ( $this->get_option( 'patchstack_userenum' ) ) { add_action( 'init', [ $this, 'stop_user_enum' ], 1 ); } // Attempt to hide the WordPress version? if ( $this->get_option( 'patchstack_hidewpversion' ) ) { remove_action( 'wp_head', 'wp_generator' ); add_filter( 'the_generator', [ $this, 'remove_generator' ] ); } // Auto update software? $update = get_site_option( 'patchstack_auto_update', [] ); if ( is_array( $update ) ) { foreach ( $update as $type ) { if ( $type != 'vulnerable' ) { add_filter( 'auto_update_' . $type, '__return_true' ); } } } } /** * Perform updates if the software upload call returns vulnerabilities. * This is only executed when auto updates are enabled for vulnerable plugins. * * @param array $plugins * @return void */ public function update_vulnerable_plugins() { // Is the auto update setting for vulnerable plugins enabled? $update = get_site_option( 'patchstack_auto_update', [] ); if ( ! is_array( $update ) || ! in_array( 'vulnerable', $update ) ) { return; } // Do we even have any vulnerable plugins to auto update? $plugins = get_site_option( 'patchstack_vulnerable_plugins', [] ); if ( ! is_array( $plugins ) || count( $plugins ) == 0 ) { return; } // Might not be necessary, but should prevent any hanging issues. @set_time_limit( 180 ); // Require some files we need to execute the upgrade. @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; if ( file_exists( ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php' ) ) { @include_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php'; } @include_once ABSPATH . 'wp-admin/includes/plugin.php'; @include_once ABSPATH . 'wp-admin/includes/misc.php'; @include_once ABSPATH . 'wp-admin/includes/file.php'; @wp_update_plugins(); $all_plugins = get_plugins(); // New array with all available plugins and the ones we want to upgrade. $upgrade = []; foreach ( $all_plugins as $path => $data ) { if ( in_array( $path, $plugins ) ) { array_push( $upgrade, $path ); } } // Upgrade the plugins. $upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() ); $upgrader->bulk_upgrade( $upgrade ); // Reset the option that holds the vulnerable plugins. update_site_option( 'patchstack_vulnerable_plugins', [] ); // Resend the sofware data to the API. do_action( 'patchstack_send_software_data' ); } /** * Determine the country of the user and if we should block the user. * * @return void */ public function geo_block_check() { $countries = $this->get_option( 'patchstack_geo_block_countries', [] ); $ip = $this->get_ip(); // Don't block Patchstack. if ( ( isset( $_POST['patchstack_secret'] ) && $this->plugin->listener->verifyToken( $_POST['patchstack_secret'] ) ) || isset( $_POST['patchstack_ott_action'] )) { // OTT action. if ( isset( $_POST['patchstack_ott_action'] ) ) { $ott = get_option( 'patchstack_ott_action', '' ); if ( ! empty( $ott ) && hash_equals( $ott, $_POST['patchstack_ott_action'] ) ) { return; } } else { return; } } // Load the required libraries. try { require_once __DIR__ . '/../lib/geoip2-php/autoload.php'; $reader = new GeoIp2\Database\Reader( __DIR__ . '/../lib/GeoLite2-Country.mmdb' ); $record = $reader->country( $ip ); // Determine if we want to do an inverse check or not. $match = in_array( $record->country->isoCode, $countries ); $match = $this->get_option( 'patchstack_geo_block_inverse', false ) ? ! $match : $match; // Check if there's a match. if ( $match ) { $this->plugin->firewall_base->display_error_page( 23 ); } } catch ( \Exception $e ) { } } /** * Prevent unauthorized users from accessing wp-json. * * @return void|WP_Error */ public function disable_wpjson() { // Some default exceptions. $path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ); $whitelists = [ '/wp-json/contact-form-7/' ]; foreach ( $whitelists as $whitelist ) { if ( stripos( $path, $whitelist ) !== false ) { return; } } // Block unauthorized users. if ( ! is_user_logged_in() ) { $msg = apply_filters( 'disable_wp_rest_api_error', esc_attr__( 'The WP REST API cannot be accessed by unauthorized users.', 'disable-wp-rest-api' ) ); return new WP_Error( 'rest_authorization_required', $msg, [ 'status' => rest_authorization_required_code() ] ); } } /** * Set security headers if the option is enabled. * * @param array $headers * @return void|array */ public function set_security_headers( $headers ) { if ( get_option( 'patchstack_add_security_headers' ) ) { $headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'; $headers['X-Frame-Options'] = 'SAMEORIGIN'; $headers['X-XSS-Protection'] = '1; mode=block'; $headers['X-Content-Type-Options'] = 'nosniff'; $headers['X-Powered-By'] = null; $headers['Server'] = null; $headers['Strict-Transport-Security'] = 'max-age=31536000'; } return $headers; } /** * Determine if the reCAPTCHA is valid upon comment submission. * * @param array $comment_data * @return void|array */ public function verify_recaptcha( $comment_data ) { $result = $this->captcha_check(); if ( ! $result['response'] && ( $result['reason'] === 'VERIFICATION_FAILED' || $result['reason'] === 'RECAPTCHA_EMPTY_RESPONSE' ) ) { wp_clear_auth_cookie(); wp_die( 'reCaptcha was not solved or response was empty', 'Error' ); } return $comment_data; } /** * Add the captcha to the comments form. * * @return void */ public function captcha_display() { switch ( $this->get_option( 'patchstack_captcha_type' ) ) { case 'v2': $site_key = trim( $this->get_option( 'patchstack_captcha_public_key' ) ); require dirname( __FILE__ ) . '/views/captcha_v2.php'; break; case 'invisible': $site_key = trim( $this->get_option( 'patchstack_captcha_public_key_v3' ) ); require dirname( __FILE__ ) . '/views/captcha_invisible.php'; break; case 'v3': $site_key = trim( $this->get_option( 'patchstack_captcha_public_key_v3_new' ) ); require dirname( __FILE__ ) . '/views/captcha_v3.php'; break; case 'turnstile': $site_key = trim( $this->get_option( 'patchstack_captcha_public_key_turnstile' ) ); require dirname( __FILE__ ) . '/views/captcha_turnstile.php'; break; } } /** * Check if the submitted reCAPTCHA is valid. * * @return array */ public function captcha_check() { switch ( $this->get_option( 'patchstack_captcha_type' ) ) { case 'v2': $secret_key = trim( $this->get_option( 'patchstack_captcha_private_key' ) ); $site_key = trim( $this->get_option( 'patchstack_captcha_public_key' ) ); break; case 'invisible': $secret_key = trim( $this->get_option( 'patchstack_captcha_private_key_v3' ) ); $site_key = trim( $this->get_option( 'patchstack_captcha_public_key_v3' ) ); break; case 'v3': $secret_key = trim( $this->get_option( 'patchstack_captcha_private_key_v3_new' ) ); $site_key = trim( $this->get_option( 'patchstack_captcha_public_key_v3_new' ) ); break; case 'turnstile': $secret_key = trim( $this->get_option( 'patchstack_captcha_private_key_turnstile' ) ); $site_key = trim( $this->get_option( 'patchstack_captcha_public_key_turnstile' ) ); break; } if ( ! $secret_key || ! $site_key ) { return [ 'response' => false, 'reason' => 'ERROR_NO_KEYS', ]; } if ( ! isset( $_POST['g-recaptcha-response'] ) || empty( $_POST['g-recaptcha-response'] ) ) { return [ 'response' => false, 'reason' => 'RECAPTCHA_EMPTY_RESPONSE', ]; } $response = $this->get_captcha_response( $secret_key, $this->get_option( 'patchstack_captcha_type' ) ); if ( isset( $response['success'] ) && ! empty( $response['success'] ) ) { return [ 'response' => true, 'reason' => '', ]; } return [ 'response' => false, 'reason' => 'VERIFICATION_FAILED', ]; } /** * Query Google for reAPTCHA validation and response. * * @param string $privatekey * @param string $type * @return array */ public function get_captcha_response( $privatekey, $type ) { $args = [ 'body' => [ 'secret' => $privatekey, 'response' => $_POST['g-recaptcha-response'], ], 'sslverify' => false, ]; if ($type != 'turnstile') { $resp = wp_remote_post( 'https://www.google.com/recaptcha/api/siteverify', $args ); } else { $resp = wp_remote_post( 'https://challenges.cloudflare.com/turnstile/v0/siteverify', $args ); } return json_decode( wp_remote_retrieve_body( $resp ), true ); } /** * Disable user enumeration with ?author= and the REST endpoint. * * @return void */ public function stop_user_enum() { if ( isset( $_GET['author'] ) && ! is_user_logged_in() && ! is_admin() ) { die( wp_safe_redirect( get_site_url() ) ); } if ( stripos( $_SERVER['REQUEST_URI'], 'v2/users' ) !== false || ( isset( $_REQUEST['rest_route'] ) && stripos( $_REQUEST['rest_route'], 'v2/users' ) !== false ) ) { if ( ! is_user_logged_in() ) { die( wp_safe_redirect( get_site_url() ) ); } } } /** * Hide the WordPress generator version in response. * * @return string */ public function remove_generator() { return ''; } }