Programação Por Equipe DevBras

Capa do artigo: target="_blank" sem noopener: o link que deixa a outra aba mexer na sua

target="_blank" sem noopener: o link que deixa a outra aba mexer na sua

Entenda o reverse tabnabbing, saiba onde os navegadores atuais já protegem e onde não, e feche o buraco em links, window.open e conteúdo enviado por usuários.

Terça, 15h. Um usuário do fórum de suporte da sua empresa clica num link postado num comentário, lê a página que abriu em outra aba e volta. A aba do fórum agora mostra "sua sessão expirou, entre de novo". Ele digita e-mail e senha. Só que aquela aba não é mais o seu fórum: a página do link trocou o endereço dela enquanto ele lia. O nome disso é reverse tabnabbing.

A resposta curta: toda aba aberta por você recebe uma referência de volta para a sua janela, o window.opener. Com ela, a página de destino consegue redirecionar a sua aba para onde quiser. Use rel="noopener" em links com target="_blank" e o recurso noopener em window.open. Os navegadores atuais já aplicam isso sozinhos em links comuns, mas não no JavaScript, não em navegadores antigos embutidos em apps e não no conteúdo que o seu sistema monta sem conferir.

Por que a outra aba consegue mexer na sua

Quando uma página abre outra janela, o navegador liga as duas. A nova aba ganha window.opener apontando para a janela que a abriu. A política de mesma origem impede que um site leia o conteúdo do outro, mas há exceções antigas, e uma delas é justamente trocar o endereço: uma página de outro domínio pode atribuir um valor novo a window.opener.location.

O ataque inteiro cabe em três linhas na página de destino:

<script>
  if (window.opener) {
    window.opener.location = 'https://forum-falso.example/login';
  }
</script>

A pessoa não vê nada acontecer, porque está olhando a aba nova. Quando volta, encontra uma tela de login idêntica à sua, num domínio parecido. Ela confia porque foi ela mesma que abriu aquela aba minutos antes. É phishing com a sua ajuda.

Entre 2019 e 2021, a especificação do HTML mudou: links <a> e áreas de mapa com target="_blank" passaram a se comportar como se tivessem noopener, e Chrome, Firefox, Safari e Edge adotaram a regra. Isso resolveu a maior parte dos links escritos à mão. O buraco continua em três lugares:

  • window.open: abrir janela por JavaScript continua entregando o opener, a menos que você peça o contrário.
  • Navegadores desatualizados: WebViews antigas, quiosques e computadores que não atualizam há anos não têm a regra nova.
  • rel="opener" explícito: alguém copiou um trecho que restaura o comportamento antigo de propósito, e ninguém lembra por quê.

Como fechar o buraco no fórum de exemplo

O fórum do cenário tem três fontes de link para outra aba: os links fixos do rodapé, um botão "abrir documentação" feito em JavaScript e os links que os usuários colam nos comentários. Cada um pede um ajuste diferente.

  1. Links escritos no HTML. Acrescente rel="noopener" sempre que houver target="_blank". Mesmo que o navegador do seu usuário já faça isso sozinho, o atributo explícito cobre os antigos e documenta a intenção.
  2. Janelas abertas por JavaScript. Passe noopener no terceiro argumento de window.open. Repare que, com ele, a função devolve null: se o seu código guardava o retorno para controlar a janela, ele precisa mudar.
  3. Conteúdo enviado por usuários. Sanitize o HTML e force rel e target no próprio sanitizador, em vez de confiar no que veio do editor.
  4. Uma regra no linter. Se o projeto usa React, o eslint-plugin-react tem a regra react/jsx-no-target-blank, que acusa target="_blank" sem rel seguro. Em outros projetos, uma busca no CI por target="_blank" sem noopener na mesma tag já pega a maioria dos casos.
  5. Uma camada extra no servidor. O cabeçalho Cross-Origin-Opener-Policy: same-origin separa a sua página de janelas de outra origem. Ele protege o caminho contrário também, quando a sua página é aberta por um site malicioso. Teste antes, porque ele quebra fluxos que dependem de popup entre domínios, como alguns logins de terceiros e checkouts de pagamento.

Os dois primeiros passos ficam assim:

<a href="https://docs.example.com/guia" target="_blank" rel="noopener">
  Guia de instalação (abre em nova aba)
</a>

