PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.7.2
Defender Security – Malware Scanner, Login Security & Firewall v5.7.2
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 / model / setting / class-user-agent-lockout.php

class-user-agent-lockout.php in Defender Security – Malware Scanner, Login Security & Firewall 5.7.2, at src/model/setting/class-user-agent-lockout.php

585 lines 16.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handle user agent lockout settings.
4 *
5 * @package WP_Defender\Model\Setting
6 */
7
8 namespace WP_Defender\Model\Setting;
9
10 use Calotes\Model\Setting;
11 use WP_Defender\Component\User_Agent as User_Agent_Service;
12
13 /**
14 * Model for user agent lockout settings.
15 *
16 * Since with v5.4.0 blocklisted UA has groups:
17 * 'Blocklist Presets' is associated with the properties $blocklist_presets & $blocklist_preset_values,
18 * 'Scripts Presets' is associated with the properties $script_presets & $script_preset_values,
19 * 'Custom User Agents' is associated with the property $blacklist.
20 */
21 class User_Agent_Lockout extends Setting {
22 const BOT_LOCKOUT_TYPE_ALLOWED = array( 'temporary', 'permanent' );
23 const BOT_LOCKOUT_DURATION_UNIT_ALLOWED = array( 'seconds', 'minutes', 'hours' );
24
25 /**
26 * Option name.
27 *
28 * @var string
29 */
30 protected $table = 'wd_user_agent_settings';
31
32 /**
33 * Is module enabled?
34 *
35 * @var bool
36 * @defender_property
37 */
38 public $enabled = false;
39
40 /**
41 * Blacklist User Agents.
42 *
43 * @var string
44 * @defender_property
45 */
46 public $blacklist = '';
47
48 /**
49 * Whitelist User Agents.
50 *
51 * @var string
52 * @defender_property
53 */
54 public $whitelist = '';
55
56 /**
57 * Message to display for user agent lockout.
58 *
59 * @var string
60 * @defender_property
61 * @sanitize sanitize_textarea_field
62 */
63 public $message = '';
64
65 /**
66 * Is empty headers allowed?
67 *
68 * @var bool
69 * @defender_property
70 */
71 public $empty_headers = false;
72
73 /**
74 * Is blocklist presets enabled?
75 *
76 * @var bool
77 * @defender_property
78 */
79 public $blocklist_presets = true;
80
81 /**
82 * The list of Blocklist presets.
83 *
84 * @var array
85 * @defender_property
86 */
87 public $blocklist_preset_values = array();
88
89 /**
90 * Is script presets enabled?
91 *
92 * @var bool
93 * @defender_property
94 */
95 public $script_presets = false;
96
97 /**
98 * The list of Script presets.
99 *
100 * @var array
101 * @defender_property
102 */
103 public $script_preset_values = array();
104
105 /**
106 * Is malicious bot enabled?
107 *
108 * @var bool
109 * @defender_property
110 */
111 public $malicious_bot_enabled = false;
112
113 /**
114 * How the lock is going to be, if we choose permanent, then their IP will be blacklisted.
115 *
116 * @var string
117 * @defender_property
118 * @rule in[temporary,permanent]
119 */
120 public $malicious_bot_lockout_type = 'temporary';
121
122 /**
123 * Duration for the lockout.
124 *
125 * @var int
126 * @defender_property
127 * @rule required|integer
128 */
129 public $malicious_bot_lockout_duration = 300;
130
131 /**
132 * Duration unit.
133 *
134 * @var string
135 * @defender_property
136 * @rule in[seconds,minutes,hours]
137 */
138 public $malicious_bot_lockout_duration_unit = 'seconds';
139
140 /**
141 * The message to show on frontend when a malicious bot is triggered.
142 *
143 * @var string
144 * @defender_property
145 * @sanitize sanitize_textarea_field
146 */
147 public $malicious_bot_message = '';
148
149 /**
150 * Is fake bots enabled?
151 *
152 * @var bool
153 * @defender_property
154 */
155 public $fake_bots_enabled = false;
156
157 /**
158 * How the lock is going to be, if we choose permanent, then their IP will be blacklisted.
159 *
160 * @var string
161 * @defender_property
162 * @rule in[temporary,permanent]
163 */
164 public $fake_bots_lockout_type = 'temporary';
165
166 /**
167 * Duration for the lockout.
168 *
169 * @var int
170 * @defender_property
171 * @rule required|integer
172 */
173 public $fake_bots_lockout_duration = 300;
174
175 /**
176 * Duration unit.
177 *
178 * @var string
179 * @defender_property
180 * @rule in[seconds,minutes,hours]
181 */
182 public $fake_bots_lockout_duration_unit = 'seconds';
183
184 /**
185 * The message to show on frontend when a fake bot is triggered.
186 *
187 * @var string
188 * @defender_property
189 * @sanitize sanitize_textarea_field
190 */
191 public $fake_bots_message = '';
192
193 /**
194 * Old properties of the Bot Trap option for backward compatibility. We can remove them in future versions.
195 * Is bot trap enabled?
196 *
197 * @var bool
198 * @defender_property
199 */
200 public $bot_trap_enabled = false;
201
202 /**
203 * How the lock is going to be, if we choose permanent, then their IP will be blacklisted.
204 *
205 * @var string
206 * @defender_property
207 * @rule in[temporary,permanent]
208 */
209 public $bot_trap_lockout_type = 'temporary';
210
211 /**
212 * Duration for the lockout.
213 *
214 * @var int
215 * @defender_property
216 * @rule required|integer
217 */
218 public $bot_trap_lockout_duration = 300;
219
220 /**
221 * Duration unit.
222 *
223 * @var string
224 * @defender_property
225 * @rule in[seconds,minutes,hours]
226 */
227 public $bot_trap_lockout_duration_unit = 'seconds';
228
229 /**
230 * Rules for validation.
231 *
232 * @var array
233 */
234 protected $rules = array(
235 array( array( 'enabled', 'empty_headers', 'blocklist_presets', 'script_presets' ), 'boolean' ),
236 array( array( 'malicious_bot_lockout_type' ), 'in', self::BOT_LOCKOUT_TYPE_ALLOWED ),
237 array( array( 'malicious_bot_lockout_duration_unit' ), 'in', self::BOT_LOCKOUT_DURATION_UNIT_ALLOWED ),
238 array( array( 'fake_bots_lockout_type' ), 'in', self::BOT_LOCKOUT_TYPE_ALLOWED ),
239 array( array( 'fake_bots_lockout_duration_unit' ), 'in', self::BOT_LOCKOUT_DURATION_UNIT_ALLOWED ),
240 );
241
242 /**
243 * Get default values.
244 *
245 * @return array
246 */
247 public function get_default_values(): array {
248 // Allowled User Agents.
249 $whitelist = "a6-indexer\nadsbot-google\naolbuild\napis-google\nbaidu\nbingbot\nbingpreview";
250 $whitelist .= "\nbutterfly\ncloudflare\nchrome\nduckduckbot\nembedly\nfacebookexternalhit\nfacebot\ngoogle page speed";
251 $whitelist .= "\ngooglebot\nia_archiver\nlinkedinbot\nmediapartners-google\nmsnbot\nnetcraftsurvey";
252 $whitelist .= "\noutbrain\npinterest\nquora\nslackbot\nslurp\ntweetmemebot\ntwitterbot\nuptimerobot";
253 $whitelist .= "\nurlresolver\nvkshare\nw3c_validator\nwordpress\nwp rocket\nyandex";
254 $message = esc_html__( 'You have been blocked from accessing this website.', 'defender-security' );
255
256 return array(
257 'message' => $message,
258 'malicious_bot_message' => $message,
259 'fake_bots_message' => $message,
260 'whitelist' => $whitelist,
261 // Blocked User Agents.
262 'blacklist' => '',
263 'blocklist_presets' => true,
264 'blocklist_preset_values' => array( 'mj12bot', 'dotbot' ),
265 'script_presets' => false,
266 'script_preset_values' => array(),
267 );
268 }
269
270 /**
271 * Initializes the object by setting default values for the message, whitelist, and blacklist properties.
272 * This function retrieves the default values from the get_default_values() method and assigns them to the
273 * corresponding properties of the object.
274 *
275 * @return void
276 */
277 protected function before_load(): void {
278 $default_values = $this->get_default_values();
279 $this->message = $default_values['message'];
280 $this->whitelist = $default_values['whitelist'];
281 $this->blacklist = $default_values['blacklist'];
282 $this->blocklist_presets = $default_values['blocklist_presets'];
283 $this->blocklist_preset_values = $default_values['blocklist_preset_values'];
284 $this->script_presets = $default_values['script_presets'];
285 $this->script_preset_values = $default_values['script_preset_values'];
286 $this->malicious_bot_message = $default_values['malicious_bot_message'];
287 $this->fake_bots_message = $default_values['fake_bots_message'];
288 }
289
290 /**
291 * Checks if the User Agent Lockout feature is active.
292 *
293 * @return bool Returns true if the User Agent Lockout feature is active, false otherwise.
294 */
295 public function is_active(): bool {
296 return (bool) apply_filters(
297 'wd_user_agents_enable',
298 $this->enabled
299 );
300 }
301
302 /**
303 * Get a list of blocklisted or allowlisted data.
304 *
305 * @param string $type blocklist|allowlist.
306 * @param bool $lower Whether to convert the list to lowercase.
307 *
308 * @return array
309 */
310 public function get_lockout_list( $type = 'blocklist', $lower = true ): array {
311 $data = 'blocklist' === $type ? $this->blacklist : $this->whitelist;
312 $data = trim( $data );
313
314 if ( '' === $data ) {
315 return array();
316 }
317
318 $arr = preg_split( "/\r\n|\n|\r/", $data );
319 if ( ! is_array( $arr ) ) {
320 return array();
321 }
322
323 if ( $lower ) {
324 $arr = array_map(
325 function ( $value ) {
326 return strtolower( trim( $value ) );
327 },
328 $arr
329 );
330 } else {
331 $arr = array_map( 'trim', $arr );
332 }
333
334 return array_unique( $arr );
335 }
336
337 /**
338 * Define settings labels.
339 *
340 * @return array
341 */
342 public function labels(): array {
343 return array(
344 'enabled' => self::get_module_name(),
345 'message' => esc_html__( 'Message', 'defender-security' ),
346 'blacklist' => esc_html__( 'Blocklist', 'defender-security' ),
347 'whitelist' => esc_html__( 'Allowlist', 'defender-security' ),
348 'empty_headers' => esc_html__( 'Empty Headers', 'defender-security' ),
349 );
350 }
351
352 /**
353 * Get the access status of this UA.
354 *
355 * @param string $ua User Agent.
356 *
357 * @return array
358 */
359 public function get_access_status( $ua ): array {
360 $blocklist = str_replace( '#', '\#', $this->get_all_selected_blocklist_ua() );
361 $allowlist = str_replace( '#', '\#', $this->get_lockout_list( 'allowlist' ) );
362
363 // Escape regex special characters in each item before building the pattern.
364 $blocklist_escaped = array_map( 'preg_quote', $blocklist, array_fill( 0, count( $blocklist ), '#' ) );
365 $allowlist_escaped = array_map( 'preg_quote', $allowlist, array_fill( 0, count( $allowlist ), '#' ) );
366
367 $blocklist_regex_pattern = '#' . implode( '|', $blocklist_escaped ) . '#i';
368 $allowlist_regex_pattern = '#' . implode( '|', $allowlist_escaped ) . '#i';
369
370 $blocklist_match = preg_match( $blocklist_regex_pattern, $ua );
371 $allowlist_match = preg_match( $allowlist_regex_pattern, $ua );
372
373 if ( 1 !== $blocklist_match && 1 !== $allowlist_match ) {
374 return array( 'na' );
375 }
376
377 $result = array();
378
379 // Check blocklist first - if it matches, add 'banned'.
380 if ( 1 === $blocklist_match ) {
381 $result[] = 'banned';
382 }
383
384 // Check allowlist - if it matches, add 'allowlist'.
385 if ( 1 === $allowlist_match ) {
386 $result[] = 'allowlist';
387 }
388
389 return $result;
390 }
391
392 /**
393 * Checks if a user agent is present in a given list.
394 *
395 * @param string $ua The user agent to check.
396 * @param string $collection The type of list to check against ('blocklist' or 'allowlist').
397 *
398 * @return bool Returns true if the user agent is in the list, false otherwise.
399 */
400 public function is_ua_in_list( $ua, $collection ): bool {
401 if ( 'allowlist' === $collection ) {
402 $arr = str_replace( '#', '\#', $this->get_lockout_list( $collection ) );
403 } else {
404 $arr = str_replace( '#', '\#', $this->get_all_selected_blocklist_ua() );
405 }
406
407 // Escape regex special characters in each item before building the pattern.
408 $arr_escaped = array_map( 'preg_quote', $arr, array_fill( 0, count( $arr ), '#' ) );
409 $list_regex_pattern = '#' . implode( '|', $arr_escaped ) . '#i';
410
411 return 1 === preg_match( $list_regex_pattern, $ua );
412 }
413
414 /**
415 * Remove User Agent from a list.
416 *
417 * @param string $ua The user agent to remove.
418 * @param string $collection blocklist|allowlist.
419 *
420 * @return void
421 */
422 public function remove_from_list( $ua, $collection ) {
423 if ( 'allowlist' === $collection ) {
424 $arr = $this->get_lockout_list( $collection );
425 // Array can contain uppercase.
426 $orig_arr = str_replace( '#', '\#', $this->get_lockout_list( $collection, false ) );
427 } else {
428 $arr = $this->get_all_selected_blocklist_ua();
429 // Array can contain uppercase.
430 $orig_arr = str_replace( '#', '\#', $this->get_all_selected_blocklist_ua( false ) );
431 }
432
433 $list_regex_pattern = '#' . implode( '|', $arr ) . '#i';
434 $list_match = preg_match( $list_regex_pattern, $ua );
435 if ( false !== $list_match ) {
436 if ( 'blocklist' === $collection ) {
437 // Check in 'Blocklist Presets'.
438 if ( $this->blocklist_presets ) {
439 $key = array_search( $ua, $this->blocklist_preset_values, true );
440 if ( false !== $key ) {
441 unset( $this->blocklist_preset_values[ $key ] );
442 $this->blocklist_preset_values = array_values( $this->blocklist_preset_values );
443 }
444 }
445 // Check in 'Script Presets' using Regex.
446 if ( $this->script_presets ) {
447 if ( false !== strpos( $ua, User_Agent_Service::GO_HTTP_CLIENT_KEY . '/' ) ) {
448 $key_script_preset = User_Agent_Service::GO_HTTP_CLIENT_KEY;
449 } elseif ( false !== strpos( $ua, User_Agent_Service::PYTHON_REQUESTS_KEY . '/' ) ) {
450 $key_script_preset = User_Agent_Service::PYTHON_REQUESTS_KEY;
451 } else {
452 $key_script_preset = '';
453 }
454
455 $key = array_search( $key_script_preset, $this->script_preset_values, true );
456 if ( false !== $key ) {
457 unset( $this->script_preset_values[ $key ] );
458 $this->script_preset_values = array_values( $this->script_preset_values );
459 }
460 }
461 // Check 'Custom User Agents' case.
462 $arr_blocklist = $this->get_lockout_list( 'blocklist', false );
463 if ( array() !== $arr_blocklist ) {
464 $key = array_search( $ua, $arr_blocklist, true );
465 if ( false !== $key && isset( $arr_blocklist[ $key ] ) ) {
466 unset( $arr_blocklist[ $key ] );
467 // Convert back to string.
468 $this->blacklist = implode( PHP_EOL, $arr_blocklist );
469 }
470 }
471 } elseif ( 'allowlist' === $collection ) {
472 $key = array_search( $ua, $arr, true );
473 if ( false !== $key && isset( $arr[ $key ] ) ) {
474 unset( $arr[ $key ] );
475 // Convert back to string.
476 $this->whitelist = implode( PHP_EOL, $arr );
477 }
478 }
479
480 $this->save();
481 }
482 }
483
484 /**
485 * Add an UA to the list.
486 *
487 * @param string $ua User agent name.
488 * @param string $collection blocklist|allowlist.
489 *
490 * @return void
491 */
492 public function add_to_list( $ua, $collection ) {
493 if ( 'blocklist' === $collection ) {
494 if ( User_Agent_Service::is_blocklist_presets( $ua ) ) {
495 $this->blocklist_presets = true;
496 $this->blocklist_preset_values[] = $ua;
497 } elseif ( User_Agent_Service::is_script_presets( $ua ) ) {
498 $this->script_presets = true;
499 $this->script_preset_values[] = $ua;
500 } else {
501 $this->blacklist = $this->push_ua_to_list( $ua, $collection );
502 }
503 } elseif ( 'allowlist' === $collection ) {
504 $this->whitelist = $this->push_ua_to_list( $ua, $collection );
505 }
506
507 $this->save();
508 }
509
510 /**
511 * Push the UA to either blocklist (only in 'Custom User Agents') or allowlist
512 *
513 * @param string $ua User agent name.
514 * @param string $collection List type i.e. blocklist or allowlist.
515 *
516 * @return string List as string format with UA delimited with newline character.
517 */
518 private function push_ua_to_list( string $ua, string $collection ): string {
519 $arr = $this->get_lockout_list( $collection, false );
520 $arr[] = trim( $ua );
521 $arr = array_unique( $arr );
522
523 return implode( PHP_EOL, $arr );
524 }
525
526 /**
527 * Filter the UAs, as we use a textarea to submit, so it can contain some un-valid UAs.
528 */
529 protected function after_validate(): void {
530 $lists = array(
531 'blacklist' => $this->get_lockout_list( 'blocklist' ),
532 'whitelist' => $this->get_lockout_list( 'allowlist' ),
533 );
534
535 foreach ( $lists as $key => &$collection ) {
536 // If UA collection is not valid, we should display an error message. We'll improve it by UA standard/pattern.
537 $this->$key = implode( PHP_EOL, array_filter( $collection, 'strlen' ) );
538 }
539 }
540
541 /**
542 * Get the module name.
543 *
544 * @return string
545 */
546 public static function get_module_name(): string {
547 return esc_html__( 'User Agent Banning', 'defender-security' );
548 }
549
550 /**
551 * Get the module state.
552 *
553 * @param bool $flag Module state.
554 *
555 * @return string
556 */
557 public static function get_module_state( $flag ): string {
558 return $flag ? esc_html__( 'active', 'defender-security' ) : esc_html__( 'inactive', 'defender-security' );
559 }
560
561 /**
562 * Get the list of all selected blocklisted UA values.
563 *
564 * @param bool $lower Whether to convert the list to lowercase.
565 *
566 * @return array
567 */
568 public function get_all_selected_blocklist_ua( $lower = true ): array {
569 $blocklist_custom = $this->get_lockout_list( 'blocklist', $lower );
570 $blocklist_presets = $this->blocklist_presets ? $this->blocklist_preset_values : array();
571 $script_presets = $this->script_presets ? $this->script_preset_values : array();
572
573 return array_merge( $blocklist_custom, $blocklist_presets, $script_presets );
574 }
575
576 /**
577 * Return the module slug.
578 *
579 * @return string
580 */
581 public static function get_module_slug(): string {
582 return 'ua-lockout';
583 }
584 }
585