PluginProbe ʕ •ᴥ•ʔ
Really Simple Security – Simple and Performant Security (formerly Really Simple SSL) / 9.5.11
Really Simple Security – Simple and Performant Security (formerly Really Simple SSL) v9.5.11
9.5.11 9.5.10.1 9.5.10 trunk 9.4.0 9.4.1 9.4.2 9.4.3 9.5.0 9.5.0.1 9.5.0.2 9.5.1 9.5.2 9.5.2.2 9.5.2.3 9.5.3 9.5.3.1 9.5.3.2 9.5.4 9.5.5 9.5.6 9.5.7 9.5.8 9.5.9
really-simple-ssl / security / tests.php
really-simple-ssl / security Last commit date
includes 4 weeks ago server 4 weeks ago tests 4 weeks ago wordpress 4 weeks ago class-rsssl-htaccess-file-manager.php 4 weeks ago cron.php 4 weeks ago deactivate-integration.php 4 weeks ago firewall-manager.php 4 weeks ago functions.php 4 weeks ago index.php 4 weeks ago integrations.php 4 weeks ago notices.php 4 weeks ago security.php 4 weeks ago sync-settings.php 4 weeks ago tests.php 4 weeks ago
tests.php
507 lines
1 <?php
2 defined( 'ABSPATH' ) or die();
3
4 /**
5 * Check if XML-RPC requests are allowed on this site
6 * POST a request, if the request returns a 200 response code the request is allowed
7 */
8 function rsssl_xmlrpc_allowed()
9 {
10 $allowed = get_transient( 'rsssl_xmlrpc_allowed' );
11 if ( !$allowed ) {
12 $allowed = 'allowed';
13 if ( function_exists( 'curl_init' ) ) {
14 //set a default, in case of time out
15 set_transient( 'rsssl_xmlrpc_allowed', 'no-response', DAY_IN_SECONDS );
16 $url = site_url() . '/xmlrpc.php';
17 $ch = curl_init($url);
18 // XML-RPC listMethods call
19 // Valid XML-RPC request
20 $xmlstring = '<?xml version="1.0" encoding="utf-8"?>
21 <methodCall>
22 <methodName>system.listMethods</methodName>
23 <params></params>
24 </methodCall>';
25
26 curl_setopt($ch, CURLOPT_POST, 1);
27 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
28 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
29 curl_setopt($ch, CURLOPT_HEADER, 1);
30 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
31 // Post string
32 curl_setopt($ch, CURLOPT_POSTFIELDS, $xmlstring );
33 curl_setopt($ch, CURLOPT_TIMEOUT, 3); //timeout in seconds
34 curl_exec($ch);
35 $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
36 if ($response_code === 200) {
37 $allowed = 'allowed';
38 } else {
39 $allowed = 'not-allowed';
40 }
41 }
42 set_transient( 'rsssl_xmlrpc_allowed', $allowed, DAY_IN_SECONDS );
43 }
44 return $allowed === 'allowed';
45 }
46
47 /**
48 * @return bool
49 * Test if HTTP methods are allowed
50 */
51 function rsssl_http_methods_allowed()
52 {
53 if ( ! rsssl_user_can_manage() ) {
54 return false;
55 }
56
57 $methods = [
58 'GET',
59 'POST',
60 'PUT',
61 'DELETE',
62 'HEAD',
63 'OPTIONS',
64 'CONNECT',
65 'TRACE',
66 'TRACK',
67 'PATCH',
68 'COPY',
69 'LINK',
70 'UNLINK',
71 'PURGE',
72 'LOCK',
73 'UNLOCK',
74 'PROPFIND',
75 'VIEW',
76 ];
77 $tested = get_option( 'rsssl_http_methods_allowed' );
78
79 #if the option was reset, start couting from 0
80 if ( !$tested ){
81 delete_option('rsssl_last_tested_http_method');
82 }
83 $last_tested = get_option('rsssl_last_tested_http_method', -1);
84
85 $nr_of_tests_on_batch = 4;
86 if ( !$tested || ( $last_tested < count($methods)-1 ) ) {
87 $tested = get_option( 'rsssl_http_methods_allowed', [] );
88 $next_test = $last_tested+1;
89
90 $test_methods = array_slice($methods, $next_test, $nr_of_tests_on_batch, true);
91 update_option('rsssl_last_tested_http_method', $last_tested+$nr_of_tests_on_batch, false);
92
93 foreach ( $test_methods as $method ) {
94 #set a default, in case a timeout occurs
95 $tested['not-allowed'][] = $method;
96 update_option( 'rsssl_http_methods_allowed', $tested, false );
97
98 if ( function_exists( 'curl_init' ) ) {
99
100 $ch = curl_init();
101 curl_setopt( $ch, CURLOPT_URL, site_url() );
102 curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $method );
103 curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
104 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
105 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
106 curl_setopt( $ch, CURLOPT_HEADER, true );
107 curl_setopt( $ch, CURLOPT_NOBODY, true );
108 curl_setopt( $ch, CURLOPT_VERBOSE, true );
109 curl_setopt( $ch, CURLOPT_TIMEOUT, 3 ); //timeout in seconds
110 curl_exec( $ch );
111
112 #if there are no errors, the request is allowed
113 if ( ! curl_errno( $ch ) ) {
114 //remove the not allowed entry
115 $not_allowed_index = array_search( $method, $tested['not-allowed'], true );
116 if ( $not_allowed_index !== false ) {
117 unset( $tested['not-allowed'][ $not_allowed_index ] );
118 }
119 $tested['allowed'][] = $method;
120 }
121 curl_close( $ch );
122 update_option( 'rsssl_http_methods_allowed', $tested, false );
123 }
124 }
125 }
126
127
128 if ( !empty($tested['allowed'])) {
129 return true;
130 }
131 return false;
132 }
133
134 /**
135 * @return bool
136 *
137 * Check if DB has default wp_ prefix
138 */
139
140 function rsssl_is_default_wp_prefix() {
141 global $wpdb;
142 if ( $wpdb->prefix === 'wp_' ) {
143 return true;
144 }
145 return false;
146 }
147
148 function rsssl_xmlrpc_enabled(){
149 return apply_filters('xmlrpc_enabled', true );
150 }
151
152 /**
153 * @return bool
154 *
155 * Check if user admin exists
156 */
157
158 function rsssl_has_admin_user() {
159 if ( !rsssl_user_can_manage() ) {
160 return false;
161 }
162 //transient is more persistent then wp cache set
163 $count = get_transient('rsssl_admin_user_count');
164 //get from cache, but not on settings page
165 if ( $count === false || RSSSL()->admin->is_settings_page() ){
166 //use wp_cache_get to prevent duplicate queries in one pageload
167 $count = wp_cache_get('rsssl_admin_user_count', 'really-simple-ssl');
168 if ( $count === false ) {
169 global $wpdb;
170 $count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->base_prefix}users WHERE user_login = 'admin'" );
171 wp_cache_set('rsssl_admin_user_count', $count, 'really-simple-ssl', HOUR_IN_SECONDS );
172 }
173 set_transient('rsssl_admin_user_count', $count, HOUR_IN_SECONDS);
174 }
175
176 return $count > 0;
177 }
178
179 /**
180 * Check if username is valid for use
181 * @return bool
182 */
183 function rsssl_new_username_valid(): bool {
184
185 $new_user_login = trim(sanitize_user(rsssl_get_option('new_admin_user_login')));
186 if ( $new_user_login === 'admin' ) {
187 return false;
188 }
189 $user_exists = get_user_by('login', $new_user_login);
190 if ( $user_exists ) {
191 return false;
192 }
193
194 return is_string($new_user_login) && strlen($new_user_login)>2;
195 }
196
197 /**
198 * For backward compatibility we need to wrap this function, as older versions do not have this function (<5.6)
199 * @return bool
200 */
201 function rsssl_wp_is_application_passwords_available(){
202 if ( function_exists('wp_is_application_passwords_available') ) {
203 return wp_is_application_passwords_available();
204 }
205
206 return false;
207 }
208
209 /**
210 * Get users where display name is the same as login
211 *
212 * @param bool $return_users
213 *
214 * @return bool | array
215 *
216 */
217
218 function rsssl_get_users_where_display_name_is_login( $return_users=false ) {
219 $found_users = [];
220 $users = get_transient('rsssl_admin_users');
221 if ( !$users ){
222 $args = array(
223 'role' => 'administrator',
224 );
225 $users = get_users( $args );
226 set_transient('rsssl_admin_users', $users, HOUR_IN_SECONDS);
227 }
228
229 foreach ( $users as $user ) {
230 if ($user->display_name === $user->user_login) {
231 $found_users[] = $user->user_login;
232 }
233 }
234
235 // Maybe return users in integration
236 if ( $return_users ) {
237 return $found_users;
238 }
239
240 if ( count($found_users) > 0 ) {
241 return true;
242 }
243
244 return false;
245 }
246
247 /**
248 * Check if debugging in WordPress is enabled
249 *
250 * @return bool
251 */
252 function rsssl_is_debugging_enabled() {
253 return ( defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG );
254 }
255
256 function rsssl_debug_log_value_is_default(){
257 $value = rsssl_get_debug_log_value();
258
259 return (string) $value === 'true';
260 }
261
262 /**
263 * Get value of debug_log constant
264 * Please note that for a value 'true', you should check for the string value === 'true'
265 * @return bool|string
266 */
267
268 function rsssl_get_debug_log_value(){
269 if ( !defined('WP_DEBUG_LOG')) {
270 return false;
271 }
272 $wpconfig_path = rsssl_find_wp_config_path();
273
274 if ( !$wpconfig_path ) {
275 return false;
276 }
277 $wpconfig = file_get_contents( $wpconfig_path );
278
279 // Get WP_DEBUG_LOG declaration
280 $regex = "/^\s*define\([ ]{0,2}[\'|\"]WP_DEBUG_LOG[\'|\"][ ]{0,2},[ ]{0,2}(.*)[ ]{0,2}\);/m";
281 preg_match( $regex, $wpconfig, $matches );
282 if ($matches && isset($matches[1]) ){
283 return trim($matches[1]);
284 }
285
286 return false;
287 }
288
289 /**
290 * Check if the debug log file exists in the default location, and if it contains our bogus info
291 * @return bool
292 *
293 */
294 function rsssl_debug_log_file_exists_in_default_location(){
295 $default_file = trailingslashit(WP_CONTENT_DIR).'debug.log';
296 if ( !file_exists($default_file) ) {
297 return false;
298 }
299 //limit max length of string to 500
300 $content = file_get_contents($default_file, false, null, 0, 500 );
301 return trim( $content ) !== 'Access denied';
302 }
303
304 /**
305 * @return string
306 * Test if code execution is allowed in /uploads folder
307 */
308 function rsssl_code_execution_allowed()
309 {
310 $code_execution_allowed = get_transient('rsssl_code_execution_allowed_status');
311 if ( !$code_execution_allowed ) {
312 $upload_dir = wp_get_upload_dir();
313 //set a default, in case of timeouts
314 $code_execution_allowed = 'not-allowed';
315 set_transient( 'rsssl_code_execution_allowed_status', $code_execution_allowed, DAY_IN_SECONDS );
316
317 $test_file = $upload_dir['basedir'] . '/' . 'code-execution.php';
318 if ( is_writable($upload_dir['basedir'] ) && ! file_exists( $test_file ) ) {
319 try {
320 copy( rsssl_path . 'security/tests/code-execution.php', $test_file );
321 } catch (Exception $e) {
322 $code_execution_allowed = 'not-allowed';
323 }
324 }
325
326 if ( file_exists( $test_file ) ) {
327 $uploads = wp_upload_dir();
328 $upload_url = trailingslashit($uploads['baseurl']).'code-execution.php';
329 $response = wp_remote_get($upload_url);
330 if ( !is_wp_error($response) ) {
331 if ( is_array( $response ) ) {
332 $status = wp_remote_retrieve_response_code( $response );
333 $web_source = wp_remote_retrieve_body( $response );
334 }
335
336 if ( $status != 200 ) {
337 //Could not connect to website
338 $code_execution_allowed = 'not-allowed';
339 } elseif ( strpos( $web_source, "RSSSL CODE EXECUTION MARKER" ) === false ) {
340 //Mixed content fixer marker not found in the websource
341 $code_execution_allowed = 'not-allowed';
342 } else {
343 $code_execution_allowed = 'allowed';
344 }
345 } else {
346 $code_execution_allowed = 'not-allowed';
347 }
348 }
349
350 //clean up file again
351 if ( file_exists($test_file) ) {
352 unlink($test_file);
353 }
354 set_transient('rsssl_code_execution_allowed_status', $code_execution_allowed, DAY_IN_SECONDS);
355 }
356
357 return $code_execution_allowed === 'allowed';
358 }
359
360 /**
361 * Test if directory indexing is allowed
362 * We assume allowed if test is not possible due to restrictions. Only an explicity 403 on the response results in "forbidden".
363 * On non htaccess servers, the default is non indexing, so we return forbidden.
364 *
365 * @return bool
366 */
367 function rsssl_directory_indexing_allowed() {
368 $status = get_transient('rsssl_directory_indexing_status');
369 if ( !$status ) {
370 if ( !rsssl_uses_htaccess() ) {
371 $status = 'forbidden';
372 } else {
373 $status = 'allowed';
374 //set a default, in case of timeouts
375 set_transient( 'rsssl_directory_indexing_status', $status, DAY_IN_SECONDS );
376
377 try {
378 $test_folder = 'indexing-test';
379 $test_dir = trailingslashit(ABSPATH) . $test_folder;
380 if ( ! is_dir( $test_dir ) ) {
381 mkdir( $test_dir, 0755 );
382 }
383
384 $response = wp_remote_get(trailingslashit( site_url($test_folder) ) );
385 if ( is_dir( $test_dir ) ) {
386 rmdir( $test_dir );
387 }
388
389 // WP_Error doesn't contain response code, return false
390 if ( !is_wp_error( $response ) ) {
391 $response_code = $response['response']['code'];
392 if ( $response_code === 403 ) {
393 $status = 'forbidden';
394 }
395 }
396 } catch( Exception $e ) {
397
398 }
399 }
400 set_transient('rsssl_directory_indexing_status', $status, DAY_IN_SECONDS );
401 }
402
403 return $status !== 'forbidden';
404 }
405
406 /**
407 * Check if file editing is allowed
408 * @return bool
409 */
410 function rsssl_file_editing_allowed()
411 {
412 if ( function_exists('wp_is_block_theme') && wp_is_block_theme() ) {
413 return false;
414 }
415 return !defined('DISALLOW_FILE_EDIT' ) || !DISALLOW_FILE_EDIT;
416 }
417
418 /**
419 * Check if user registration is allowed
420 * @return bool
421 */
422 function rsssl_user_registration_allowed()
423 {
424 return get_option( 'users_can_register' );
425 }
426
427 /**
428 * Check if page source contains WordPress version information
429 * @return bool
430 */
431
432 function rsssl_src_contains_wp_version() {
433 $result = get_option('rsssl_wp_version_detected' );
434 if ( $result===false ) {
435 $result = 'no-response';
436 update_option( 'rsssl_wp_version_detected', 'no-response', false );
437 try {
438 $wp_version = get_bloginfo( 'version' );
439 $web_source = "";
440 $response = wp_remote_get( home_url() );
441 if ( ! is_wp_error( $response ) ) {
442 if ( is_array( $response ) ) {
443 $status = wp_remote_retrieve_response_code( $response );
444 $web_source = wp_remote_retrieve_body( $response );
445 }
446
447 if ( $status != 200 ) {
448 $result = 'no-response';
449 } elseif ( strpos( $web_source, 'ver='.$wp_version ) === false ) {
450 $result = 'not-found';
451 } else {
452 $result = 'found';
453 }
454 }
455 update_option( 'rsssl_wp_version_detected', $result, false );
456 } catch(Exception $e) {
457 update_option( 'rsssl_wp_version_detected', 'no-response', false );
458 }
459 }
460 return $result==='found';
461 }
462
463 /**
464 * Count the number of open hardening features
465 * @return int
466 */
467 function rsssl_count_open_hardening_features() {
468 $open = 0;
469 $fields = rsssl_fields( false );
470
471 // Filter out unused fields
472 $recommended_hardening_fields = array_filter($fields, function($field){
473 return isset($field['recommended']) && $field['recommended'];
474 });
475
476 // Create $hardening_options dynamically based on recommended field IDs
477 $hardening_options = array_map(function($field) {
478 return $field['id'];
479 }, $recommended_hardening_fields);
480
481 foreach ( $hardening_options as $option ) {
482
483 // Get the field
484 $field = array_filter( $fields, function ( $f ) use ( $option ) {
485 return $f['id'] === $option;
486 } );
487
488 if ( ! empty( $field ) ) {
489 $field = reset( $field );
490 // Apply the rsssl_disable_fields filter
491 $field = apply_filters( 'rsssl_field', $field, $field['id'] );
492
493 // Check if the option is not set to true and the field is not disabled
494 if ( rsssl_get_option( $option ) !== true &&
495 ( ! isset( $field['disabled'] ) || $field['disabled'] !== true ) &&
496 ( ! isset( $field['value'] ) || $field['value'] !== true ) ) {
497 $open ++;
498 }
499 }
500 }
501
502 return $open;
503 }
504
505 function rsssl_has_open_hardening_features() {
506 return rsssl_count_open_hardening_features() > 0;
507 }