Segurança · 7 de setembro de 2026

Entidades HTML — quando escapar, quando desescapar e por que XSS está a um caractere escapado de distância

Entidades HTML transformam caracteres especiais em sequências seguras. Elas prevenem XSS, preservam a integridade do conteúdo e confundem todo desenvolvedor pelo menos uma vez. Aqui está o panorama completo.

Toda palestra de segurança web termina no mesmo slide: sempre escape a entrada do usuário antes de inseri-la no HTML. O motivo são as entidades HTML — o mecanismo que transforma <script> em &lt;script&gt; e evita que o comentário de um usuário se torne um ataque XSS.

Mas escapar é apenas metade da história. Desescapar — transformar &amp; de volta em & — é igualmente importante e igualmente perigoso quando feito de forma incorreta. Esta publicação cobre ambas as direções, os caracteres que importam e os erros que levam a vulnerabilidades reais.

Os cinco caracteres que importam

HTML tem cinco caracteres que são estruturalmente significativos — eles delimitam tags, atributos e entidades. Se qualquer um deles aparecer no conteúdo do usuário e não for escapado, o navegador os interpreta como markup HTML:

Caractere Entidade Por que importa
< &lt; Inicia uma tag. <script> é uma tag.
> &gt; Termina uma tag.
& &amp; Inicia uma entidade. &lt; é uma entidade.
" &quot; Delimita valores de atributo.
' &#39; Delimita atributos entre aspas simples.

Se você está inserindo texto no conteúdo de um elemento HTML, precisa escapar <, > e &. Se está inserindo texto em um valor de atributo, também precisa escapar " (ou ' se o atributo usar aspas simples).

Como as entidades funcionam

Uma entidade HTML é uma sequência especial que o navegador interpreta como um único caractere:

  • Entidades nomeadas: &amp;, &lt;, &gt;, &quot;, &nbsp; — fáceis de ler, conjunto limitado.
  • Entidades numéricas: &#60; (decimal), &#x3C; (hex) — qualquer caractere Unicode por ponto de código.
  • Entidades nomeadas para símbolos: &copy; (©), &euro; (€), &mdash; (—) — nomes de conveniência.

A entidade &amp; é especial porque é o escape para o próprio &. Se você escapar & primeiro, depois escapar < e >, não faz duplo escape. A ordem importa:

Correto:  & → &amp;  →  &amp;lt;   (correto)
Incorreto: < → &lt;   →  &amp;lt;   (duplo escape)

Sempre escape & primeiro. Depois os outros caracteres. Assim você não fará duplo escape.

Onde o escape é necessário

Conteúdo gerado pelo usuário. Comentários, postagens em fóruns, avaliações, mensagens — qualquer texto que venha de um usuário e apareça em uma página deve ser escapado. Um comentários contendo <img src=x onerror=alert(1)> é um ataque XSS se você não escapar < e >.

Dados em atributos. Se você coloca dados do usuário em um atributo href, src, alt ou title, escape os caracteres de entidade. Um valor como foo" onclick="alert(1) sai do atributo e injeta um manipulador de eventos.

Interpolação de strings JavaScript. Se você está construindo HTML dentro de JavaScript:

// PERIGOSO: interpolação bruta
element.innerHTML = `<p>${userComment}</p>`;

// SEGURO: interpolação escapada
element.innerHTML = `<p>${escapeHtml(userComment)}</p>`;

innerHTML interpreta a string como HTML. Se userComment contiver markup, ele é executado. Sempre escape antes de interpolar em HTML.

Renderização do lado do servidor. Motores de template (Handlebars, Jinja, EJS, Razor) geralmente escapam automaticamente por padrão. Mas se você usar um filtro “safe” ou “raw” (como as chaves triplas do Handlebars {{{ ou |safe do Django), está desativando o escape. Só faça isso quando controlar o conteúdo.

Quando desescapar

Desescapar reverte o processo: &lt; → <, &amp; → &. É necessário quando:

  • Exibindo dados codificados. Se uma API retorna HTML escapado (algumas fazem isso para prevenir renderização), você precisa desescapar antes de exibir.
  • Processando dados de formulário. Submissões de formulário podem conter valores codificados em URL ou codificados como entidades.
  • Analisando entrada do usuário. Se um usuário cola texto codificado em HTML e você quer exibí-lo como está, desescape primeiro — depois re-escape se estiver inserindo de volta no HTML.

O perigo: desescape apenas quando for exibir, nunca quando for armazenar. Armazene a forma original escapada. Desescape no momento da renderização.

XSS: a um caractere escapado de distância

Cross-site scripting (XSS) acontece quando entrada não escapada do usuário alcança o parser HTML do navegador. As variantes mais comuns:

XSS armazenado. Um usuário submete <script>steal(document.cookie)</script> como comentário. O servidor armazena sem escapar. Todo visitante que vê o comentário executa o script.

XSS refletido. Uma URL contém ?q=<script>alert(1)</script>. O servidor reflete o parâmetro q na página sem escapar. Qualquer pessoa que clica no link executa o script.

XSS baseado em DOM. JavaScript lê location.hash e o insere no DOM via innerHTML. O hash contém markup. O navegador o renderiza.

Os três são prevenidos pela mesma coisa: escape <, >, &, " e ' antes de inserir conteúdo no HTML.

A função de escape

Toda linguagem tem uma. O padrão é o mesmo:

function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

A ordem importa: & primeiro, depois os outros. Se você escapar < antes de &, obtém &amp;lt; em vez de &lt;.

Para sistemas de alto throughput, use uma biblioteca. DOMPurify sanitiza HTML (permitindo tags seguras, removendo as perigosas). O DOMParser embutido da plataforma web e textContent também lidam com escape implicitamente.

Erros comuns

  • Escapar & após < — causa duplo escape: &amp;lt; em vez de &lt;.
  • Esquecer o contexto do atributo — escapar < e > mas não " permite injeção de atributo.
  • Usar innerHTML sem escape — o vetor XSS mais comum.
  • Confiar em respostas do servidor — APIs que retornam HTML “sanitizado” podem não sanitizar tudo. Escape no lado do cliente também.
  • Desescapar cedo demais — desescapar no momento do armazenamento significa que o payload bruto fica no seu banco de dados, pronto para executar se a renderização esquecer de re-escapar.

Experimente

Se você tem texto codificado em HTML que precisa decodificar ou texto bruto que precisa escapar para inserção segura no HTML, use uma ferramenta baseada em navegador para que a conversão permaneça local.