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

554 lines 15.3 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 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 $arr = array_filter( preg_split( "/\r\n|\n|\r/", $data ), 'boolval' );
313 $arr = array_map( 'trim', $arr );
314 if ( $lower ) {
315 $arr = array_map( 'strtolower', $arr );
316 }
317
318 return $arr;
319 }
320
321 /**
322 * Define settings labels.
323 *
324 * @return array
325 */
326 public function labels(): array {
327 return array(
328 'enabled' => self::get_module_name(),
329 'message' => esc_html__( 'Message', 'defender-security' ),
330 'blacklist' => esc_html__( 'Blocklist', 'defender-security' ),
331 'whitelist' => esc_html__( 'Allowlist', 'defender-security' ),
332 'empty_headers' => esc_html__( 'Empty Headers', 'defender-security' ),
333 );
334 }
335
336 /**
337 * Get the access status of this UA.
338 *
339 * @param string $ua User Agent.
340 *
341 * @return array
342 */
343 public function get_access_status( $ua ): array {
344 $blocklist = str_replace( '#', '\#', $this->get_all_selected_blocklist_ua() );
345 $allowlist = str_replace( '#', '\#', $this->get_lockout_list( 'allowlist' ) );
346
347 // Escape regex special characters in each item before building the pattern.
348 $blocklist_escaped = array_map( 'preg_quote', $blocklist, array_fill( 0, count( $blocklist ), '#' ) );
349 $allowlist_escaped = array_map( 'preg_quote', $allowlist, array_fill( 0, count( $allowlist ), '#' ) );
350
351 $blocklist_regex_pattern = '#' . implode( '|', $blocklist_escaped ) . '#i';
352 $allowlist_regex_pattern = '#' . implode( '|', $allowlist_escaped ) . '#i';
353
354 $blocklist_match = preg_match( $blocklist_regex_pattern, $ua );
355 $allowlist_match = preg_match( $allowlist_regex_pattern, $ua );
356
357 if ( 1 !== $blocklist_match && 1 !== $allowlist_match ) {
358 return array( 'na' );
359 }
360
361 $result = array();
362
363 // Check blocklist first - if it matches, add 'banned'.
364 if ( 1 === $blocklist_match ) {
365 $result[] = 'banned';
366 }
367
368 // Check allowlist - if it matches, add 'allowlist'.
369 if ( 1 === $allowlist_match ) {
370 $result[] = 'allowlist';
371 }
372
373 return $result;
374 }
375
376 /**
377 * Checks if a user agent is present in a given list.
378 *
379 * @param string $ua The user agent to check.
380 * @param string $collection The type of list to check against ('blocklist' or 'allowlist').
381 *
382 * @return bool Returns true if the user agent is in the list, false otherwise.
383 */
384 public function is_ua_in_list( $ua, $collection ): bool {
385 if ( 'allowlist' === $collection ) {
386 $arr = str_replace( '#', '\#', $this->get_lockout_list( $collection ) );
387 } else {
388 $arr = str_replace( '#', '\#', $this->get_all_selected_blocklist_ua() );
389 }
390
391 // Escape regex special characters in each item before building the pattern.
392 $arr_escaped = array_map( 'preg_quote', $arr, array_fill( 0, count( $arr ), '#' ) );
393 $list_regex_pattern = '#' . implode( '|', $arr_escaped ) . '#i';
394
395 return 1 === preg_match( $list_regex_pattern, $ua );
396 }
397
398 /**
399 * Remove User Agent from a list.
400 *
401 * @param string $ua The user agent to remove.
402 * @param string $collection blocklist|allowlist.
403 *
404 * @return void
405 */
406 public function remove_from_list( $ua, $collection ) {
407 if ( 'allowlist' === $collection ) {
408 $arr = $this->get_lockout_list( $collection );
409 // Array can contain uppercase.
410 $orig_arr = str_replace( '#', '\#', $this->get_lockout_list( $collection, false ) );
411 } else {
412 $arr = $this->get_all_selected_blocklist_ua();
413 // Array can contain uppercase.
414 $orig_arr = str_replace( '#', '\#', $this->get_all_selected_blocklist_ua( false ) );
415 }
416
417 $list_regex_pattern = '#' . implode( '|', $arr ) . '#i';
418 $list_match = preg_match( $list_regex_pattern, $ua );
419 if ( false !== $list_match ) {
420 if ( 'blocklist' === $collection ) {
421 // Check in 'Blocklist Presets'.
422 if ( $this->blocklist_presets ) {
423 $key = array_search( $ua, $this->blocklist_preset_values, true );
424 if ( false !== $key ) {
425 unset( $this->blocklist_preset_values[ $key ] );
426 $this->blocklist_preset_values = array_values( $this->blocklist_preset_values );
427 }
428 }
429 // Check in 'Script Presets' using Regex.
430 if ( $this->script_presets ) {
431 if ( false !== strpos( $ua, User_Agent_Service::GO_HTTP_CLIENT_KEY . '/' ) ) {
432 $key_script_preset = User_Agent_Service::GO_HTTP_CLIENT_KEY;
433 } elseif ( false !== strpos( $ua, User_Agent_Service::PYTHON_REQUESTS_KEY . '/' ) ) {
434 $key_script_preset = User_Agent_Service::PYTHON_REQUESTS_KEY;
435 } else {
436 $key_script_preset = '';
437 }
438
439 $key = array_search( $key_script_preset, $this->script_preset_values, true );
440 if ( false !== $key ) {
441 unset( $this->script_preset_values[ $key ] );
442 $this->script_preset_values = array_values( $this->script_preset_values );
443 }
444 }
445 // Check 'Custom User Agents' case.
446 $arr_blocklist = $this->get_lockout_list( 'blocklist', false );
447 if ( array() !== $arr_blocklist ) {
448 $key = array_search( $ua, $arr_blocklist, true );
449 if ( false !== $key && isset( $arr_blocklist[ $key ] ) ) {
450 unset( $arr_blocklist[ $key ] );
451 // Convert back to string.
452 $this->blacklist = implode( PHP_EOL, $arr_blocklist );
453 }
454 }
455 } elseif ( 'allowlist' === $collection ) {
456 $key = array_search( $ua, $arr, true );
457 if ( false !== $key && isset( $arr[ $key ] ) ) {
458 unset( $arr[ $key ] );
459 // Convert back to string.
460 $this->whitelist = implode( PHP_EOL, $arr );
461 }
462 }
463
464 $this->save();
465 }
466 }
467
468 /**
469 * Add an UA to the list.
470 *
471 * @param string $ua User agent name.
472 * @param string $collection blocklist|allowlist.
473 *
474 * @return void
475 */
476 public function add_to_list( $ua, $collection ) {
477 if ( 'blocklist' === $collection ) {
478 if ( User_Agent_Service::is_blocklist_presets( $ua ) ) {
479 $this->blocklist_presets = true;
480 $this->blocklist_preset_values[] = $ua;
481 } elseif ( User_Agent_Service::is_script_presets( $ua ) ) {
482 $this->script_presets = true;
483 $this->script_preset_values[] = $ua;
484 } else {
485 $this->blacklist = $this->push_ua_to_list( $ua, $collection );
486 }
487 } elseif ( 'allowlist' === $collection ) {
488 $this->whitelist = $this->push_ua_to_list( $ua, $collection );
489 }
490
491 $this->save();
492 }
493
494 /**
495 * Push the UA to either blocklist (only in 'Custom User Agents') or allowlist
496 *
497 * @param string $ua User agent name.
498 * @param string $collection List type i.e. blocklist or allowlist.
499 *
500 * @return string List as string format with UA delimited with newline character.
501 */
502 private function push_ua_to_list( string $ua, string $collection ): string {
503 $arr = $this->get_lockout_list( $collection, false );
504 $arr[] = trim( $ua );
505 $arr = array_unique( $arr );
506
507 return implode( PHP_EOL, $arr );
508 }
509
510 /**
511 * Get the module name.
512 *
513 * @return string
514 */
515 public static function get_module_name(): string {
516 return esc_html__( 'User Agent Banning', 'defender-security' );
517 }
518
519 /**
520 * Get the module state.
521 *
522 * @param bool $flag Module state.
523 *
524 * @return string
525 */
526 public static function get_module_state( $flag ): string {
527 return $flag ? esc_html__( 'active', 'defender-security' ) : esc_html__( 'inactive', 'defender-security' );
528 }
529
530 /**
531 * Get the list of all selected blocklisted UA values.
532 *
533 * @param bool $lower Whether to convert the list to lowercase.
534 *
535 * @return array
536 */
537 public function get_all_selected_blocklist_ua( $lower = true ): array {
538 $blocklist_custom = $this->get_lockout_list( 'blocklist', $lower );
539 $blocklist_presets = $this->blocklist_presets ? $this->blocklist_preset_values : array();
540 $script_presets = $this->script_presets ? $this->script_preset_values : array();
541
542 return array_merge( $blocklist_custom, $blocklist_presets, $script_presets );
543 }
544
545 /**
546 * Return the module slug.
547 *
548 * @return string
549 */
550 public static function get_module_slug(): string {
551 return 'ua-lockout';
552 }
553 }
554