69 lines
2.3 KiB
PHP
69 lines
2.3 KiB
PHP
<?php
|
|
|
|
|
|
class RateLimitRule implements RuleInterface
|
|
{
|
|
const MAX_404_REQUESTS = 20; // Allow a maximum of 20 dead links...
|
|
const TIME_WINDOW = 300; // ...within 300 seconds (5 minutes)
|
|
|
|
public function execute()
|
|
{
|
|
$ip = BotLogger::getRealIp();
|
|
if (BotLogger::isWhitelisted($ip)) {
|
|
return true;
|
|
}
|
|
$context = Context::getContext();
|
|
|
|
// 1. Instantly skip if this is NOT a 404 error page.
|
|
// During the hookActionFrontControllerInitBefore hook, PrestaShop has already
|
|
// resolved the route. If it failed, the controller is set to PageNotFoundController.
|
|
if (!($context->controller instanceof PageNotFoundController)) {
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Safely check if the Cache module is installed and enabled
|
|
if (!Module::isInstalled('dbmemorycache') || !Module::isEnabled('dbmemorycache')) {
|
|
return true; // Pass gracefully if the module is missing or disabled
|
|
}
|
|
|
|
/** @var DbMemoryCache $cache */
|
|
$cache = Module::getInstanceByName('dbmemorycache');
|
|
if (!$cache) {
|
|
return true;
|
|
}
|
|
|
|
// 3. Generate a unique hash for this specific IP's 404 traffic
|
|
$cacheKey = hash('sha256', '404_spam_limiter_' . $ip);
|
|
|
|
// 4. Lookup their current 404 hit count in the memory table
|
|
$currentCount = 0;
|
|
if ($cache->existsValue($cacheKey)) {
|
|
$currentCount = (int) $cache->getValue($cacheKey);
|
|
}
|
|
|
|
// 5. Increment their strike counter
|
|
$currentCount++;
|
|
|
|
// 6. Have they triggered too many 404s?
|
|
if ($currentCount > self::MAX_404_REQUESTS) {
|
|
|
|
// Log it so Fail2ban can block them at the server firewall level
|
|
BotLogger::logBan($ip, '404_RATE_LIMIT_EXCEEDED');
|
|
|
|
// Drop the connection instantly to save server resources
|
|
header('HTTP/1.1 429 Too Many Requests');
|
|
die('429 Too Many Requests - Stop Scanning');
|
|
}
|
|
|
|
// 7. Save the new strike count back to the cache
|
|
// Note: Because we overwrite it here, this creates a "Sliding Window".
|
|
// If a bot keeps spamming, the 300s timer resets every time, keeping them trapped!
|
|
$cache->setValue($cacheKey, $currentCount, self::TIME_WINDOW);
|
|
|
|
return true;
|
|
}
|
|
}
|