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 +167 -124 5.4.0 → 6.3.0 View file →
@@ -18,10 +18,10 @@
18 18 * 'Scripts Presets' is associated with the properties $script_presets & $script_preset_values,
19 19 * 'Custom User Agents' is associated with the property $blacklist.
20 20 */
21 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' );
22 + const BOT_LOCKOUT_TYPE_ALLOWED = array( 'temporary', 'permanent' );
23 + const BOT_LOCKOUT_DURATION_UNIT_ALLOWED = array( 'seconds', 'minutes', 'hours' );
24 24
25 25 /**
26 26 * Option name.
27 27 *
@@ -34,9 +34,9 @@
34 34 *
35 35 * @var bool
36 36 * @defender_property
37 37 */
38 - public $enabled = false;
38 + public bool $enabled = false;
39 39
40 40 /**
41 41 * Blacklist User Agents.
42 42 *
@@ -102,14 +102,14 @@
102 102 */
103 103 public $script_preset_values = array();
104 104
105 105 /**
106 - * Is bot trap enabled?
106 + * Is malicious bot enabled?
107 107 *
108 108 * @var bool
109 109 * @defender_property
110 110 */
111 - public $bot_trap_enabled = false;
111 + public bool $malicious_bot_enabled = false;
112 112
113 113 /**
114 114 * How the lock is going to be, if we choose permanent, then their IP will be blacklisted.
115 115 *
@@ -116,9 +116,9 @@
116 116 * @var string
117 117 * @defender_property
118 118 * @rule in[temporary,permanent]
119 119 */
120 - public $bot_trap_lockout_type = 'temporary';
120 + public $malicious_bot_lockout_type = 'temporary';
121 121
122 122 /**
123 123 * Duration for the lockout.
124 124 *
@@ -125,9 +125,9 @@
125 125 * @var int
126 126 * @defender_property
127 127 * @rule required|integer
128 128 */
129 - public $bot_trap_lockout_duration = 300;
129 + public $malicious_bot_lockout_duration = 300;
130 130
131 131 /**
132 132 * Duration unit.
133 133 *
@@ -134,28 +134,121 @@
134 134 * @var string
135 135 * @defender_property
136 136 * @rule in[seconds,minutes,hours]
137 137 */
138 - public $bot_trap_lockout_duration_unit = 'seconds';
138 + public $malicious_bot_lockout_duration_unit = 'seconds';
139 139
140 140 /**
141 - * The message to show on frontend when a bot trap is triggered.
141 + * The message to show on frontend when a malicious bot is triggered.
142 142 *
143 143 * @var string
144 144 * @defender_property
145 145 * @sanitize sanitize_textarea_field
146 146 */
147 - public $bot_trap_message = '';
147 + public $malicious_bot_message = '';
148 148
149 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 + /**
150 230 * Rules for validation.
151 231 *
152 232 * @var array
153 233 */
154 234 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 ),
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 ),
158 251 );
159 252
160 253 /**
161 254 * Get default values.
@@ -172,9 +265,10 @@
172 265 $message = esc_html__( 'You have been blocked from accessing this website.', 'defender-security' );
173 266
174 267 return array(
175 268 'message' => $message,
176 - 'bot_trap_message' => $message,
269 + 'malicious_bot_message' => $message,
270 + 'fake_bots_message' => $message,
177 271 'whitelist' => $whitelist,
178 272 // Blocked User Agents.
179 273 'blacklist' => '',
180 274 'blocklist_presets' => true,
@@ -199,9 +293,10 @@
199 293 $this->blocklist_presets = $default_values['blocklist_presets'];
200 294 $this->blocklist_preset_values = $default_values['blocklist_preset_values'];
201 295 $this->script_presets = $default_values['script_presets'];
202 296 $this->script_preset_values = $default_values['script_preset_values'];
203 - $this->bot_trap_message = $default_values['bot_trap_message'];
297 + $this->malicious_bot_message = $default_values['malicious_bot_message'];
298 + $this->fake_bots_message = $default_values['fake_bots_message'];
204 299 }
205 300
206 301 /**
207 302 * Checks if the User Agent Lockout feature is active.
@@ -208,31 +303,49 @@
208 303 *
209 304 * @return bool Returns true if the User Agent Lockout feature is active, false otherwise.
210 305 */
211 306 public function is_active(): bool {
212 - return (bool) apply_filters(
307 + $enable = apply_filters(
213 308 'wd_user_agents_enable',
214 309 $this->enabled
215 310 );
311 +
312 + return is_bool( $enable ) ? $enable : (bool) $enable;
216 313 }
217 314
218 315 /**
219 - * Get list of blocklisted or allowlisted data.
316 + * Get a list of blocklisted or allowlisted data.
220 317 *
221 - * @param string $type blocklist|allowlist.
318 + * @param string $type blocklist|allowlist.
222 319 * @param bool $lower Whether to convert the list to lowercase.
223 320 *
224 321 * @return array
225 322 */
226 323 public function get_lockout_list( $type = 'blocklist', $lower = true ): array {
227 - $data = ( 'blocklist' === $type ) ? $this->blacklist : $this->whitelist;
228 - $arr = is_array( $data ) ? $data : array_filter( preg_split( "/\r\n|\n|\r/", $data ) );
229 - $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 +
230 336 if ( $lower ) {
231 - $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 );
232 345 }
233 346
234 - return $arr;
347 + return array_unique( $arr );
235 348 }
236 349
237 350 /**
238 351 * Define settings labels.
@@ -256,10 +369,10 @@
256 369 *
257 370 * @return array
258 371 */
259 372 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' ) );
373 + $blocklist = $this->get_all_selected_blocklist_ua();
374 + $allowlist = $this->get_lockout_list( 'allowlist' );
262 375
263 376 // Escape regex special characters in each item before building the pattern.
264 377 $blocklist_escaped = array_map( 'preg_quote', $blocklist, array_fill( 0, count( $blocklist ), '#' ) );
265 378 $allowlist_escaped = array_map( 'preg_quote', $allowlist, array_fill( 0, count( $allowlist ), '#' ) );
@@ -266,12 +379,12 @@
266 379
267 380 $blocklist_regex_pattern = '#' . implode( '|', $blocklist_escaped ) . '#i';
268 381 $allowlist_regex_pattern = '#' . implode( '|', $allowlist_escaped ) . '#i';
269 382
270 - $blocklist_match = preg_match( $blocklist_regex_pattern, $ua );
271 - $allowlist_match = preg_match( $allowlist_regex_pattern, $ua );
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 );
272 385
273 - if ( empty( $blocklist_match ) && empty( $allowlist_match ) ) {
386 + if ( ! $blocklist_match && ! $allowlist_match ) {
274 387 return array( 'na' );
275 388 }
276 389
277 390 $result = array();
@@ -276,14 +389,14 @@
276 389
277 390 $result = array();
278 391
279 392 // Check blocklist first - if it matches, add 'banned'.
280 - if ( ! empty( $blocklist_match ) ) {
393 + if ( $blocklist_match ) {
281 394 $result[] = 'banned';
282 395 }
283 396
284 397 // Check allowlist - if it matches, add 'allowlist'.
285 - if ( ! empty( $allowlist_match ) ) {
398 + if ( $allowlist_match ) {
286 399 $result[] = 'allowlist';
287 400 }
288 401
289 402 return $result;
@@ -289,102 +402,8 @@
289 402 return $result;
290 403 }
291 404
292 405 /**
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 - $list_match = preg_match( $list_regex_pattern, $ua );
312 -
313 - return ! empty( $list_match );
314 - }
315 -
316 - /**
317 - * Remove User Agent from a list.
318 - *
319 - * @param string $ua The user agent to remove.
320 - * @param string $collection blocklist|allowlist.
321 - *
322 - * @return void
323 - */
324 - public function remove_from_list( $ua, $collection ) {
325 - if ( 'allowlist' === $collection ) {
326 - $arr = $this->get_lockout_list( $collection );
327 - // Array can contain uppercase.
328 - $orig_arr = str_replace( '#', '\#', $this->get_lockout_list( $collection, false ) );
329 - } else {
330 - $arr = $this->get_all_selected_blocklist_ua();
331 - // Array can contain uppercase.
332 - $orig_arr = str_replace( '#', '\#', $this->get_all_selected_blocklist_ua( false ) );
333 - }
334 -
335 - $list_regex_pattern = '#' . implode( '|', $arr ) . '#i';
336 - $list_match = preg_match( $list_regex_pattern, $ua );
337 - if ( false !== $list_match ) {
338 - if ( 'blocklist' === $collection ) {
339 - // Check in 'Blocklist Presets'.
340 - if ( $this->blocklist_presets ) {
341 - $key = array_search( $ua, $this->blocklist_preset_values, true );
342 - if ( false !== $key ) {
343 - unset( $this->blocklist_preset_values[ $key ] );
344 - $this->blocklist_preset_values = array_values( $this->blocklist_preset_values );
345 - }
346 - }
347 - // Check in 'Script Presets' using Regex.
348 - if ( $this->script_presets ) {
349 - if ( false !== strpos( $ua, User_Agent_Service::GO_HTTP_CLIENT_KEY . '/' ) ) {
350 - $key_script_preset = User_Agent_Service::GO_HTTP_CLIENT_KEY;
351 - } elseif ( false !== strpos( $ua, User_Agent_Service::PYTHON_REQUESTS_KEY . '/' ) ) {
352 - $key_script_preset = User_Agent_Service::PYTHON_REQUESTS_KEY;
353 - } else {
354 - $key_script_preset = '';
355 - }
356 -
357 - $key = array_search( $key_script_preset, $this->script_preset_values, true );
358 - if ( false !== $key ) {
359 - unset( $this->script_preset_values[ $key ] );
360 - $this->script_preset_values = array_values( $this->script_preset_values );
361 - }
362 - }
363 - // Check 'Custom User Agents' case.
364 - $arr_blocklist = $this->get_lockout_list( 'blocklist', false );
365 - if ( ! empty( $arr_blocklist ) ) {
366 - $key = array_search( $ua, $arr_blocklist, true );
367 - if ( false !== $key && isset( $arr_blocklist[ $key ] ) ) {
368 - unset( $arr_blocklist[ $key ] );
369 - // Convert back to string.
370 - $this->blacklist = implode( PHP_EOL, $arr_blocklist );
371 - }
372 - }
373 - } elseif ( 'allowlist' === $collection ) {
374 - $key = array_search( $ua, $arr, true );
375 - if ( false !== $key && isset( $arr[ $key ] ) ) {
376 - unset( $arr[ $key ] );
377 - // Convert back to string.
378 - $this->whitelist = implode( PHP_EOL, $arr );
379 - }
380 - }
381 -
382 - $this->save();
383 - }
384 - }
385 -
386 - /**
387 406 * Add an UA to the list.
388 407 *
389 408 * @param string $ua User agent name.
390 409 * @param string $collection blocklist|allowlist.
@@ -425,8 +444,23 @@
425 444 return implode( PHP_EOL, $arr );
426 445 }
427 446
428 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 + /**
429 463 * Get the module name.
430 464 *
431 465 * @return string
432 466 */
@@ -457,6 +491,15 @@
457 491 $blocklist_presets = $this->blocklist_presets ? $this->blocklist_preset_values : array();
458 492 $script_presets = $this->script_presets ? $this->script_preset_values : array();
459 493
460 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';
461 504 }
462 505 }