���� JFIF �� �
"" $(4,$&1'-=-157:::#+?D?8C49:7
7%%77777777777777777777777777777777777777777777777777�� { �" �� �� 5 !1AQa"q�2��BR��#b������� �� �� ? ��D@DDD@DDD@DDkK��6 �UG�4V�1��
�����릟�@�#���RY�dqp�
����� �o�7�m�s�<��VPS�e~V�چ8���X�T��$��c�� 9��ᘆ�m6@ WU�f�Don��r��5}9��}��hc�fF��/r=hi�� �͇�*�� b�.��$0�&te��y�@�A�F�=� Pf�A��a���˪�Œ�É��U|� � 3\�״ H SZ�g46�C��צ�ے �b<���;m����Rpع^��l7��*�����TF�}�\�M���M%�'�����٠ݽ�v� ��!-�����?�N!La��A+[`#���M����'�~oR�?��v^)��=��h����A��X�.���˃����^Æ��ܯsO"B�c>;
�e�4��5�k��/CB��.
�J?��;�҈�������������������~�<�VZ�ê¼2/)Í”jC���ע�V�G�!���!�F������\�� Kj�R�oc�h���:Þ I��1"2�q×°8��Р@ז���_C0�ր��A��lQ��@纼�!7��F�� �]�sZ
B�62r�v�z~�K�7�c��5�.���ӄq&�Z�d�<�kk���T&8�|���I���� Ws}���ǽ�cqnΑ�_���3��|N�-y,��i���ȗ_�\60���@��6����D@DDD@DDD@DDD@DDD@DDc�KN66<�c��64=r�����
Ď0��h���t&(�hnb[� ?��^��\��â|�,�/h�\��R��5�?
�0�!צ܉-����G����٬��Q�zA���1�����V��� �:R���`�$��ik��H����D4�����#dk����� h�}����7���w%�������*o8wG�LycuT�.���ܯ7��I��u^���)��/c�,s�Nq�ۺ�;�ך�YH2���.5B���DDD@DDD@DDD@DDD@DDD@V|�a�j{7c��X�F\�3MuA׾hb� ��n��F������ ��8�(��e����Pp�\"G�`s��m��ާaW�K��O����|;ei����֋�[�q��";a��1����Y�G�W/�߇�&�<���Ќ�H'q�m���)�X+!���=�m�ۚ丷~6a^X�)���,�>#&6G���Y��{����"" """ """ """ """ ""��at\/�a�8 �yp%�lhl�n����)���i�t��B�������������?��
', $code_arr);
return '' . $code . '
';
}
function rsssl_general_security_notices( $notices ) {
$code = rsssl_parse_htaccess_to_html( get_site_option('rsssl_htaccess_rules','') );
$uploads_code = rsssl_parse_htaccess_to_html( get_site_option('rsssl_uploads_htaccess_rules','') );
$notices['application-passwords'] = array(
'callback' => 'rsssl_wp_is_application_passwords_available',
'score' => 5,
'output' => array(
'true' => array(
'msg' => __("Disable application passwords.", "really-simple-ssl"),
'icon' => 'premium',
'url' => 'https://really-simple-ssl.com/definition/what-are-application-passwords/',
'dismissible' => true,
'highlight_field_id' => 'disable_application_passwords',
),
),
);
$notices['htaccess_status'] = array(
'callback' => 'rsssl_htaccess_status',
'score' => 5,
'output' => array(
'not-writable' => array(
'title' => __(".htaccess not writable", "really-simple-ssl"),
'msg' => __("An option that requires the .htaccess file is enabled, but the file is not writable.", "really-simple-ssl").' '.__("Please add the following lines to your .htaccess, or set it to writable:", "really-simple-ssl").$code,
'icon' => 'warning',
'dismissible' => true,
'plusone' => true,
'url' => 'https://really-simple-ssl.com/manual/editing-htaccess/',
),
'not-exists' => array(
'title' => __(".htaccess does not exist", "really-simple-ssl"),
'msg' => __("An option that requires the .htaccess file is enabled, but the file does not exist.", "really-simple-ssl").' '.__("Please add the following lines to your .htaccess, or set it to writable:", "really-simple-ssl").$code,
'icon' => 'warning',
'dismissible' => true,
'plusone' => true,
'url' => 'https://really-simple-ssl.com/manual/editing-htaccess/',
),
),
'show_with_options' => [
'disable_indexing',
'redirect'
]
);
$notices['htaccess_status_uploads'] = array(
'callback' => 'rsssl_uploads_htaccess_status',
'score' => 5,
'output' => array(
'not-writable' => array(
'title' => __(".htaccess in uploads not writable", "really-simple-ssl"),
'msg' => __("An option that requires the .htaccess file in the uploads directory is enabled, but the file is not writable.", "really-simple-ssl").' '.__("Please add the following lines to your .htaccess, or set it to writable:", "really-simple-ssl").$uploads_code,
'icon' => 'warning',
'dismissible' => true,
'plusone' => true,
'url' => 'https://really-simple-ssl.com/manual/editing-htaccess/',
),
),
'show_with_options' => [
'block_code_execution_uploads',
]
);
$notices['block_display_is_login_enabled'] = array(
'condition' => ['NOT option_block_display_is_login'],
'callback' => '_true_',
'score' => 5,
'output' => array(
'true' => array(
'highlight_field_id' => 'block_display_is_login',
'msg' => __("It is currently possible to create an administrator user with the same login and display name.", "really-simple-ssl"),
'icon' => 'open',
'dismissible' => true,
),
),
);
$notices['display_name_is_login_exists'] = array(
'condition' => ['rsssl_get_users_where_display_name_is_login'],
'callback' => '_true_',
'score' => 5,
'output' => array(
'true' => array(
'url' => 'https://really-simple-ssl.com/manual/login-and-display-names-should-be-different-for-wordpress/',
'msg' => __("We have detected administrator roles where the login and display names are the same.", "really-simple-ssl") . " " . rsssl_list_users_where_display_name_is_login_name() . "",
'icon' => 'open',
'dismissible' => true,
),
),
);
$notices['debug_log'] = array(
'condition' => ['rsssl_debug_log_file_exists_in_default_location'],
'callback' => 'rsssl_is_debugging_enabled',
'score' => 5,
'output' => array(
'true' => array(
'highlight_field_id' => 'change_debug_log_location',
'title' => __("Debugging", "really-simple-ssl"),
'msg' => __("Your site logs information to a public debugging file.", "really-simple-ssl"),
'url' => 'https://really-simple-ssl.com/instructions/about-hardening-features/',
'icon' => 'premium',
'dismissible' => true,
),
),
'show_with_options' => [
'change_debug_log_location',
],
);
$notices['user_id_one'] = array(
'condition' => ['NOT option_disable_user_enumeration'],
'callback' => '_true_',
'score' => 5,
'output' => array(
'true' => array(
'msg' => __("Your site is vulnerable to user enumeration attacks.", "really-simple-ssl"),
'icon' => 'warning',
'title' => __('Prevent user enumeration','really-simple-ssl'),
'url' => 'https://really-simple-ssl.com/what-are-user-enumeration-attacks/',
'dismissible' => true,
'highlight_field_id' => 'disable_user_enumeration',
),
),
'show_with_options' => [
'disable_user_enumeration',
],
);
$notices['username_admin_exists'] = array(
'condition' => ['rsssl_has_admin_user'],
'callback' => '_true_',
'score' => 5,
'output' => array(
'true' => array(
'highlight_field_id' => 'rename_admin_user',
'title' => __("Username", "really-simple-ssl"),
'msg' => __("Your site registered a user with the name 'admin'.", "really-simple-ssl"),
'icon' => 'warning',
'dismissible' => true,
),
),
'show_with_options' => [
'rename_admin_user',
],
);
$notices['new_username_empty'] = array(
'condition' => ['rsssl_has_admin_user', 'option_rename_admin_user', 'NOT rsssl_new_username_valid'],
'callback' => '_true_',
'score' => 5,
'output' => array(
'true' => array(
'highlight_field_id' => 'rename_admin_user',
'title' => __("Username", "really-simple-ssl"),
'msg' => __("Rename admin user enabled: Please choose a new username of at least 3 characters, which is not in use yet.", "really-simple-ssl"),
'icon' => 'warning',
'dismissible' => true,
),
),
'show_with_options' => [
'new_admin_user_login',
],
);
$notices['code-execution-uploads-allowed'] = array(
'callback' => 'rsssl_code_execution_allowed',
'score' => 5,
'output' => array(
'true' => array(
'highlight_field_id' => 'block_code_execution_uploads',
'msg' => __("Code execution is allowed in the public 'Uploads' folder.", "really-simple-ssl"),
'icon' => 'open',
'dismissible' => true,
),
),
);
$notices['db-prefix-notice'] = array(
'callback' => 'rsssl_is_default_wp_prefix',
'score' => 5,
'output' => array(
'false' => array(
'msg' => __("Your database prefix is renamed and randomized. Awesome!", "really-simple-ssl"),
'icon' => 'success',
'dismissible' => true,
),
'true' => array(
'msg' => __("Your database prefix is set to the default 'wp_'.", "really-simple-ssl"),
'icon' => 'premium',
'dismissible' => true,
'url' => 'https://really-simple-ssl.com/instructions/about-hardening-features/'
),
),
);
$notices['vulnerabilities'] = array(
'condition' => ['NOT option_enable_vulnerability_scanner'],
'callback' => '_true_',
'score' => 5,
'output' => array(
'true' => array(
'msg' => __("Really Simple SSL has a new feature! Introducing Vulnerability Detection, enable it now.", "really-simple-ssl"),
'icon' => 'open',
'dismissible' => true,
'url' => 'https://really-simple-ssl.com/vulnerability-detection/',
'highlight_field_id' => 'enable_vulnerability_scanner',
'plusone' => true,
),
),
);
// $notices['xmlrpc'] = array(
// 'callback' => 'rsssl_xmlrpc_allowed',
// 'score' => 10,
// 'output' => array(
// 'true' => array(
// 'highlight_field_id' => 'xmlrpc',
// 'msg' => __("XMLRPC is enabled on your site.", "really-simple-ssl"),
// 'icon' => 'warning',
// 'plusone' => true,
// ),
// ),
// 'show_with_options' => [
// 'xmlrpc',
// ],
// );
$notices['file-editing'] = array(
'callback' => 'rsssl_file_editing_allowed',
'score' => 5,
'output' => array(
'true' => array(
'highlight_field_id' => 'disable_file_editing',
'msg' => __("The built-in file editors are accessible to others.", "really-simple-ssl"),
// 'url' => 'https://wordpress.org/support/article/editing-wp-config-php/#disable-the-plugin-and-theme-editor',
'icon' => 'open',
'dismissible' => true,
),
),
);
$notices['registration'] = array(
'callback' => 'rsssl_user_registration_allowed',
'score' => 5,
'output' => array(
'true' => array(
'highlight_field_id' => 'disable_anyone_can_register',
'msg' => __("Anyone can register an account on your site. Consider disabling this option in the WordPress general settings.", "really-simple-ssl"),
'icon' => 'open',
'plusone' => false,
'dismissible' => true,
),
),
);
$notices['hide-wp-version'] = array(
'callback' => 'rsssl_src_contains_wp_version',
'score' => 5,
'output' => array(
'true' => array(
'highlight_field_id' => 'hide_wordpress_version',
'msg' => __("Your WordPress version is visible to others.", "really-simple-ssl"),
'icon' => 'open',
'dismissible' => true,
),
),
);
// $notices['login-url-not-working'] = array(
// 'callback' => 'NOT rsssl_new_login_url_working',
// 'score' => 5,
// 'output' => array(
// 'true' => array(
// 'msg' => __("Your new login URL does not seem to work. Still using /wp-admin and /wp-login.php.", "really-simple-ssl"),
// 'url' => 'https://really-simple-ss.com/',
// 'icon' => 'warning',
// 'dismissible' => true,
// ),
// ),
// );
return $notices;
}
add_filter('rsssl_notices', 'rsssl_general_security_notices');
PK !
?SB B
functions.phpnu [ admin->mixed_content_fixer_detected();
}
//expire in five minutes
$headers = get_transient('rsssl_can_use_curl_headers_check');
set_transient('rsssl_can_use_curl_headers_check', $headers, 5 * MINUTE_IN_SECONDS);
//no change
if ( $field_value === $prev_value ) {
return;
}
if ( $field_id === 'disable_http_methods' ) {
delete_option( 'rsssl_http_methods_allowed' );
rsssl_http_methods_allowed();
}
if ( $field_id === 'xmlrpc' ) {
delete_transient( 'rsssl_xmlrpc_allowed' );
rsssl_xmlrpc_allowed();
}
if ( $field_id === 'disable_indexing' ) {
delete_transient( 'rsssl_directory_indexing_status' );
rsssl_directory_indexing_allowed();
}
if ( $field_id === 'block_code_execution_uploads' ) {
delete_transient( 'rsssl_code_execution_allowed_status' );
rsssl_code_execution_allowed();
}
if ( $field_id === 'hide_wordpress_version' ) {
delete_option( 'rsssl_wp_version_detected' );
rsssl_src_contains_wp_version();
}
if ( $field_id === 'rename_admin_user' ) {
delete_transient('rsssl_admin_user_count');
rsssl_has_admin_user();
}
}
add_action( "rsssl_after_save_field", 'rsssl_maybe_clear_transients', 100, 4 );
}
if ( !function_exists('rsssl_remove_htaccess_security_edits') ) {
/**
* Clean up on deactivation
*
* @return void
*/
function rsssl_remove_htaccess_security_edits() {
if ( ! rsssl_user_can_manage() ) {
return;
}
if ( ! rsssl_uses_htaccess() ) {
return;
}
$htaccess_file = RSSSL()->admin->htaccess_file();
if ( ! file_exists( $htaccess_file ) ) {
return;
}
$start = "\n" . '#Begin Really Simple Security';
$end = '#End Really Simple Security' . "\n";
$pattern = '/'.$start.'(.*?)'.$end.'/is';
/**
* htaccess in uploads dir
*/
$upload_dir = wp_get_upload_dir();
$htaccess_file_uploads = trailingslashit( $upload_dir['basedir']).'.htaccess';
$content_htaccess_uploads = file_exists($htaccess_file_uploads ) ? file_get_contents($htaccess_file_uploads) : '';
if (preg_match($pattern, $content_htaccess_uploads) && is_writable( $htaccess_file_uploads )) {
$content_htaccess_uploads = preg_replace($pattern, "", $content_htaccess_uploads);
file_put_contents( $htaccess_file_uploads, $content_htaccess_uploads );
}
/**
* htaccess in root dir
*/
$htaccess_file = RSSSL()->admin->htaccess_file();
$content_htaccess = file_get_contents($htaccess_file);
//remove old style rules
$pattern_1 = "/#\s?BEGIN\s?rlrssslReallySimpleSSL.*?#\s?END\s?rlrssslReallySimpleSSL/s";
$pattern_2 = "/#\s?BEGIN\s?Really Simple SSL Redirect.*?#\s?END\s?Really Simple SSL Redirect/s";
$content_htaccess = preg_replace([$pattern_1, $pattern_2], "", $content_htaccess);
if (preg_match($pattern, $content_htaccess) && is_writable( $htaccess_file ) ) {
$content_htaccess = preg_replace($pattern, "", $content_htaccess);
file_put_contents( $htaccess_file, $content_htaccess );
}
}
}
/**
* Wrap the security headers
*/
if ( ! function_exists('rsssl_wrap_htaccess' ) ) {
function rsssl_wrap_htaccess() {
if ( !rsssl_user_can_manage() ) {
return;
}
if ( ! rsssl_uses_htaccess() ) {
return;
}
if ( rsssl_get_option('do_not_edit_htaccess') ) {
return;
}
if (
!rsssl_is_logged_in_rest() &&
!RSSSL()->admin->is_settings_page() &&
current_filter() !== 'rocket_activation' &&
current_filter() !== 'rocket_deactivation'
) {
return;
}
if ( get_site_option('rsssl_htaccess_error') ) {
delete_site_option( 'rsssl_htaccess_error' );
delete_site_option( 'rsssl_htaccess_rules' );
}
if ( get_site_option('rsssl_uploads_htaccess_error') ) {
delete_site_option( 'rsssl_uploads_htaccess_error' );
delete_site_option( 'rsssl_uploads_htaccess_rules' );
}
if ( get_option('rsssl_updating_htaccess') ) {
return;
}
update_option('rsssl_updating_htaccess', true, false );
$start = '#Begin Really Simple Security';
$end = "\n" . '#End Really Simple Security' . "\n";
$pattern_content = '/'.$start.'(.*?)'.$end.'/is';
$pattern = '/'.$start.'.*?'.$end.'/is';
/**
* htaccess in uploads dir
*/
$rules_uploads = apply_filters( 'rsssl_htaccess_security_rules_uploads', []);
$upload_dir = wp_get_upload_dir();
$htaccess_file_uploads = trailingslashit( $upload_dir['basedir']).'.htaccess';
if ( ! file_exists( $htaccess_file_uploads ) && count($rules_uploads)>0 ) {
if ( is_writable(trailingslashit( $upload_dir['basedir'])) ) {
file_put_contents($htaccess_file_uploads, '');
} else {
update_site_option( 'rsssl_uploads_htaccess_error', 'not-writable' );
$rules_uploads_result = implode( '', array_column( $rules_uploads, 'rules' ) );
update_site_option( 'rsssl_uploads_htaccess_rules', $rules_uploads_result );
}
}
if ( file_exists( $htaccess_file_uploads ) ) {
$content_htaccess_uploads = file_exists( $htaccess_file_uploads ) ? file_get_contents( $htaccess_file_uploads ) : '';
preg_match( $pattern_content, $content_htaccess_uploads, $matches );
if ( ( ! empty( $matches[1] ) && empty( $rules_uploads ) ) || ! empty( $rules_uploads ) ) {
$rules_uploads_result = '';
foreach ( $rules_uploads as $rule_uploads ) {
//check if the rule exists outside RSSSL, but not within
if ( strpos($content_htaccess_uploads, $rule_uploads['identifier'])!==false && !preg_match('/#Begin Really Simple Security.*?('.preg_quote($rule_uploads['identifier'],'/').').*?#End Really Simple Security/is', $content_htaccess_uploads, $matches) ) {
continue;
}
$rules_uploads_result .= $rule_uploads['rules'];
}
//We differ between missing rules, and a complete set. As we don't want the replace all rules with just the missing set.
//should replace if rules is not empty, OR if rules is empty and htaccess is not.
$htaccess_has_rsssl_rules = preg_match( '/#Begin Really Simple Security(.*?)#End Really Simple Security/is', $content_htaccess_uploads, $matches);
if ( ! empty( $rules_uploads_result ) || $htaccess_has_rsssl_rules ) {
if ( ! file_exists( $htaccess_file_uploads ) ) {
file_put_contents( $htaccess_file_uploads, '' );
}
$new_rules = empty($rules_uploads_result) ? '' : $start . $rules_uploads_result . $end;
if ( ! is_writable( $htaccess_file_uploads ) ) {
update_site_option( 'rsssl_uploads_htaccess_error', 'not-writable' );
update_site_option( 'rsssl_uploads_htaccess_rules', $rules_uploads_result );
} else {
delete_site_option( 'rsssl_uploads_htaccess_error' );
delete_site_option( 'rsssl_uploads_htaccess_rules' );
//remove current rules
$content_htaccess_uploads = preg_replace( $pattern, '', $content_htaccess_uploads );
//add rules as new block
$new_htaccess = $content_htaccess_uploads . "\n" . $new_rules;
#clean up
if (strpos($new_htaccess, "\n" ."\n" . "\n" )!==false) {
$new_htaccess = str_replace("\n" . "\n" . "\n", "\n" ."\n", $new_htaccess);
}
file_put_contents( $htaccess_file_uploads, $new_htaccess );
}
}
}
}
/**
* htaccess in root dir
*/
$rules = apply_filters( 'rsssl_htaccess_security_rules', [] );
$htaccess_file = RSSSL()->admin->htaccess_file();
if ( !file_exists( $htaccess_file ) && count($rules)>0 ) {
update_site_option('rsssl_htaccess_error', 'not-exists');
$rules_result = implode('',array_column($rules, 'rules'));
update_site_option('rsssl_htaccess_rules', $rules_result);
}
if ( file_exists( $htaccess_file ) ) {
$content_htaccess = file_get_contents( $htaccess_file );
//remove old style rules
//we do this beforehand, so we don't accidentally assume redirects are already in place
$content_htaccess = preg_replace(
[
"/#\s?BEGIN\s?rlrssslReallySimpleSSL.*?#\s?END\s?rlrssslReallySimpleSSL/s",
"/#\s?BEGIN\s?Really Simple SSL Redirect.*?#\s?END\s?Really Simple SSL Redirect/s"
], "", $content_htaccess);
preg_match( $pattern_content, $content_htaccess, $matches );
if ( ( ! empty( $matches[1] ) && empty( $rules ) ) || ! empty( $rules ) ) {
$rules_result = '';
foreach ( $rules as $rule ) {
//check if the rule exists outside RSSSL, but not within
if ( strpos($content_htaccess, $rule['identifier'])!==false && !preg_match('/#Begin Really Simple Security.*?('.preg_quote($rule['identifier'],'/').').*?#End Really Simple Security/is', $content_htaccess, $matches) ) {
continue;
}
$rules_result .= $rule['rules'];
}
//should replace if rules is not empty, OR if rules is empty and htaccess is not.
$htaccess_has_rsssl_rules = preg_match( '/#Begin Really Simple Security(.*?)#End Really Simple Security/is', $content_htaccess, $matches );
if ( ! empty( $rules_result ) || $htaccess_has_rsssl_rules ) {
if ( ! is_writable( $htaccess_file ) ) {
update_site_option( 'rsssl_htaccess_error', 'not-writable' );
update_site_option( 'rsssl_htaccess_rules', get_site_option( 'rsssl_htaccess_rules' ) . $rules_result );
} else {
delete_site_option( 'rsssl_htaccess_error' );
delete_site_option( 'rsssl_htaccess_rules' );
$new_rules = empty($rules_result) ? '' : $start . $rules_result . $end;
//remove current rules
$content_htaccess = preg_replace( $pattern, '', $content_htaccess );
//add rules as new block
if ( strpos($content_htaccess, '# BEGIN WordPress')!==false ) {
$new_htaccess = str_replace('# BEGIN WordPress', "\n" . $new_rules.'# BEGIN WordPress', $content_htaccess);
} else {
$new_htaccess = "\n" . $new_rules . $content_htaccess;
}
#clean up
if (strpos($new_htaccess, "\n" ."\n" . "\n" )!==false) {
$new_htaccess = str_replace("\n" . "\n" . "\n", "\n" ."\n", $new_htaccess);
}
file_put_contents( $htaccess_file, $new_htaccess );
}
}
}
}
delete_option('rsssl_updating_htaccess');
}
add_action('admin_init', 'rsssl_wrap_htaccess' );
add_action('rsssl_after_saved_fields', 'rsssl_wrap_htaccess', 30);
}
/**
* Store warning blocks for later use in the mailer
*
* @param array $changed_fields
*
* @return void
*/
function rsssl_gather_warning_blocks_for_mail( array $changed_fields ){
if (!rsssl_user_can_manage() ) {
return;
}
if ( !rsssl_get_option('send_notifications_email') ) {
return;
}
$fields = array_filter($changed_fields, static function($field) {
// Check if email_condition exists and call the function, else assume true
if ( !isset($field['email']['condition']) ) {
$email_condition_result = true;
} else if (is_array($field['email']['condition'])) {
//rsssl option check
$fieldname = array_key_first($field['email']['condition']);
$value = $field['email']['condition'][$fieldname];
$email_condition_result = rsssl_get_option($fieldname) === $value;
} else {
//function check
$function = $field['email']['condition'];
$email_condition_result = function_exists($function) && $function();
}
return isset($field['email']['message']) && $field['value'] && $email_condition_result;
});
if ( count($fields)===0 ) {
return;
}
$current_fields = get_option('rsssl_email_warning_fields', []);
//if it's empty, we start counting time. 30 mins later we send a mail.
update_option('rsssl_email_warning_fields_saved', time(), false );
$current_ids = array_column($current_fields, 'id');
foreach ($fields as $field){
if ( !in_array( $field['id'], $current_ids, true ) ) {
$current_fields[] = $field;
}
}
update_option('rsssl_email_warning_fields', $current_fields, false);
}
add_action('rsssl_after_saved_fields', 'rsssl_gather_warning_blocks_for_mail', 40);
/**
* Check if server uses .htaccess
* @return bool
*/
function rsssl_uses_htaccess() {
//when using WP CLI, the get_server check does not work, so we assume .htaccess is being used
//and rely on the file exists check to catch if not.
if ( defined( 'WP_CLI' ) && WP_CLI ) {
return true;
}
return rsssl_get_server() === 'apache' || rsssl_get_server() === 'litespeed';
}
/**
* Get htaccess status
* @return string | bool
*/
function rsssl_htaccess_status(){
if ( empty(get_site_option('rsssl_htaccess_rules','')) ) {
return false;
}
return get_site_option('rsssl_htaccess_error');
}
/**
* Get htaccess status
* @return string | bool
*/
function rsssl_uploads_htaccess_status(){
if ( empty(get_site_option('rsssl_uploads_htaccess_rules','')) ) {
return false;
}
return get_site_option('rsssl_uploads_htaccess_error');
}
/**
* @return string|null
* Get the wp-config.php path
*/
function rsssl_find_wp_config_path()
{
if ( !rsssl_user_can_manage() ) {
return null;
}
//limit nr of iterations to 5
$i = 0;
$dir = __DIR__;
do {
$i++;
if (file_exists($dir . "/wp-config.php")) {
return $dir . "/wp-config.php";
}
} while (($dir = realpath("$dir/..")) && ($i < 10));
return null;
}
/**
* Returns the server type of the plugin user.
*
* @return string|bool server type the user is using of false if undetectable.
*/
function rsssl_get_server() {
//Allows to override server authentication for testing or other reasons.
if ( defined( 'RSSSL_SERVER_OVERRIDE' ) ) {
return RSSSL_SERVER_OVERRIDE;
}
$server_raw = strtolower( htmlspecialchars( $_SERVER['SERVER_SOFTWARE'] ) );
//figure out what server they're using
if ( strpos( $server_raw, 'apache' ) !== false ) {
return 'apache';
} elseif ( strpos( $server_raw, 'nginx' ) !== false ) {
return 'nginx';
} elseif ( strpos( $server_raw, 'litespeed' ) !== false ) {
return 'litespeed';
} else { //unsupported server
return false;
}
}
/**
* @return string
* Generate a random prefix
*/
function rsssl_generate_random_string($length) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';
for ( $i = 0; $i < $length; $i++ ) {
$index = rand(0, strlen($characters) - 1);
$randomString .= $characters[$index];
}
return $randomString;
}
/**
* @return string
*
* Get users as string to display
*/
function rsssl_list_users_where_display_name_is_login_name() {
if ( !rsssl_user_can_manage() ) {
return '';
}
$users = rsssl_get_users_where_display_name_is_login( true );
if ( is_array( $users ) ) {
$ext = count($users)>=10 ? '...' : '';
$users = array_slice($users, 0, 10);
return implode( ', ', $users ).$ext;
}
return '';
}
/**
* @return bool|void
*
* Check if user e-mail is verified
*/
function rsssl_is_email_verified() {
if ( ! rsssl_user_can_manage() ) {
return false;
}
if ( get_option('rsssl_email_verification_status') == 'completed' ) {
// completed
return true;
}
if ( get_option('rsssl_email_verification_status') == 'started' ) {
// started
return false;
}
if ( get_option('rsssl_email_verification_status') == 'email_changed' ) {
// e-mail changed, has to re-verify
return false;
}
return false;
}
function rsssl_remove_prefix_from_version($version) {
return preg_replace('/^[^\d]*(?=\d)/', '', $version);
}
function rsssl_version_compare($version, $compare_to, $operator = null) {
$version = rsssl_remove_prefix_from_version($version);
$compare_to = rsssl_remove_prefix_from_version($compare_to);
return version_compare($version, $compare_to, $operator);
}PK ! l index.phpnu [
%s
', __( 'No known vulnerabilities detected.', 'really-simple-ssl' ) ), 'actions' => '', 'test' => 'health_test', ); } $total = 0; foreach ($this->risk_levels as $risk_level => $value) { $total += $risks[ $risk_level ] ?? 0; } return array( 'label' => __( 'Vulnerabilities detected','really-simple-ssl' ), 'status' => 'critical', 'badge' => array( 'label' => __( 'Security' ), 'color' => 'blue', ), 'description' => sprintf( '%s
', sprintf(_n( '%s vulnerability has been detected.', '%s vulnerabilities have been detected.', $total, 'really-simple-ssl' ), number_format_i18n( $total )) . ' '. __( 'Please check the vulnerabilities overview for more information and take appropriate action.' ,'really-simple-ssl' ) ), 'actions' => sprintf( '', esc_url( __( add_query_arg(array('page'=>'really-simple-security#settings/vulnerabilities/vulnerabilities-overview'), rsssl_admin_url() ) ) ), __( 'View vulnerabilities', 'really-simple-ssl' ) ), 'test' => 'rsssl_vulnerabilities', ); } public function show_help_notices($notices) { $this->cache_installed_plugins(); $risks = $this->count_risk_levels(); $level_to_show_on_dashboard = rsssl_get_option('vulnerability_notification_dashboard'); $level_to_show_sitewide = rsssl_get_option('vulnerability_notification_sitewide'); foreach ($this->risk_levels as $risk_level => $value) { if ( !isset($risks[$risk_level]) ) { continue; } //this is shown bases on the config of vulnerability_notification_dashboard $siteWide = false; $dashboardNotice = false; if ( $level_to_show_on_dashboard && $level_to_show_on_dashboard !== '*') { if ($value >= $this->risk_levels[$level_to_show_on_dashboard]) { $dashboardNotice = true; } } if ($level_to_show_sitewide && $level_to_show_sitewide !== '*') { if ($value >= $this->risk_levels[$level_to_show_sitewide]) { $siteWide = true; } } if ( !$dashboardNotice && !$siteWide ) { continue; } $count = $risks[$risk_level]; $title = $this->get_warning_string($risk_level, $count); $notice = [ 'callback' => '_true_', 'score' => 1, 'show_with_options' => ['enable_vulnerability_scanner'], 'output' => [ 'true' => [ 'title' => $title, 'msg' => $title.' '.__('Please take appropriate action.','really-simple-ssl'), 'icon' => ($risk_level === 'c' || $risk_level==='h') ? 'warning' : 'open', 'type' => 'warning', 'dismissible' => true, 'admin_notice' => $siteWide, 'plusone' => true, 'highlight_field_id' => 'vulnerabilities-overview', ] ], ]; $notices['risk_level_' . $risk_level] = $notice; } //now we add the test notices for admin and dahboard. //if the option is filled, we add the test notice. $test_id = get_option('test_vulnerability_tester'); if($test_id) { $dashboard = rsssl_get_option('vulnerability_notification_dashboard'); $side_wide = rsssl_get_option('vulnerability_notification_sitewide'); $site_wide_icon = $side_wide === 'l' || $side_wide === 'm' ? 'open' : 'warning'; if ( $side_wide === 'l' || $side_wide === 'm' || $side_wide === 'h' || $side_wide === 'c') { $notices[ 'test_vulnerability_sitewide_' .$test_id ] = [ 'callback' => '_true_', 'score' => 1, 'show_with_options' => [ 'enable_vulnerability_scanner' ], 'output' => [ 'true' => [ 'title' => __( 'Site wide - Test Notification', 'really-simple-ssl' ), 'msg' => __( 'This is a test notification from Really Simple SSL. You can safely dismiss this message.', 'really-simple-ssl' ), 'url' => add_query_arg(['page'=>'really-simple-security#settings/vulnerabilities/vulnerabilities-overview'], rsssl_admin_url() ), 'icon' => $site_wide_icon, 'dismissible' => true, 'admin_notice' => true, 'plusone' => true, ] ] ]; } //don't add this one if the same level $dashboard_icon = $dashboard === 'l' || $dashboard === 'm' ? 'open' : 'warning'; if ($dashboard_icon !== $site_wide_icon) { if ( $dashboard === 'l' || $dashboard === 'm' || $dashboard === 'h' || $dashboard === 'c' ) { $notices[ 'test_vulnerability_dashboard_' .$test_id ] = [ 'callback' => '_true_', 'score' => 1, 'show_with_options' => [ 'enable_vulnerability_scanner' ], 'output' => [ 'true' => [ 'title' => __( 'Dashboard - Test Notification', 'really-simple-ssl' ), 'msg' => __( 'This is a test notification from Really Simple SSL. You can safely dismiss this message.', 'really-simple-ssl' ), 'icon' => $dashboard_icon, 'dismissible' => true, 'admin_notice' => false, 'plusone' => true, ] ] ]; } } } return $notices; } /** * Generate plugin files for testing purposes. * * @return array */ public static function testGenerator(): array { $mail_notification = rsssl_get_option('vulnerability_notification_email_admin'); if ( $mail_notification === 'l' || $mail_notification === 'm' || $mail_notification === 'h' || $mail_notification === 'c' ) { $mailer = new rsssl_mailer(); $mailer->send_test_mail(); } return []; } /* Public Section 2: DataGathering */ /** * @param $stats * * @return array */ public function get_stats($stats): array { if ( ! rsssl_user_can_manage() ) { return $stats; } $this->cache_installed_plugins(); //now we only get the data we need. $vulnerabilities = array_filter($this->workable_plugins, static function ($plugin) { if (isset($plugin['vulnerable']) && $plugin['vulnerable']) { return $plugin; } return false; }); $time = $this->get_file_stored_info(true); $stats['vulnerabilities'] = count($vulnerabilities); $stats['vulList'] = $vulnerabilities; $riskData = $this->measures_data(); $stats['riskData'] = $riskData['data']; $stats['lastChecked'] = $time; return $stats; } /** * This combines the vulnerabilities with the installed plugins * * And loads it into a memory cache on page load * */ public function cache_installed_plugins($force_update=false): void { if ( ! rsssl_admin_logged_in() ) { return; } if ( !$force_update && !empty($this->workable_plugins) ) { return; } //first we get all installed plugins $installed_plugins = get_plugins(); $installed_themes = wp_get_themes(); //we flatten the array $update = get_site_transient('update_themes'); //we make the installed_themes look like the installed_plugins $installed_themes = array_map( static function ($theme) use ($update) { return [ 'Name' => $theme->get('Name'), 'Slug' => $theme->get('TextDomain'), 'description' => $theme->get('Description'), 'Version' => $theme->get('Version'), 'Author' => $theme->get('Author'), 'AuthorURI' => $theme->get('AuthorURI'), 'PluginURI' => $theme->get('ThemeURI'), 'TextDomain' => $theme->get('TextDomain'), 'RequiresWP' => $theme->get('RequiresWP'), 'RequiresPHP' => $theme->get('RequiresPHP'), 'update_available' => isset($update->response[$theme->get('TextDomain')]), ]; }, $installed_themes); //we add a column type to all values in the array $installed_themes = array_map( static function ($theme) { $theme['type'] = 'theme'; return $theme; }, $installed_themes); //we add a column type to all values in the array //this resets the array keys (currently slugs) so we preserve them in the 'Slug' column. $update = get_site_transient('update_plugins'); $installed_plugins = array_map( static function ($plugin, $slug) use ($update) { $plugin['type'] = 'plugin'; $plugin['update_available'] = isset($update->response[$slug]); $plugin['Slug'] = dirname($slug); $plugin['File'] = $slug; return $plugin; }, $installed_plugins, array_keys($installed_plugins) ); //we merge the two arrays $installed_plugins = array_merge($installed_plugins, $installed_themes); //now we get the components from the file $components = $this->get_components(); //We loop through plugins and check if they are in the components array foreach ($installed_plugins as $plugin) { $slug = $plugin['Slug']; $plugin['vulnerable'] = false; if( $plugin['type'] === 'theme' ) { // we check if the theme exists as a directory $plugin['folder_exists'] = file_exists(get_theme_root() . '/' . $slug ); } if( $plugin['type'] === 'plugin' ) { //also we check if the folder exists for the plugin we added this check for later purposes $plugin['folder_exists'] = file_exists(WP_PLUGIN_DIR . '/' . dirname($slug) ); } //if there are no components, we return if ( !empty($components) ) { foreach ($components as $component) { if ($plugin['Slug'] === $component->slug) { if (!empty($component->vulnerabilities) && $plugin['folder_exists'] === true) { $plugin['vulnerable'] = true; $plugin['risk_level'] = $this->get_highest_vulnerability($component->vulnerabilities); $plugin['rss_identifier'] = $this->getLinkedUUID($component->vulnerabilities, $plugin['risk_level']); $plugin['risk_name'] = $this->risk_naming[$plugin['risk_level']]; $plugin['date'] = $this->getLinkedDate($component->vulnerabilities, $plugin['risk_level']); } } } } //we walk through the components array $this->workable_plugins[$slug] = $plugin; } //now we get the core information $core = $this->get_core(); //we create a plugin like entry for core to add to the workable_plugins array $core_plugin = [ 'Name' => 'WordPress', 'Slug' => 'wordpress', 'Version' => $core->version?? '', 'Author' => 'WordPress', 'AuthorURI' => 'https://wordpress.org/', 'PluginURI' => 'https://wordpress.org/', 'TextDomain' => 'wordpress', 'type' => 'core', ]; $core_plugin['vulnerable'] = false; //we check if there is an update available $update = get_site_transient('update_core'); if (isset($update->updates[0]->response) && $update->updates[0]->response === 'upgrade') { $core_plugin['update_available'] = true; } else { $core_plugin['update_available'] = false; } //if there are no components, we return if ( !empty($core->vulnerabilities) ) { $core_plugin['vulnerable'] = true; $core_plugin['risk_level'] = $this->get_highest_vulnerability($core->vulnerabilities); $core_plugin['rss_identifier'] = $this->getLinkedUUID($core->vulnerabilities, $core_plugin['risk_level']); $core_plugin['risk_name'] = $this->risk_naming[$core_plugin['risk_level']]; $core_plugin['date'] = $this->getLinkedDate($core->vulnerabilities, $core_plugin['risk_level']); } //we add the core plugin to the workable_plugins array $this->workable_plugins['wordpress'] = $core_plugin; } /* Public Section 3: The plugin page add-on */ /** * Callback for the manage_plugins_columns hook to add the vulnerability column * * @param $columns */ public function add_vulnerability_column($columns) { $columns['vulnerability'] = __('Vulnerabilities', 'really-simple-ssl'); return $columns; } /** * Get the data for the risk vulnerabilities table * @param $data * @return array */ public function measures_data(): array { $measures = []; $measures[] = [ 'id' => 'force_update', 'name' => __('Force update', 'really-simple-ssl'), 'value' => get_option('rsssl_force_update'), 'description' => sprintf(__('Will run a frequent update process on vulnerable components.', 'really-simple-ssl'), $this->riskNaming('l')), ]; $measures[] = [ 'id' => 'quarantine', 'name' => __('Quarantine', 'really-simple-ssl'), 'value' => get_option('rsssl_quarantine'), 'description' => sprintf(__('Components will be quarantined if the update process fails.', 'really-simple-ssl'), $this->riskNaming('m')), ]; return [ "request_success" => true, 'data' => $measures ]; } /** * Store the mesures from the api * @param $measures * * @return array */ public function measures_set($measures): array { if (!rsssl_user_can_manage()) { return []; } $risk_data = $measures['riskData'] ?? []; foreach ( $risk_data as $risk ) { if ( !isset($risk['value']) ) { continue; } update_option('rsssl_'.sanitize_title($risk['id']), $this->sanitize_measure($risk['value']), false ); } return []; } /** * Sanitize a measure * * @param string $measure * * @return mixed|string */ public function sanitize_measure($measure) { return isset($this->risk_levels[$measure]) ? $measure : '*'; } /** * Callback for the manage_plugins_custom_column hook to add the vulnerability field * * @param string $column_name * @param string $plugin_file */ public function add_vulnerability_field( string $column_name, string $plugin_file): void { if ( ( $column_name === 'vulnerability' ) ) { $this->cache_installed_plugins(); if ($this->check_vulnerability( $plugin_file ) ) { switch ( $this->check_severity( $plugin_file ) ) { case 'c': echo sprintf( '%s', 'https://really-simple-ssl.com/vulnerabilities/' . $this->getIdentifier( $plugin_file ), ucfirst( $this->risk_naming['c'] ) ); break; case 'h': echo sprintf( '%s', 'https://really-simple-ssl.com/vulnerabilities/' . $this->getIdentifier( $plugin_file ), ucfirst( $this->risk_naming['h'] ) ); break; case 'm': echo sprintf( '%s', 'https://really-simple-ssl.com/vulnerabilities/' . $this->getIdentifier( $plugin_file ), ucfirst( $this->risk_naming['m'] ) ); break; default: echo sprintf( '%s', 'https://really-simple-ssl.com/vulnerabilities/' . $this->getIdentifier( $plugin_file ), ucfirst( $this->risk_naming['l'] ) ); break; } } if ( $this->is_quarantined($plugin_file)) { echo sprintf( '%s', 'https://really-simple-ssl.com/instructions/about-vulnerabilities/#quarantine' , __("Quarantined","really-simple-ssl") ); } } } /** * Callback for the admin_enqueue_scripts hook to add the vulnerability styles * * @param $hook * * @return void */ public function add_vulnerability_styles($hook) { if ('plugins.php' !== $hook) { return; } //only on settings page $rtl = is_rtl() ? 'rtl/' : ''; $url = trailingslashit(rsssl_url) . "assets/css/{$rtl}rsssl-plugin.min.css"; $path = trailingslashit(rsssl_path) . "assets/css/{$rtl}rsssl-plugin.min.css"; if (file_exists($path)) { wp_enqueue_style('rsssl-plugin', $url, array(), rsssl_version); } } /** * checks if the plugin is vulnerable * * @param $plugin_file * * @return mixed */ private function check_vulnerability($plugin_file) { return $this->workable_plugins[ dirname($plugin_file) ]['vulnerable'] ?? false; } /** * Check if a plugin is quarantined * * @param string $plugin_file * * @return bool */ private function is_quarantined(string $plugin_file): bool { return strpos($plugin_file, '-rsssl-q-')!==false; } /** * checks if the plugin's severity closed * * @param $plugin_file * * @return mixed */ private function check_severity($plugin_file) { return $this->workable_plugins[dirname($plugin_file)]['risk_level']; } private function getIdentifier($plugin_file) { return $this->workable_plugins[dirname($plugin_file)]['rss_identifier']; } /* End of plug-in page add-on */ /* Public and private functions | Files and storage */ /** * Checks the files on age and downloads if needed. * * @return void */ public function check_files(): void { if ( ! rsssl_admin_logged_in() ) { return; } //we download the manifest file if it doesn't exist or is older than 12 hours if ($this->validate_local_file(false, true)) { if ( $this->get_file_stored_info(false, true) < time() - $this->interval ) { $this->download_manifest(); } } else { $this->download_manifest(); } //We check the core vulnerabilities and validate age and existence if ($this->validate_local_file(true, false)) { //if the file is younger than 12 hours, we don't download it again. if ($this->get_file_stored_info(true) < time() - $this->interval ) { $this->download_core_vulnerabilities(); } } else { $this->download_core_vulnerabilities(); } //We check the plugin vulnerabilities and validate age and existence if ($this->validate_local_file()) { if ($this->get_file_stored_info() < time() - $this->interval ) { $this->download_plugin_vulnerabilities(); } } else { $this->download_plugin_vulnerabilities(); } } /** * Checks if the file is valid and exists. It checks three files: the manifest, the core vulnerabilities and the plugin vulnerabilities. * * @param bool $isCore * @param bool $manifest * * @return bool */ private function validate_local_file(bool $isCore = false, bool $manifest = false): bool { if ( ! rsssl_admin_logged_in() ) { return false; } if (!$manifest) { //if we don't check for the manifest, we check the other files. $isCore ? $file = 'core.json' : $file = 'components.json'; } else { $file = 'manifest.json'; } $upload_dir = Rsssl_File_Storage::get_upload_dir(); $file = $upload_dir . '/' . $file; if (file_exists($file)) { //now we check if the file is older than 3 days, if so, we download it again $file_time = filemtime($file); $now = time(); $diff = $now - $file_time; $days = floor($diff / (60 * 60 * 24)); if ($days < 1) { return true; } } return false; } /** * Downloads bases on given url * * @param string $url * * @return mixed|null * @noinspection PhpComposerExtensionStubsInspection */ private function download(string $url) { if ( ! rsssl_admin_logged_in() ) { return null; } //now we check if the file remotely exists and then log an error if it does not. $response = wp_remote_get( $url ); if ( is_wp_error( $response ) ) { return null; } if ( wp_remote_retrieve_response_code($response) !== 200 ) { return null; } $json = wp_remote_retrieve_body($response); return json_decode($json); } private function remote_file_exists($url): bool { try { $headers = @get_headers($url); if ($headers === false) { // URL is not accessible or some error occurred return false; } // Check if the HTTP status code starts with "200" (indicating success) return strpos($headers[0], '200') !== false; // Rest of your code handling $headers goes here } catch (Exception $e) { return false; } } /** * Stores a full core or component file in the upload folder * * @param $data * @param bool $isCore * @param bool $manifest * * @return void */ private function store_file($data, bool $isCore = false, bool $manifest = false): void { if ( ! rsssl_admin_logged_in() ) { return; } //we get the upload directory $upload_dir = Rsssl_File_Storage::get_upload_dir(); if ( !$manifest ) { $file = $upload_dir . '/' . ($isCore ? 'core.json' : 'components.json'); } else { $file = $upload_dir . '/manifest.json'; } //we delete the old file if it exists if ( file_exists($file) ) { wp_delete_file($file); } //if the data is empty, we return null if ( empty($data) ) { return; } Rsssl_File_Storage::StoreFile($file, $data); $this->jsons_files_updated = true; } public function get_file_stored_info($isCore = false, $manifest = false) { if ( ! rsssl_admin_logged_in() ) { return false; } $upload_dir = Rsssl_File_Storage::get_upload_dir(); if ($manifest) { $file = $upload_dir . '/manifest.json'; if (!file_exists($file)) { return false; } return Rsssl_File_Storage::GetDate($file); } $file = $upload_dir . '/' . ($isCore ? 'core.json' : 'components.json'); if (!file_exists($file)) { return false; } return Rsssl_File_Storage::GetDate($file); } /* End of files and Storage */ /* Section for the core files Note: No manifest is needed */ /** * Downloads the vulnerabilities for the current core version. * * @return void */ protected function download_core_vulnerabilities(): void { if ( ! rsssl_admin_logged_in() ) { return; } global $wp_version; $url = self::RSSSL_SECURITY_API . 'core/WordPress.json'; $data = $this->download($url); if (!$data) { return; } $data->vulnerabilities = $this->filter_vulnerabilities($data->vulnerabilities, $wp_version, true); //first we store this as a json file in the uploads folder $this->store_file($data, true); } /* End of core files section */ /* Section for the plug-in files */ /** * Downloads the vulnerabilities for the current plugins. * * @return void */ protected function download_plugin_vulnerabilities(): void { if ( ! rsssl_admin_logged_in() ) { return; } //we get all the installed plugins $installed_plugins = get_plugins(); //first we get the manifest file $manifest = $this->getManifest(); $vulnerabilities = []; foreach ($installed_plugins as $file => $plugin) { $slug = dirname($file); $installed_plugins[ $file ]['Slug'] = $slug; $url = self::RSSSL_SECURITY_API . 'plugin/' . $slug . '.json'; //if the plugin is not in the manifest, we skip it if (!in_array($slug, (array)$manifest)) { continue; } $data = $this->download($url); if ($data !== null) { $vulnerabilities[] = $data; } } //we also do it for all the installed themes $installed_themes = wp_get_themes(); foreach ($installed_themes as $theme) { $theme = $theme->get('TextDomain'); $url = self::RSSSL_SECURITY_API . 'theme/' . $theme . '.json'; //if the plugin is not in the manifest, we skip it if (!in_array($theme, (array)$manifest)) { continue; } $data = $this->download($url); if ($data !== null) { $vulnerabilities[] = $data; } } //we make the installed_themes look like the installed_plugins $installed_themes = array_map( static function ($theme) { return [ 'Name' => $theme->get('Name'), 'Slug' => $theme->get('TextDomain'), 'description' => $theme->get('Description'), 'Version' => $theme->get('Version'), 'Author' => $theme->get('Author'), 'AuthorURI' => $theme->get('AuthorURI'), 'PluginURI' => $theme->get('ThemeURI'), 'TextDomain' => $theme->get('TextDomain'), 'RequiresWP' => $theme->get('RequiresWP'), 'RequiresPHP' => $theme->get('RequiresPHP'), ]; }, $installed_themes); //we merge $installed_plugins and $installed_themes $installed_plugins = array_merge($installed_plugins, $installed_themes); //we filter the vulnerabilities $vulnerabilities = $this->filter_active_components($vulnerabilities, $installed_plugins); $this->store_file($vulnerabilities); } /** * Loads the info from the files Note this is also being used for the themes. * * @return mixed|null */ private function get_components() { if ( ! rsssl_admin_logged_in() ) { return []; } $upload_dir = Rsssl_File_Storage::get_upload_dir(); $file = $upload_dir . '/components.json'; if (!file_exists($file)) { return []; } $components = Rsssl_File_Storage::GetFile($file); if (!is_array($components)) $components = []; return $components; } /* End of plug-in files section */ /* Section for the core files Note: No manifest is needed */ private function get_core() { if ( ! rsssl_admin_logged_in() ) { return null; } $upload_dir = Rsssl_File_Storage::get_upload_dir(); $file = $upload_dir . '/core.json'; if (!file_exists($file)) { return false; } return Rsssl_File_Storage::GetFile($file); } /* Section for the theme files */ public function enable_feedback_in_theme(): void { //Logic here for theme warning Create Callback and functions for these steps //we only display the warning for the theme page add_action('current_screen', [$this, 'show_theme_warning']); } public function show_theme_warning($hook) { $screen = get_current_screen(); if ($screen && $screen->id !== 'themes') { return; } //we add warning scripts to themes add_action('admin_enqueue_scripts', [$this, 'enqueue_theme_warning_scripts']); } public function show_inline_code($hook): void { if ($hook !== 'themes.php') { return; } //we add warning scripts to themes add_action('admin_footer', [$this, 'enqueue_theme_warning_scripts']); } public function enqueue_theme_warning_scripts(): void { //we get all components with vulnerabilities $components = $this->get_components(); ob_start();?> slug) && $component->slug === $active_plugin['Slug']) { //now we filter out the relevant vulnerabilities $component->vulnerabilities = $this->filter_vulnerabilities($component->vulnerabilities, $active_plugin['Version']); //if we have vulnerabilities, we add the component to the active components or when the plugin is closed if (count($component->vulnerabilities) > 0 || $component->status === 'closed') { $active_components[] = $component; } } } } return $active_components; } /** * This function adds the vulnerability with the highest risk to the plugins page * * @param $vulnerabilities * * @return string */ private function get_highest_vulnerability($vulnerabilities): string { //we loop through the vulnerabilities and get the highest risk level $highest_risk_level = 0; foreach ($vulnerabilities as $vulnerability) { if ($vulnerability->severity === null) { continue; } if (!isset($this->risk_levels[$vulnerability->severity])) { continue; } if ($this->risk_levels[$vulnerability->severity] > $highest_risk_level) { $highest_risk_level = $this->risk_levels[$vulnerability->severity]; } } //we now loop through the risk levels and return the highest one foreach ($this->risk_levels as $key => $value) { if ($value === $highest_risk_level) { return $key; } } return 'l'; } /* End of private functions | Filtering and walks */ /* Private functions | End of Filtering and walks */ /* Private functions | Feedback, Styles and scripts */ /** * This function shows the feedback in the plugin * * @return void */ private function enable_feedback_in_plugin(): void { //we add some styling to this page add_action('admin_enqueue_scripts', array($this, 'add_vulnerability_styles')); //we add an extra column to the plugins page add_filter('manage_plugins_columns', array($this, 'add_vulnerability_column')); add_filter('manage_plugins-network_columns', array($this, 'add_vulnerability_column')); //now we add the field to the plugins page add_action('manage_plugins_custom_column', array($this, 'add_vulnerability_field'), 10, 3); add_action('manage_plugins-network_custom_column', array($this, 'add_vulnerability_field'), 10, 3); } /* End of private functions | Feedback, Styles and scripts */ /** * This function downloads the manifest file from the api server * * @return void */ private function download_manifest(): void { if ( ! rsssl_admin_logged_in() ) { return; } $url = self::RSSSL_SECURITY_API . 'manifest.json'; $data = $this->download($url); //we convert the data to an array $data = json_decode(json_encode($data), true); //first we store this as a json file in the uploads folder $this->store_file($data, true, true); } /** * This function downloads the created file from the uploads * * @return false|void */ private function getManifest() { if ( ! rsssl_admin_logged_in() ) { return false; } $upload_dir = Rsssl_File_Storage::get_upload_dir(); $file = $upload_dir . '/manifest.json'; if (!file_exists($file)) { return false; } return Rsssl_File_Storage::GetFile($file); } private function filter_vulnerabilities($vulnerabilities, $Version, $core = false): array { $filtered_vulnerabilities = array(); foreach ($vulnerabilities as $vulnerability) { //if fixed_in contains a version, and the current version is higher than the fixed_in version, we skip it as fixed. //This needs to be a positive check only, as the fixed_in value is less accurate than the version_from and version_to values if ($vulnerability->fixed_in !== 'not fixed' && rsssl_version_compare($Version, $vulnerability->fixed_in, '>=') ) { continue; } //we have the fields version_from and version_to and their needed operators $version_from = $vulnerability->version_from; $version_to = $vulnerability->version_to; $operator_from = $vulnerability->operator_from; $operator_to = $vulnerability->operator_to; //we now check if the version is between the two versions if (rsssl_version_compare($Version, $version_from, $operator_from) && rsssl_version_compare($Version, $version_to, $operator_to)) { $filtered_vulnerabilities[] = $vulnerability; } } return $filtered_vulnerabilities; } /** * Get count of risk occurrence for each risk level * @return array */ public function count_risk_levels(): array { $plugins = $this->workable_plugins; $risk_levels = array(); foreach ($plugins as $plugin) { if (isset($plugin['risk_level'])) { if (isset($risk_levels[$plugin['risk_level']])) { $risk_levels[$plugin['risk_level']]++; } else { $risk_levels[$plugin['risk_level']] = 1; } } } return $risk_levels; } /** * check if a a dismissed notice should be reset * * @param string $risk_level * * @return bool */ private function should_reset_notification(string $risk_level): bool { $plugins = $this->workable_plugins; $vulnerable_plugins = array(); foreach ($plugins as $plugin) { if (isset($plugin['risk_level']) && $plugin['risk_level'] === $risk_level) { $vulnerable_plugins[] = $plugin['rss_identifier']; } } $dismissed_for = get_option("rsssl_{$risk_level}_notification_dismissed_for",[]); //cleanup. Check if plugins in mail_sent_for exist in the $plugins array foreach ($dismissed_for as $key => $rss_identifier) { if ( ! in_array($rss_identifier, $vulnerable_plugins) ) { unset($dismissed_for[$key]); } } $diff = array_diff($vulnerable_plugins, $dismissed_for); foreach ($diff as $rss_identifier) { if (!in_array($rss_identifier, $dismissed_for)){ $dismissed_for[] = $rss_identifier; } } //add the new plugins to the $dismissed_for array update_option("rsssl_{$risk_level}_notification_dismissed_for", $dismissed_for, false ); return !empty($diff); } /** * check if a new mail should be sent about vulnerabilities * @return bool */ private function should_send_mail(): bool { $plugins = $this->workable_plugins; $vulnerable_plugins = array(); foreach ($plugins as $plugin) { if (isset($plugin['risk_level'])) { $vulnerable_plugins[] = $plugin['rss_identifier']; } } $mail_sent_for = get_option('rsssl_vulnerability_mail_sent_for',[]); //cleanup. Check if plugins in mail_sent_for exist in the $plugins array foreach ($mail_sent_for as $key => $rss_identifier) { if ( ! in_array($rss_identifier, $vulnerable_plugins) ) { unset($mail_sent_for[$key]); } } $diff = array_diff($vulnerable_plugins, $mail_sent_for); foreach ($diff as $rss_identifier) { if (!in_array($rss_identifier, $mail_sent_for)){ $mail_sent_for[] = $rss_identifier; } } //add the new plugins to the mail_sent_for array update_option('rsssl_vulnerability_mail_sent_for',$mail_sent_for, false ); return !empty($diff); } /** * Get id by risk level * @param array $vulnerabilities * @param string $risk_level * * @return mixed|void */ private function getLinkedUUID( array $vulnerabilities, string $risk_level) { foreach ($vulnerabilities as $vulnerability) { if ($vulnerability->severity === $risk_level) { return $vulnerability->rss_identifier; } } } private function getLinkedDate($vulnerabilities, string $risk_level) { foreach ($vulnerabilities as $vulnerability) { if ($vulnerability->severity === $risk_level) { //we return the date in a readable format return date(get_option('date_format'), strtotime($vulnerability->published_date)); } } } /** * Send email warning * @return void */ public function send_vulnerability_mail(): void { if ( ! rsssl_admin_logged_in() ) { return; } //first we check if the user wants to receive emails if ( !rsssl_get_option('send_notifications_email') ) { return; } $level_for_email = rsssl_get_option('vulnerability_notification_email_admin'); if ( !$level_for_email || $level_for_email === '*' ) { return; } //now based on the risk level we send a different email $risk_levels = $this->count_risk_levels(); $total = 0; $blocks = []; foreach ($risk_levels as $risk_level => $count) { if ( $this->risk_levels[$risk_level] >= $this->risk_levels[$level_for_email] ) { $blocks[] = $this->createBlock($risk_level, $count); $total += $count; } } //date format is named month day year $mailer = new rsssl_mailer(); $mailer->subject = sprintf(__("Vulnerability Alert: %s", "really-simple-ssl"), $this->site_url() ); $mailer->title = sprintf(_n("%s: %s vulnerability found", "%s: %s vulnerabilities found", $total, "really-simple-ssl"), $this->date(), $total); $message = sprintf(__("This is a vulnerability alert from Really Simple SSL for %s. ","really-simple-ssl"), $this->domain() ); $mailer->message = $message; $mailer->warning_blocks = $blocks; if ($total > 0) { //if for some reason the total is 0, we don't send an email $mailer->send_mail(); } } /** * Create an email block by risk level * * @param string $risk_level * @param int $count * * @return array */ protected function createBlock(string $risk_level, int $count): array { $plugin_name = ''; //if we have only one plugin with this risk level, we can show the plugin name //we search it in the list if ( $count===1 ){ $plugins = $this->workable_plugins; foreach ($plugins as $plugin) { if (isset($plugin['risk_level']) && $plugin['risk_level'] === $risk_level) { $plugin_name = $plugin['Name']; } } } $risk = $this->risk_naming[$risk_level]; $title = $this->get_warning_string($risk_level, $count); $message = $count === 1 ? sprintf(__("A %s vulnerability is found in %s.", "really-simple-ssl"),$risk, $plugin_name) : sprintf(__("Multiple %s vulnerabilities have been found.", "really-simple-ssl"), $risk); return [ 'title' => $title, 'message' => $message . ' ' . __('Based on your settings, Really Simple SSL will take appropriate action, or you will need to solve it manually.','really-simple-ssl') .' '. sprintf(__('Get more information from the Really Simple SSL dashboard on %s'), $this->domain() ), 'url' => rsssl_admin_url('#settings/vulnerabilities_notifications'), ]; } /** * @param string $risk_level * @param int $count * * @return string */ public function get_warning_string( string $risk_level, int $count): string { switch ($risk_level){ case 'c': $warning = sprintf(_n('You have %s critical vulnerability', 'You have %s critical vulnerabilities', $count, 'really-simple-ssl'), $count); break; case 'h': $warning = sprintf(_n('You have %s high-risk vulnerability', 'You have %s high-risk vulnerabilities', $count, 'really-simple-ssl'), $count); break; case 'm': $warning = sprintf(_n('You have %s medium-risk vulnerability', 'You have %s medium-risk vulnerabilities', $count, 'really-simple-ssl'), $count); break; default: $warning = sprintf(_n('You have %s low-risk vulnerability', 'You have %s low-risk vulnerabilities', $count, 'really-simple-ssl'), $count); break; } return $warning; } /** * Get a nicely formatted date for today's date * * @return string */ public function date(): string { return date_i18n( get_option( 'date_format' )); } /** * Get the domain name in a clickable format * * @return string */ public function domain(): string { return ''.$this->site_url().''; } /** * Cron triggers may sometimes result in http URL's, even though SSL is enabled in Really Simple SSL. * We ensure that the URL is returned with https if SSL is enabled. * * @return string */ public function site_url(): string { $ssl_enabled = rsssl_get_option('ssl_enabled') || is_ssl(); $scheme = $ssl_enabled ? 'https' : 'http'; return get_site_url(null, '', $scheme); } } //we initialize the class //add_action('init', array(rsssl_vulnerabilities::class, 'instance')); if ( !defined('rsssl_pro') ) { $vulnerabilities = new rsssl_vulnerabilities(); } } ######################################################################################### # Functions for the vulnerability scanner # # These functions are used in the vulnerability scanner like the notices and the api's # ######################################################################################### //we clear all the cache when the vulnerability scanner is enabled function rsssl_vulnerabilities_api( array $response, string $action, $data ): array { if ( ! rsssl_user_can_manage() ) { return $response; } switch ($action) { case 'vulnerabilities_test_notification': //creating a random string based on time. $random_string = md5( time() ); update_option( 'test_vulnerability_tester', $random_string, false ); //clear admin notices cache delete_option('rsssl_admin_notices'); $response = rsssl_vulnerabilities::testGenerator(); break; case 'vulnerabilities_scan_files': $response = rsssl_vulnerabilities::firstRun(); break; case 'vulnerabilities_measures_get': $response = ( new rsssl_vulnerabilities )->measures_data(); break; case 'vulnerabilities_measures_set': $response = ( new rsssl_vulnerabilities )->measures_set($data); break; } return $response; } add_filter( 'rsssl_do_action', 'rsssl_vulnerabilities_api', 10, 3 ); /* End of Routing and API's */ PK ! z| * wordpress/block-code-execution-uploads.phpnu [ 'rsssl_code_execution_allowed', 'score' => 5, 'output' => array( 'file-not-found' => array( 'msg' => __("Could not find code execution test file.", "really-simple-ssl"), 'icon' => 'open', 'dismissible' => true, ), 'uploads-folder-not-writable' => array( 'msg' => __("Uploads folder not writable.", "really-simple-ssl"), 'icon' => 'open', 'dismissible' => true, ), 'could-not-create-test-file' => array( 'msg' => __("Could not copy code execution test file.", "really-simple-ssl"), 'icon' => 'open', 'dismissible' => true, ), ), ); if ( rsssl_get_server() === 'nginx') { $notices['code-execution-uploads-nginx'] = array( 'callback' => 'rsssl_code_execution_allowed', 'score' => 5, 'output' => array( 'true' => array( 'msg' => __("The code to block code execution in the uploads folder cannot be added automatically on nginx. Add the following code to your nginx.conf file:", "really-simple-ssl") . "location ~* /uploads/.*\.php$ {' . "
";
$code .= ' return 503;' . "
";
$code .= '}
' . "