extend rate limiter
This commit is contained in:
41
classes/RateLimiter.php
Normal file
41
classes/RateLimiter.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
class RateLimiter
|
||||
{
|
||||
/**
|
||||
* @param string $ip The IP to check
|
||||
* @param string $action A unique string for the action (e.g., '404', 'verify_attempt')
|
||||
* @param int $max_requests
|
||||
* @param int $window seconds
|
||||
* @return bool True if allowed, false if limit exceeded
|
||||
*/
|
||||
public static function checkIsRateLimited($ip, $action, $max_requests, $window)
|
||||
{
|
||||
// 1. Check if the Cache module is available
|
||||
if (!Module::isInstalled('dbmemorycache') || !Module::isEnabled('dbmemorycache')) {
|
||||
return false; // Not limited if we can't track it
|
||||
}
|
||||
|
||||
$cache = Module::getInstanceByName('dbmemorycache');
|
||||
if (!$cache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Generate unique key for this IP + Action
|
||||
$cacheKey = hash('sha256', 'bot_limit_' . $action . '_' . $ip);
|
||||
|
||||
// 3. Get current count
|
||||
$currentCount = 0;
|
||||
if ($cache->existsValue($cacheKey)) {
|
||||
$currentCount = (int) $cache->getValue($cacheKey);
|
||||
}
|
||||
|
||||
$currentCount++;
|
||||
|
||||
// 4. Save back with the window (Resets timer on every hit = Sliding Window)
|
||||
$cache->setValue($cacheKey, $currentCount, $window);
|
||||
|
||||
// 5. Return status
|
||||
return ($currentCount > $max_requests);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ require_once dirname(__FILE__) . '/rules/HeadRequestRule.php';
|
||||
require_once dirname(__FILE__) . '/rules/FilterTrapRule.php';
|
||||
require_once dirname(__FILE__) . '/rules/ScanBotsRule.php';
|
||||
require_once dirname(__FILE__) . '/rules/RateLimitRule.php';
|
||||
require_once dirname(__FILE__) . '/RateLimiter.php';
|
||||
|
||||
|
||||
class RuleManager
|
||||
|
||||
@@ -69,7 +69,11 @@ class FilterTrapRule implements RuleInterface
|
||||
die('Access Denied');
|
||||
}
|
||||
}
|
||||
|
||||
if (RateLimiter::checkIsRateLimited($ip, 'filter_trap', 10, 60)) {
|
||||
BotLogger::logBan($ip, 'FILTER_TRAP_SPAM');
|
||||
header('HTTP/1.1 429 Too Many Requests');
|
||||
die('Too many filter attempts.');
|
||||
}
|
||||
// 5. If we are here: Heavy request + No Cookie + No Token.
|
||||
// Redirect to the Trap (Verify Controller)
|
||||
|
||||
|
||||
@@ -21,48 +21,12 @@ class RateLimitRule implements RuleInterface
|
||||
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
|
||||
if (RateLimiter::checkIsRateLimited($ip, '404_spam', self::MAX_404_REQUESTS, self::TIME_WINDOW)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
<?php
|
||||
|
||||
require_once dirname(__FILE__) . '/../../classes/BotLogger.php';
|
||||
require_once dirname(__FILE__) . '/../../classes/RuleManager.php';
|
||||
|
||||
class BotLimiterVerifyModuleFrontController extends ModuleFrontController
|
||||
{
|
||||
public function initContent()
|
||||
{
|
||||
$ip = BotLogger::getRealIp();
|
||||
|
||||
// If they hit the verify page itself more than 5 times in 30 seconds
|
||||
if (RateLimiter::checkIsRateLimited($ip, 'verify_page_load', 5, 30)) {
|
||||
BotLogger::logBan($ip, 'VERIFY_PAGE_FLOOD');
|
||||
header('HTTP/1.1 429 Too Many Requests');
|
||||
die('Too many verification attempts.');
|
||||
}
|
||||
|
||||
parent::initContent(); // This initializes the Standard PS Cookie
|
||||
|
||||
$ip = BotLogger::getRealIp();
|
||||
|
||||
Reference in New Issue
Block a user