Todo lo que PHP imprime se convierte en parte del HTML de la página. Si lo que se
imprime lo escribió un usuario, ese usuario está escribiendo HTML en tu página.
Esta página muestra por qué eso es un problema y cómo se evita con una sola función.
Con <b> solo cambia el formato. Pero con <script>, quien arme ese enlace puede
ejecutar JavaScript en tu página, en el navegador de cualquiera que lo abra:
leer lo que escribe en un formulario, cambiar el contenido o mandarlo a otro
sitio. Se llama XSS (cross-site scripting) y es una de las vulnerabilidades
más comunes de la web.
La solución: htmlspecialchars
htmlspecialchars() reemplaza los caracteres que tienen significado en HTML por su
entidad: un código que el navegador muestra como el carácter, pero no interpreta
como etiqueta.
Con ?q=<script>…</script>, la página muestra literalmente
Resultados para: <script>…</script> como texto, sin ejecutar nada.
Validar al entrar, escapar al salir
flowchart LR
A["Dato del usuario<br/>$_GET, $_POST"] --> B["Validar<br/>¿sirve este dato?"]
B --> C["Usar<br/>calcular, comparar, guardar"]
C --> D["htmlspecialchars<br/>al imprimir"]
D --> E["HTML seguro"]
Son dos cosas distintas. Validar decide si el dato se acepta (un correo con
formato válido). Escapar decide cómo se imprime. Un apellido perfectamente
válido como O'Higgins igual trae un apóstrofo que puede romper un atributo.
Dónde se usa
En cualquier lugar donde se imprima algo que vino de afuera: el contenido de una
etiqueta y, sobre todo, dentro de un atributo.
Incorrecto: un atributo sin escapar
<input name="nombre" value="<?= $nombre ?>">
Si alguien escribe Ana" autofocus onfocus="…, la comilla cierra el value y el
resto se convierte en atributos nuevos del <input>. En el caso más inocente, el
campo se rompe con cualquier nombre que tenga comillas.
Un dato que guardó un usuario (en la Unidad 3, lo que viene de la base de datos)
Sí
Un texto que escribiste tú en el código ("Escribe tu nombre.")
No hace falta
Un número que calculó PHP (count($lista))
No hace falta
En la Unidad 3 la regla se amplía
Lo que hoy escribe un usuario en un formulario, mañana se guarda en la base de
datos y se muestra a todos los demás. Por eso, desde la Unidad 3, todo lo que
venga de la base de datos se imprime también con htmlspecialchars().
Escapar solo al imprimir
htmlspecialchars se usa en el momento de imprimir, no al leer el dato. Mientras
el programa trabaja (valida, compara, calcula), el dato se mantiene tal como llegó.
El nombre O'Higgins queda guardado como O'Higgins. Si después se compara,
se cuenta su largo o se escapa otra vez al imprimir, sale mal (O&#039;Higgins
en pantalla).
Correcto: el dato se guarda limpio y se escapa al imprimir
<?php$nombre = trim($_POST["nombre"] ?? ""); // se valida y se usa tal cual?><p><?= htmlspecialchars($nombre) ?></p> <!-- se escapa solo aquí -->
Incorrecto: strip_tags en vez de htmlspecialchars
strip_tags()borra las etiquetas en vez de mostrarlas. Cambia lo que escribió
el usuario (alguien que escribe a < b pierde parte del texto) y no protege los
atributos de las comillas. Para imprimir, htmlspecialchars.
Pruébalo
Haz una página que muestre $_GET["q"] sin escapar y entra con ?q=<b>hola</b>
Repite con htmlspecialchars y compara lo que se ve y el código fuente (Ctrl + U)
En un value, escribe un nombre con comillas dobles y mira cómo se rompe el campo sin escapar
Revisa tu sitio: busca cada $_GET y $_POST que se imprime y comprueba que pasa por htmlspecialchars()
Errores comunes
Síntoma
Causa
Solución
Un texto del usuario aparece en negrita, o desordena la página
Se imprimió sin escapar
htmlspecialchars()
Un campo con comillas pierde parte del texto al volver
El value no se escapó
value="<?= htmlspecialchars($campo) ?>"
En pantalla aparece & o '
El dato se escapó dos veces
Escapa solo al imprimir, nunca al guardar
Checkpoint
Intenta responder antes de desplegar cada pregunta.
¿Qué ve el usuario si imprimes htmlspecialchars("<b>hola</b>")?
El texto <b>hola</b> tal cual, con los símbolos visibles y sin negrita. El
navegador recibe <b>hola</b> y lo dibuja como texto.
¿Qué diferencia hay entre validar y escapar?
Validar decide si un dato se acepta (al recibirlo). Escapar decide cómo se
imprime de forma segura (al mostrarlo). Un dato válido igual se escapa.
¿Por qué no conviene guardar el dato ya pasado por htmlspecialchars?
Porque deja de ser el dato original: cambia su largo, falla al compararlo y, si se
vuelve a escapar al imprimir, aparecen entidades como & en pantalla.
Un compañero dice que su sitio no necesita htmlspecialchars porque solo lo usa él. ¿Qué le respondes?
Que cualquiera puede armar un enlace con un ?q= malicioso y enviárselo a otra
persona: el ataque ocurre en el navegador de quien abre el enlace, no del autor.