PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.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
← All changes | src/model/setting/class-user-agent-lockout.php +292 -95 5.3.1 → 6.3.0 View file →
@@ -7,13 +7,21 @@
7 7
8 8 namespace WP_Defender\Model\Setting;
9 9
10 10 use Calotes\Model\Setting;
11 +use WP_Defender\Component\User_Agent as User_Agent_Service;
11 12
12 13 /**
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.
14 20 */
15 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' );
16 24
17 25 /**
18 26 * Option name.
19 27 *
@@ -21,14 +29,14 @@
21 29 */
22 30 protected $table = 'wd_user_agent_settings';
23 31
24 32 /**
25 - * Is module enabled.
33 + * Is module enabled?
26 34 *
27 35 * @var bool
28 36 * @defender_property
29 37 */
30 - public $enabled = false;
38 + public bool $enabled = false;
31 39
32 40 /**
33 41 * Blacklist User Agents.
34 42 *
@@ -62,14 +70,185 @@
62 70 */
63 71 public $empty_headers = false;
64 72
65 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 bool $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 bool $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 bool $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 + /**
66 230 * Rules for validation.
67 231 *
68 232 * @var array
69 233 */
70 234 protected $rules = array(
71 - array( array( 'enabled', 'empty_headers' ), 'boolean' ),
235 + array(
236 + array(
237 + 'enabled',
238 + 'empty_headers',
239 + 'blocklist_presets',
240 + 'script_presets',
241 + 'malicious_bot_enabled',
242 + 'bot_trap_enabled',
243 + 'fake_bots_enabled',
244 + ),
245 + 'boolean',
246 + ),
247 + array( array( 'malicious_bot_lockout_type' ), 'in', self::BOT_LOCKOUT_TYPE_ALLOWED ),
248 + array( array( 'malicious_bot_lockout_duration_unit' ), 'in', self::BOT_LOCKOUT_DURATION_UNIT_ALLOWED ),
249 + array( array( 'fake_bots_lockout_type' ), 'in', self::BOT_LOCKOUT_TYPE_ALLOWED ),
250 + array( array( 'fake_bots_lockout_duration_unit' ), 'in', self::BOT_LOCKOUT_DURATION_UNIT_ALLOWED ),
72 251 );
73 252
74 253 /**
75 254 * Get default values.
@@ -82,14 +261,21 @@
82 261 $whitelist .= "\nbutterfly\ncloudflare\nchrome\nduckduckbot\nembedly\nfacebookexternalhit\nfacebot\ngoogle page speed";
83 262 $whitelist .= "\ngooglebot\nia_archiver\nlinkedinbot\nmediapartners-google\nmsnbot\nnetcraftsurvey";
84 263 $whitelist .= "\noutbrain\npinterest\nquora\nslackbot\nslurp\ntweetmemebot\ntwitterbot\nuptimerobot";
85 264 $whitelist .= "\nurlresolver\nvkshare\nw3c_validator\nwordpress\nwp rocket\nyandex";
265 + $message = esc_html__( 'You have been blocked from accessing this website.', 'defender-security' );
86 266
87 267 return array(
88 - 'message' => esc_html__( 'You have been blocked from accessing this website.', 'defender-security' ),
89 - 'whitelist' => $whitelist,
268 + 'message' => $message,
269 + 'malicious_bot_message' => $message,
270 + 'fake_bots_message' => $message,
271 + 'whitelist' => $whitelist,
90 272 // Blocked User Agents.
91 - 'blacklist' => "MJ12Bot\nDotBot",
273 + 'blacklist' => '',
274 + 'blocklist_presets' => true,
275 + 'blocklist_preset_values' => array( 'mj12bot', 'dotbot' ),
276 + 'script_presets' => false,
277 + 'script_preset_values' => array(),
92 278 );
93 279 }
94 280
95 281 /**
@@ -99,12 +285,18 @@
99 285 *
100 286 * @return void
101 287 */
102 288 protected function before_load(): void {
103 - $default_values = $this->get_default_values();
104 - $this->message = $default_values['message'];
105 - $this->whitelist = $default_values['whitelist'];
106 - $this->blacklist = $default_values['blacklist'];
289 + $default_values = $this->get_default_values();
290 + $this->message = $default_values['message'];
291 + $this->whitelist = $default_values['whitelist'];
292 + $this->blacklist = $default_values['blacklist'];
293 + $this->blocklist_presets = $default_values['blocklist_presets'];
294 + $this->blocklist_preset_values = $default_values['blocklist_preset_values'];
295 + $this->script_presets = $default_values['script_presets'];
296 + $this->script_preset_values = $default_values['script_preset_values'];
297 + $this->malicious_bot_message = $default_values['malicious_bot_message'];
298 + $this->fake_bots_message = $default_values['fake_bots_message'];
107 299 }
108 300
109 301 /**
110 302 * Checks if the User Agent Lockout feature is active.
@@ -111,31 +303,49 @@
111 303 *
112 304 * @return bool Returns true if the User Agent Lockout feature is active, false otherwise.
113 305 */
114 306 public function is_active(): bool {
115 - return (bool) apply_filters(
307 + $enable = apply_filters(
116 308 'wd_user_agents_enable',
117 309 $this->enabled
118 310 );
311 +
312 + return is_bool( $enable ) ? $enable : (bool) $enable;
119 313 }
120 314
121 315 /**
122 - * Get list of blocklisted or allowlisted data.
316 + * Get a list of blocklisted or allowlisted data.
123 317 *
124 - * @param string $type blocklist|allowlist.
318 + * @param string $type blocklist|allowlist.
125 319 * @param bool $lower Whether to convert the list to lowercase.
126 320 *
127 321 * @return array
128 322 */
129 323 public function get_lockout_list( $type = 'blocklist', $lower = true ): array {
130 - $data = ( 'blocklist' === $type ) ? $this->blacklist : $this->whitelist;
131 - $arr = is_array( $data ) ? $data : array_filter( preg_split( "/\r\n|\n|\r/", $data ) );
132 - $arr = array_map( 'trim', $arr );
324 + $data = 'blocklist' === $type ? $this->blacklist : $this->whitelist;
325 + $data = trim( $data );
326 +
327 + if ( '' === $data ) {
328 + return array();
329 + }
330 +
331 + $arr = preg_split( "/\r\n|\n|\r/", $data );
332 + if ( ! is_array( $arr ) ) {
333 + return array();
334 + }
335 +
133 336 if ( $lower ) {
134 - $arr = array_map( 'strtolower', $arr );
337 + $arr = array_map(
338 + function ( $value ) {
339 + return strtolower( trim( $value ) );
340 + },
341 + $arr
342 + );
343 + } else {
344 + $arr = array_map( 'trim', $arr );
135 345 }
136 346
137 - return $arr;
347 + return array_unique( $arr );
138 348 }
139 349
140 350 /**
141 351 * Define settings labels.
@@ -159,28 +369,34 @@
159 369 *
160 370 * @return array
161 371 */
162 372 public function get_access_status( $ua ): array {
163 - $blocklist = str_replace( '#', '\#', $this->get_lockout_list( 'blocklist' ) );
164 - $allowlist = str_replace( '#', '\#', $this->get_lockout_list( 'allowlist' ) );
373 + $blocklist = $this->get_all_selected_blocklist_ua();
374 + $allowlist = $this->get_lockout_list( 'allowlist' );
165 375
166 - $blocklist_regex_pattern = '#' . implode( '|', $blocklist ) . '#i';
167 - $allowlist_regex_pattern = '#' . implode( '|', $allowlist ) . '#i';
376 + // Escape regex special characters in each item before building the pattern.
377 + $blocklist_escaped = array_map( 'preg_quote', $blocklist, array_fill( 0, count( $blocklist ), '#' ) );
378 + $allowlist_escaped = array_map( 'preg_quote', $allowlist, array_fill( 0, count( $allowlist ), '#' ) );
168 379
169 - $blocklist_match = preg_match( $blocklist_regex_pattern, $ua );
170 - $allowlist_match = preg_match( $allowlist_regex_pattern, $ua );
380 + $blocklist_regex_pattern = '#' . implode( '|', $blocklist_escaped ) . '#i';
381 + $allowlist_regex_pattern = '#' . implode( '|', $allowlist_escaped ) . '#i';
171 382
172 - if ( empty( $blocklist_match ) && empty( $allowlist_match ) ) {
383 + $blocklist_match = count( $blocklist ) > 0 && 1 === preg_match( $blocklist_regex_pattern, $ua );
384 + $allowlist_match = count( $allowlist ) > 0 && 1 === preg_match( $allowlist_regex_pattern, $ua );
173 385
386 + if ( ! $blocklist_match && ! $allowlist_match ) {
174 387 return array( 'na' );
175 388 }
176 389
177 390 $result = array();
178 391
179 - if ( ! empty( $blocklist_match ) && empty( $allowlist_match ) ) {
392 + // Check blocklist first - if it matches, add 'banned'.
393 + if ( $blocklist_match ) {
180 394 $result[] = 'banned';
181 395 }
182 - if ( ! empty( $allowlist_match ) ) {
396 +
397 + // Check allowlist - if it matches, add 'allowlist'.
398 + if ( $allowlist_match ) {
183 399 $result[] = 'allowlist';
184 400 }
185 401
186 402 return $result;
@@ -186,74 +402,8 @@
186 402 return $result;
187 403 }
188 404
189 405 /**
190 - * Checks if a user agent is present in a given list.
191 - *
192 - * @param string $ua The user agent to check.
193 - * @param string $collection The type of list to check against ('blocklist' or 'allowlist').
194 - *
195 - * @return bool Returns true if the user agent is in the list, false otherwise.
196 - */
197 - public function is_ua_in_list( $ua, $collection ): bool {
198 - $arr = str_replace( '#', '\#', $this->get_lockout_list( $collection ) );
199 -
200 - $list_regex_pattern = '#' . implode( '|', $arr ) . '#i';
201 -
202 - $list_match = preg_match( $list_regex_pattern, $ua );
203 -
204 - return ! empty( $list_match );
205 - }
206 -
207 - /**
208 - * Remove User Agent from a list.
209 - *
210 - * @param string $ua The user agent to remove.
211 - * @param string $collection blocklist|allowlist.
212 - *
213 - * @return void
214 - */
215 - public function remove_from_list( $ua, $collection ) {
216 - $arr = $this->get_lockout_list( $collection );
217 - // Array can contain uppercase.
218 - $orig_arr = str_replace( '#', '\#', $this->get_lockout_list( $collection, false ) );
219 -
220 - $list_regex_pattern = '#' . implode( '|', $arr ) . '#i';
221 -
222 - $list_match = preg_match( $list_regex_pattern, $ua );
223 -
224 - if ( false !== $list_match ) {
225 -
226 - // Plain string match. For e.g. r.n regex matches ran & run but we can add/block UA string name if user send the useragent name as run then we include that in allowlist so run won't be blocked but ran will be blocked.
227 - $key = array_search( $ua, $arr, true );
228 -
229 - if ( false !== $key && isset( $orig_arr[ $key ] ) ) {
230 - unset( $orig_arr[ $key ] );
231 - $is_string_match = true;
232 - } else {
233 - // If plain string not matched then add the user agent in opposite list if unban is clicked then add that string to allow list, else if ban user agent clicked then add that string to blocklist though allow list take higher priority i.e. in allow list r.n present then adding r.n or run or ran in blocklist won't block the user agent because of priority.
234 - $is_string_match = false;
235 - }
236 -
237 - if ( 'blocklist' === $collection ) {
238 - $this->blacklist = implode( PHP_EOL, $orig_arr );
239 -
240 - if ( false === $is_string_match ) {
241 - $this->whitelist = $this->push_ua_to_list( $ua, 'allowlist' );
242 - }
243 - } elseif ( 'allowlist' === $collection ) {
244 - $this->whitelist = implode( PHP_EOL, $orig_arr );
245 -
246 - if ( false === $is_string_match ) {
247 - $this->blacklist = $this->push_ua_to_list( $ua, 'blocklist' );
248 - }
249 - }
250 -
251 - $this->save();
252 - }
253 - }
254 -
255 - /**
256 406 * Add an UA to the list.
257 407 *
258 408 * @param string $ua User agent name.
259 409 * @param string $collection blocklist|allowlist.
@@ -261,9 +411,17 @@
261 411 * @return void
262 412 */
263 413 public function add_to_list( $ua, $collection ) {
264 414 if ( 'blocklist' === $collection ) {
265 - $this->blacklist = $this->push_ua_to_list( $ua, $collection );
415 + if ( User_Agent_Service::is_blocklist_presets( $ua ) ) {
416 + $this->blocklist_presets = true;
417 + $this->blocklist_preset_values[] = $ua;
418 + } elseif ( User_Agent_Service::is_script_presets( $ua ) ) {
419 + $this->script_presets = true;
420 + $this->script_preset_values[] = $ua;
421 + } else {
422 + $this->blacklist = $this->push_ua_to_list( $ua, $collection );
423 + }
266 424 } elseif ( 'allowlist' === $collection ) {
267 425 $this->whitelist = $this->push_ua_to_list( $ua, $collection );
268 426 }
269 427
@@ -270,9 +428,9 @@
270 428 $this->save();
271 429 }
272 430
273 431 /**
274 - * Push the UA to either blocklist or allowlist
432 + * Push the UA to either blocklist (only in 'Custom User Agents') or allowlist
275 433 *
276 434 * @param string $ua User agent name.
277 435 * @param string $collection List type i.e. blocklist or allowlist.
278 436 *
@@ -286,8 +444,23 @@
286 444 return implode( PHP_EOL, $arr );
287 445 }
288 446
289 447 /**
448 + * Filter the UAs, as we use a textarea to submit, so it can contain some un-valid UAs.
449 + */
450 + protected function after_validate(): void {
451 + $this->blacklist = implode(
452 + PHP_EOL,
453 + array_filter( $this->get_lockout_list( 'blocklist' ), 'strlen' )
454 + );
455 +
456 + $this->whitelist = implode(
457 + PHP_EOL,
458 + array_filter( $this->get_lockout_list( 'allowlist' ), 'strlen' )
459 + );
460 + }
461 +
462 + /**
290 463 * Get the module name.
291 464 *
292 465 * @return string
293 466 */
@@ -303,6 +476,30 @@
303 476 * @return string
304 477 */
305 478 public static function get_module_state( $flag ): string {
306 479 return $flag ? esc_html__( 'active', 'defender-security' ) : esc_html__( 'inactive', 'defender-security' );
480 + }
481 +
482 + /**
483 + * Get the list of all selected blocklisted UA values.
484 + *
485 + * @param bool $lower Whether to convert the list to lowercase.
486 + *
487 + * @return array
488 + */
489 + public function get_all_selected_blocklist_ua( $lower = true ): array {
490 + $blocklist_custom = $this->get_lockout_list( 'blocklist', $lower );
491 + $blocklist_presets = $this->blocklist_presets ? $this->blocklist_preset_values : array();
492 + $script_presets = $this->script_presets ? $this->script_preset_values : array();
493 +
494 + return array_merge( $blocklist_custom, $blocklist_presets, $script_presets );
495 + }
496 +
497 + /**
498 + * Return the module slug.
499 + *
500 + * @return string
501 + */
502 + public static function get_module_slug(): string {
503 + return 'ua-lockout';
307 504 }
308 505 }