PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / trunk
Defender Security – Malware Scanner, Login Security & Firewall vtrunk
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 3.1.1 All 140 releases
defender-security / src / controller / class-malicious-bot.php

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

271 lines 7.0 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 protected $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 $known_bots = Known_Bots_Factory::create();
113 $bot_ips = $known_bots->get_all_bot_ips();
114
115 // Flatten 2D array into a single array.
116 $flattened_bot_ips = array();
117 foreach ( $bot_ips as $ips ) {
118 foreach ( $ips as $ip ) {
119 $flattened_bot_ips[] = $ip;
120 }
121 }
122
123 $model = $this->service->model;
124 $ips = $this->service->get_user_ip();
125
126 foreach ( $ips as $ip ) {
127 // Skip if the IP is a known bot IP.
128 if ( $this->is_ip_in_format( $ip, $flattened_bot_ips ) ) {
129 continue;
130 }
131
132 $lockout_model = Lockout_Ip::get( $ip );
133 $remaining_time = 0;
134 if ( 'permanent' === $model->malicious_bot_lockout_type ) {
135 $lockout_model->attempt = 0;
136 $lockout_model->meta['login'] = array();
137 $lockout_model->meta['nf'] = array();
138 $lockout_model->save();
139 // We block IP here unlike other UA lockout cases.
140 do_action( 'wd_blacklist_this_ip', $ip );
141 } else {
142 $lockout_model->status = Lockout_Ip::STATUS_BLOCKED;
143 $lockout_model->lock_time = time();
144
145 $this->service->create_blocked_lockout(
146 $lockout_model,
147 $model->malicious_bot_message,
148 strtotime( '+' . $model->malicious_bot_lockout_duration . ' ' . $model->malicious_bot_lockout_duration_unit )
149 );
150
151 $remaining_time = $lockout_model->remaining_release_time();
152 }
153
154 // Need to create a log.
155 $this->service->log_event( $ip, $used_hash, Malicious_Bot_Component::SCENARIO_MALICIOUS_BOT );
156
157 wd_di()->get( Firewall::class )->actions_for_blocked(
158 $model->malicious_bot_message,
159 $remaining_time,
160 Malicious_Bot_Component::SCENARIO_MALICIOUS_BOT,
161 $ips,
162 true
163 );
164 }
165 }
166 }
167
168 /**
169 * Injects the malicious bot URL into the footer of frontend pages.
170 * This URL is hidden.
171 */
172 public function inject_footer() {
173 if ( is_admin() ) {
174 return;
175 }
176
177 $hash = $this->service->get_hash();
178 echo '<div style="display:none;"><a href="' . esc_url( home_url( "/{$hash}" ) ) . '" rel="nofollow">Secret Link</a></div>';
179 }
180
181 /**
182 * Checks if the current request is for the malicious bot hash URL.
183 *
184 * @return bool True if the request is for the hash URL, false otherwise.
185 */
186 public function is_hash_request(): bool {
187 $hash = $this->service->get_hash();
188 if ( ! is_string( $hash ) || '' === trim( $hash ) ) {
189 return false;
190 }
191
192 $uri = defender_get_data_from_request( 'REQUEST_URI', 's' );
193 if ( ! is_string( $uri ) || '' === $uri ) {
194 return false;
195 }
196
197 // Get request path.
198 $uri = wp_parse_url( $uri, PHP_URL_PATH );
199 $uri = trim( $uri, '/' );
200
201 // Always check last segment only.
202 $segments = explode( '/', $uri );
203 $last = end( $segments );
204
205 return $last === $hash;
206 }
207
208 /**
209 * Rotate the malicious bot hash and refresh the related rules.
210 */
211 public function rotate_hash() {
212 $this->service->rotate_hash();
213 }
214
215 /**
216 * Delete all the data & the cache.
217 */
218 public function remove_data() {
219 // Remove the malicious bot hash from options.
220 delete_site_option( Malicious_Bot_Component::URL_HASH_KEY );
221
222 $this->service->remove_rule();
223
224 // Flush rewrite rules to remove the malicious bot URL.
225 flush_rewrite_rules();
226 }
227
228 /**
229 * Exports strings.
230 *
231 * @return array An array of strings.
232 */
233 public function export_strings(): array {
234 return array();
235 }
236
237 /**
238 * Converts the object data to an array.
239 *
240 * @return array An array representation of the object.
241 */
242 public function to_array(): array {
243 return array();
244 }
245
246 /**
247 * Imports data into the model.
248 *
249 * @param array $data Data to be imported into the model.
250 *
251 * @throws Exception If table is not defined.
252 */
253 public function import_data( array $data ) {
254 }
255
256 /**
257 * Removes settings for all submodules.
258 */
259 public function remove_settings(): void {
260 }
261
262 /**
263 * Provides data for the frontend.
264 *
265 * @return array An array of data for the frontend.
266 */
267 public function data_frontend(): array {
268 return array();
269 }
270 }
271