PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.0.1
Defender Security – Malware Scanner, Login Security & Firewall v3.0.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / component / security-tweaks / servers / apache.php

apache.php in Defender Security – Malware Scanner, Login Security & Firewall 3.0.1, at src/component/security-tweaks/servers/apache.php

588 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WP_Defender\Component\Security_Tweaks\Servers;
4
5 use WP_Error;
6
7 class Apache {
8
9 /**
10 * Exclude file paths.
11 *
12 * @var array
13 */
14 public $new_htaccess_config = array();
15
16 /**
17 * The htaccess inside wp-content.
18 *
19 * @var string
20 */
21 public $contentdir_path = null;
22
23 /**
24 * The htaccess path inside wp-includes.
25 *
26 * @var null
27 */
28 public $includedir_path = null;
29
30 /**
31 * Service type.
32 *
33 * @var string
34 */
35 private $type = null;
36
37 public function __construct( $type ) {
38 $this->type = $type;
39 }
40
41 /**
42 * Check whether the issue has been resolved or not.
43 *
44 * @return bool
45 */
46 public function check() {
47 $url = '';
48
49 if ( 'prevent-php-executed' === $this->type ) {
50 $dir = wp_upload_dir();
51 $url = $dir['baseurl'] . '/wp-defender/index.php';
52 }
53
54 if ( 'protect-information' === $this->type ) {
55 $url = defender_asset_url( '/languages/wpdef-default.pot' );
56 }
57
58 return Server::ping_test_failed( $url );
59 }
60
61 /**
62 * @param string $file_path
63 *
64 * @return WP_Error
65 * @since 2.8.3
66 */
67 public function get_error_by_file_path( $file_path ) {
68 return new WP_Error(
69 'defender_file_not_writable',
70 sprintf(
71 /* translators: %s - file path */
72 __( 'The file %s is not writable', 'wpdef' ),
73 $file_path
74 )
75 );
76 }
77
78 /**
79 * Process the rule.
80 *
81 * @return bool|WP_Error
82 */
83 public function process() {
84 if ( 'protect-information' === $this->type ) {
85 $ht_access_path = ABSPATH . '.htaccess';
86 // Create a htaccess-file if it doesn't exist.
87 if ( ! is_file( $ht_access_path )
88 && false === (bool) file_put_contents( $ht_access_path, '', LOCK_EX )
89 ) {
90 return $this->get_error_by_file_path( $ht_access_path );
91 }
92 // If the htaccess-file is existed.
93 if ( ! is_writable( $ht_access_path ) ) {
94 return $this->get_error_by_file_path( $ht_access_path );
95 }
96
97 $ht_access_config = file( $ht_access_path );
98 $ht_access_config = array_map( 'trim', $ht_access_config );
99 $rules = $this->get_rules();
100 $contains_search = array_diff( array_map( 'trim', $rules ), $ht_access_config );
101
102 if ( count( $contains_search ) < count( $rules ) ) {
103 // Search the wrapper block.
104 $ht_access_content = file_get_contents( $ht_access_path );
105 preg_match( '/## WP Defender(.*?)## WP Defender - End ##/s', $ht_access_content, $matches );
106
107 if ( count( $matches ) ) {
108 // Remove the whole parts as its partial done.
109 $ht_access_content = str_replace( $matches[0], '', $ht_access_content );
110 $ht_access_config = explode( PHP_EOL, $ht_access_content );
111 $ht_access_config = array_merge( $ht_access_config, $rules );
112
113 return (bool) file_put_contents( $ht_access_path, implode( PHP_EOL, $ht_access_config ), LOCK_EX );
114 }
115 }
116
117 if ( 0 === count( $contains_search ) || ( count( $contains_search ) === count( $rules ) ) ) {
118 $ht_access_config = array_merge( $ht_access_config, $rules );
119
120 return (bool) file_put_contents( $ht_access_path, implode( PHP_EOL, $ht_access_config ), LOCK_EX );
121 }
122 }
123
124 if ( 'prevent-php-executed' === $this->type ) {
125 if ( ! in_array( Server::get_current_server(), array( 'apache', 'litespeed' ), true ) ) {
126 return new WP_Error(
127 'defender_unable_to_apply_rules',
128 __( 'The rules can\'t be applied. This can be either because your host doesn\'t allow editing the file, or you\'ve selected the wrong server type.', 'wpdef' )
129 );
130 }
131
132 $response = $this->protect_content_directory();
133
134 if ( is_wp_error( $response ) ) {
135 return $response;
136 }
137
138 $response = $this->protect_includes_directory();
139
140 if ( is_wp_error( $response ) ) {
141 return $response;
142 }
143
144 $response = $this->protect_uploads_directory();
145
146 if ( is_wp_error( $response ) ) {
147 return $response;
148 }
149
150 $settings = array(
151 'new_htaccess_config' => $this->get_new_htaccess_config(),
152 );
153
154 update_site_option( "defender_security_tweeks_{$this->type}", $settings );
155
156 return true;
157 }
158 }
159
160 /**
161 * Revert the rules.
162 *
163 * @return bool|WP_Error
164 */
165 public function revert() {
166 if ( 'protect-information' === $this->type ) {
167 $ht_access_path = ABSPATH . '.htaccess';
168 // Quick exit if the file does not exist.
169 if ( ! is_file( $ht_access_path ) ) {
170 return true;
171 }
172
173 if ( ! is_writable( $ht_access_path ) ) {
174 return $this->get_error_by_file_path( $ht_access_path );
175 }
176
177 $ht_access_config = file_get_contents( $ht_access_path );
178 $rules = $this->get_rules();
179
180 preg_match_all( '/## WP Defender(.*?)## WP Defender - End ##/s', $ht_access_config, $matches );
181
182 if ( is_array( $matches ) && count( $matches ) > 0 ) {
183 $ht_access_config = str_replace( implode( '', $matches[0] ), '', $ht_access_config );
184 } else {
185 $ht_access_config = str_replace( implode( '', $rules ), '', $ht_access_config );
186 }
187
188 $ht_access_config = trim( $ht_access_config );
189
190 return (bool) file_put_contents( $ht_access_path, $ht_access_config, LOCK_EX );
191 }
192
193 if ( 'prevent-php-executed' === $this->type ) {
194 // Content-folder.
195 $response = $this->unprotect_content_directory();
196 if ( is_wp_error( $response ) ) {
197 return wp_send_json_error(
198 array( 'message' => $response->get_error_message() )
199 );
200 }
201 // Includes-folder.
202 $response = $this->unprotect_includes_directory();
203 if ( is_wp_error( $response ) ) {
204 return wp_send_json_error(
205 array( 'message' => $response->get_error_message() )
206 );
207 }
208 // Uploads-folder.
209 $response = $this->unprotect_upload_directory();
210 if ( is_wp_error( $response ) ) {
211 return wp_send_json_error(
212 array( 'message' => $response->get_error_message() )
213 );
214 }
215
216 return delete_site_option( "defender_security_tweeks_{$this->type}" );
217 }
218 }
219
220 /**
221 * Get Apache rule depending on the version.
222 *
223 * @return array
224 */
225 public function get_rules() {
226 $rules = array(
227 PHP_EOL . '## WP Defender - Prevent information disclosure ##' . PHP_EOL,
228 '<FilesMatch "\.(md|exe|sh|bak|inc|pot|po|mo|log|sql)$">' . PHP_EOL .
229 'Require all denied' . PHP_EOL .
230 '</FilesMatch>' . PHP_EOL,
231 '<Files robots.txt>' . PHP_EOL .
232 'Require all granted' . PHP_EOL .
233 '</Files>' . PHP_EOL,
234 '<Files ads.txt>' . PHP_EOL .
235 'Require all granted' . PHP_EOL .
236 '</Files>' . PHP_EOL,
237 '## WP Defender - End ##',
238 );
239
240 if ( version_compare( $this->get_version(), '2.4', '<' ) ) {
241 $rules = array(
242 PHP_EOL . '## WP Defender - Prevent information disclosure ##' . PHP_EOL,
243 '<FilesMatch "\.(md|exe|sh|bak|inc|pot|po|mo|log|sql)$">' . PHP_EOL .
244 'Order allow,deny' . PHP_EOL .
245 'Deny from all' . PHP_EOL .
246 '</FilesMatch>' . PHP_EOL,
247 '<Files robots.txt>' . PHP_EOL .
248 'Allow from all' . PHP_EOL .
249 '</Files>' . PHP_EOL,
250 '<Files ads.txt>' . PHP_EOL .
251 'Allow from all' . PHP_EOL .
252 '</Files>' . PHP_EOL,
253 '## WP Defender - End ##',
254 );
255 }
256
257 return $rules;
258 }
259
260 /**
261 * Get Apache rule depending on the version for instruction on browser.
262 *
263 * @return string
264 */
265 public function get_rules_for_instruction() {
266 $rules = '';
267
268 if ( 'prevent-php-executed' === $this->type ) {
269 $rules = '## WP Defender - Protect PHP Executed ##' . PHP_EOL;
270 $rules .= PHP_EOL;
271 $rules .= '<Files *.php>' . PHP_EOL;
272 $rules .= 'Require all denied' . PHP_EOL;
273 $rules .= '</Files>' . PHP_EOL;
274 $rules .= PHP_EOL;
275 $rules .= '## WP Defender - End ##' . PHP_EOL;
276
277 if ( version_compare( $this->get_version(), '2.4', '<' ) ) {
278 $rules = '## WP Defender - Protect PHP Executed ##' . PHP_EOL;
279 $rules .= PHP_EOL;
280 $rules .= '<Files *.php>' . PHP_EOL;
281 $rules .= 'Order allow,deny' . PHP_EOL;
282 $rules .= 'Deny from all' . PHP_EOL;
283 $rules .= '</Files>' . PHP_EOL;
284 $rules .= PHP_EOL;
285 $rules .= '## WP Defender - End ##' . PHP_EOL;
286 }
287 }
288
289 if ( 'protect-information' === $this->type ) {
290 $rules = '## WP Defender - Prevent information disclosure ##' . PHP_EOL;
291 $rules .= PHP_EOL;
292 $rules .= '<FilesMatch "\.(md|exe|sh|bak|inc|pot|po|mo|log|sql)$">' . PHP_EOL;
293 $rules .= 'Require all denied' . PHP_EOL;
294 $rules .= '</FilesMatch>' . PHP_EOL;
295 $rules .= PHP_EOL;
296 $rules .= '<Files robots.txt>' . PHP_EOL;
297 $rules .= 'Require all granted' . PHP_EOL;
298 $rules .= '</Files>' . PHP_EOL;
299 $rules .= PHP_EOL;
300 $rules .= '<Files ads.txt>' . PHP_EOL;
301 $rules .= 'Require all granted' . PHP_EOL;
302 $rules .= '</Files>' . PHP_EOL;
303 $rules .= PHP_EOL;
304 $rules .= '## WP Defender - End ##';
305
306 if ( version_compare( $this->get_version(), '2.4', '>' ) ) {
307 $rules = '## WP Defender - Prevent information disclosure ##' . PHP_EOL;
308 $rules .= PHP_EOL;
309 $rules .= '<FilesMatch "\.(md|exe|sh|bak|inc|pot|po|mo|log|sql)$">' . PHP_EOL;
310 $rules .= 'Order allow,deny' . PHP_EOL;
311 $rules .= 'Deny from all' . PHP_EOL;
312 $rules .= '</FilesMatch>' . PHP_EOL;
313 $rules .= PHP_EOL;
314 $rules .= '<Files robots.txt>' . PHP_EOL;
315 $rules .= 'Allow from all' . PHP_EOL;
316 $rules .= '</Files>' . PHP_EOL;
317 $rules .= PHP_EOL;
318 $rules .= '<Files ads.txt>' . PHP_EOL;
319 $rules .= 'Allow from all' . PHP_EOL;
320 $rules .= '</Files>' . PHP_EOL;
321 $rules .= PHP_EOL;
322 $rules .= '## WP Defender - End ##';
323 }
324 }
325
326 return $rules;
327 }
328
329 /**
330 * Determine the Apache version.
331 * Most web servers have apache_get_version disabled, so we just get a simple curl of the headers.
332 *
333 * @return string
334 */
335 public function get_version() {
336 if ( ! function_exists( 'apache_get_version' ) ) {
337 // The default support is 2.2.
338 $version = '2.2';
339 $url = home_url();
340 $apache_version = get_site_transient( 'defender_apache_version' );
341
342 if ( ! is_array( $apache_version ) ) {
343 $apache_version = array();
344 }
345
346 if ( isset( $apache_version[ $url ] ) && ! empty( $apache_version[ $url ] ) ) {
347 return strtolower( $apache_version[ $url ] );
348 }
349 // The default support is 2.2.
350 $apache_version[ $url ] = $version;
351
352 if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
353 $server = explode( ' ', $_SERVER['SERVER_SOFTWARE'] );
354 if ( is_array( $server ) && count( $server ) > 1 ) {
355 $server = $server[0];
356 $server = explode( '/', $server );
357 if ( is_array( $server ) && count( $server ) > 1 ) {
358 $version = $server[1];
359 $apache_version[ $url ] = $version;
360 }
361 }
362 }
363
364 set_site_transient( 'defender_apache_version', $apache_version, 3600 );
365 } else {
366 $version = apache_get_version();
367 $version = explode( '/', $version );
368 $version = ! empty( $version[1] ) ? $version[1] : $version[0];
369 }
370 return $version;
371 }
372
373 /**
374 * Protect content directory.
375 *
376 * @return void|WP_Error
377 */
378 public function protect_content_directory() {
379 $ht_access_path = $this->contentdir_path;
380
381 if ( empty( $ht_access_path ) ) {
382 $ht_access_path = WP_CONTENT_DIR . '/' . '.htaccess';
383 }
384 // Create a htaccess-file if it doesn't exist.
385 if ( ! is_file( $ht_access_path )
386 && false === (bool) file_put_contents( $ht_access_path, '', LOCK_EX )
387 ) {
388 return $this->get_error_by_file_path( $ht_access_path );
389 }
390 // If the htaccess-file is existed.
391 if ( ! is_writable( $ht_access_path ) ) {
392 return $this->get_error_by_file_path( $ht_access_path );
393 }
394
395 $exists_rules = $this->cleanup_old_rules( file_get_contents( $ht_access_path ) );
396 $rule = array(
397 '## WP Defender - Protect PHP Executed ##',
398 '<Files *.php>',
399 $this->generate_htaccess_rule( false ),
400 '</Files>',
401 );
402
403 $rule[] = '## WP Defender - End ##';
404 file_put_contents( $ht_access_path, $exists_rules . implode( PHP_EOL, $rule ), LOCK_EX );
405 }
406
407 /**
408 * Protect includes directory.
409 *
410 * @return void|WP_Error
411 */
412 public function protect_includes_directory() {
413 $ht_access_path = $this->includedir_path;
414
415 if ( empty( $ht_access_path ) ) {
416 $ht_access_path = ABSPATH . WPINC . '/' . '.htaccess';
417 }
418 // Create a htaccess-file if it doesn't exist.
419 if ( ! is_file( $ht_access_path )
420 && false === (bool) file_put_contents( $ht_access_path, '', LOCK_EX )
421 ) {
422 return $this->get_error_by_file_path( $ht_access_path );
423 }
424 // If the htaccess-file is existed.
425 if ( ! is_writable( $ht_access_path ) ) {
426 return $this->get_error_by_file_path( $ht_access_path );
427 }
428
429 $exists_rules = $this->cleanup_old_rules( file_get_contents( $ht_access_path ) );
430
431 $rule = array(
432 '## WP Defender - Protect PHP Executed ##',
433 '<Files *.php>',
434 $this->generate_htaccess_rule( false ),
435 '</Files>',
436 '<Files wp-tinymce.php>',
437 $this->generate_htaccess_rule( true ),
438 '</Files>',
439 '<Files ms-files.php>',
440 $this->generate_htaccess_rule( true ),
441 '</Files>',
442 '## WP Defender - End ##',
443 );
444
445 file_put_contents( $ht_access_path, $exists_rules . implode( PHP_EOL, $rule ), LOCK_EX );
446 }
447
448 /**
449 * Return the correct apache rules for allow/deny.
450 *
451 * @return string
452 */
453 protected function generate_htaccess_rule( $allow = true ) {
454 if ( version_compare( $this->get_version(), 2.4, '>=' ) ) {
455 if ( $allow ) {
456 return 'Require all granted';
457 } else {
458 return 'Require all denied';
459 }
460 } else {
461 if ( $allow ) {
462 return 'Allow from all';
463 } else {
464 return 'Order allow,deny' . PHP_EOL .
465 'Deny from all';
466 }
467 }
468 }
469
470 /**
471 * Protect uploads directory.
472 * This only when user provide a custom uploads.
473 *
474 * @return void
475 */
476 public function protect_uploads_directory() {
477 if ( defined( 'UPLOADS' ) ) {
478 $this->contentdir_path = ABSPATH . UPLOADS . '/' . '.htaccess';
479
480 return $this->protect_content_directory();
481 }
482 }
483
484 /**
485 * Unprotect content directory.
486 *
487 * @return void|WP_Error
488 */
489 public function unprotect_content_directory() {
490 $ht_access_path = $this->contentdir_path;
491
492 if ( empty( $ht_access_path ) ) {
493 $ht_access_path = WP_CONTENT_DIR . '/' . '.htaccess';
494 }
495 // Quick exit if the file does not exist.
496 if ( ! is_file( $ht_access_path ) ) {
497 return;
498 }
499 // If the htaccess-file is existed.
500 if ( ! is_writable( $ht_access_path ) ) {
501 return $this->get_error_by_file_path( $ht_access_path );
502 }
503
504 $ht_config = $this->cleanup_old_rules( file_get_contents( $ht_access_path ) );
505 $ht_config = trim( $ht_config );
506
507 file_put_contents( $ht_access_path, trim( $ht_config ), LOCK_EX );
508 }
509
510 /**
511 * Unprotect includes-directory.
512 * Todo: can be combined with unprotect_content_directory()?
513 *
514 * @return void|WP_Error
515 */
516 public function unprotect_includes_directory() {
517 $ht_access_path = $this->includedir_path;
518
519 if ( empty( $ht_access_path ) ) {
520 $ht_access_path = ABSPATH . WPINC . '/' . '.htaccess';
521 }
522 // Quick exit if the file does not exist.
523 if ( ! is_file( $ht_access_path ) ) {
524 return;
525 }
526 // If the htaccess-file is existed.
527 if ( ! is_writable( $ht_access_path ) ) {
528 return $this->get_error_by_file_path( $ht_access_path );
529 }
530
531 $ht_config = $this->cleanup_old_rules( file_get_contents( $ht_access_path ) );
532 file_put_contents( $ht_access_path, trim( $ht_config ), LOCK_EX );
533 }
534
535 /**
536 * Unprotect upload directory.
537 *
538 * @return void
539 */
540 public function unprotect_upload_directory() {
541 if ( defined( 'UPLOADS' ) ) {
542 $this->contentdir_path = ABSPATH . UPLOADS . '/' . '.htaccess';
543 $this->unprotect_content_directory();
544 }
545 }
546
547 /**
548 * Set the exclude file paths.
549 *
550 * @param array $config
551 */
552 public function set_new_htaccess_config( $config = array() ) {
553 if ( ! empty( $config ) ) {
554 $this->new_htaccess_config = $config;
555 }
556 }
557
558 /**
559 * Get the new HT config.
560 *
561 * @return array - $new_htaccess_config
562 */
563 public function get_new_htaccess_config() {
564 return $this->new_htaccess_config;
565 }
566
567 /**
568 * @param $exists_rules
569 *
570 * @return string|string[]|null
571 */
572 public function cleanup_old_rules( $exists_rules ) {
573 $pattern = '/(## WP Defender - Protect PHP Executed ##((.|\n)*)## WP Defender - End ##)/';
574
575 if ( preg_match( $pattern, $exists_rules ) ) {
576 $exists_rules = preg_replace( $pattern, '', $exists_rules );
577 }
578
579 $exists_rules = trim( $exists_rules );
580
581 if ( strlen( $exists_rules ) ) {
582 $exists_rules .= PHP_EOL;
583 }
584
585 return $exists_rules;
586 }
587 }
588