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
defender-security / src / controller / class-malicious-bot.php

class-malicious-bot.php in Defender Security – Malware Scanner, Login Security & Firewall 6.3.0, at src/controller/class-malicious-bot.php

321 lines 8.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles malicious bot functionality.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use WP_Defender\Controller;
11 use WP_Defender\Component\Blacklist_Lockout;
12 use WP_Defender\Component\Network_Cron_Manager;
13 use WP_Defender\Component\Malicious_Bot as Malicious_Bot_Component;
14 use WP_Defender\Component\Known_Bots\Known_Bots_Factory;
15 use WP_Defender\Model\Lockout_Ip;
16 use WP_Defender\Traits\IP;
17
18 /**
19 * Handles operations to insert a weekly rotating hash URL into the footer,
20 * and blocking IP addresses that access this URL.
21 */
22 class Malicious_Bot extends Controller {
23 use IP;
24
25 /**
26 * Service for handling logic.
27 *
28 * @var Malicious_Bot_Component
29 */
30 public $service;
31
32 /**
33 * Constructor for the Malicious_Bot class.
34 * Initializes the service and sets up necessary hooks.
35 *
36 * @param Malicious_Bot_Component $service The service instance for malicious bot functionality.
37 */
38 public function __construct( Malicious_Bot_Component $service ) {
39 $this->service = $service;
40
41 if ( $this->service->is_enabled() ) {
42 add_action( 'init', array( $this, 'init' ) );
43 add_action( 'wpdef_rotate_malicious_bot_secret_hash', array( $this->service, 'rotate_hash' ) );
44 add_filter( 'query_vars', array( $this, 'add_query_var' ) );
45
46 $service = wd_di()->get( Blacklist_Lockout::class );
47 $ip = $this->get_user_ip();
48 if ( ! $service->are_ips_whitelisted( $ip ) ) {
49 add_action( 'wp_footer', array( $this, 'inject_footer' ) );
50 add_action( 'login_footer', array( $this, 'inject_footer' ) );
51 add_action( 'template_redirect', array( $this, 'handle_hash_url' ) );
52 }
53 }
54 }
55
56 /**
57 * Initializes the malicious bot functionality.
58 * Schedules a weekly cron job to rotate the hash and registers a rewrite rule.
59 */
60 public function init() {
61 $this->schedule_cron();
62
63 if ( ! $this->service->get_hash() ) {
64 $this->service->rotate_hash();
65 } else {
66 $this->service->register_rewrite_rule();
67 }
68
69 $this->service->handle_robots_txt();
70 }
71
72 /**
73 * Schedules a weekly cron job to rotate the malicious bot hash.
74 * This ensures that the malicious bot URL changes weekly.
75 */
76 public function schedule_cron() {
77 /**
78 * Network Cron Manager
79 *
80 * @var Network_Cron_Manager $network_cron_manager
81 */
82 $network_cron_manager = wd_di()->get( Network_Cron_Manager::class );
83 $network_cron_manager->register_callback(
84 'wpdef_rotate_malicious_bot_secret_hash',
85 array( $this->service, 'rotate_hash' ),
86 WEEK_IN_SECONDS
87 );
88 }
89
90 /**
91 * Adds a query variable for the malicious bot URL.
92 * This allows us to capture the hash from the URL.
93 *
94 * @param array $vars Existing query variables.
95 * @return array Modified query variables.
96 */
97 public function add_query_var( $vars ) {
98 $vars[] = Malicious_Bot_Component::URL_QUERY;
99 return $vars;
100 }
101
102 /**
103 * Handles the malicious bot URL when accessed.
104 * If the hash in the URL matches the stored hash, block the IP.
105 * Otherwise, it will do nothing.
106 */
107 public function handle_hash_url() {
108 $used_hash = get_query_var( Malicious_Bot_Component::URL_QUERY );
109 $valid_hash = $this->service->get_hash();
110
111 if ( $used_hash === $valid_hash ) {
112 if ( $this->is_cross_site_request() ) {
113 return;
114 }
115
116 $known_bots = Known_Bots_Factory::create();
117 $bot_ips = $known_bots->get_all_bot_ips();
118
119 // Flatten 2D array into a single array.
120 $flattened_bot_ips = array();
121 foreach ( $bot_ips as $ips ) {
122 foreach ( $ips as $ip ) {
123 $flattened_bot_ips[] = $ip;
124 }
125 }
126
127 $model = $this->service->model;
128 $ips = $this->service->get_user_ip();
129
130 foreach ( $ips as $ip ) {
131 // Skip if the IP is a known bot IP.
132 if ( $this->is_ip_in_format( $ip, $flattened_bot_ips ) ) {
133 continue;
134 }
135
136 $lockout_model = Lockout_Ip::get( $ip );
137 $remaining_time = 0;
138 if ( 'permanent' === $model->malicious_bot_lockout_type ) {
139 $lockout_model->attempt = 0;
140 $lockout_model->meta['login'] = array();
141 $lockout_model->meta['nf'] = array();
142 $lockout_model->save();
143 // We block IP here unlike other UA lockout cases.
144 do_action( 'wd_blacklist_this_ip', $ip );
145 } else {
146 $lockout_model->status = Lockout_Ip::STATUS_BLOCKED;
147 $lockout_model->lock_time = time();
148
149 $this->service->create_blocked_lockout(
150 $lockout_model,
151 $model->malicious_bot_message,
152 strtotime( '+' . $model->malicious_bot_lockout_duration . ' ' . $model->malicious_bot_lockout_duration_unit )
153 );
154
155 $remaining_time = $lockout_model->remaining_release_time();
156 }
157
158 // Need to create a log.
159 $this->service->log_event( $ip, $used_hash, Malicious_Bot_Component::SCENARIO_MALICIOUS_BOT );
160
161 wd_di()->get( Firewall::class )->actions_for_blocked(
162 $model->malicious_bot_message,
163 $remaining_time,
164 Malicious_Bot_Component::SCENARIO_MALICIOUS_BOT,
165 $ips,
166 true
167 );
168 }
169 }
170 }
171
172 /**
173 * Checks if the current request is a cross-site or embedded request.
174 *
175 * @return bool True if cross-site or embedded, false otherwise.
176 */
177 public function is_cross_site_request(): bool {
178 $sec_fetch_site = defender_get_data_from_request( 'HTTP_SEC_FETCH_SITE', 's' );
179 if ( is_string( $sec_fetch_site ) && 'cross-site' === strtolower( $sec_fetch_site ) ) {
180 return true;
181 }
182
183 $sec_fetch_dest = defender_get_data_from_request( 'HTTP_SEC_FETCH_DEST', 's' );
184 if ( is_string( $sec_fetch_dest ) && in_array( strtolower( $sec_fetch_dest ), array( 'image', 'iframe', 'frame', 'embed', 'object', 'audio', 'video', 'track', 'style', 'script', 'font' ), true ) ) {
185 return true;
186 }
187
188 $sec_fetch_mode = defender_get_data_from_request( 'HTTP_SEC_FETCH_MODE', 's' );
189 if ( is_string( $sec_fetch_mode ) && 'no-cors' === strtolower( $sec_fetch_mode ) ) {
190 return true;
191 }
192
193 $site_host = wp_parse_url( home_url(), PHP_URL_HOST );
194 $site_host = is_string( $site_host ) ? strtolower( $site_host ) : '';
195
196 $http_host = defender_get_data_from_request( 'HTTP_HOST', 's' );
197 if ( is_string( $http_host ) && '' !== $http_host ) {
198 $http_host = strtolower( explode( ':', $http_host )[0] );
199 } else {
200 $http_host = '';
201 }
202
203 foreach ( array( 'HTTP_REFERER', 'HTTP_ORIGIN' ) as $header ) {
204 $header_val = defender_get_data_from_request( $header, 's' );
205 if ( is_string( $header_val ) && '' !== $header_val ) {
206 $header_host = wp_parse_url( $header_val, PHP_URL_HOST );
207 if ( is_string( $header_host ) && '' !== $header_host ) {
208 $header_host_lower = strtolower( $header_host );
209 if ( $header_host_lower !== $site_host && ( '' === $http_host || $header_host_lower !== $http_host ) ) {
210 return true;
211 }
212 }
213 }
214 }
215
216 return false;
217 }
218
219 /**
220 * Injects the malicious bot URL into the footer of frontend pages.
221 * This URL is hidden.
222 */
223 public function inject_footer() {
224 if ( is_admin() ) {
225 return;
226 }
227
228 $hash = $this->service->get_hash();
229 echo '<div style="display:none;"><a href="' . esc_url( home_url( "/{$hash}" ) ) . '" rel="nofollow">Secret Link</a></div>';
230 }
231
232 /**
233 * Checks if the current request is for the malicious bot hash URL.
234 *
235 * @return bool True if the request is for the hash URL, false otherwise.
236 */
237 public function is_hash_request(): bool {
238 $hash = $this->service->get_hash();
239 if ( ! is_string( $hash ) || '' === trim( $hash ) ) {
240 return false;
241 }
242
243 $uri = defender_get_data_from_request( 'REQUEST_URI', 's' );
244 $path = is_string( $uri ) ? wp_parse_url( $uri, PHP_URL_PATH ) : null;
245 if ( ! is_string( $path ) || '' === $path ) {
246 return false;
247 }
248
249 $path = trim( $path, '/' );
250
251 // Always check last segment only.
252 $segments = explode( '/', $path );
253 $last = end( $segments );
254
255 return $last === $hash;
256 }
257
258 /**
259 * Rotate the malicious bot hash and refresh the related rules.
260 */
261 public function rotate_hash() {
262 $this->service->rotate_hash();
263 }
264
265 /**
266 * Delete all the data & the cache.
267 */
268 public function remove_data() {
269 // Remove the malicious bot hash from options.
270 delete_site_option( Malicious_Bot_Component::URL_HASH_KEY );
271
272 $this->service->remove_rule();
273
274 // Flush rewrite rules to remove the malicious bot URL.
275 flush_rewrite_rules();
276 }
277
278 /**
279 * Exports strings.
280 *
281 * @return array An array of strings.
282 */
283 public function export_strings(): array {
284 return array();
285 }
286
287 /**
288 * Converts the object data to an array.
289 *
290 * @return array An array representation of the object.
291 */
292 public function to_array(): array {
293 return array();
294 }
295
296 /**
297 * Imports data into the model.
298 *
299 * @param array $data Data to be imported into the model.
300 *
301 * @throws Exception If table is not defined.
302 */
303 public function import_data( array $data ) {
304 }
305
306 /**
307 * Removes settings for all submodules.
308 */
309 public function remove_settings(): void {
310 }
311
312 /**
313 * Provides data for the frontend.
314 *
315 * @return array An array of data for the frontend.
316 */
317 public function data_frontend(): array {
318 return array();
319 }
320 }
321