Menu

PHP htmlspecialchars(): escapar HTML y prevenir XSS

htmlspecialchars($text) convierte en entidades los cinco caracteres que significan algo en HTML (& < > " '), de modo que la entrada del usuario se muestra como texto en lugar de ejecutarse como marcado. Aprende ENT_QUOTES, el escapado de atributos, la doble codificación, htmlentities y strip_tags.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

htmlspecialchars($text) convierte &, <, >, " y ' en &amp;, &lt;, &gt;, &quot; y &#039;. Llámala con cada dato de entrada del usuario que imprimas en una página, y la entrada se mostrará como texto en lugar de leerse como HTML.

El bloque imprime el mismo comentario dos veces, una sin modificar y otra escapada. Ejecútalo y compara las dos líneas en la pestaña Página; luego escribe tu propio HTML en el formulario, por ejemplo <h1>big</h1> o <img src=x>, y pulsa Show.

En la línea sin modificar el navegador obedece a las etiquetas: la palabra en negrita sale en negrita, y un visitante que escriba <script> consigue que su script se ejecute en el navegador de todos los demás lectores. Ese ataque se llama cross-site scripting (XSS). En la línea escapada los mismos caracteres llegan como &lt;b&gt; y el navegador los dibuja como texto. Cambia a la pestaña Salida para ver las entidades que imprimió realmente PHP.

Qué convierte htmlspecialchars

Cinco caracteres y nada más. Las letras, las tildes y los emojis pasan sin cambios.

& está en la lista porque es el comienzo de toda entidad: si se dejara tal cual, un comentario que menciona &lt; se mostraría como <.

Escapar los atributos, no solo el texto

La entrada del usuario dentro de un atributo necesita escaparse igual. Sin escapar, unas comillas en el valor cierran el atributo y el resto de la entrada se convierte en atributos nuevos. Aquí el "nombre" cuela un atributo style; ejecútalo y mira las dos cajas.

En la caja insegura el navegador ve value="Ada" seguido de un atributo style nuevo, así que la caja se vuelve roja y solo muestra Ada. Un atacante escribiría ahí onfocus="..." en lugar de style, y su código se ejecutaría. En la caja segura cada " se convirtió en &quot;, así que toda la cadena se queda dentro de value y se muestra tal como se escribió.

Desde PHP 8.1 las opciones por defecto son ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, así que las comillas simples también se escapan y los atributos escritos con '...' son seguros. El código antiguo suele pasar ENT_QUOTES a mano, y en PHP 7 y anteriores era obligatorio:

Una función auxiliar corta para las plantillas

Escribir htmlspecialchars($x, ENT_QUOTES, 'UTF-8') decenas de veces en una plantilla es ruidoso, así que la mayoría de los proyectos la envuelven en una función de una letra. Los motores de plantillas como Twig y Blade hacen lo mismo automáticamente con cada {{ $var }}.

El tipo ?string y el ?? '' importan: pasar null a htmlspecialchars() está obsoleto desde PHP 8.1, y tu PHP imprimiría un aviso de obsolescencia por cada usuario sin biografía.

Doble codificación y htmlspecialchars_decode

Si un valor se escapa dos veces, el lector ve las entidades: & se convierte en &amp; la primera vez y en &amp;amp; la segunda, que el navegador muestra como &amp;. Normalmente significa que el valor se escapó al guardarlo y otra vez al imprimirlo. Pasa double_encode: false para dejar intactas las entidades que ya existen, y usa htmlspecialchars_decode() para volver atrás.

La solución real es guardar el texto sin modificar y escapar solo en la salida. double_encode: false es para texto que ya contiene entidades que no creaste tú, como un feed importado.

htmlspecialchars vs htmlentities vs strip_tags

Estas tres se confunden a menudo. El bloque las ejecuta todas con la misma entrada y muestra, para cada una, lo que imprime PHP y lo que hace con ello el navegador:

  • htmlspecialchars() escapa los cinco caracteres de HTML. Úsala con cualquier texto que imprimas en HTML.
  • htmlentities() convierte además é en &eacute;. Era útil cuando las páginas no eran UTF-8; hoy solo hace más difícil de leer el código fuente.
  • strip_tags() borra las etiquetas y conserva su texto. Sirve para convertir HTML en texto plano (una vista previa de un email, una meta descripción), no para la seguridad: la última fila muestra que un <b> permitido conserva su onclick, y el texto colocado dentro de un atributo no se toca en absoluto.

Dónde no basta htmlspecialchars

htmlspecialchars() es el escapado adecuado para el texto HTML y los atributos entre comillas. Otros lugares de una página tienen otras reglas:

  • En una URL, http_build_query() o urlencode() codifican el valor; luego htmlspecialchars() hace que el & entre parámetros sea HTML válido.
  • En JavaScript, json_encode() produce un valor JS válido, y JSON_HEX_TAG convierte < y > en \u003C y \u003E, para que un </script> en los datos no pueda cerrar la etiqueta script.
  • Nunca imprimas entrada del usuario en un href sin comprobar el esquema: htmlspecialchars('javascript:alert(1)') no cambia y sigue ejecutándose al hacer clic. Acepta solo URLs http y https, como se muestra en la página de filter_var.

Para el manejo de formularios que reúne todo esto, consulta los formularios en PHP.

Preguntas frecuentes

¿Qué hace htmlspecialchars en PHP?

Sustituye &, <, >, " y ' por &amp;, &lt;, &gt;, &quot; y &#039;. El navegador muestra entonces esos caracteres en lugar de leerlos como HTML, así que un <script> escrito en un formulario se muestra como texto y nunca se ejecuta.

¿Cuál es la diferencia entre htmlspecialchars y htmlentities?

htmlspecialchars() convierte solo los cinco caracteres especiales de HTML. htmlentities() convierte además todos los caracteres que tienen una entidad con nombre, así que café se convierte en caf&eacute;. Con páginas UTF-8 las dos son igual de seguras, y htmlspecialchars() mantiene legible la salida, así que es la opción habitual.

¿Sigo necesitando ENT_QUOTES en PHP 8?

No por seguridad: desde PHP 8.1 las opciones por defecto son ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, así que las comillas simples también se escapan. Muchos proyectos siguen pasando ENT_QUOTES, 'UTF-8' de forma explícita para que la llamada se comporte igual en versiones antiguas y resulte obvia a quien la lee.

¿Uso htmlspecialchars en la entrada o en la salida?

En la salida. Guarda y valida el valor sin modificar, y escápalo en el momento de imprimirlo en HTML. Escapar en la entrada guarda &lt; en tu base de datos, rompe las longitudes y las búsquedas, y lleva a un doble escapado como &amp;lt;.

¿Basta strip_tags para prevenir XSS?

No. strip_tags() elimina las etiquetas, pero su parámetro de etiquetas permitidas conserva sus atributos, así que <b onclick="..."> sobrevive, y no hace nada con el texto colocado dentro de un atributo. Usa htmlspecialchars() al imprimir la entrada del usuario.

Ilustración de los lenguajes de programación de Coddy

Aprende a programar con Coddy

COMENZAR