Файловый менеджер - Редактировать - /home/avadvi5/public_html/fcolliver.com/wp-content/plugins/really-simple-ssl-pro/pro/class-headers.php
Ðазад
<?php defined('ABSPATH') or die(); class rsssl_headers { private static $_this; public $security_headers; public $invalid_headers; public $directives; public function __construct() { if (isset(self::$_this)) wp_die(sprintf(__('%s is a singleton class and you cannot create a second instance.', 'really-simple-ssl'), get_class($this))); self::$_this = $this; $this->security_headers = $this->getSecurityHeaders(); $this->invalid_headers = [ 'x-content-security-policy' ]; /** * Cache plugin integrations, force loading of static cache through php * If we don't do this, the php headers won't run on cached pages */ add_filter( 'rocket_htaccess_mod_rewrite', '__return_false' ); if ( ! defined('WPFC_SERVE_ONLY_VIA_CACHE') ) { define( "WPFC_SERVE_ONLY_VIA_CACHE", true ); } add_action( 'rocket_activation', array( $this, 'update_cache_rewrites_htaccess') ); $fastest_cache = 'wp-fastest-cache/wpFastestCache.php'; add_action( "activate_$fastest_cache", array( $this, 'update_cache_rewrites_htaccess' ) ); $flyingpress = 'flying-press/flying-press.php'; add_action( "activate_$flyingpress", array( $this, 'update_cache_rewrites_htaccess' ) ); add_filter( 'rsssl_firewall_rules', array($this, 'insert_security_headers')); add_action( "rsssl_after_save_field", array($this, 'save_time_on_report_only_start'), 100, 4 ); add_action( "admin_init", array($this, 'store_csp_endpoint'), 100 ); add_action( "update_option_permalink_structure", array($this, 'update_csp_endpoint'), 10, 2 ); add_filter( 'rsssl_notices', array($this,'get_notices_list'),20, 1 ); add_filter( 'rsssl_notices', array( $this, 'add_non_recommended_header_notices'), 25, 1 ); add_filter( 'rsssl_clear_test_caches', array( $this, 'rest_api_clear_cache' ), 20, 3 ); $this->directives = array( 'child-src' => "child-src 'self' {uri}; ", 'connect-src' => "connect-src 'self' {uri}; ", 'font-src' => "font-src 'self' {uri}; ", 'frame-src' => "frame-src 'self' {uri}; ", 'img-src' => "img-src 'self' data: {uri}; ", 'manifest-src' => "manifest-src 'self' {uri}; ", 'media-src' => "media-src 'self' {uri}; ", 'object-src' => "object-src 'self' {uri}; ", 'script-src' => "script-src 'self' 'unsafe-inline' {uri}; ", 'script-src-elem' => "script-src-elem 'self' 'unsafe-inline' {uri}; ", 'script-src-attr' => "script-src-attr 'self' {uri}; ", 'style-src' => "style-src 'self' 'unsafe-inline' {uri}; ", 'style-src-elem' => "style-src-elem 'self' 'unsafe-inline' {uri}; ", 'style-src-attr' => "style-src-attr 'self' {uri}; ", 'worker-src' => "worker-src 'self' {uri}; ", ); add_action( "rsssl_after_save_field", array( $this, 'clear_headers_check_cache'), 110, 4 ); add_action( 'rsssl_after_saved_fields', array( $this, 'reload_headers_check_cache'), 20); add_filter( 'rsssl_fields', array($this,'mark_headers_with_non_recommended_values'), 10, 1); } static function this(): rsssl_headers { return self::$_this; } /** * Get the security headers. * * @internal Recommended headers should contain these key-value pairs to be * compatible with the WP CLI functionality in: class-wp-cli.php * - recommended_setting * - disabled_setting * * @uses apply_filters rsssl_pro_detected_security_headers */ private function getSecurityHeaders(): array { $securityHeaders = [ [ 'name' => 'Upgrade Insecure Requests', 'pattern' => ['header'=>'Content-Security-Policy', 'value' => 'upgrade-insecure-requests'], 'option_name' => 'upgrade_insecure_requests', 'recommended_value' => false, 'is_recommended_header' => true, 'recommended_setting' => '1', 'disabled_setting' => '0', ], [ 'name' => 'Content Security Policy', 'pattern' => ['header'=>'Content-Security-Policy', 'value' => 'src'], 'option_name' => 'content_security_policy', 'recommended_value' => false, 'is_recommended_header' => false, ], [ 'name' => 'Frame Ancestors', 'pattern' => ['header'=>'Content-Security-Policy', 'value' => 'frame-ancestors(?:\s*(?:[\'"]([^\'"]*)[\'"]|\S*))?'], 'option_name' => 'csp_frame_ancestors', 'recommended_value' => false, 'non_recommended_value' => "*", 'is_recommended_header' => true, 'recommended_setting' => 'self', 'disabled_setting' => 'disabled', ], [ 'name' => 'X-XSS protection', 'pattern' => ['header'=>'X-XSS-Protection', 'value' => false], 'option_name' => 'x_xss_protection', 'recommended_value' => '0', 'is_recommended_header' => true, 'recommended_setting' => 'zero', 'disabled_setting' => 'disabled', ], [ 'name' => 'X-Content Type Options', 'pattern' => ['header'=>'X-Content-Type-Options', 'value' => false], 'option_name' => 'x_content_type_options', 'recommended_value' => 'nosniff', 'is_recommended_header' => true, 'recommended_setting' => '1', 'disabled_setting' => '0', ], [ 'name' => 'Referrer-Policy', 'pattern' => ['header'=>'Referrer-Policy', 'value' => false], 'option_name' => 'referrer_policy', 'recommended_value' => array('strict-origin-when-cross-origin', 'no-referrer', 'strict-origin' ), 'is_recommended_header' => true, 'recommended_setting' => 'strict-origin-when-cross-origin', 'disabled_setting' => 'disabled', ], [ 'name' => 'Permissions-Policy', 'pattern' => ['header'=>'Permissions-Policy', 'value' => false], 'option_name' => 'permissions_policy', 'recommended_value' => false, 'is_recommended_header' => false, ], [ 'name' => 'X-Frame-Options', 'pattern' => ['header'=>'X-Frame-Options', 'value' => false], 'option_name' => 'x_frame_options', 'recommended_value' => ['SAMEORIGIN', 'DENY'], 'is_recommended_header' => false, ], [ 'name' => 'HSTS Max-age', 'pattern' => ['header'=>'Strict-Transport-Security', 'value' => 'max-age=(.*?);'], 'option_name' => 'hsts_max_age', 'recommended_value' => array('31536000' , '63072000'), 'is_recommended_header' => false, ], [ 'name' => 'HSTS Preload', 'pattern' => ['header'=>'Strict-Transport-Security', 'value' => 'preload'], 'option_name' => 'hsts_preload', 'recommended_value' => false, 'is_recommended_header' => false, ], [ 'name' => 'HSTS Include Subdomains', 'pattern' => ['header'=>'Strict-Transport-Security', 'value' => 'includeSubDomains'], 'option_name' => 'hsts_subdomains', 'recommended_value' => false, 'is_recommended_header' => false, ], [ 'name' => 'HTTP Strict Transport Security', 'pattern' => ['header'=>'Strict-Transport-Security', 'value' => false], 'option_name' => 'hsts', 'recommended_value' => false, 'is_recommended_header' => true, 'recommended_setting' => '1', 'disabled_setting' => '0', ], [ 'name' => 'Cross-Origin-Opener-Policy', 'pattern' => ['header'=>'Cross-Origin-Opener-Policy', 'value' => false], 'option_name' => 'cross_origin_opener_policy', 'recommended_value' => array('disabled', 'unsafe-none', 'same-origin-allow-popups', 'same-origin-allow-popups'), 'is_recommended_header' => false, ], [ 'name' => 'Cross-Origin-Resource-Policy', 'pattern' => ['header'=>'Cross-Origin-Resource-Policy', 'value' => false], 'option_name' => 'cross_origin_resource_policy', 'recommended_value' => array('disabled', 'same-site', 'same-origin', 'cross-origin'), 'is_recommended_header' => false, ], [ 'name' => 'Cross-Origin-Embedder-Policy', 'pattern' => ['header'=>'Cross-Origin-Embedder-Policy', 'value' => false], 'option_name' => 'cross_origin_embedder_policy', 'recommended_value' => array('disabled' , 'require-corp', 'same-origin', 'unsafe-none'), 'is_recommended_header' => false, ], ]; /** * Filter: rsssl_pro_detected_security_headers * Can be used to add, remove or manipulate the security headers. * * Key must match rsssl_recommended_security_headers filter in free * Patterns have to be written as a regex. A match should.. (insert removed line) * * @param array $securityHeaders * @return array */ return apply_filters('rsssl_pro_detected_security_headers', $securityHeaders); } /** * Points where we want to rewrite the .htaccess * - activation of Really Simple Security pro * - activation of a caching plugin * * @return void */ public function update_cache_rewrites_htaccess(){ if ( function_exists('wpfastestcache_activate')) { wpfastestcache_activate(); } if ( class_exists('FlyingPress\Purge') && class_exists('FlyingPress\Preload') && class_exists('FlyingPress\Config')) { \FlyingPress\Config::update_config(); \FlyingPress\Purge::purge_pages(); \FlyingPress\Preload::preload_cache(false); } } /** * Add warning and tooltip to the fields array for each header that is set with a non recommended value * @param array $fields * * @return array */ public function mark_headers_with_non_recommended_values($fields){ $non_recommended_headers = $this->get_non_recommended_headers(); $security_header_names = array_column($this->security_headers, 'name'); foreach ($non_recommended_headers as $header => $value ) { $found_key = array_search($header, $security_header_names); $option_name = $this->security_headers[ $found_key ]['option_name']; $index = array_search($option_name, array_column($fields, 'id') ); $fields[$index]['warning'] = true; $fields[$index]['tooltip'] = sprintf(__("The %s security header is not set by Really Simple Security, but has a non-recommended value: \"%s\".", 'really-simple-ssl'), $header, $value ); } return $fields; } /** * Get list of notices for the dashboard * @param array $notices * * @return array */ public function get_notices_list( $notices ) { unset( $notices['recommended_security_headers_not_set']); $notices['hsts_preload'] = array( 'condition' => array('rsssl_ssl_enabled'), 'callback' => 'RSSSL()->headers->hsts_status', 'score' => 10, 'output' => array( 'preload' => array( 'title' => __("HSTS Preload", 'really-simple-ssl'), 'msg' => sprintf(__("Your site has been configured for the HSTS preload list. If you have submitted your site, it will be preloaded. Click %shere%s to submit.", 'really-simple-ssl'),'<a target="_blank" href="https://hstspreload.org/?domain='.$this->non_www_domain().'">', '</a>' ), 'icon' => 'success' ), 'no_preload' => array( 'highlight_field_id' => 'hsts_preload', 'title' => __("HSTS Preload", 'really-simple-ssl'), 'msg' => __("Your site is not yet configured for the HSTS preload list.", 'really-simple-ssl'), 'icon' => 'open', 'dismissible' => true, ), 'no_hsts' => array( 'highlight_field_id' => 'hsts', 'title' => __("HSTS not enabled", 'really-simple-ssl'), 'msg' => __("Your site is not configured for HSTS yet.", 'really-simple-ssl'), 'icon' => 'open', 'dismissible' => true, ), ), ); return $notices; } /** * Clear the headers * Called on plugin deactivation * @return void */ public function remove_advanced_headers() { if ( ! defined( 'rsssl_plugin' ) ) { return; } //update with cleared options $disable = [ "csp_frame_ancestors", "upgrade_insecure_requests", "enable_permissions_policy", "mixedcontentscan", "cross_origin_opener_policy", "hsts", "referrer_policy", "x_frame_options", "x_content_type_options", "x_xss_protection", "content_security_policy", "csp_status", ]; foreach ( $disable as $name ) { rsssl_update_option( $name, false ); } if ( class_exists( 'RSSSL_SECURITY' ) ) { RSSSL_SECURITY()->firewall_manager->install(); } } /** * Get HSTS status * @return string */ public function hsts_status() { $thirdparty_headers = RSSSL()->headers->get_detected_security_headers('thirdparty'); $detected_max_age = rsssl_get_option( 'hsts_max_age' ); $detected_preload = rsssl_get_option( 'hsts_preload' ); $detected_subdomains = rsssl_get_option( 'hsts_subdomains' ); $hsts = rsssl_get_option( 'hsts' ); foreach( $thirdparty_headers as $header => $data ) { $detected_option = $data['option_name']; if ( $detected_option==='hsts') { $hsts = true; } if ($detected_option==='hsts_max_age') { $detected_max_age = $data['value']; } if ( $detected_option==='hsts_preload') { $detected_preload = true; } if ( $detected_option==='hsts_subdomains') { $detected_subdomains = true; } } if ( !$hsts ) { return 'no_hsts'; } if ( $detected_preload && (int) $detected_max_age >= 31536000 && $detected_subdomains ) { return 'preload'; } return 'no_preload'; } /** * Get list of headers with non recommended values * * @return array //in name => value format */ private function get_non_recommended_headers(){ $non_recommended_headers = array(); $used_headers = RSSSL()->headers->get_detected_security_headers( 'thirdparty' ); $security_header_names = array_column($this->security_headers, 'name'); foreach ( $used_headers as $header_name => $header_value ) { // Skip CORS && CSP if ( $header_name === 'Cross-Origin-Opener-Policy' || $header_name === 'Cross-Origin-Resource-Policy') continue; $found_key = array_search($header_name, $security_header_names); if ( isset( $header_value['value'] ) && $this->security_headers[ $found_key ]['recommended_value']!==false && $found_key ) { $recommended_values = $this->security_headers[ $found_key ]['recommended_value']; #ensure it's an array if ( !is_array($recommended_values) ) { $recommended_values = [ $recommended_values ]; } $found = in_array( $header_value['value'], $recommended_values ); if ( !$found ) { // If $header_value['value'] is contained in substr of one $recommended_value, e.g. in the HSTS max-age header we do not want to add it foreach( $recommended_values as $recommended_value ) { if ( stripos( $header_value['value'], $recommended_value) !== false ) { $found = true; } } } // Add to non-recommend when value if not found in substring if ( !$found ) { $non_recommended_headers[ $header_name ] = $header_value['value']; } } #check for non recommended values if ( isset( $header_value['value'], $this->security_headers[ $found_key ]['non_recommended_value'] ) && $this->security_headers[ $found_key ]['non_recommended_value'] !== false && $found_key ) { $non_recommended_values = $this->security_headers[ $found_key ]['non_recommended_value']; #ensure it's an array if ( !is_array($non_recommended_values) ) { $non_recommended_values = [ $non_recommended_values ]; } $found = in_array( $header_value['value'], $non_recommended_values ); if ( !$found ) { // If $header_value['value'] is contained in substr of one $recommended_value, e.g. in the HSTS max-age header we do not want to add it foreach( $non_recommended_values as $non_recommended_value ) { if ( stripos( $header_value['value'], $non_recommended_value) !== false ) { $found = true; } } } // Add to non-recommend when value if not found in substring if ( $found ) { $non_recommended_headers[ $header_name ] = $header_value['value']; } } } return $non_recommended_headers; } /** * Add notice(s) for headers with non-recommended values * * @param array $notices * * @return array */ public function add_non_recommended_header_notices( array $notices ): array { if ( RSSSL()->headers->is_header_check_running() ) { return $notices; } $non_recommended_headers = $this->get_non_recommended_headers(); foreach ( $non_recommended_headers as $header => $value ) { $notices[ 'wrong_value_'.$header ] = array( 'callback' => '_true_', 'score' => 5, 'output' => array( 'true' => array( 'url' => "how-to-find-where-security-headers-are-set/", 'msg' => sprintf(__("The %s security header is not set by Really Simple Security, but has a non-recommended value: \"%s\".", 'really-simple-ssl'), $header, $value ), 'icon' => 'open', 'dismissible' => true, 'clear_cache_id' => 'detected_headers', ), ), ); } $all_headers = RSSSL()->headers->get_detected_security_headers( 'all' ); $security_header_names = array_column($this->get_recommended_security_headers(), 'name'); $missing_headers = []; foreach ( $security_header_names as $header ) { if ( isset($all_headers[$header]) ) { continue; } //not found yet. Check if it's enabled by RSSSL. $found_key = array_search($header, $security_header_names); $option_name = $this->security_headers[ $found_key ]['option_name']; if ( rsssl_get_option($option_name) ) continue; $missing_headers[] = $header; } if ( count($missing_headers)>0 ) { $missing = implode(', ', $missing_headers); $notices[ 'missing_headers' ] = array( 'callback' => '_true_', 'score' => 5, 'output' => array( 'true' => array( 'url' => "how-to-find-where-security-headers-are-set/", 'msg' => sprintf(__("The following essential security headers have not been set: %s", 'really-simple-ssl'), $missing ), 'icon' => 'open', 'dismissible' => true, 'clear_cache_id' => 'detected_headers', ), ), ); } //remove free notice, to prevent duplicates unset($notices['recommended_security_headers_not_set']); return $notices; } /** * Get array of recommended headers * @return mixed|null */ public function get_recommended_security_headers(){ return array_filter($this->security_headers, function($value, $key) { return $value['is_recommended_header'] === true; }, ARRAY_FILTER_USE_BOTH); } /** * If csp reporting is enabled, save the time, so we track how long it's running * @return void */ public function save_time_on_report_only_start($field_id, $field_value, $prev_value, $field_type ){ if ( $field_id==='csp_status' && $field_value==='learning_mode' ){ update_site_option("rsssl_csp_report_only_activation_time", time() ); } } /** * Retrieving it in the firewall update is too early for WP, so we store it here * @return void */ public function store_csp_endpoint(): void { if ( !get_option('rsssl_csp_report_url') ) { update_option('rsssl_csp_report_url', get_rest_url(null, 'really-simple-security/v1/csp'), false ); } } /** * Update the rest URL if the permalink structure is changed * * @param string $old_value * @param string $new_value * * @return void */ public function update_csp_endpoint( string $old_value, string $new_value): void { if ( $new_value !== $old_value ) { update_option('rsssl_csp_report_url', get_rest_url(null, 'really-simple-security/v1/csp'), false ); } } /** * Check for www * * @return array|string|string[] */ public function non_www_domain(){ return str_replace(array("https://", "http://", "https://www.", "http://www.", "www."), "", get_home_url() ); } /** * This class has its own settings page, to ensure it can always be called * * @return bool */ public function is_settings_page(): bool { if ( rsssl_is_logged_in_rest()){ return true; } if (isset($_GET["page"]) && ($_GET["page"] === "really-simple-security" || $_GET["page"] === "really-simple-ssl") ) { return true; } return false; } /** * Get CSP rules for any type or output type * * @return string * @throws Exception */ public function get_csp_rules(): string { global $wpdb; $header = 'Content-Security-Policy'; $rules = ''; $frame_ancestors_rules = ''; $upgrade_insecure_request_rules = ''; //drop paused status if we're not in learning mode if ( rsssl_get_option( 'csp_status' ) !== 'learning_mode' ) { delete_site_option( 'rsssl_csp_reporting_temp_paused' ); } if ( ! get_site_option( 'rsssl_csp_reporting_temp_paused' ) && ( rsssl_get_option( 'csp_status' ) === 'enforce' || rsssl_get_option( 'csp_status' ) === 'learning_mode' ) ) { #The base content security policy rules, used in later functions to generate the Content Security Policy $rules_array = [ 'img-src' => "img-src 'self' data: https://secure.gravatar.com https://ts.w.org https://s.w.org https://ps.w.org", 'default-src' => "default-src 'self'", 'script-src' => "script-src 'self' 'unsafe-inline' 'unsafe-eval'", 'script-src-elem' => "script-src-elem 'self' 'unsafe-inline'", 'style-src' => "style-src 'self' 'unsafe-inline'", 'style-src-elem' => "style-src-elem 'self' 'unsafe-inline'", 'font-src' => "font-src 'self' data:", 'frame-src' => "frame-src 'self' blob:", ]; $enabled_captcha_provider = rsssl_get_option( 'enabled_captcha_provider' ); if ( $enabled_captcha_provider === 'recaptcha' ) { $rules_array['script-src'] .= " https://www.google.com/recaptcha/"; $rules_array['script-src-elem'] .= " https://google.com https://www.google.com https://gstatic.com https://www.gstatic.com"; $rules_array['frame-src'] .= " https://google.com https://www.google.com https://gstatic.com https://www.gstatic.com"; } elseif ( $enabled_captcha_provider === 'hcaptcha' ) { $rules_array['script-src'] .= " https://hcaptcha.com"; $rules_array['script-src-elem'] .= " https://hcaptcha.com https://www.hcaptcha.com"; $rules_array['frame-src'] .= " *.hcaptcha.com"; } // Add semicolon at the end of each rule foreach ( $rules_array as $key => $value ) { $rules_array[ $key ] = $value . ';'; } $table_name = $wpdb->base_prefix . "rsssl_csp_log"; $rows = $wpdb->get_results( "SELECT * FROM $table_name WHERE status=1 ORDER BY time DESC" ); if ( ! empty( $rows ) ) { foreach ( $rows as $row ) { if ( $row->status == 1 ) { $violatedirective = $row->violateddirective; $blockeduri = $row->blockeduri; //Get uri value $uri = rsssl_sanitize_uri_value( $blockeduri ); //Generate CSP rule based on input $rules_array = $this->generate_csp_rule( $violatedirective, $uri, $rules_array ); if ( $violatedirective === 'script-src-elem' ) { $rules_array = $this->generate_csp_rule( 'script-src', $uri, $rules_array ); } if ( $violatedirective === 'style-src-elem' ) { $rules_array = $this->generate_csp_rule( 'style-src', $uri, $rules_array ); } } } } $rules = implode( " ", $rules_array ); //if reporting is temporarily paused, return empty if ( rsssl_get_option( 'csp_status' ) === 'learning_mode' ) { $csp_violation_endpoint = get_option( 'rsssl_csp_report_url' ); $token = get_site_option( 'rsssl_csp_report_token' ); if ( ! $token ) { $token = random_int( 1000, 999999999 ); update_site_option( 'rsssl_csp_report_token', $token ); } //allow for fallback method $glue = strpos( $csp_violation_endpoint, '?' ) === false ? '?' : '&'; $csp_violation_endpoint .= "{$glue}rsssl_apitoken=$token"; //report-uri is deprecated, but report-to not yet supported widely // $header = 'Report-To'; // $csp_endpoint_rules = "{'url': '".$csp_violation_endpoint."', 'group': 'csp-endpoint', 'max-age': 10886400}"; // $report_to_header = $this->wrap_header($header, $csp_endpoint_rules); $header = 'Content-Security-Policy-Report-Only'; // $rules = "$rules report-uri $csp_violation_endpoint; report-to csp-endpoint"; $rules = "$rules report-uri $csp_violation_endpoint;"; } } // If frame ancestors are set, and the site is in learning mode, add both Content-Security-Policy and Content-Security-Policy-Report-Only headers if ( ! empty( rsssl_get_option( 'csp_frame_ancestors' ) ) && rsssl_get_option( 'csp_frame_ancestors' ) !== 'disabled' && ! $this->header_is_set_by_thirdparty( 'csp_frame_ancestors' ) ) { $urls = trim( rsssl_get_option( 'csp_frame_ancestors_urls' ) ); if ( ! empty( $urls ) ) { $urls = explode( ",", $urls ); foreach ( $urls as $index => $url ) { $urls[ $index ] = str_replace( 'http://', 'https://', esc_url_raw( trim( $url ) ) ); } $urls = implode( " ", $urls ); } if ( rsssl_get_option( 'csp_frame_ancestors' ) === 'none' ) { $frame_ancestors_rules = "frame-ancestors $urls; "; } elseif ( rsssl_get_option( 'csp_frame_ancestors' ) === 'self' ) { $frame_ancestors_rules = "frame-ancestors 'self' $urls; "; } // Add Frame Ancestors to rules if not learning mode if ( rsssl_get_option( 'csp_status' ) !== 'learning_mode' ) { $rules .= $frame_ancestors_rules; } } elseif ( ! empty( rsssl_get_option( 'csp_frame_ancestors' ) ) && rsssl_get_option( 'csp_frame_ancestors' ) === 'none' && empty( rsssl_get_option( 'csp_frame_ancestors_urls' ))) { $frame_ancestors_rules = "frame-ancestors 'none'; "; } // Add upgrade-insecure-requests if ( rsssl_get_option( 'upgrade_insecure_requests' ) && ! $this->header_is_set_by_thirdparty( 'upgrade_insecure_requests' ) ) { $upgrade_insecure_request_rules = "upgrade-insecure-requests;"; // Add Upgrade Insecure Requests to rules if not learning mode if ( rsssl_get_option( 'csp_status' ) !== 'learning_mode' ) { $rules .= $upgrade_insecure_request_rules; } } // If learning mode is true and frame ancestors set,return both Content-Security-Policy and Content-Security-Policy-Report-Only if ( rsssl_get_option( 'csp_status' ) === 'learning_mode' ) { $headers = [ $this->wrap_header( 'Content-Security-Policy', $upgrade_insecure_request_rules . $frame_ancestors_rules ), $this->wrap_header( 'Content-Security-Policy-Report-Only', $rules ) ]; return implode( '', $headers ); // Else, we return either the Content-Security-Policy or Content-Security-Policy-Report-Only saved in $rules } elseif ( ! empty( $rules ) ) { return $this->wrap_header( $header, $rules ); } return ''; } public function header_is_set_by_thirdparty($option_name) { $headers_set_by_thirdparty = $this->get_detected_security_headers('thirdparty'); $third_party_options = array_column($headers_set_by_thirdparty, 'option_name'); return in_array($option_name, $third_party_options); } /** * Generate security headers, and insert in .htaccess file * * @param string $rules * @param bool $include_csp * * @return string * @throws Exception */ public function insert_security_headers( string $rules, bool $include_csp = true ): string { $rule = ''; $break = "\n"; if ( is_ssl() && rsssl_get_option( 'hsts' ) ) { $subdomains = rsssl_get_option( 'hsts_subdomains' ) ? " includeSubDomains;" : ""; $preload = rsssl_get_option("hsts_preload") ? "preload":""; $max_age = rsssl_get_option( 'hsts_max_age', '31536000'); # only add hsts when on SSL # the advanced headers file will be included after the RSSSL fixes, so we should also have a server_https var if the host does not provide any of the default ones # WordPress is_ssl is not available yet, so we need our own # In some cases, even if the $_SERVER['HTTPS'] variable is available in WordPress, it might not yet be defined here. So we do a generic check. $rule .= 'if ( !function_exists("rsssl_is_ssl" ) ) {'.$break; $rule .= ' function rsssl_is_ssl() {'.$break; $rule .= ' if ('; $rule .= ' ( isset($_SERVER["HTTPS"]) && ("on" === $_SERVER["HTTPS"] || "1" === $_SERVER["HTTPS"]) )' . "\n"; $rule .= ' || (isset($_ENV["HTTPS"]) && ("on" === $_ENV["HTTPS"]))' . "\n"; $rule .= ' || (isset($_SERVER["SERVER_PORT"]) && ( "443" === $_SERVER["SERVER_PORT"] ) )' . "\n"; $rule .= ' || (isset($_SERVER["HTTP_X_FORWARDED_SSL"]) && (strpos($_SERVER["HTTP_X_FORWARDED_SSL"], "1") !== false))' . "\n"; $rule .= ' || (isset($_SERVER["HTTP_X_FORWARDED_SSL"]) && (strpos($_SERVER["HTTP_X_FORWARDED_SSL"], "on") !== false))' . "\n"; $rule .= ' || (isset($_SERVER["HTTP_CF_VISITOR"]) && (strpos($_SERVER["HTTP_CF_VISITOR"], "https") !== false))' . "\n"; $rule .= ' || (isset($_SERVER["HTTP_CLOUDFRONT_FORWARDED_PROTO"]) && (strpos($_SERVER["HTTP_CLOUDFRONT_FORWARDED_PROTO"], "https") !== false))' . "\n"; $rule .= ' || (isset($_SERVER["HTTP_X_FORWARDED_PROTO"]) && (strpos($_SERVER["HTTP_X_FORWARDED_PROTO"], "https") !== false))' . "\n"; $rule .= ' || (isset($_SERVER["HTTP_X_PROTO"]) && (strpos($_SERVER["HTTP_X_PROTO"], "SSL") !== false))' . "\n"; $rule .= ' ) {' .$break; $rule .= ' return true;' .$break; $rule .= ' }' .$break; $rule .= ' return false;'.$break; $rule .= ' }'.$break; $rule .= '}'.$break; $rule .= 'if ( rsssl_is_ssl() ) '.$this->wrap_header('Strict-Transport-Security', "max-age=$max_age;$subdomains$preload"); } if ( rsssl_get_option( 'x_xss_protection' )!=='disabled' && !$this->header_is_set_by_thirdparty('x_xss_protection') ) { if (rsssl_get_option( 'x_xss_protection' )==='zero') { $rule .= $this->wrap_header( 'X-XSS-Protection', "0" ); } if (rsssl_get_option( 'x_xss_protection' )==='one') { $rule .= $this->wrap_header( 'X-XSS-Protection', "1" ); } if (rsssl_get_option( 'x_xss_protection' )==='mode_block') { $rule .= $this->wrap_header( 'X-XSS-Protection', "1; mode=block"); } } if ( rsssl_get_option( 'x_content_type_options' ) && ! $this->header_is_set_by_thirdparty('x_content_type_options' ) ) { $rule .= $this->wrap_header( 'X-Content-Type-Options', "nosniff" ); } $referrer_policy = rsssl_get_option( 'referrer_policy' ); if ( $referrer_policy && $referrer_policy !== 'disabled' && ! $this->header_is_set_by_thirdparty('referrer_policy' ) ) { $rule .= $this->wrap_header( 'Referrer-Policy', $referrer_policy ); } if ( rsssl_get_option( 'enable_permissions_policy' ) ) { $rule .= $this->generate_permissions_policy_header( ); } $frame_ancestors_value = rsssl_get_option( 'csp_frame_ancestors' ); $x_frame_options_value = ''; if ( empty( rsssl_get_option( 'csp_frame_ancestors_urls' ) ) ) { switch ( $frame_ancestors_value ) { case 'disabled': # frame-ancestors → off - x-frame-options → off $x_frame_options_value = 'disabled'; break; case 'none': # frame-ancestors → none - x-frame-options → DENY $x_frame_options_value = 'DENY'; break; case 'self': # frame-ancestors → self - x-frame-options → SAMEORIGIN $x_frame_options_value = 'SAMEORIGIN'; break; default: $x_frame_options_value = 'disabled'; break; } } else { # frame-ancestors → self + url - x-frame-options → off $x_frame_options_value = 'disabled'; } if ( $x_frame_options_value && $x_frame_options_value !== 'disabled' && ! $this->header_is_set_by_thirdparty('x_frame_options' )) { $rule .= $this->wrap_header( 'X-Frame-Options', strtoupper($x_frame_options_value) ); } $cross_origin_opener_policy = rsssl_get_option('cross_origin_opener_policy'); if ( !empty($cross_origin_opener_policy) && $cross_origin_opener_policy !== 'disabled' && ! $this->header_is_set_by_thirdparty('cross_origin_opener_policy' ) ) { $rule .= $this->wrap_header( 'Cross-Origin-Opener-Policy', $cross_origin_opener_policy ); } $cross_origin_resource_policy = rsssl_get_option('cross_origin_resource_policy'); if ( !empty($cross_origin_resource_policy) && $cross_origin_resource_policy !== 'disabled' && ! $this->header_is_set_by_thirdparty('cross_origin_resource_policy' ) ) { $rule .= $this->wrap_header( 'Cross-Origin-Resource-Policy', $cross_origin_resource_policy ); } $cross_origin_embedder_policy = rsssl_get_option( 'cross_origin_embedder_policy' ); if ( ! empty( $cross_origin_embedder_policy ) && $cross_origin_embedder_policy !== 'disabled' && ! $this->header_is_set_by_thirdparty( 'cross_origin_embedder_policy' ) ) { $rule .= $this->wrap_header( 'Cross-Origin-Embedder-Policy', $cross_origin_embedder_policy ); } // Remove X-Powered-By header $disable_x_powered_by = rsssl_get_option( 'disable_x_powered_by_header' ); if ( $disable_x_powered_by ) { $rule .= $break; $rule .= "if (function_exists('header_remove')) {\n"; $rule .= " header_remove('X-Powered-By');\n"; $rule .= "} else {\n"; $rule .= " header('X-Powered-By: ');\n"; $rule .= "}\n"; $rule .= $break; } $csp_rules = $include_csp ? $this->get_csp_rules() : ''; $rule .= apply_filters('rsssl_csp_rules', $csp_rules ); //wrap in headers_sent check if ( !empty(trim($rule)) ) { $rule = 'if ( !headers_sent() ) {' . $break . $rule . $break . '}' . $break; } // Check if $rule is already in $rules if ( $rule && strpos( $rules, $rule ) !== false ) { // If $rule is already in $rules, just return $rules without appending return $rules; } /** * Test if the CSP rules cause errors on the server because of max header length */ if (rsssl_get_option('csp_status')==='disabled') { delete_option('rsssl_csp_max_size_exceeded'); } if ( $include_csp ) { $this->write_advanced_headers_test_file($rule); if ( $this->header_length_ok() ) { delete_option('rsssl_csp_max_size_exceeded'); return $rules."\n".$rule; } else { //not ok, so get rules without CSP and disable CSP update_option('rsssl_csp_max_size_exceeded', true, false); return $this->insert_security_headers($rules, false ); } } else { //csp not included, return without testing. return $rules."\n".$rule; } } /** * Write a file to test only the headers, without loading wordpress * @param $rules * * @return void */ private function write_advanced_headers_test_file($rules){ if ( !rsssl_user_can_manage() ) { return; } // Remove any existing X-REALLY-SIMPLE-SSL-TEST headers $rules = preg_replace('/\s*header\(\s*[\'"]X-REALLY-SIMPLE-SSL-TEST[\'"].*?\);/i', '', $rules); $size = apply_filters('rsssl_advanced_headers_test_size', 500 ); $rules .= $this->wrap_header( 'X-REALLY-SIMPLE-SSL-TEST', substr(urlencode(random_bytes($size)),0,$size ) ); $contents = '<?php' . "\n"; $contents .= '/**' . "\n"; $contents .= '* This file is created by Really Simple Security to test the CSP header length' . "\n"; $contents .= '* It will not load during regular wordpress execution' . "\n"; $contents .= '*/' . "\n\n"; $contents .= "\n".$rules; $contents .= "\n echo '<html><head><meta charset=\"UTF-8\"><META NAME=\"ROBOTS\" CONTENT=\"NOINDEX, NOFOLLOW\"></head><body>Really Simple Security headers test page</body></html>';"; // write to advanced-header.php file if ( is_writable( ABSPATH . 'wp-content' ) ) { file_put_contents( ABSPATH . "wp-content/advanced-headers-test.php", $contents ); } } private function header_length_ok(){ if ( !file_exists(ABSPATH . "wp-content/advanced-headers-test.php")) { return true; } // Check if cached status is still valid if ( $headerTestStatusCache = $this->getHeaderTestStatusCache() ) { return $headerTestStatusCache === 'ok'; } $status = 400; $nonce = get_site_option( "rsssl_header_detection_nonce" ); $test_url = site_url('wp-content/advanced-headers-test.php')."?rsssl_header_test=$nonce"; $response = wp_remote_get($test_url); if ( !is_wp_error($response) ) { if ( is_array( $response ) ) { $status = wp_remote_retrieve_response_code( $response ); } if ( $status >= 500 ) { $this->setHeaderTestStatusCache('error'); return false; } } else { $err = $response->get_error_message(); //check if we get the bytes header warning if (strpos($err, 'bytes header')!==false) { $this->setHeaderTestStatusCache('error'); return false; } } //in all cases other then a 50x response we return success. $this->setHeaderTestStatusCache('ok'); return true; } /** * Get header test status cache, cleanup if expired * @return string|false Returns cached status or false if invalid/expired */ private function getHeaderTestStatusCache() { $expiry = get_option('rsssl_csp_header_test_status_expiry'); $status = get_option('rsssl_csp_header_test_status'); // Clean up if expired OR if we have status but no expiry (edge case) if ( ($expiry && time() > $expiry) || ($status && !$expiry) ) { delete_option('rsssl_csp_header_test_status'); delete_option('rsssl_csp_header_test_status_expiry'); return false; } return $status; } /** * Set header test status cache with expiry * @param string $status The status to cache ('ok' or 'error') */ private function setHeaderTestStatusCache( string $status ) { update_option('rsssl_csp_header_test_status', $status); update_option('rsssl_csp_header_test_status_expiry', time() + (5 * MINUTE_IN_SECONDS)); } /** * Get permissions policy rules * @return string */ public function generate_permissions_policy_header(): string { $permissions_policy_values = rsssl_get_option('permissions_policy'); $rules = []; if ( !is_array($permissions_policy_values) ) { $permissions_policy_values = []; } foreach ( $permissions_policy_values as $policy ) { switch ($policy['value']) { case '*': $rules[] = $policy['id'] ."=(*)"; break; case 'self': $rules[] = $policy['id'] ."=(self)"; break; default: case '()': $rules[] = $policy['id'] ."=()"; } } $php_rule = ''; if ( !empty( $rules) ) { $rules = implode(', ', $rules); $php_rule = $this->wrap_header('Permissions-Policy', $rules); } update_option('rsssl_pro_permissions_policy_headers_for_php', $php_rule); return $php_rule; } /** * Wrap a header in the correct format * * @param string $header * @param string $rules * * @return string */ public function wrap_header( string $header, string $rules ): string { $break = "\n"; return 'header(' . '"' . $header . ": " . $rules . '"' . ');' . $break; } /** * Generate CSP rules * * @param string $violateddirective * @param string $uri * @param array $rules //previously detected rules * * @return array */ public function generate_csp_rule( string $violateddirective, string $uri, array $rules ): array { $uri = rsssl_sanitize_uri_value($uri); // Check the violateddirective is valid if ( isset($this->directives[$violateddirective]) ){ // If the violated directive has an existing rule, update it if ( isset($rules[$violateddirective]) ) { $rule_template = $this->directives[$violateddirective]; //'script-src-elem' => "script-src-elem 'self' {uri}; ", //get existing rule $existing_rule = $rules[$violateddirective]; //'script-src-elem' => "script-src-elem 'self' 'unsafe-inline';"; //get part of directive before {uri} $rule_part = substr($rule_template, 0, strpos($rule_template, '{uri}')); //"script-src-elem 'self' " // URI can be both URL or a directive (for example script-src) // Check if the current rule already contains the URI if (strpos($existing_rule, $uri) !== false) { // If it contains the uri, do not add it again. Keep existing rule $new_rule = $existing_rule; } else { //does not contain the uri, add it. we add trim to remove the space at the end of the rule part. $new_rule = str_replace(trim($rule_part), $rule_part . $uri . " ", $existing_rule); } //insert in array $rules[$violateddirective] = $new_rule; } else { $rules[$violateddirective] = str_replace('{uri}', $uri, $this->directives[$violateddirective]); } } return $rules; } /** * Check if a header check is currently running * * @return bool */ public function is_header_check_running(): bool { $headers = $this->get_admin_transient('detected_headers'); if ( !$headers || !isset($headers['header_check_active']) ){ return true; } return $headers['header_check_active']; } /** * Clear the headers caches * * @param array $data * * @return array */ public function rest_api_clear_cache( $data): array { if ( !rsssl_user_can_manage() ) { return []; } $cache_id = sanitize_title($data['cache_id']); if ($cache_id==='detected_headers') { $this->delete_admin_transient( 'detected_headers' ); $this->get_detected_security_headers(); } return []; } /** * Delete the cached header option * @return void */ public function clear_headers_check_cache($field_id, $field_value, $prev_value, $field_type) { if ( !rsssl_user_can_manage()) { return; } if ( $field_value === $prev_value ) { return; } $header_ids = array_column($this->security_headers, 'option_name'); if ( in_array( $field_id, $header_ids, true ) ) { $this->delete_admin_transient( 'detected_headers' ); } } public function reload_headers_check_cache(){ if ( !rsssl_user_can_manage()) { return; } if ( !$this->get_admin_transient( 'detected_headers' )){ $this->get_detected_security_headers(); } } public function get_detected_security_headers( $type = false ) { $nonce = get_site_option( "rsssl_header_detection_nonce" ); if ( ! in_array( $type, [ 'thirdparty', 'all' ] ) ) { $type = 'thirdparty'; } $headers = $this->get_admin_transient( 'detected_headers' ); if ( ! $headers ) { // Set a default $headers = [ 'curl_exists' => function_exists( 'curl_init' ), 'thirdparty' => [], 'all' => [], 'header_check_active' => true, ]; $this->set_admin_transient( 'detected_headers', $headers, DAY_IN_SECONDS ); if ( function_exists( 'curl_init' ) ) { // Check with RSSSL $url = get_site_url(); $detected_headers_with_rsssl = $this->get_headers_after_redirect( $url ); // Check without RSSSL $url_exclude_rsssl = get_site_url() . '?rsssl_header_test=' . $nonce; $detected_headers_without_rsssl = $this->get_headers_after_redirect( $url_exclude_rsssl ); $headers['thirdparty'] = $this->parse_headers( $detected_headers_without_rsssl ); $headers['all'] = $this->parse_headers( $detected_headers_with_rsssl ); } $headers['header_check_active'] = false; $this->set_admin_transient( 'detected_headers', $headers, DAY_IN_SECONDS ); } $headers = wp_parse_args( $headers, [ 'curl_exists' => true, 'all' => [], 'thirdparty' => [] ] ); return $headers[ $type ]; } private function get_headers_after_redirect( $url ) { $ch = curl_init(); $detected_headers = []; curl_setopt( $ch, CURLOPT_URL, $url ); curl_setopt( $ch, CURLOPT_NOBODY, true ); curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_TIMEOUT, 3 ); // Timeout in seconds curl_exec( $ch ); $final_url = curl_getinfo( $ch, CURLINFO_EFFECTIVE_URL ); curl_close( $ch ); if ( $final_url ) { $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, $final_url ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_HEADERFUNCTION, function ( $curl, $header ) use ( &$detected_headers ) { $len = strlen( $header ); $header = explode( ':', $header, 2 ); if ( count( $header ) < 2 ) { // Ignore invalid headers return $len; } // Force skip deprecated headers such as x-content-security-policy if ( ! in_array( strtolower( trim( $header[0] ) ), $this->invalid_headers ) ) { $detected_headers[] = [ 'name' => strtolower( trim( $header[0] ) ), 'value' => trim( $header[1] ), ]; } return $len; } ); curl_exec( $ch ); curl_close( $ch ); } return $detected_headers; } /** * We use our own transient, as the wp transient is not always persistent * Specifically made for license transients, as it stores on network level if multisite. * * @param string $name * * @return mixed */ private function get_admin_transient( string $name ){ if ( !rsssl_user_can_manage() ) return false; $value = false; $now = time(); $transients = get_option('rsssl_transients', array()); if (isset($transients[$name])) { $data = $transients[$name]; $expires = isset($data['expires']) ? $data['expires'] : 0; $value = isset($data['value']) ? $data['value'] : false; if ( $expires < $now ) { unset($transients[$name]); update_option('rsssl_transients', $transients, false ); $value = false; } } return $value; } /** * @param string $name * * @return void */ private function delete_admin_transient( string $name ){ if ( !rsssl_user_can_manage() ) return; $transients = get_option('rsssl_transients', array()); if ( is_array( $transients ) ) { if ( isset( $transients[ $name ] ) ) { unset( $transients[ $name ] ); update_option( 'rsssl_transients', $transients, false ); } } } /** * We use our own transient, as the wp transient is not always persistent AND we don't want to autoload these transients * * @param string $name * @param mixed $value * @param int $expiration * * @return void */ private function set_admin_transient( string $name, $value, int $expiration ){ if ( !rsssl_user_can_manage() ) return; $transients = get_option('rsssl_transients', array()); $transients[$name] = array( 'value' => $value, 'expires' => time() + intval($expiration), ); update_option('rsssl_transients', $transients, false ); } /** * Parse detected headers in structured array * @param $headers * * @return array */ public function parse_headers($headers){ $output_headers = []; if ( !empty($headers) ) { // Loop through each header and check if it's one of the recommended security headers. If so, add to used_headers array. foreach ($headers as $detected_header) { $name = $detected_header['name']; $value = $detected_header['value']; //e.g. "upgrade-insecure-requests; frame-ancestors 'none'; font-src 'self'" if ( !is_string($value) ) { continue; } foreach ( $this->security_headers as $header) { $name_pattern = $header['pattern']['header']; $value_pattern = $header['pattern']['value']; $value = str_replace(PHP_EOL, '', $value); // The header pattern should always match. If there is a value pattern, it has to match as well. // Except for frame-ancestors, as that can be empty. Then set to empty $actual_value = $value; $value_pattern_has_match = false; if ( $value_pattern && preg_match( '/' . $value_pattern . '/i', $value, $matches ) ) { $actual_value = isset($matches[1]) && $matches[1] !== '' ? $matches[1] : 'empty'; $value_pattern_has_match = true; } if (( !$value_pattern || $value_pattern_has_match) && stripos($name, $name_pattern) !== false ) { $output_headers[$header['name']]['value'] = $actual_value; #add option name info $output_headers[$header['name']]['option_name'] = $header['option_name']; #check if it's duplicate if ( in_array($header['name'], $output_headers) ) { $output_headers[$header['name']]['count'] = 2; } else { $output_headers[$header['name']]['count'] = 1; } } } } } return $output_headers; } }
| ver. 1.1 | |
.
| PHP 8.3.30 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка