first commit

This commit is contained in:
O K
2025-12-07 13:58:49 +02:00
commit bbe4168168
9 changed files with 412 additions and 0 deletions

15
classes/BotLogger.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
class BotLogger
{
const LOG_FILE = _PS_ROOT_DIR_ . '/var/logs/botlimiter_ban.log';
public static function logBan($ip, $reason)
{
$date = date('Y-m-d H:i:s');
$message = sprintf("[%s] [IP:%s] [REASON:%s]" . PHP_EOL, $date, $ip, $reason);
// Append to log file
file_put_contents(self::LOG_FILE, $message, FILE_APPEND | LOCK_EX);
}
}

27
classes/RuleManager.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
require_once dirname(__FILE__) . '/rules/RuleInterface.php';
require_once dirname(__FILE__) . '/rules/HeadRequestRule.php';
require_once dirname(__FILE__) . '/rules/FilterTrapRule.php';
class RuleManager
{
private $rules = [];
private $context;
public function __construct($context)
{
$this->context = $context;
}
public function addRule(RuleInterface $rule)
{
$this->rules[] = $rule;
}
public function process()
{
foreach ($this->rules as $rule) {
$rule->execute();
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
use PrestaShop\PrestaShop\Core\Crypto\PhpEncryption;
class FilterTrapRule implements RuleInterface
{
public function execute()
{
// 1. Only analyze heavy requests (filters/sorting)
if (!Tools::getIsset('q') && !Tools::getIsset('order')) {
return true;
}
$context = Context::getContext();
$ip = $_SERVER['REMOTE_ADDR'];
// 2. Allow whitelisted Bots (Google/Bing)
// We trust them not to spam. If they do, use robots.txt.
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? strtolower($_SERVER['HTTP_USER_AGENT']) : '';
if (strpos($ua, 'googlebot') !== false || strpos($ua, 'bingbot') !== false) {
return true;
}
// 3. Check if user is already verified in this session
if (isset($context->cookie->bot_verified) && $context->cookie->bot_verified == 1) {
return true;
}
// 4. Check for Incoming Token (Returning from Verify Controller)
$incoming_token = Tools::getValue('bot_token');
if ($incoming_token) {
try {
// Verify Token
$encryption = new PhpEncryption(_NEW_COOKIE_KEY_);
$decrypted_ip = $encryption->decrypt($incoming_token);
if ($decrypted_ip === $ip) {
// SUCCESS: User ran JS, posted back, and IP matches.
$context->cookie->bot_verified = 1;
$context->cookie->write(); // Force save
return true;
} else {
// Token mismatch (stolen URL?)
BotLogger::logBan($ip, 'INVALID_TOKEN');
header('HTTP/1.1 403 Forbidden');
die('Security Token Mismatch');
}
} catch (Exception $e) {
// Garbage token
BotLogger::logBan($ip, 'GARBAGE_TOKEN');
die('Access Denied');
}
}
// 5. If we are here: Heavy request + No Cookie + No Token.
// Redirect to the Trap (Verify Controller)
$current_url = $_SERVER['REQUEST_URI'];
// Remove existing bot_token if present to avoid loops
$current_url = preg_replace('/([?&])bot_token=[^&]+(&|$)/', '$1', $current_url);
// Encode target URL safely
$target = urlencode($current_url);
$link = $context->link->getModuleLink('botlimiter', 'verify', ['return_url' => $target]);
Tools::redirect($link);
exit;
}
}

View File

@@ -0,0 +1,18 @@
<?php
class HeadRequestRule implements RuleInterface
{
public function execute()
{
// Detect HEAD request with Filter parameters
if ($_SERVER['REQUEST_METHOD'] === 'HEAD' && (Tools::getIsset('q') || Tools::getIsset('order'))) {
// Log for Fail2Ban
BotLogger::logBan($_SERVER['REMOTE_ADDR'], 'HEAD_REQUEST_SPAM');
header('HTTP/1.1 405 Method Not Allowed');
die('Method Not Allowed');
}
return true;
}
}

View File

@@ -0,0 +1,8 @@
<?php
interface RuleInterface
{
/**
* @return bool|void Returns true if passed, calls exit/redirect if failed
*/
public function execute();
}