💉 Prevencion de SQL Injection en PrestaShop

Actualizado: 2024-12-01

SQL Injection es la vulnerabilidad mas critica en bases de datos. En PrestaShop, cualquier dato proveniente del usuario (GET, POST, Cookie) debe escaparse o castearse antes de incluirlo en una query SQL.

#Funciones de escape disponibles

Arsenal completo de funciones de escape
php
<?php

// ── pSQL($string, $htmlOk = false) ──
// Para strings en queries SQL. Escapa comillas y caracteres especiales.
$name = pSQL(Tools::getValue('name'));          // 'O\'Hara' → 'O\\'Hara'
$html = pSQL(Tools::getValue('html'), true);    // Permite HTML (escapa menos agresivamente)

// ── Casting numerico (el mas seguro) ──
$id    = (int)   Tools::getValue('id');          // '1 OR 1=1' → 1
$price = (float) Tools::getValue('price');       // '1.5; DROP...' → 1.5
$flag  = (bool)  Tools::getValue('active');      // Cualquier input → true/false

// ── bqSQL($string) ──
// Para nombres de tablas y columnas (sin comillas — las pones tu)
$table  = bqSQL(Tools::getValue('table'));       // Elimina backticks y chars peligrosos
$column = bqSQL(Tools::getValue('col'));         // Uso: WHERE `" . bqSQL($col) . "` = ...

// ── Arrays de IDs ──
$ids = Tools::getValue('ids');  // '1,2,3' o array
if (!is_array($ids)) {
    $ids = explode(',', $ids);
}
$safeIds = implode(',', array_map('intval', $ids));  // '1,2,3' seguro

// ── Para uso en LIKE ──
$search = pSQL(str_replace(['%', '_'], ['\\%', '\\_'], Tools::getValue('q')));
$sql    = "WHERE name LIKE '%" . $search . "%'";

#Queries vulnerables vs seguras

Comparativa de queries vulnerables vs seguras
php
<?php

// ══════════════════════════════════════
// ❌ VULNERABLES — NO HACER
// ══════════════════════════════════════

// Vulnerability 1: ID sin castear
$id  = $_GET['id'];  // Puede ser '1; DROP TABLE ps_product'
$sql = 'SELECT * FROM ps_product WHERE id_product = ' . $id;

// Vulnerability 2: String sin escapar
$n   = $_POST['name'];
$sql = "SELECT * FROM ps_product_lang WHERE name = '" . $n . "'";
// Payload: name = ' OR '1'='1
// Result: WHERE name = '' OR '1'='1' → Devuelve todos los registros

// Vulnerability 3: ORDER BY injection
$col = $_GET['sort'];  // Puede ser 'id_product; SELECT...' o '1 UNION SELECT...'
$sql = 'SELECT * FROM ps_product ORDER BY ' . $col;

// Vulnerability 4: IN() sin validar
$ids = $_GET['ids'];  // Puede ser '1,2,3 UNION SELECT password FROM ps_employee'
$sql = 'SELECT * FROM ps_product WHERE id_product IN (' . $ids . ')';

// ══════════════════════════════════════
// ✅ SEGURAS — ASI SE HACE
// ══════════════════════════════════════

$id  = (int) Tools::getValue('id');
$sql = 'SELECT * FROM `' . _DB_PREFIX_ . 'product` WHERE id_product = ' . $id;

$n   = pSQL(Tools::getValue('name'));
$sql = "SELECT * FROM `" . _DB_PREFIX_ . "product_lang` WHERE name = '" . $n . "'";

// Lista blanca para columnas de ordenacion
$allowed = ['price', 'date_add', 'name', 'reference'];
$col     = Tools::getValue('sort', 'date_add');
if (!in_array($col, $allowed, true)) { $col = 'date_add'; }
$sql = 'SELECT * FROM `' . _DB_PREFIX_ . 'product` ORDER BY `' . bqSQL($col) . '`';

$ids     = array_map('intval', (array) Tools::getValue('ids'));
$safeIds = implode(',', $ids) ?: '0';
$sql = 'SELECT * FROM `' . _DB_PREFIX_ . 'product` WHERE id_product IN (' . $safeIds . ')';

#Casos especiales

Casos especiales de escape
php
<?php

// ── JSON en base de datos ──
$data   = ['key' => 'value', 'foo' => 'bar'];
$json   = json_encode($data, JSON_UNESCAPED_UNICODE);
$safeJson = pSQL($json);  // Escapar el JSON resultante
$db->insert('mymodule_data', ['data' => $safeJson]);

// Al leer:
$row    = $db->getRow('SELECT data FROM ...');
$parsed = json_decode($row['data'], true);

// ── ENUM / valores fijos (usar lista blanca) ──
$validStatuses = ['pending', 'processing', 'completed', 'cancelled'];
$status = Tools::getValue('status');
if (!in_array($status, $validStatuses, true)) {
    $status = 'pending'; // Valor por defecto
}
$sql = "SET status = '" . pSQL($status) . "'";

// ── Fechas ──
$date = Tools::getValue('date');
if (!Validate::isDate($date)) {
    $date = date('Y-m-d');
}
$sql = "WHERE date_add >= '" . pSQL($date) . "'";

// ── Booleanos en MySQL ──
$active = (int) (bool) Tools::getValue('active');  // Siempre 0 o 1
$sql = 'WHERE active = ' . $active;

#DbQuery como alternativa segura

DbQuery — la alternativa mas legible y segura
php
<?php

// DbQuery no usa prepared statements, pero obliga a estructurar
// la query de forma que es mas dificil olvidar el escape

$idProduct = (int) Tools::getValue('id_product'); // SIEMPRE castear antes
$name      = pSQL(Tools::getValue('name'));         // SIEMPRE escapar antes
$minPrice  = (float) Tools::getValue('min_price');

$results = Db::getInstance()->executeS(
    (new DbQuery())
        ->select('p.id_product, pl.name, p.price')
        ->from('product', 'p')
        ->leftJoin(
            'product_lang', 'pl',
            'p.id_product = pl.id_product AND pl.id_lang = ' . (int) $this->context->language->id
        )
        ->where('p.active = 1')
        ->where('p.price >= ' . $minPrice)           // float ya esta seguro
        ->where("pl.name LIKE '%" . $name . "%'")    // pSQL aplicado antes
        ->orderBy('p.date_add DESC')
        ->limit(20)
);
Descargar en Markdown Pensado para pegar en ChatGPT, Claude u otra IA. Incluye solo el contenido de esta pagina.