htmlspecialchars y strip_tags en PHP: sanitizar HTML correctamente

La mayoría de las vulnerabilidades XSS en PHP vienen de mostrar datos de usuario sin sanitizar. htmlspecialchars() convierte los caracteres especiales de HTML en sus entidades equivalentes, haciendo que el navegador los muestre como texto en lugar de interpretarlos como código. strip_tags() elimina directamente las etiquetas HTML, pero no es tan segura como parece a primera vista.

htmlspecialchars(): la barrera básica contra XSS

<?php
htmlspecialchars(
    string $string,
    int    $flags     = ENT_QUOTES | ENT_SUBSTITUTE,
    string $encoding  = 'UTF-8',
    bool   $double_encode = true
): string
?>

Los caracteres que convierte por defecto son &, ", ', < y >. El flag ENT_QUOTES asegura que tanto las comillas simples como las dobles queden escapadas, lo que es necesario cuando el valor va dentro de un atributo HTML:

<?php
$nombre = $_GET['nombre'] ?? '';

// Sin escaping: si nombre = '">' el XSS funciona
echo '<input value="' . $nombre . '">'; // VULNERABLE

// Con htmlspecialchars: el script queda inofensivo
$seguro = htmlspecialchars($nombre, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
echo '<input value="' . $seguro . '">'; // SEGURO
?>

ENT_QUOTES: por qué es imprescindible

Sin ENT_QUOTES, htmlspecialchars() no escapa las comillas simples. Si insertas el valor dentro de un atributo delimitado por comillas simples, el atacante puede romper el atributo:

<?php
$entrada = "' onmouseover='alert(1)";

// Sin ENT_QUOTES: vulnerable con atributos entre comillas simples
echo "<a href='" . htmlspecialchars($entrada) . "'>link</a>";
// link  ? XSS

// Con ENT_QUOTES: seguro
echo "<a href='" . htmlspecialchars($entrada, ENT_QUOTES, 'UTF-8') . "'>link</a>";
// link  ? texto inofensivo
?>

htmlspecialchars_decode(): la operación inversa

<?php
$entidades = '<b>Hola</b> & bienvenido';

echo htmlspecialchars_decode($entidades);
// Hola & bienvenido

// html_entity_decode() también convierte entidades nombradas como é
echo html_entity_decode('El café está listo');
// El café está listo
?>

strip_tags(): eliminar HTML, no sanitizar

strip_tags() elimina todas las etiquetas HTML de una cadena. Puedes pasar un segundo argumento con las etiquetas que quieres conservar:

<?php
$html = '<p>Texto con <strong>énfasis</strong> y <script>alert(1)</script>.</p>';

// Eliminar todo el HTML
echo strip_tags($html);
// Texto con énfasis y alert(1).

// Conservar algunas etiquetas
echo strip_tags($html, ['p', 'strong']);
// 

Texto con énfasis y alert(1). ?>

Por qué strip_tags no basta como barrera de seguridad

strip_tags() no elimina los atributos de las etiquetas permitidas. Esto permite inyectar manejadores de eventos incluso con etiquetas aparentemente inofensivas:

<?php
$malicioso = '<p onmouseover="robarCookies()">Texto</p>';

// strip_tags conserva la etiqueta 

pero no filtra los atributos echo strip_tags($malicioso, ['p']); //

Texto ? XSS activo ?>

Para aceptar HTML de usuarios de forma segura necesitas una librería de sanitización real como HTML Purifier o, en aplicaciones modernas, la extensión DOMDocument para construir el HTML en lugar de parsearlo. strip_tags() es útil para obtener el texto plano de un fragmento HTML, no para protegerse de HTML malicioso.

La documentación oficial de htmlspecialchars() y la de strip_tags() detallan todos los flags disponibles y los cambios de comportamiento entre versiones de PHP.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP