Menu

PHP htmlspecialchars(): escapar HTML e evitar XSS

htmlspecialchars($text) transforma os cinco caracteres que têm significado no HTML (& < > " ') em entidades, para que a entrada do usuário apareça como texto em vez de rodar como marcação. Veja ENT_QUOTES, como escapar atributos, codificação dupla, htmlentities e strip_tags.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

htmlspecialchars($text) converte &, <, >, " e ' em &amp;, &lt;, &gt;, &quot; e &#039;. Chame-o em cada pedaço de entrada do usuário que você imprime numa página, e a entrada aparece como texto em vez de ser lida como HTML.

O bloco imprime o mesmo comentário duas vezes, uma cru e uma escapado. Rode e compare as duas linhas na aba Page, depois digite o seu próprio HTML no formulário, por exemplo <h1>big</h1> ou <img src=x>, e clique em Show.

Na linha crua o navegador obedece às tags: a palavra em negrito fica em negrito, e um visitante que digita <script> faz o script dele rodar no navegador de todos os outros leitores. Esse ataque se chama cross-site scripting (XSS). Na linha escapada, os mesmos caracteres chegam como &lt;b&gt; e o navegador os desenha como texto. Mude para a aba Output para ver as entidades que o PHP de fato imprimiu.

O que o htmlspecialchars converte

Cinco caracteres, nada mais. Letras, acentos e emoji passam sem mudança.

O & está na lista porque começa toda entidade: se ele ficasse como está, um comentário que menciona &lt; seria exibido como <.

Escapar atributos, não só texto

Uma entrada do usuário dentro de um atributo precisa de escape tanto quanto. Sem ele, uma aspa no valor fecha o atributo e o resto da entrada vira novos atributos. Aqui o "nome" contrabandeia um atributo style; rode e olhe as duas caixas.

Na caixa insegura, o navegador vê value="Ada" seguido de um novo atributo style, então a caixa fica vermelha e mostra só Ada. Um atacante escreveria onfocus="..." ali em vez de style, e o código dele rodaria. Na caixa segura, cada " virou &quot;, então a string inteira fica dentro do value e aparece como foi digitada.

Desde o PHP 8.1, as flags padrão são ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, então aspas simples também são escapadas e atributos escritos com '...' ficam seguros. Código antigo muitas vezes passa ENT_QUOTES à mão, e no PHP 7 e anteriores isso era obrigatório:

Uma função auxiliar curta para templates

Escrever htmlspecialchars($x, ENT_QUOTES, 'UTF-8') dezenas de vezes num template polui o código, então a maioria dos projetos o envolve numa função de uma letra. Motores de template como Twig e Blade fazem a mesma coisa automaticamente para cada {{ $var }}.

O tipo ?string e o ?? '' importam: passar null para o htmlspecialchars() está depreciado desde o PHP 8.1, e o seu PHP imprimiria um aviso de depreciação para cada usuário sem bio.

Codificação dupla e htmlspecialchars_decode

Se um valor for escapado duas vezes, o leitor vê as entidades: & vira &amp; na primeira vez e &amp;amp; na segunda, que o navegador mostra como &amp;. Normalmente isso significa que o valor foi escapado quando foi salvo e de novo quando foi impresso. Passe double_encode: false para deixar as entidades existentes como estão, e use htmlspecialchars_decode() para voltar.

A correção de verdade é guardar o texto cru e escapar só na saída. double_encode: false é para textos que já contêm entidades que você não criou, como um feed importado.

htmlspecialchars, htmlentities ou strip_tags

Essas três costumam ser confundidas. O bloco roda todas na mesma entrada e mostra, para cada uma, o que o PHP imprime e o que o navegador faz com isso:

  • O htmlspecialchars() escapa os cinco caracteres do HTML. Use para qualquer texto que você imprime no HTML.
  • O htmlentities() também transforma é em &eacute;. Era útil quando as páginas não eram UTF-8; hoje só deixa o código-fonte mais difícil de ler.
  • O strip_tags() apaga as tags e mantém o texto delas. Serve para transformar HTML em texto puro (uma prévia de e-mail, uma meta description), não para segurança: a última linha mostra que um <b> permitido mantém o onclick, e texto colocado dentro de um atributo não é tocado.

Onde o htmlspecialchars não basta

O htmlspecialchars() é o escape certo para texto HTML e atributos entre aspas. Outros lugares de uma página têm outras regras:

  • Numa URL, http_build_query() ou urlencode() codifica o valor; o htmlspecialchars() então torna válido no HTML o & entre os parâmetros.
  • No JavaScript, o json_encode() produz um valor JS válido, e o JSON_HEX_TAG transforma < e > em \u003C e \u003E, para que um </script> nos dados não consiga fechar a tag script.
  • Nunca imprima entrada do usuário num href sem verificar o esquema: htmlspecialchars('javascript:alert(1)') não muda nada e continua rodando quando clicado. Aceite só URLs http e https, como mostra a página sobre filter_var.

Para o processamento de formulários que junta tudo isso, veja formulários em PHP.

Perguntas frequentes

O que faz o htmlspecialchars no PHP?

Ele substitui &, <, >, " e ' por &amp;, &lt;, &gt;, &quot; e &#039;. O navegador então exibe esses caracteres em vez de lê-los como HTML, então um <script> digitado num formulário aparece como texto e nunca roda.

Qual a diferença entre htmlspecialchars e htmlentities?

O htmlspecialchars() converte só os cinco caracteres especiais do HTML. O htmlentities() também converte todo caractere que tem uma entidade nomeada, então café vira caf&eacute;. Em páginas UTF-8 os dois são igualmente seguros, e o htmlspecialchars() mantém a saída legível, então é a escolha comum.

Ainda preciso de ENT_QUOTES no PHP 8?

Não por segurança: desde o PHP 8.1 as flags padrão são ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, então aspas simples também são escapadas. Muitos projetos ainda passam ENT_QUOTES, 'UTF-8' explicitamente para que a chamada se comporte igual em versões antigas e fique óbvia para quem lê.

Devo usar htmlspecialchars na entrada ou na saída?

Na saída. Guarde e valide o valor cru, e escape no momento em que imprimi-lo no HTML. Escapar na entrada guarda &lt; no seu banco de dados, atrapalha tamanhos e buscas e leva a escapes duplos como &amp;lt;.

O strip_tags basta para evitar XSS?

Não. O strip_tags() remove tags, mas o parâmetro de tags permitidas mantém os atributos delas, então <b onclick="..."> sobrevive, e ele não faz nada com texto colocado dentro de um atributo. Use htmlspecialchars() ao imprimir entradas do usuário.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

COMEÇAR