💉 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.