---
title: Cross-Site Scripting (XSS) — prevencion completa
section: security
slug: xss
description: "Prevencion de XSS en PrestaShop: htmlspecialchars en PHP, escape en Smarty y Twig, Tools::purifyHTML y validacion de contenido HTML."
keywords: prestashop XSS cross-site scripting htmlspecialchars Smarty escape Twig purifyHTML prevencion
last_updated: 2024-12-01
source_url: "https://ayudaprestashop.es/security/xss"
---

# Cross-Site Scripting (XSS) — prevencion completa

> Prevencion de XSS en PrestaShop: htmlspecialchars en PHP, escape en Smarty y Twig, Tools::purifyHTML y validacion de contenido HTML.

XSS (Cross-Site Scripting) permite a un atacante inyectar JavaScript malicioso en la pagina. En PrestaShop, todo contenido proveniente del usuario debe escaparse antes de mostrarse en el HTML.

## XSS en PHP — htmlspecialchars

*Escape en PHP antes de mostrar datos*

```php
<?php

// ── Escape basico para HTML ──
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// Convierte: < > " ' & en sus entidades HTML

// ── Escape para atributos HTML ──
echo '<input value="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '">';

// ── Escape para URLs ──
echo '<a href="' . urlencode($url) . '">' . htmlspecialchars($text, ENT_QUOTES, 'UTF-8') . '</a>';

// ── Escape para JSON en atributos data- ──
echo '<div data-config="' . htmlspecialchars(json_encode($config), ENT_QUOTES, 'UTF-8') . '">';

// ── Clase Tools de PrestaShop ──
echo Tools::safeOutput($userInput);            // htmlspecialchars simplificado
echo Tools::htmlentitiesDecodeUTF8($string);   // Para mostrar entidades previamente codificadas

// ── NO HACER: mostrar datos sin escape ──
echo $userInput;               // ❌ VULNERABLE
echo $_POST['comment'];        // ❌ VULNERABLE
echo Tools::getValue('name');  // ❌ VULNERABLE (sin escape)
```

## XSS en Smarty — escape

*Modificadores de escape en Smarty*

```smarty
{* ── Escape HTML (para contenido de texto) ── *}
{$user_input|escape:'html':'UTF-8'}

{* ── Escape para atributos HTML ── *}
<input value="{$value|escape:'html':'UTF-8'}">
<div id="{$id|escape:'html'}">...</div>

{* ── Escape para URLs ── *}
<a href="{$url|escape:'url'}">Enlace</a>

{* ── Escape para JavaScript ── *}
<script>
var name = "{$name|escape:'javascript'}";
</script>

{* ── Sin escape (solo para contenido HTML de confianza del admin) ── *}
{$product.description nofilter}
{* ⚠️ SOLO usar nofilter con contenido que ya fue saneado en el servidor *}

{* ── Smarty 3 escapa por defecto con {$var} en algunos contextos ── *}
{* ── Siempre usar |escape:'html' explicitamente para seguridad ── *}

{* ── En variables de URL ── *}
<a href="{$urls.base_url}{$page.canonical|escape:'url'}">
  {$page.title|escape:'html'}
</a>
```

## XSS en Twig — auto-escape

*Auto-escape y raw en Twig*

```twig
{# Twig escapa automaticamente por defecto #}
{{ user_input }}           {# Escapado automaticamente — SEGURO #}
{{ user_input|e }}         {# Escape explicito — equivalente #}
{{ user_input|e('html') }} {# Escape HTML explicito #}

{# Para contenido HTML de confianza (admin WYSIWYG) #}
{{ product_description|raw }}
{# ⚠️ SOLO usar |raw con contenido sanitizado en el servidor #}

{# Escape para JavaScript #}
<script>
var config = {{ json_encode(config)|raw }};
</script>
{# json_encode ya escapa para JavaScript #}

{# Escape para atributos #}
<div data-url="{{ url|e('html_attr') }}">...</div>

{# Desactivar auto-escape en un bloque (usar con cuidado) #}
{% autoescape false %}
  {{ trusted_html_content }}
{% endautoescape %}
```

## HTML permitido — purifyHTML

*Sanear HTML de usuario con Tools::purifyHTML()*

```php
<?php

// ── Tools::purifyHTML() usa HTMLPurifier internamente ──
// Permite HTML basico pero elimina scripts, event handlers, etc.

$comment = Tools::getValue('comment');
$safeHtml = Tools::purifyHTML($comment);
// <script>alert('xss')</script> → eliminado
// <p onclick="evil()">texto</p> → <p>texto</p>
// <b>Negrita</b> → permitido

// ── Validacion con Validate::isCleanHtml() ──
$htmlContent = Tools::getValue('description');
if (!Validate::isCleanHtml($htmlContent)) {
    $this->errors[] = 'Contenido HTML invalido';
} else {
    // El HTML es relativamente seguro (no perfecto)
    // Guardar en BD:
    $db->insert('mymodule_data', [
        'content' => pSQL($htmlContent, true) // true = allow HTML chars in pSQL
    ]);
}

// ── strip_tags() — solo texto plano, sin HTML ──
$plainText = strip_tags(Tools::getValue('text'));

// ── strip_tags() con tags permitidas ──
$allowedTags = '<p><br><strong><em><ul><ol><li>';
$filtered = strip_tags(Tools::getValue('content'), $allowedTags);
```

## Content Security Policy

*Agregar CSP headers desde un modulo*

```php
<?php

// ── Agregar Content Security Policy en el FO ──
public function hookActionFrontControllerSetMedia(): void
{
    // CSP restrictiva: bloquea scripts inline y solo permite del propio origen
    header(
        "Content-Security-Policy: " .
        "default-src 'self'; " .
        "script-src 'self' 'nonce-" . base64_encode(random_bytes(16)) . "'; " .
        "style-src 'self' 'unsafe-inline'; " .
        "img-src 'self' data: https:; " .
        "connect-src 'self' https://api.miservicio.com;"
    );
}

// ── Header X-XSS-Protection (legacy, mayormente obsoleto en PS moderno) ──
header('X-XSS-Protection: 1; mode=block');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
```


---

*Fuente: [https://ayudaprestashop.es/security/xss](https://ayudaprestashop.es/security/xss). Version Markdown generada automaticamente para consumo por LLMs.*
