commit bbe41681680ab8d01b00e283b78b4d2765616add Author: O K Date: Sun Dec 7 13:58:49 2025 +0200 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1fdbb3 --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +# BotLimiter - Smart Traffic Control & Firewall for PrestaShop + +* **Version:** 1.0.0 +* **Author:** Panariga +* **Compatibility:** PrestaShop 1.7 / 8.x / 9.x + +## Overview + +**BotLimiter** is a high-performance firewall designed specifically to stop "Faceted Search Scrapers" and aggressive bots that overload PrestaShop servers by iterating through filter combinations (e.g., `?q=Category-Food` or `?order=price.asc`). + +Unlike standard CAPTCHAs that annoy users, BotLimiter uses a **"Cookie Trap"** mechanism. It detects heavy requests (filters/sorting) and seamlessly verifies the visitor using a lightweight JavaScript redirection flow. Real users pass instantly; dumb bots get stuck; aggressive scrapers are logged for **Fail2Ban**. + +## Features + +1. **Request Interception:** Hooks into `actionFrontControllerInitBefore` to stop processing before the heavy Database/Theme loading starts. +2. **HEAD Request Block:** Instantly drops `HEAD` requests on filtered pages (a common bot tactic to check for 200 OK without downloading content). +3. **The "Trap" logic:** + * Redirects visitors without a valid session to a lightweight HTML/JS verification page. + * Generates an encrypted token based on the user's IP. + * Validates the token upon auto-submission. +4. **Extensible Rules:** Built on a generic `RuleInterface` architecture. +5. **Fail2Ban Integration:** Writes offending IPs to a dedicated log file compatible with server-side banning tools. + +--- + +## Installation + +1. Upload the `botlimiter` folder to your `/modules/` directory. +2. Go to **Back Office > Modules > Module Manager**. +3. Find **Bot Limiter** and click **Install**. +4. Clear your PrestaShop cache (`Advanced Parameters > Performance`). + +--- + +## Architecture + +The module uses the following flow: + +1. **Hook:** `actionFrontControllerInitBefore` +2. **Rule Engine:** Checks if the request is "Heavy" (contains `q=` or `order=` parameters). +3. **Allowlist:** Passes `Googlebot`, `Bingbot`, and legitimate users with a verified session cookie. +4. **Verification:** + * If unverified, redirects to `module-botlimiter-verify`. + * This controller renders a minimal template (no heavy footer/header). + * Javascript automatically appends an encrypted token and redirects back to the original URL. +5. **Validation:** The module decrypts the token. If it matches the IP, a session cookie is set, and the user can browse freely. + +--- + +## Fail2Ban Configuration (Virtualmin / Universal) + +The module writes logs to: +`/your_shop_root/var/logs/botlimiter_ban.log` + +The format is: +`[YYYY-MM-DD HH:MM:SS] [IP:192.168.1.1] [REASON:HEAD_REQUEST_SPAM]` + +### 1. Create the Filter + +Create a new file `/etc/fail2ban/filter.d/prestashop-bot.conf`: + +```ini +[Definition] +# Regex to match the custom log format from BotLimiter +failregex = ^\[.*\] \[IP:\] \[REASON:.*\]$ + +# Ignore nothing +ignoreregex = +``` + +### 2. Create the Jail (Virtualmin Universal Rule) + +We need a rule that scans **all** virtual servers on your machine. Virtualmin typically stores web roots in `/home/USERNAME/public_html` or `/home/USERNAME/domains/DOMAIN/public_html`. + +Add this to your `/etc/fail2ban/jail.local` (or `jail.conf`): + +```ini +[prestashop-bot] +enabled = true +port = http,https +filter = prestashop-bot +# Multi-path configuration to scan all Virtualmin domains +logpath = /home/*/public_html/var/logs/botlimiter_ban.log + /home/*/domains/*/public_html/var/logs/botlimiter_ban.log + +# OPTIONS: +# maxretry: Number of times a bot can hit the trap before ban +maxretry = 5 + +# findtime: Time window (seconds) to count the retries (10 minutes) +findtime = 600 + +# bantime: How long to ban them (1 hour) +bantime = 3600 + +# Backend: standard "auto" or "pyinotify" works best for wildcards +backend = auto +``` + +### 3. Restart Fail2Ban + +```bash +service fail2ban restart +# OR +systemctl restart fail2ban +``` + +### 4. Verify It Works + +To check if Fail2Ban is correctly monitoring the files across your Virtualmin domains: + +```bash +fail2ban-client status prestashop-bot +``` + +You should see a list of "File list" showing the log paths it found across your home directories. + +--- + +## Troubleshooting + +* **Users complain about loops:** Ensure JavaScript is enabled. The verification controller relies on JS to append the token. +* **Whitelisting:** If you have a specific monitoring service (like UptimeRobot), ensure you add their User-Agent to `classes/rules/FilterTrapRule.php`. +``` \ No newline at end of file diff --git a/botlimiter.php b/botlimiter.php new file mode 100644 index 0000000..4e935cb --- /dev/null +++ b/botlimiter.php @@ -0,0 +1,56 @@ +name = 'botlimiter'; + $this->tab = 'administration'; + $this->version = '1.0.0'; + $this->author = 'Panariga'; + $this->need_instance = 0; + $this->bootstrap = true; + + parent::__construct(); + + $this->displayName = $this->l('Bot Limiter & Firewall'); + $this->description = $this->l('Intelligent protection against faceted search scrapers.'); + } + + public function install() + { + return parent::install() && + $this->registerHook('actionFrontControllerInitBefore'); + } + + public function hookActionFrontControllerInitBefore($params) + { + // 1. Skip if we are currently ON the verification page to avoid loops + if ($this->context->controller instanceof BotLimiterVerifyModuleFrontController) { + return; + } + + // 2. Initialize Manager + $manager = new RuleManager($this->context); + + // 3. Register Rules (Add more here in future) + $manager->addRule(new HeadRequestRule()); + $manager->addRule(new FilterTrapRule()); + + // 4. Execute Rules + // If a rule returns FALSE, it means traffic is blocked or redirected. + // The rule is responsible for the redirect/exit. + $manager->process(); + } +} \ No newline at end of file diff --git a/classes/BotLogger.php b/classes/BotLogger.php new file mode 100644 index 0000000..56046d6 --- /dev/null +++ b/classes/BotLogger.php @@ -0,0 +1,15 @@ +context = $context; + } + + public function addRule(RuleInterface $rule) + { + $this->rules[] = $rule; + } + + public function process() + { + foreach ($this->rules as $rule) { + $rule->execute(); + } + } +} \ No newline at end of file diff --git a/classes/rules/FilterTrapRule.php b/classes/rules/FilterTrapRule.php new file mode 100644 index 0000000..c5e5e81 --- /dev/null +++ b/classes/rules/FilterTrapRule.php @@ -0,0 +1,69 @@ +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; + } +} \ No newline at end of file diff --git a/classes/rules/HeadRequestRule.php b/classes/rules/HeadRequestRule.php new file mode 100644 index 0000000..d5b45c9 --- /dev/null +++ b/classes/rules/HeadRequestRule.php @@ -0,0 +1,18 @@ +shop->getBaseURL(true); + } + + // Generate Encrypted Token + // Using IP ensures the token cannot be generated on one machine and used on another + $encryption = new PhpEncryption(_NEW_COOKIE_KEY_); + $token = $encryption->encrypt($ip); + + $this->context->smarty->assign([ + 'return_url' => $return_url, + 'bot_token' => $token, + ]); + + $this->setTemplate('module:botlimiter/views/templates/front/verify.tpl'); + } +} \ No newline at end of file diff --git a/views/templates/front/verify.tpl b/views/templates/front/verify.tpl new file mode 100644 index 0000000..0730e20 --- /dev/null +++ b/views/templates/front/verify.tpl @@ -0,0 +1,65 @@ +{extends file='layouts/layout-content-only.tpl'} + +{block name='content'} +
+
+
🛡️
+ +

{l s='Security Check' mod='botlimiter'}

+ +

+ {l s='We are checking your browser to ensure you are not a bot.' mod='botlimiter'} +
+ {l s='This helps us keep prices low and the server fast for real humans.' mod='botlimiter'} +

+ +
+ +
+ +
+
+ {l s='Verifying...' mod='botlimiter'} +
+
+
+ + + + + + +{/block} \ No newline at end of file