<script>
  document.querySelector('#abrir-docs').addEventListener('click', () => {
    window.open('https://docs.example.com/guia', '_blank', 'noopener,noreferrer');
  });
</script>

Para os comentários, o DOMPurify permite um gancho que roda depois da limpeza. Ele é o lugar certo para decidir, no servidor ou no navegador, como todo link de comentário sai:

import DOMPurify from 'dompurify';

DOMPurify.addHook('afterSanitizeAttributes', (node) => {
  if (node.tagName === 'A' && node.hasAttribute('href')) {
    node.setAttribute('target', '_blank');
    node.setAttribute('rel', 'noopener noreferrer nofollow ugc');
  }
});

const htmlSeguro = DOMPurify.sanitize(comentario.html);

O nofollow ugc não tem nada a ver com segurança da aba. Ele avisa aos buscadores que aquele link foi colocado por um usuário, e evita que o seu domínio empreste reputação a spam.

noopener ou noreferrer?

noreferrer faz tudo o que noopener faz e ainda impede o envio do cabeçalho Referer. A página de destino não fica sabendo de onde a visita veio. Isso é bom quando a URL de origem contém algo privado, como o endereço de um ticket interno. É ruim quando você quer que o parceiro veja nas estatísticas dele que o tráfego veio de você. Escolha de propósito: para links de usuários, noreferrer; para links seus a parceiros, noopener basta.

Abrir em nova aba é decisão, não padrão

O tutorial original ensinava a abrir links em nova aba com jQuery. A pergunta anterior é se deveria. Aba nova quebra o botão voltar, confunde quem usa leitor de tela e, no celular, empilha abas que a pessoa nunca fecha. Abra em nova aba quando sair da página faria a pessoa perder trabalho, como um formulário pela metade, ou quando o destino é um documento de consulta paralela, como um PDF de referência.

Quando abrir, avise no texto do link ou com um ícone que tenha nome acessível. "Guia de instalação (abre em nova aba)" resolve sem mistério. E mantenha um link por destino, com texto que diga para onde ele leva, como em link com o nome do destino.

Se o link sai de um parâmetro da URL, o problema é outro e maior: aí o seu site vira trampolim para qualquer endereço. Esse caso está em URL aberta não é redirecionar.

Como conferir que funcionou

Abra um link externo do seu site, vá até a aba nova, abra o console do navegador e digite window.opener. O resultado tem de ser null. Faça o mesmo com o botão que usa window.open. Se aparecer um objeto Window, aquela origem ainda consegue mexer na sua aba.

Repita o teste com um comentário de usuário recém-publicado. É ali que o atributo costuma faltar, porque o editor de texto rico gera o HTML e ninguém olha o resultado.

Erros comuns

  • Achar que o navegador já resolve tudo. A regra implícita vale para links comuns em navegadores atuais. window.open sem noopener continua entregando a sua janela.
  • Usar noopener e continuar usando o retorno de window.open. A função passa a devolver null, e o código que fechava ou movia a janela quebra em silêncio.
  • Confiar no HTML que vem do editor rico. Comentário, descrição de produto e assinatura de fórum são HTML de terceiros. Sem forçar rel no sanitizador, cada usuário escolhe o próprio atributo.
  • Abrir tudo em nova aba "para não perder a visita". A pessoa perde o voltar, acumula abas e muitas vezes fecha o seu site junto.
  • Ligar Cross-Origin-Opener-Policy sem testar o login social e o pagamento. Popups entre domínios param de conversar e o fluxo trava no meio.

Checklist

  • Todo target="_blank" escrito à mão tem rel="noopener" ou rel="noopener noreferrer".
  • Nenhum rel="opener" sobrou no código sem um comentário explicando por quê.
  • Toda chamada de window.open para outro domínio passa noopener.
  • O sanitizador de conteúdo de usuário força rel nos links.
  • Há uma regra de linter ou uma busca no CI que pega target="_blank" sem rel.
  • Links que abrem nova aba avisam isso no texto.
  • No console da aba aberta, window.opener retorna null.

Abra agora o último comentário com link no seu sistema, clique e digite window.opener no console da aba nova: deu null ou deu a sua janela?

Avalie este artigo

Referência: http://www.codigosnaweb.com/forum/Abrir-link-em-nova-aba-com-jQuery_3_7265.html