🛡️ Seguridad en PrestaShop
Guia completa de seguridad para el desarrollo de modulos: tokens CSRF, prevencion de SQL Injection, XSS, uploads seguros y cumplimiento OWASP Top 10.
Regla de oro
Nunca confies en datos que llegan del cliente. Valida, sanitiza y escapa SIEMPRE: Tools::getValue(), $_POST, $_GET, $_FILES.
Sistema de tokens CSRF
php
<?php
// En el Admin Controller — verificar token
if (!Tools::isSubmit('submit' . $this->name)) {
return;
}
// Verificar token de admin
if (!Tools::getAdminToken(
'AdminMyController' . (int)Tab::getIdFromClassName('AdminMyController') . (int)$this->context->employee->id
)) {
die('Token invalido');
}
// Front office — token estatico (no requiere login)
$static_token = Tools::getToken(false);
// En Smarty asignar el token
$this->context->smarty->assign('static_token', Tools::getToken(false));
// En el template verificar en AJAX
// JavaScript: fetch('/module/mymodule/ajax?token=' + ps_static_token)
// PHP: verificar en el controlador AJAX
if (!Tools::getValue('token') || !Tools::isToken(
Tools::getValue('token')
)) {
die(json_encode(['error' => 'Token invalido']));
}
Prevencion de SQL Injection
php
<?php
// ❌ NUNCA HACER ESTO:
$id = Tools::getValue('id_product');
$sql = 'SELECT * FROM ps_product WHERE id_product = ' . $id; // VULNERABLE
// ✅ CORRECTO — casting a tipo esperado:
$id_product = (int) Tools::getValue('id_product');
$sql = 'SELECT * FROM `' . _DB_PREFIX_ . 'product` WHERE id_product = ' . $id_product;
// ✅ CORRECTO — para strings usar pSQL():
$name = pSQL(Tools::getValue('name'));
$sql = "SELECT * FROM `ps_product_lang` WHERE name = '" . $name . "'";
// ✅ CORRECTO — para nombres de tablas/columnas usar bqSQL():
$table = bqSQL(Tools::getValue('table'));
$sql = 'SELECT * FROM `' . _DB_PREFIX_ . $table . '`';
// Funciones de escape disponibles:
// pSQL($str) → Escapa string para usar en queries
// (int)$val → Casting a entero
// (float)$val → Casting a float
// (bool)$val → Casting a booleano
// bqSQL($str) → Escapa nombre de tabla/columna (sin comillas)
// addslashes($s) → Solo para nombres de archivo, NO para queries
Prevencion de XSS
template.tpl
smarty
{* ❌ NUNCA mostrar variables sin escapar: *}
{$user_input}
{* ✅ SIEMPRE escapar variables de usuario: *}
{$user_input|escape:'html':'UTF-8'}
{* ✅ Para URLs: *}
<a href="{$url|escape:'url'}">Enlace</a>
{* ✅ HTML permitido (solo si confias en la fuente): *}
{$description nofilter} {* No escapar — usar SOLO con contenido del admin *}
PHP
php
<?php
// En PHP, siempre escapar antes de mostrar:
echo htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8');
// Para contenido HTML (editor TinyMCE, etc):
echo strip_tags($html, '<p><b><i><strong><em><ul><ol><li><br><a>');
// Tools::purifyHTML() — usa HTMLPurifier internamente:
echo Tools::purifyHTML($html_content);
// Validar con los metodos de Validate:
if (!Validate::isCleanHtml($html)) {
// Contiene codigo peligroso
}
if (!Validate::isName($name)) {
// Nombre invalido
}
File Uploads seguros
php
<?php
// Subida de archivos segura en un modulo
if (isset($_FILES['my_file']) && $_FILES['my_file']['error'] === UPLOAD_ERR_OK) {
$file = $_FILES['my_file'];
// 1. Verificar tamano maximo
$maxSize = 2 * 1024 * 1024; // 2MB
if ($file['size'] > $maxSize) {
throw new Exception('Archivo demasiado grande');
}
// 2. Verificar MIME type real (no confiar en extension)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($mime, $allowed, true)) {
throw new Exception('Tipo de archivo no permitido');
}
// 3. Generar nombre aleatorio (NUNCA usar el nombre original)
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$ext = strtolower(preg_replace('/[^a-z0-9]/', '', $ext));
$filename = bin2hex(random_bytes(16)) . '.' . $ext;
// 4. Mover al directorio seguro (fuera del webroot si es posible)
$uploadDir = _PS_MODULE_DIR_ . $this->name . '/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
file_put_contents($uploadDir . 'index.php', '<?php header("Location: ../../../");');
}
move_uploaded_file($file['tmp_name'], $uploadDir . $filename);
}
OWASP Top 10 en PrestaShop
| # | Vulnerabilidad | Prevencion en PS |
|---|---|---|
A01 |
Broken Access Control | Validar permisos con `$this->context->employee->isLoggedBack()`, tokens de admin |
A02 |
Cryptographic Failures | Usar `Tools::passwdGen()` y `Tools::encrypt()`, no MD5 directo |
A03 |
SQL Injection | `pSQL()`, `(int)`, `bqSQL()`, nunca concatenar input sin escapar |
A05 |
Security Misconfiguration | Modo debug desactivado en produccion, errores no expuestos |
A06 |
Vulnerable Components | Mantener PS actualizado, auditar dependencias Composer |
A07 |
Authentication Failures | Usar sistema de auth de PS, no implementar autenticacion propia |
A08 |
Software Integrity Failures | Validar checksums de modulos de terceros antes de instalar |
A10 |
SSRF | Validar URLs antes de hacer peticiones cURL en el servidor |