🛡️ Modulo de seguridad — ejemplo real ecom_shield
Actualizado: 2024-12-01
Codigo real de produccion
Extraido de ecom_shield (IA Security), modulo de proteccion contra bots y ataques que opera en Fase 0 (antes de que PS cargue completamente).
#Que hace este modulo
ecom_shield es un firewall a nivel de modulo PrestaShop. Intercepta requests via actionDispatcher, analiza el user-agent e IP, decide si bloquear o permitir, y registra todo en una tabla de logs. Incluye geo-bloqueo por pais y whitelist de IPs.
#Install con DB + Tab + Configuration
install/uninstall completo con multiples subsistemas
php
<?php
class Ecom_Shield extends Module
{
public function __construct()
{
$this->name = 'ecom_shield';
$this->tab = 'market_place';
$this->version = '1.3.0';
$this->author = 'Ecom Experts';
$this->need_instance = 0;
$this->bootstrap = true;
parent::__construct();
$this->displayName = $this->l('Ecom Shield - IA Security');
$this->description = $this->l('Proteccion contra bots y ataques.');
$this->ps_versions_compliancy = ['min' => '1.7.0.0', 'max' => _PS_VERSION_];
}
public function install()
{
return parent::install() &&
// HOOKS: interceptar ANTES y DURANTE el dispatching
$this->registerHook('actionDispatcher') &&
$this->registerHook('actionFrontControllerInit') &&
// DB: tabla de logs
$this->installDb() &&
// BO: tab de administracion
$this->installTab() &&
// CONFIG: valores por defecto
Configuration::updateValue('GRAVITY_BLOCK_MODE', false) &&
Configuration::updateValue('GRAVITY_GEO_BLOCK', false) &&
Configuration::updateValue('GRAVITY_ALLOWED_COUNTRIES', 'ES,PT,FR,DE,IT') &&
Configuration::updateValue('GRAVITY_WHITELIST_IPS', '') &&
Configuration::updateValue('GRAVITY_BLOCKED_BOTS', '') &&
Configuration::updateValue('GRAVITY_BLOCKED_IPS', '') &&
Configuration::updateValue('GRAVITY_DEBUG_LOGS', false);
}
public function uninstall()
{
return parent::uninstall() &&
$this->uninstallTab() &&
Configuration::deleteByName('GRAVITY_BLOCK_MODE') &&
Configuration::deleteByName('GRAVITY_GEO_BLOCK') &&
Configuration::deleteByName('GRAVITY_ALLOWED_COUNTRIES') &&
Configuration::deleteByName('GRAVITY_WHITELIST_IPS') &&
Configuration::deleteByName('GRAVITY_BLOCKED_BOTS') &&
Configuration::deleteByName('GRAVITY_BLOCKED_IPS') &&
Configuration::deleteByName('GRAVITY_DEBUG_LOGS');
}
}
#Tabla de logs con indices
CREATE TABLE con UNIQUE KEY compuesto e indices
php
<?php
protected function installDb()
{
$sql = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'ecom_shield_log` (
`id_ecom_shield_log` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`ip` VARCHAR(45) NOT NULL,
`country_code` VARCHAR(3) DEFAULT NULL,
`user_agent` TEXT,
`is_bot` TINYINT(1) DEFAULT 0,
`context` VARCHAR(64) DEFAULT "Front",
`reason` TEXT,
`hits` INT UNSIGNED DEFAULT 1,
`avg_interval` DECIMAL(10,2) DEFAULT 0.00,
`blocked` TINYINT(1) DEFAULT 0,
`request_uri` TEXT,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id_ecom_shield_log`),
UNIQUE KEY `ip_blocked_context` (`ip`, `blocked`, `context`),
INDEX (`hits`),
INDEX (`date_upd`)
) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8;';
return Db::getInstance()->execute($sql);
}
// NOTAS DE DISEÑO:
// - VARCHAR(45) para IP: soporta IPv6 (hasta 45 chars)
// - UNIQUE KEY compuesto (ip, blocked, context):
// Permite INSERT ON DUPLICATE KEY UPDATE para el mismo IP
// Separando Front/BO/API como contextos distintos
// - INDEX en hits y date_upd: para ordenar las vistas admin
// - DECIMAL(10,2) para avg_interval: precision de milisegundos
// PATRON DE MIGRACION para añadir columnas despues:
public function addNewFeatures()
{
try {
Db::getInstance()->execute(
'ALTER TABLE `' . _DB_PREFIX_ . 'ecom_shield_log` '
. 'ADD COLUMN IF NOT EXISTS `context` VARCHAR(64) '
. 'DEFAULT "Front" AFTER `is_bot`'
);
} catch (Exception $e) {
// Silenciar si ya existe
}
return true;
}
#Hook actionDispatcher — interceptar requests
Bloquear antes de que PS procese la peticion
php
<?php
// actionDispatcher se ejecuta MUY temprano en el ciclo de vida
// Ideal para seguridad: bloquear antes de cargar controllers
public function hookActionDispatcher(array $params): void
{
// No bloquear el BO (el admin necesita acceder)
if ($this->isBackOfficeContext()) {
return;
}
$ip = $this->getClientIp();
// 1. Whitelist: permitir siempre
if ($this->isWhitelisted($ip)) {
return;
}
// 2. Blacklist: bloquear siempre
if ($this->isBlacklisted($ip)) {
$this->blockRequest($ip, 'IP blacklisted');
return;
}
// 3. Geo-bloqueo por pais
if (Configuration::get('GRAVITY_GEO_BLOCK')) {
$country = $this->getCountryByIp($ip);
$allowed = explode(',', Configuration::get('GRAVITY_ALLOWED_COUNTRIES'));
if ($country && !in_array($country, $allowed)) {
$this->blockRequest($ip, 'Country blocked: ' . $country);
return;
}
}
// 4. Deteccion de bots agresivos
$this->analyzeRequest($ip);
}
private function blockRequest(string $ip, string $reason): void
{
// Registrar en log
$this->logRequest($ip, true, $reason);
// Responder 403 y terminar
http_response_code(403);
header('Content-Type: text/plain');
echo 'Access denied';
exit;
}
private function getClientIp(): string
{
// Orden de prioridad para proxies/CDN
foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP', 'REMOTE_ADDR'] as $key) {
if (!empty($_SERVER[$key])) {
$ip = trim(explode(',', $_SERVER[$key])[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
#Patrones de seguridad
| Patron | Implementacion | Beneficio |
|---|---|---|
| Hook temprano | actionDispatcher (antes de controllers) | Bloquea antes de gastar recursos |
| IP detection robusta | CF-Connecting-IP → X-Forwarded → REMOTE_ADDR | Funciona detras de Cloudflare, proxies, CDNs |
| Whitelist antes de blacklist | Check whitelist primero, luego blacklist | Admin nunca se bloquea a si mismo |
| Excluir BO del bloqueo | isBackOfficeContext() → return | Evita lockout del admin |
| UNIQUE KEY compuesto | (ip, blocked, context) | Permite upsert y separar contextos Front/BO |
| ALTER TABLE con IF NOT EXISTS | ADD COLUMN IF NOT EXISTS | Migraciones seguras sin romper en re-ejecucion |
| exit despues de bloqueo | exit tras http_response_code(403) | Evita que PS siga procesando |
Descargar en Markdown
Pensado para pegar en ChatGPT, Claude u otra IA. Incluye solo el contenido de esta pagina.