| @@ -7,14 +7,15 @@ | ||
| 7 | 7 | |
| 8 | 8 | namespace WP_Defender\Component; |
| 9 | 9 | |
| 10 | 10 | use WP_Defender\Component; |
| 11 | +use WP_Defender\Traits\Country; | |
| 12 | +use WP_Defender\Model\Lockout_Ip; | |
| 13 | +use WP_Defender\Model\Lockout_Log; | |
| 11 | 14 | use WP_Defender\Controller\Firewall; |
| 12 | -use WP_Defender\Model\Lockout_Log; | |
| 13 | 15 | use WP_Defender\Model\Setting\User_Agent_Lockout; |
| 14 | -use WP_Defender\Model\Lockout_Ip; | |
| 16 | +use WP_Defender\Component\Known_Bots\Bots\Facebook_Bot; | |
| 15 | 17 | use WP_Defender\Model\Notification\Firewall_Notification; |
| 16 | -use WP_Defender\Traits\Country; | |
| 17 | 18 | |
| 18 | 19 | /** |
| 19 | 20 | * Class Fake_Bot_Detection |
| 20 | 21 | * |
| @@ -78,13 +79,9 @@ | ||
| 78 | 79 | * API endpoints to query for IP info. |
| 79 | 80 | * |
| 80 | 81 | * @var string[] |
| 81 | 82 | */ |
| 82 | - public const IP_INFO_SERVICES = array( | |
| 83 | - 'https://ipinfo.io/%s/json', | |
| 84 | - 'https://ipwho.is/%s', | |
| 85 | - 'http://ip-api.com/json/%s', | |
| 86 | - ); | |
| 83 | + public const IP_INFO_SERVICES = Facebook_Bot::IP_INFO_SERVICES; | |
| 87 | 84 | |
| 88 | 85 | /** |
| 89 | 86 | * Prefix for site transient cache keys. |
| 90 | 87 | * |
| @@ -89,15 +86,15 @@ | ||
| 89 | 86 | * Prefix for site transient cache keys. |
| 90 | 87 | * |
| 91 | 88 | * @var string |
| 92 | 89 | */ |
| 93 | - public const FB_CACHE_KEY_PREFIX = 'wpdef_ip_is_fb_'; | |
| 90 | + public const FB_CACHE_KEY_PREFIX = Facebook_Bot::FB_CACHE_KEY_PREFIX; | |
| 94 | 91 | |
| 95 | 92 | /** |
| 96 | 93 | * Cache lifetimes. |
| 97 | 94 | */ |
| 98 | - public const FB_CACHE_TTL_CONFIRMED = WEEK_IN_SECONDS; | |
| 99 | - public const FB_CACHE_TTL_UNCONFIRMED = HOUR_IN_SECONDS; | |
| 95 | + public const FB_CACHE_TTL_CONFIRMED = Facebook_Bot::FB_CACHE_TTL_CONFIRMED; | |
| 96 | + public const FB_CACHE_TTL_UNCONFIRMED = Facebook_Bot::FB_CACHE_TTL_UNCONFIRMED; | |
| 100 | 97 | |
| 101 | 98 | /** |
| 102 | 99 | * Fake_Bot_Detection constructor. |
| 103 | 100 | * |
| @@ -137,12 +134,8 @@ | ||
| 137 | 134 | if ( array() === $ips || '' === $agent ) { |
| 138 | 135 | return; |
| 139 | 136 | } |
| 140 | 137 | |
| 141 | - if ( false !== stripos( $agent, 'facebook' ) && false !== stripos( $agent, 'twitter' ) ) { | |
| 142 | - return; | |
| 143 | - } | |
| 144 | - | |
| 145 | 138 | foreach ( $this->crawlers as $name => $data ) { |
| 146 | 139 | // 1. User-Agent check. |
| 147 | 140 | if ( ! $this->match_user_agent( $agent, $data['user_agents'] ?? array() ) ) { |
| 148 | 141 | continue; |
| @@ -160,15 +153,10 @@ | ||
| 160 | 153 | return; |
| 161 | 154 | } |
| 162 | 155 | |
| 163 | 156 | // Facebook specific IP verification. |
| 164 | - if ( 'facebook' === strtolower( $name ) ) { | |
| 165 | - if ( $this->is_facebook_ip( $ip ) ) { | |
| 166 | - return; // Confirmed legit Facebook bot. | |
| 167 | - } | |
| 168 | - | |
| 169 | - $this->block_ip( $ip, $agent, $name ); | |
| 170 | - return; | |
| 157 | + if ( 'facebook' === strtolower( $name ) && $this->is_facebook_ip( $ip ) ) { | |
| 158 | + return; // Confirmed legit Facebook bot. | |
| 171 | 159 | } |
| 172 | 160 | |
| 173 | 161 | // 3. Reverse DNS lookup. |
| 174 | 162 | $hostname = gethostbyaddr( $ip ); |
| @@ -189,9 +177,11 @@ | ||
| 189 | 177 | return; // Verified by forward + reverse DNS. |
| 190 | 178 | } |
| 191 | 179 | } |
| 192 | 180 | |
| 193 | - $this->block_ip( $ip, $agent, $name ); | |
| 181 | + if ( isset( $ip ) ) { | |
| 182 | + $this->block_ip( $ip, $agent, $name ); | |
| 183 | + } | |
| 194 | 184 | return; // UA matched, but DNS validation failed. |
| 195 | 185 | } |
| 196 | 186 | } |
| 197 | 187 | |
| @@ -300,22 +290,10 @@ | ||
| 300 | 290 | * @param string $scenario The scenario under which the event is logged. |
| 301 | 291 | * @param string $bot_name The name of the bot being impersonated. |
| 302 | 292 | */ |
| 303 | 293 | public function log_event( $ip, $scenario, $bot_name ) { |
| 304 | - $model = new Lockout_Log(); | |
| 305 | - $model->ip = $ip; | |
| 306 | - $user_agent = defender_get_data_from_request( 'HTTP_USER_AGENT', 's' ); | |
| 307 | - $model->user_agent = isset( $user_agent ) ? User_Agent::fast_cleaning( $user_agent ) : null; | |
| 308 | - $model->date = time(); | |
| 309 | - $model->tried = $user_agent; | |
| 310 | - $model->blog_id = get_current_blog_id(); | |
| 294 | + $model = Lockout_Log::create( $ip ); | |
| 311 | 295 | |
| 312 | - $ip_to_country = $this->ip_to_country( $ip ); | |
| 313 | - | |
| 314 | - if ( isset( $ip_to_country['iso'] ) ) { | |
| 315 | - $model->country_iso_code = $ip_to_country['iso']; | |
| 316 | - } | |
| 317 | - | |
| 318 | 296 | switch ( $scenario ) { |
| 319 | 297 | case self::SCENARIO_FAKE_BOT: |
| 320 | 298 | default: |
| 321 | 299 | $model->type = Lockout_Log::LOCKOUT_FAKE_BOT; |
| @@ -350,63 +328,9 @@ | ||
| 350 | 328 | * @param string $ip IPv4 or IPv6 address. |
| 351 | 329 | * @return bool True if owned by Facebook, false otherwise. |
| 352 | 330 | */ |
| 353 | 331 | public function is_facebook_ip( string $ip ): bool { |
| 354 | - if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) { | |
| 355 | - return false; | |
| 356 | - } | |
| 357 | - | |
| 358 | - $cache_key = self::FB_CACHE_KEY_PREFIX . md5( $ip ); | |
| 359 | - | |
| 360 | - $cached = get_site_transient( $cache_key ); | |
| 361 | - if ( false !== $cached ) { | |
| 362 | - return (bool) $cached; | |
| 363 | - } | |
| 364 | - | |
| 365 | - $is_fb = false; | |
| 366 | - | |
| 367 | - // Fetch from APIs. | |
| 368 | - foreach ( self::IP_INFO_SERVICES as $endpoint ) { | |
| 369 | - $url = sprintf( $endpoint, $ip ); | |
| 370 | - | |
| 371 | - $response = wp_remote_get( | |
| 372 | - $url, | |
| 373 | - array( | |
| 374 | - 'timeout' => 3, | |
| 375 | - 'headers' => array( 'Accept' => 'application/json' ), | |
| 376 | - ) | |
| 377 | - ); | |
| 378 | - | |
| 379 | - if ( is_wp_error( $response ) ) { | |
| 380 | - continue; | |
| 381 | - } | |
| 382 | - | |
| 383 | - $body = wp_remote_retrieve_body( $response ); | |
| 384 | - $data = json_decode( $body, true ); | |
| 385 | - | |
| 386 | - if ( ! is_array( $data ) ) { | |
| 387 | - continue; | |
| 388 | - } | |
| 389 | - | |
| 390 | - $org = $data['org'] ?? $data['connection']['org'] ?? null; | |
| 391 | - $isp = $data['isp'] ?? $data['connection']['isp'] ?? null; | |
| 392 | - | |
| 393 | - if ( | |
| 394 | - preg_match( '/facebook|meta/i', $org ?? '' ) || | |
| 395 | - preg_match( '/facebook|meta/i', $isp ?? '' ) | |
| 396 | - ) { | |
| 397 | - $is_fb = true; | |
| 398 | - break; | |
| 399 | - } | |
| 400 | - } | |
| 401 | - | |
| 402 | - set_site_transient( | |
| 403 | - $cache_key, | |
| 404 | - $is_fb ? 1 : 0, | |
| 405 | - $is_fb ? self::FB_CACHE_TTL_CONFIRMED : self::FB_CACHE_TTL_UNCONFIRMED | |
| 406 | - ); | |
| 407 | - | |
| 408 | - return $is_fb; | |
| 332 | + return ( new Facebook_Bot() )->is_ip( $ip ); | |
| 409 | 333 | } |
| 410 | 334 | |
| 411 | 335 | /** |
| 412 | 336 | * Clear all Facebook IP check transients. |
| @@ -411,28 +335,7 @@ | ||
| 411 | 335 | /** |
| 412 | 336 | * Clear all Facebook IP check transients. |
| 413 | 337 | */ |
| 414 | 338 | public function clear_fb_transients(): void { |
| 415 | - global $wpdb; | |
| 416 | - | |
| 417 | - $cache_key = self::FB_CACHE_KEY_PREFIX; | |
| 418 | - $like_pattern = "_site_transient_{$cache_key}_%"; | |
| 419 | - $timeout_pattern = "_site_transient_timeout_{$cache_key}_%"; | |
| 420 | - | |
| 421 | - if ( is_multisite() ) { | |
| 422 | - $table = $wpdb->sitemeta; | |
| 423 | - $key_column = 'meta_key'; | |
| 424 | - } else { | |
| 425 | - $table = $wpdb->options; | |
| 426 | - $key_column = 'option_name'; | |
| 427 | - } | |
| 428 | - | |
| 429 | - // Delete Facebook IP check transients. | |
| 430 | - $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching | |
| 431 | - $wpdb->prepare( | |
| 432 | - "DELETE FROM {$table} WHERE {$key_column} LIKE %s OR {$key_column} LIKE %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 433 | - $like_pattern, | |
| 434 | - $timeout_pattern | |
| 435 | - ) | |
| 436 | - ); | |
| 339 | + ( new Facebook_Bot() )->clear_fb_transients(); | |
| 437 | 340 | } |
| 438 | 341 | } |