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

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

461 lines 13.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_TRAP_LOCKOUT_TYPE_ALLOWED = array( 'temporary', 'permanent' );
23 const BOT_TRAP_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 bot trap enabled?
107 *
108 * @var bool
109 * @defender_property
110 */
111 public $bot_trap_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 $bot_trap_lockout_type = 'temporary';
121
122 /**
123 * Duration for the lockout.
124 *
125 * @var int
126 * @defender_property
127 * @rule required|integer
128 */
129 public $bot_trap_lockout_duration = 300;
130
131 /**
132 * Duration unit.
133 *
134 * @var string
135 * @defender_property
136 * @rule in[seconds,minutes,hours]
137 */
138 public $bot_trap_lockout_duration_unit = 'seconds';
139
140 /**
141 * The message to show on frontend when a bot trap is triggered.
142 *
143 * @var string
144 * @defender_property
145 * @sanitize sanitize_textarea_field
146 */
147 public $bot_trap_message = '';
148
149 /**
150 * Rules for validation.
151 *
152 * @var array
153 */
154 protected $rules = array(
155 array( array( 'enabled', 'empty_headers', 'blocklist_presets', 'script_presets' ), 'boolean' ),
156 array( array( 'bot_trap_lockout_type' ), 'in', self::BOT_TRAP_LOCKOUT_TYPE_ALLOWED ),
157 array( array( 'bot_trap_lockout_duration_unit' ), 'in', self::BOT_TRAP_LOCKOUT_DURATION_UNIT_ALLOWED ),
158 );
159
160 /**
161 * Get default values.
162 *
163 * @return array
164 */
165 public function get_default_values(): array {
166 // Allowled User Agents.
167 $whitelist = "a6-indexer\nadsbot-google\naolbuild\napis-google\nbaidu\nbingbot\nbingpreview";
168 $whitelist .= "\nbutterfly\ncloudflare\nchrome\nduckduckbot\nembedly\nfacebookexternalhit\nfacebot\ngoogle page speed";
169 $whitelist .= "\ngooglebot\nia_archiver\nlinkedinbot\nmediapartners-google\nmsnbot\nnetcraftsurvey";
170 $whitelist .= "\noutbrain\npinterest\nquora\nslackbot\nslurp\ntweetmemebot\ntwitterbot\nuptimerobot";
171 $whitelist .= "\nurlresolver\nvkshare\nw3c_validator\nwordpress\nwp rocket\nyandex";
172 $message = esc_html__( 'You have been blocked from accessing this website.', 'defender-security' );
173
174 return array(
175 'message' => $message,
176 'bot_trap_message' => $message,
177 'whitelist' => $whitelist,
178 // Blocked User Agents.
179 'blacklist' => '',
180 'blocklist_presets' => true,
181 'blocklist_preset_values' => array( 'mj12bot', 'dotbot' ),
182 'script_presets' => false,
183 'script_preset_values' => array(),
184 );
185 }
186
187 /**
188 * Initializes the object by setting default values for the message, whitelist, and blacklist properties.
189 * This function retrieves the default values from the get_default_values() method and assigns them to the
190 * corresponding properties of the object.
191 *
192 * @return void
193 */
194 protected function before_load(): void {
195 $default_values = $this->get_default_values();
196 $this->message = $default_values['message'];
197 $this->whitelist = $default_values['whitelist'];
198 $this->blacklist = $default_values['blacklist'];
199 $this->blocklist_presets = $default_values['blocklist_presets'];
200 $this->blocklist_preset_values = $default_values['blocklist_preset_values'];
201 $this->script_presets = $default_values['script_presets'];
202 $this->script_preset_values = $default_values['script_preset_values'];
203 $this->bot_trap_message = $default_values['bot_trap_message'];
204 }
205
206 /**
207 * Checks if the User Agent Lockout feature is active.
208 *
209 * @return bool Returns true if the User Agent Lockout feature is active, false otherwise.
210 */
211 public function is_active(): bool {
212 return (bool) apply_filters(
213 'wd_user_agents_enable',
214 $this->enabled
215 );
216 }
217
218 /**
219 * Get list of blocklisted or allowlisted data.
220 *
221 * @param string $type blocklist|allowlist.
222 * @param bool $lower Whether to convert the list to lowercase.
223 *
224 * @return array
225 */
226 public function get_lockout_list( $type = 'blocklist', $lower = true ): array {
227 $data = 'blocklist' === $type ? $this->blacklist : $this->whitelist;
228 $arr = array_filter( preg_split( "/\r\n|\n|\r/", $data ), 'boolval' );
229 $arr = array_map( 'trim', $arr );
230 if ( $lower ) {
231 $arr = array_map( 'strtolower', $arr );
232 }
233
234 return $arr;
235 }
236
237 /**
238 * Define settings labels.
239 *
240 * @return array
241 */
242 public function labels(): array {
243 return array(
244 'enabled' => self::get_module_name(),
245 'message' => esc_html__( 'Message', 'defender-security' ),
246 'blacklist' => esc_html__( 'Blocklist', 'defender-security' ),
247 'whitelist' => esc_html__( 'Allowlist', 'defender-security' ),
248 'empty_headers' => esc_html__( 'Empty Headers', 'defender-security' ),
249 );
250 }
251
252 /**
253 * Get the access status of this UA.
254 *
255 * @param string $ua User Agent.
256 *
257 * @return array
258 */
259 public function get_access_status( $ua ): array {
260 $blocklist = str_replace( '#', '\#', $this->get_all_selected_blocklist_ua() );
261 $allowlist = str_replace( '#', '\#', $this->get_lockout_list( 'allowlist' ) );
262
263 // Escape regex special characters in each item before building the pattern.
264 $blocklist_escaped = array_map( 'preg_quote', $blocklist, array_fill( 0, count( $blocklist ), '#' ) );
265 $allowlist_escaped = array_map( 'preg_quote', $allowlist, array_fill( 0, count( $allowlist ), '#' ) );
266
267 $blocklist_regex_pattern = '#' . implode( '|', $blocklist_escaped ) . '#i';
268 $allowlist_regex_pattern = '#' . implode( '|', $allowlist_escaped ) . '#i';
269
270 $blocklist_match = preg_match( $blocklist_regex_pattern, $ua );
271 $allowlist_match = preg_match( $allowlist_regex_pattern, $ua );
272
273 if ( 1 !== $blocklist_match && 1 !== $allowlist_match ) {
274 return array( 'na' );
275 }
276
277 $result = array();
278
279 // Check blocklist first - if it matches, add 'banned'.
280 if ( 1 === $blocklist_match ) {
281 $result[] = 'banned';
282 }
283
284 // Check allowlist - if it matches, add 'allowlist'.
285 if ( 1 === $allowlist_match ) {
286 $result[] = 'allowlist';
287 }
288
289 return $result;
290 }
291
292 /**
293 * Checks if a user agent is present in a given list.
294 *
295 * @param string $ua The user agent to check.
296 * @param string $collection The type of list to check against ('blocklist' or 'allowlist').
297 *
298 * @return bool Returns true if the user agent is in the list, false otherwise.
299 */
300 public function is_ua_in_list( $ua, $collection ): bool {
301 if ( 'allowlist' === $collection ) {
302 $arr = str_replace( '#', '\#', $this->get_lockout_list( $collection ) );
303 } else {
304 $arr = str_replace( '#', '\#', $this->get_all_selected_blocklist_ua() );
305 }
306
307 // Escape regex special characters in each item before building the pattern.
308 $arr_escaped = array_map( 'preg_quote', $arr, array_fill( 0, count( $arr ), '#' ) );
309 $list_regex_pattern = '#' . implode( '|', $arr_escaped ) . '#i';
310
311 return 1 === preg_match( $list_regex_pattern, $ua );
312 }
313
314 /**
315 * Remove User Agent from a list.
316 *
317 * @param string $ua The user agent to remove.
318 * @param string $collection blocklist|allowlist.
319 *
320 * @return void
321 */
322 public function remove_from_list( $ua, $collection ) {
323 if ( 'allowlist' === $collection ) {
324 $arr = $this->get_lockout_list( $collection );
325 // Array can contain uppercase.
326 $orig_arr = str_replace( '#', '\#', $this->get_lockout_list( $collection, false ) );
327 } else {
328 $arr = $this->get_all_selected_blocklist_ua();
329 // Array can contain uppercase.
330 $orig_arr = str_replace( '#', '\#', $this->get_all_selected_blocklist_ua( false ) );
331 }
332
333 $list_regex_pattern = '#' . implode( '|', $arr ) . '#i';
334 $list_match = preg_match( $list_regex_pattern, $ua );
335 if ( false !== $list_match ) {
336 if ( 'blocklist' === $collection ) {
337 // Check in 'Blocklist Presets'.
338 if ( $this->blocklist_presets ) {
339 $key = array_search( $ua, $this->blocklist_preset_values, true );
340 if ( false !== $key ) {
341 unset( $this->blocklist_preset_values[ $key ] );
342 $this->blocklist_preset_values = array_values( $this->blocklist_preset_values );
343 }
344 }
345 // Check in 'Script Presets' using Regex.
346 if ( $this->script_presets ) {
347 if ( false !== strpos( $ua, User_Agent_Service::GO_HTTP_CLIENT_KEY . '/' ) ) {
348 $key_script_preset = User_Agent_Service::GO_HTTP_CLIENT_KEY;
349 } elseif ( false !== strpos( $ua, User_Agent_Service::PYTHON_REQUESTS_KEY . '/' ) ) {
350 $key_script_preset = User_Agent_Service::PYTHON_REQUESTS_KEY;
351 } else {
352 $key_script_preset = '';
353 }
354
355 $key = array_search( $key_script_preset, $this->script_preset_values, true );
356 if ( false !== $key ) {
357 unset( $this->script_preset_values[ $key ] );
358 $this->script_preset_values = array_values( $this->script_preset_values );
359 }
360 }
361 // Check 'Custom User Agents' case.
362 $arr_blocklist = $this->get_lockout_list( 'blocklist', false );
363 if ( array() !== $arr_blocklist ) {
364 $key = array_search( $ua, $arr_blocklist, true );
365 if ( false !== $key && isset( $arr_blocklist[ $key ] ) ) {
366 unset( $arr_blocklist[ $key ] );
367 // Convert back to string.
368 $this->blacklist = implode( PHP_EOL, $arr_blocklist );
369 }
370 }
371 } elseif ( 'allowlist' === $collection ) {
372 $key = array_search( $ua, $arr, true );
373 if ( false !== $key && isset( $arr[ $key ] ) ) {
374 unset( $arr[ $key ] );
375 // Convert back to string.
376 $this->whitelist = implode( PHP_EOL, $arr );
377 }
378 }
379
380 $this->save();
381 }
382 }
383
384 /**
385 * Add an UA to the list.
386 *
387 * @param string $ua User agent name.
388 * @param string $collection blocklist|allowlist.
389 *
390 * @return void
391 */
392 public function add_to_list( $ua, $collection ) {
393 if ( 'blocklist' === $collection ) {
394 if ( User_Agent_Service::is_blocklist_presets( $ua ) ) {
395 $this->blocklist_presets = true;
396 $this->blocklist_preset_values[] = $ua;
397 } elseif ( User_Agent_Service::is_script_presets( $ua ) ) {
398 $this->script_presets = true;
399 $this->script_preset_values[] = $ua;
400 } else {
401 $this->blacklist = $this->push_ua_to_list( $ua, $collection );
402 }
403 } elseif ( 'allowlist' === $collection ) {
404 $this->whitelist = $this->push_ua_to_list( $ua, $collection );
405 }
406
407 $this->save();
408 }
409
410 /**
411 * Push the UA to either blocklist (only in 'Custom User Agents') or allowlist
412 *
413 * @param string $ua User agent name.
414 * @param string $collection List type i.e. blocklist or allowlist.
415 *
416 * @return string List as string format with UA delimited with newline character.
417 */
418 private function push_ua_to_list( string $ua, string $collection ): string {
419 $arr = $this->get_lockout_list( $collection, false );
420 $arr[] = trim( $ua );
421 $arr = array_unique( $arr );
422
423 return implode( PHP_EOL, $arr );
424 }
425
426 /**
427 * Get the module name.
428 *
429 * @return string
430 */
431 public static function get_module_name(): string {
432 return esc_html__( 'User Agent Banning', 'defender-security' );
433 }
434
435 /**
436 * Get the module state.
437 *
438 * @param bool $flag Module state.
439 *
440 * @return string
441 */
442 public static function get_module_state( $flag ): string {
443 return $flag ? esc_html__( 'active', 'defender-security' ) : esc_html__( 'inactive', 'defender-security' );
444 }
445
446 /**
447 * Get the list of all selected blocklisted UA values.
448 *
449 * @param bool $lower Whether to convert the list to lowercase.
450 *
451 * @return array
452 */
453 public function get_all_selected_blocklist_ua( $lower = true ): array {
454 $blocklist_custom = $this->get_lockout_list( 'blocklist', $lower );
455 $blocklist_presets = $this->blocklist_presets ? $this->blocklist_preset_values : array();
456 $script_presets = $this->script_presets ? $this->script_preset_values : array();
457
458 return array_merge( $blocklist_custom, $blocklist_presets, $script_presets );
459 }
460 }
461