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