PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.8.1
Defender Security – Malware Scanner, Login Security & Firewall v5.8.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 / controller / class-malicious-bot.php

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

291 lines 7.5 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 const URL_QUERY = 'wpdef-malicious-bot-url';
26
27 /**
28 * Service for handling logic.
29 *
30 * @var Malicious_Bot_Component
31 */
32 protected $service;
33
34 /**
35 * Constructor for the Malicious_Bot class.
36 * Initializes the service and sets up necessary hooks.
37 *
38 * @param Malicious_Bot_Component $service The service instance for malicious bot functionality.
39 */
40 public function __construct( Malicious_Bot_Component $service ) {
41 $this->service = $service;
42
43 if ( $this->service->is_enabled() ) {
44 add_action( 'init', array( $this, 'init' ) );
45 add_action( 'wpdef_rotate_malicious_bot_secret_hash', array( $this, 'rotate_hash' ) );
46 add_filter( 'query_vars', array( $this, 'add_query_var' ) );
47 add_action( 'after_switch_theme', array( $this, 'flush_rewrite' ) );
48
49 $service = wd_di()->get( Blacklist_Lockout::class );
50 $ip = $this->get_user_ip();
51 if ( ! $service->are_ips_whitelisted( $ip ) ) {
52 add_action( 'wp_footer', array( $this, 'inject_footer' ) );
53 add_action( 'login_footer', array( $this, 'inject_footer' ) );
54 add_action( 'template_redirect', array( $this, 'handle_hash_url' ) );
55 }
56 }
57 }
58
59 /**
60 * Initializes the malicious bot functionality.
61 * Schedules a weekly cron job to rotate the hash and registers a rewrite rule.
62 */
63 public function init() {
64 $this->schedule_cron();
65
66 if ( ! $this->service->get_hash() ) {
67 $this->service->rotate_hash();
68 }
69
70 $this->flush_rewrite();
71 $this->service->handle_robots_txt();
72 }
73
74 /**
75 * Schedules a weekly cron job to rotate the malicious bot hash.
76 * This ensures that the malicious bot URL changes weekly.
77 */
78 public function schedule_cron() {
79 /**
80 * Network Cron Manager
81 *
82 * @var Network_Cron_Manager $network_cron_manager
83 */
84 $network_cron_manager = wd_di()->get( Network_Cron_Manager::class );
85 $network_cron_manager->register_callback(
86 'wpdef_rotate_malicious_bot_secret_hash',
87 array( $this->service, 'rotate_hash' ),
88 WEEK_IN_SECONDS
89 );
90 }
91
92 /**
93 * Registers a rewrite rule for the malicious bot URL.
94 * The URL will be in the format: /{hash}/
95 * where {hash} is a 16-character hexadecimal string.
96 */
97 public function register_rewrite_rule() {
98 $hash = $this->service->get_hash();
99 add_rewrite_rule( "^{$hash}/?$", 'index.php?' . self::URL_QUERY . '=' . $hash, 'top' );
100 }
101
102 /**
103 * Rotates the malicious bot hash and flushes rewrite rules.
104 */
105 public function rotate_hash() {
106 $this->service->rotate_hash();
107 }
108
109 /**
110 * Adds a query variable for the malicious bot URL.
111 * This allows us to capture the hash from the URL.
112 *
113 * @param array $vars Existing query variables.
114 * @return array Modified query variables.
115 */
116 public function add_query_var( $vars ) {
117 $vars[] = self::URL_QUERY;
118 return $vars;
119 }
120
121 /**
122 * Handles the malicious bot URL when accessed.
123 * If the hash in the URL matches the stored hash, block the IP.
124 * Otherwise, it will do nothing.
125 */
126 public function handle_hash_url() {
127 $used_hash = get_query_var( self::URL_QUERY );
128 $valid_hash = $this->service->get_hash();
129
130 if ( $used_hash === $valid_hash ) {
131 $known_bots = Known_Bots_Factory::create();
132 $bot_ips = $known_bots->get_all_bot_ips();
133
134 // Flatten 2D array into a single array.
135 $flattened_bot_ips = array();
136 foreach ( $bot_ips as $ips ) {
137 foreach ( $ips as $ip ) {
138 $flattened_bot_ips[] = $ip;
139 }
140 }
141
142 $model = $this->service->model;
143 $ips = $this->service->get_user_ip();
144
145 foreach ( $ips as $ip ) {
146 // Skip if the IP is a known bot IP.
147 if ( $this->is_ip_in_format( $ip, $flattened_bot_ips ) ) {
148 continue;
149 }
150
151 $lockout_model = Lockout_Ip::get( $ip );
152 $remaining_time = 0;
153 if ( 'permanent' === $model->malicious_bot_lockout_type ) {
154 $lockout_model->attempt = 0;
155 $lockout_model->meta['login'] = array();
156 $lockout_model->meta['nf'] = array();
157 $lockout_model->save();
158 // We block IP here unlike other UA lockout cases.
159 do_action( 'wd_blacklist_this_ip', $ip );
160 } else {
161 $lockout_model->status = Lockout_Ip::STATUS_BLOCKED;
162 $lockout_model->lock_time = time();
163
164 $this->service->create_blocked_lockout(
165 $lockout_model,
166 $model->malicious_bot_message,
167 strtotime( '+' . $model->malicious_bot_lockout_duration . ' ' . $model->malicious_bot_lockout_duration_unit )
168 );
169
170 $remaining_time = $lockout_model->remaining_release_time();
171 }
172
173 // Need to create a log.
174 $this->service->log_event( $ip, $used_hash, Malicious_Bot_Component::SCENARIO_MALICIOUS_BOT );
175
176 wd_di()->get( Firewall::class )->actions_for_blocked(
177 $model->malicious_bot_message,
178 $remaining_time,
179 Malicious_Bot_Component::SCENARIO_MALICIOUS_BOT,
180 $ips,
181 true
182 );
183 }
184 }
185 }
186
187 /**
188 * Injects the malicious bot URL into the footer of frontend pages.
189 * This URL is hidden.
190 */
191 public function inject_footer() {
192 if ( is_admin() ) {
193 return;
194 }
195
196 $hash = $this->service->get_hash();
197 echo '<div style="display:none;"><a href="' . esc_url( home_url( "/{$hash}" ) ) . '" rel="nofollow">Secret Link</a></div>';
198 }
199
200 /**
201 * Flushes the rewrite rules to ensure the new malicious bot URL is recognized.
202 */
203 public function flush_rewrite() {
204 $this->register_rewrite_rule();
205 flush_rewrite_rules();
206 }
207
208 /**
209 * Checks if the current request is for the malicious bot hash URL.
210 *
211 * @return bool True if the request is for the hash URL, false otherwise.
212 */
213 public function is_hash_request(): bool {
214 $hash = $this->service->get_hash();
215 if ( ! is_string( $hash ) || '' === trim( $hash ) ) {
216 return false;
217 }
218
219 $uri = defender_get_data_from_request( 'REQUEST_URI', 's' );
220 if ( ! is_string( $uri ) || '' === $uri ) {
221 return false;
222 }
223
224 // Get request path.
225 $uri = wp_parse_url( $uri, PHP_URL_PATH );
226 $uri = trim( $uri, '/' );
227
228 // Always check last segment only.
229 $segments = explode( '/', $uri );
230 $last = end( $segments );
231
232 return $last === $hash;
233 }
234
235 /**
236 * Delete all the data & the cache.
237 */
238 public function remove_data() {
239 // Remove the malicious bot hash from options.
240 delete_site_option( Malicious_Bot_Component::URL_HASH_KEY );
241
242 $this->service->remove_rule();
243
244 // Flush rewrite rules to remove the malicious bot URL.
245 flush_rewrite_rules();
246 }
247
248 /**
249 * Exports strings.
250 *
251 * @return array An array of strings.
252 */
253 public function export_strings(): array {
254 return array();
255 }
256
257 /**
258 * Converts the object data to an array.
259 *
260 * @return array An array representation of the object.
261 */
262 public function to_array(): array {
263 return array();
264 }
265
266 /**
267 * Imports data into the model.
268 *
269 * @param array $data Data to be imported into the model.
270 *
271 * @throws Exception If table is not defined.
272 */
273 public function import_data( array $data ) {
274 }
275
276 /**
277 * Removes settings for all submodules.
278 */
279 public function remove_settings(): void {
280 }
281
282 /**
283 * Provides data for the frontend.
284 *
285 * @return array An array of data for the frontend.
286 */
287 public function data_frontend(): array {
288 return array();
289 }
290 }
291