Files
animexe/app/Http/Middleware/BotDetector.php
T
2026-07-14 00:01:48 +03:00

219 lines
8.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class BotDetector
{
// Tamamen engelle — 403
private const BAD_BOTS = [
'scrapy', 'httrack', 'webcopier', 'webzip', 'teleportpro', 'webstripper',
'offline explorer', 'larbin', 'libwww-perl',
'masscan', 'nikto', 'sqlmap', 'nmap', 'zgrab', 'nuclei',
'dirsearch', 'gobuster', 'feroxbuster', 'ffuf',
'ahrefsbot', 'semrushbot', 'dotbot', 'mj12bot', 'blexbot',
'bytespider', 'gptbot', 'claudebot', 'chatgpt-user',
'ccbot', 'anthropic-ai', 'cohere-ai', 'omgili', 'omgilibot',
'pinterestbot', 'petalbot', 'proximic', 'mediatoolkitbot',
'dataforseobot', 'sitechecker', 'seokicks', 'linkfluence',
'serpstatbot', 'serendeputy', 'riddler', 'netcraftsurveyagent',
'netsystemsresearch', 'ioncrawl', 'brandverity',
'wp_is_mobile', 'wordpress', 'wpbot',
];
// Bu IP prefix'leri için tüm kontroller atlanır (güvenilir crawler'lar)
private const TRUSTED_IP_PREFIXES = [
'66.249.', // Googlebot
'64.233.', // Google
'74.125.', // Google
'209.85.', // Google
'216.239.', // Google
'34.68.', // Google Cloud us-central1
'34.64.', // Google Cloud
'35.187.', // Google Cloud
'35.190.', // Google Cloud
];
// İzin ver ama bot olarak işaretle (SEO crawler'lar)
private const GOOD_BOTS = [
'googlebot', 'google-inspectiontool', 'google-structured-data-testing-tool',
'adsbot-google', 'adsbot-google-mobile', 'mediapartners-google',
'apis-google', 'feedfetcher-google', 'google-adwords-instant',
'bingbot', 'msnbot', 'adidxbot', 'bingpreview',
'duckduckbot', 'baiduspider', 'yandexbot', 'yandexmobilebot',
'slurp', 'teoma', 'ia_archiver',
'facebot', 'facebookexternalhit',
'twitterbot', 'linkedinbot', 'whatsapp',
'applebot', 'discordbot', 'telegrambot',
'slackbot', 'pingdom',
];
// Rate-limit uygula (dakikada 10'dan fazla → uyarı, 30'dan fazla → otomatik engel)
private const GENERIC_BOTS = [
'curl/', 'wget/', 'python-requests', 'python-urllib',
'go-http-client', 'java/', 'okhttp/', 'libcurl',
'node-fetch', 'node.js', 'axios', 'got/',
'apache-httpclient', 'php/', 'perl/', 'ruby',
'postman', 'insomnia', 'httpie',
];
// Bu path'ler için sadece log tut, engelleme yapma
private const SKIP_PATHS = [
'/up', '/api/', '/sitemap',
];
public function handle(Request $request, Closure $next)
{
$ip = $request->ip();
$ua = strtolower($request->userAgent() ?? '');
$path = $request->path();
// Skip paths
foreach (self::SKIP_PATHS as $skip) {
if (str_starts_with('/' . $path, $skip)) {
return $next($request);
}
}
// Güvenilir IP (Google vb.) — tüm kontrolleri atla
foreach (self::TRUSTED_IP_PREFIXES as $prefix) {
if (str_starts_with($ip, $prefix)) {
return $next($request);
}
}
// Manuel engelli IP kontrolü
if ($this->isBlockedIp($ip)) {
$this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'ip_blocked', 'blocked_ip');
return response('Erişim engellendi.', 403);
}
// UA boşsa bot olarak işaretle
if (empty($ua)) {
$request->attributes->set('is_bot', true);
$request->attributes->set('bot_type', 'noua');
$this->logBot($ip, '', '/' . $path, $request->method(), 'allowed', 'no_ua');
return $next($request);
}
// Kötü bot mu?
foreach (self::BAD_BOTS as $pattern) {
if (str_contains($ua, $pattern)) {
$this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'blocked', $pattern);
return response('', 403);
}
}
// İyi bot mu?
foreach (self::GOOD_BOTS as $pattern) {
if (str_contains($ua, $pattern)) {
$request->attributes->set('is_bot', true);
$request->attributes->set('bot_type', 'good');
// İyi botlar için çok agresif rate limit (dakikada 60)
if ($this->isRateLimited($ip, 60, 'good_bot')) {
return response('', 429);
}
return $next($request);
}
}
// Generic araç mı?
foreach (self::GENERIC_BOTS as $pattern) {
if (str_contains($ua, $pattern)) {
$request->attributes->set('is_bot', true);
$request->attributes->set('bot_type', 'generic');
if ($this->isRateLimited($ip, 10, 'generic')) {
$this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'rate_limited', $pattern);
// 30+ istek → otomatik engelle
$count = Cache::get("bot_count_{$ip}", 0);
if ($count > 30) {
$this->autoBlock($ip, 'Otomatik: dakikada 30+ generic bot isteği');
}
return response('', 429);
}
$this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'allowed', $pattern);
return $next($request);
}
}
// Normal kullanıcı — genel rate limit (dakikada 120 istek)
if ($this->isRateLimited($ip, 120, 'human')) {
$this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'rate_limited', 'human_flood');
$count = Cache::get("bot_count_{$ip}", 0);
if ($count > 200) {
$this->autoBlock($ip, 'Otomatik: dakikada 200+ istek flood');
}
return response('', 429);
}
$request->attributes->set('is_bot', false);
return $next($request);
}
private function isBlockedIp(string $ip): bool
{
return Cache::remember("blocked_ip_{$ip}", 300, function () use ($ip) {
try {
return DB::table('blocked_ips')
->where('ip', $ip)
->where(function ($q) {
$q->whereNull('expires_at')->orWhere('expires_at', '>', now());
})
->exists();
} catch (\Exception) {
return false;
}
});
}
private function isRateLimited(string $ip, int $maxPerMinute, string $type): bool
{
$key = "rl_{$type}_{$ip}";
$count = Cache::get($key, 0);
if ($count === 0) {
Cache::put($key, 1, 60);
} else {
Cache::increment($key);
}
// Bot count ayrı izle
Cache::put("bot_count_{$ip}", Cache::get("bot_count_{$ip}", 0) + 1, 60);
return $count >= $maxPerMinute;
}
private function autoBlock(string $ip, string $reason): void
{
try {
DB::table('blocked_ips')->insertOrIgnore([
'ip' => $ip,
'reason' => $reason,
'auto_blocked' => 1,
'blocked_at' => now(),
'expires_at' => now()->addHours(24),
]);
Cache::forget("blocked_ip_{$ip}");
} catch (\Exception) {}
}
private function logBot(string $ip, ?string $ua, string $path, string $method, string $action, string $botName): void
{
try {
DB::table('analytics_bot_logs')->insert([
'ip' => $ip,
'user_agent' => mb_substr($ua ?? '', 0, 500),
'path' => mb_substr($path, 0, 500),
'method' => $method,
'action' => $action,
'bot_name' => mb_substr($botName, 0, 100),
'created_at' => now(),
]);
} catch (\Exception) {}
}
}