🛡️ 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

PatronImplementacionBeneficio
Hook tempranoactionDispatcher (antes de controllers)Bloquea antes de gastar recursos
IP detection robustaCF-Connecting-IP → X-Forwarded → REMOTE_ADDRFunciona detras de Cloudflare, proxies, CDNs
Whitelist antes de blacklistCheck whitelist primero, luego blacklistAdmin nunca se bloquea a si mismo
Excluir BO del bloqueoisBackOfficeContext() → returnEvita lockout del admin
UNIQUE KEY compuesto(ip, blocked, context)Permite upsert y separar contextos Front/BO
ALTER TABLE con IF NOT EXISTSADD COLUMN IF NOT EXISTSMigraciones seguras sin romper en re-ejecucion
exit despues de bloqueoexit 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.