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/FilterTrapRule.php';
|
||||||
require_once dirname(__FILE__) . '/rules/ScanBotsRule.php';
|
require_once dirname(__FILE__) . '/rules/ScanBotsRule.php';
|
||||||
require_once dirname(__FILE__) . '/rules/RateLimitRule.php';
|
require_once dirname(__FILE__) . '/rules/RateLimitRule.php';
|
||||||
|
require_once dirname(__FILE__) . '/RateLimiter.php';
|
||||||
|
|
||||||
|
|
||||||
class RuleManager
|
class RuleManager
|
||||||
|
|||||||
@@ -69,7 +69,11 @@ class FilterTrapRule implements RuleInterface
|
|||||||
die('Access Denied');
|
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.
|
// 5. If we are here: Heavy request + No Cookie + No Token.
|
||||||
// Redirect to the Trap (Verify Controller)
|
// Redirect to the Trap (Verify Controller)
|
||||||
|
|
||||||
|
|||||||
@@ -21,48 +21,12 @@ class RateLimitRule implements RuleInterface
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (RateLimiter::checkIsRateLimited($ip, '404_spam', self::MAX_404_REQUESTS, self::TIME_WINDOW)) {
|
||||||
|
|
||||||
|
|
||||||
// 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');
|
BotLogger::logBan($ip, '404_RATE_LIMIT_EXCEEDED');
|
||||||
|
|
||||||
// Drop the connection instantly to save server resources
|
|
||||||
header('HTTP/1.1 429 Too Many Requests');
|
header('HTTP/1.1 429 Too Many Requests');
|
||||||
die('429 Too Many Requests - Stop Scanning');
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once dirname(__FILE__) . '/../../classes/BotLogger.php';
|
require_once dirname(__FILE__) . '/../../classes/BotLogger.php';
|
||||||
|
require_once dirname(__FILE__) . '/../../classes/RuleManager.php';
|
||||||
|
|
||||||
class BotLimiterVerifyModuleFrontController extends ModuleFrontController
|
class BotLimiterVerifyModuleFrontController extends ModuleFrontController
|
||||||
{
|
{
|
||||||
public function initContent()
|
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
|
parent::initContent(); // This initializes the Standard PS Cookie
|
||||||
|
|
||||||
$ip = BotLogger::getRealIp();
|
$ip = BotLogger::getRealIp();
|
||||||
|
|||||||
Reference in New Issue
Block a user