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.

El problema

Un buscador que muestra lo que se buscó:

<?php $busqueda = $_GET["q"] ?? ""; ?>
<p>Resultados para: <?= $busqueda ?></p>
Alguien entra aEl navegador recibeSe ve
buscar.php?q=php<p>Resultados para: php</p>Resultados para: php
buscar.php?q=<b>hola</b><p>Resultados para: <b>hola</b></p>Resultados para: hola
buscar.php?q=<script>…</script><p>Resultados para: <script>…</script></p>El navegador ejecuta el script

Incorrecto: imprimir un dato del usuario tal cual

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.

<?php
echo htmlspecialchars("<b>hola</b>") . "\n";
echo htmlspecialchars('Dijo "sí" & se fue') . "\n";
&lt;b&gt;hola&lt;/b&gt;
Dijo &quot;sí&quot; &amp; se fue

Lo que reemplaza

CarácterSe convierte enPor qué es peligroso
<&lt;Abre una etiqueta
>&gt;Cierra una etiqueta
"&quot;Cierra un atributo: value="…"
'&#039;Cierra un atributo con comillas simples
&&amp;Empieza una entidad

En la pantalla se ve el texto exactamente como lo escribió el usuario, con sus < y >. Pero el navegador ya no lo trata como código.

Correcto: htmlspecialchars al imprimir

<?php $busqueda = $_GET["q"] ?? ""; ?>
<p>Resultados para: <?= htmlspecialchars($busqueda) ?></p>

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.

Correcto

<input name="nombre" value="<?= htmlspecialchars($nombre) ?>">
Imprimes¿htmlspecialchars?
Algo de $_GET o $_POSTSí, siempre
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ó.

Incorrecto: guardar el dato ya escapado

<?php
$nombre = htmlspecialchars(trim($_POST["nombre"] ?? ""));

El nombre O'Higgins queda guardado como O&#039;Higgins. Si después se compara, se cuenta su largo o se escapa otra vez al imprimir, sale mal (O&amp;#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íntomaCausaSolución
Un texto del usuario aparece en negrita, o desordena la páginaSe imprimió sin escaparhtmlspecialchars()
Un campo con comillas pierde parte del texto al volverEl value no se escapóvalue="<?= htmlspecialchars($campo) ?>"
En pantalla aparece &amp; o &#039;El dato se escapó dos vecesEscapa solo al imprimir, nunca al guardar

Checkpoint

Intenta responder antes de desplegar cada pregunta.