Evite vulnerabilidades de scripting em vários locais baseado em DOM com os Tipos confiáveis

Krzysztof Kotowicz
Krzysztof Kotowicz

Browser Support

  • Chrome: 83.
  • Edge: 83.
  • Firefox: behind a flag.
  • Safari: 26.

Source

O scripting entre sites baseado em DOM (DOM XSS) acontece quando dados de uma fonte controlada pelo usuário (como um nome de usuário ou um URL de redirecionamento extraído do fragmento de URL) chegam a um coletor, que é uma função como eval() ou um setter de propriedade como .innerHTML que pode executar código JavaScript arbitrário.

O DOM XSS é uma das vulnerabilidades de segurança da Web mais comuns, e é comum que as equipes de desenvolvimento o introduzam acidentalmente nos apps. Os Tipos confiáveis oferecem as ferramentas para escrever, revisar a segurança e manter os aplicativos livres de vulnerabilidades de DOM XSS, tornando as funções perigosas da API da Web seguras por padrão. Os Trusted Types estão disponíveis como um polyfill para navegadores que ainda não oferecem suporte a eles.

Contexto

Por muitos anos, o DOM XSS foi uma das vulnerabilidades de segurança da Web mais comuns e perigosas.

Há dois tipos de cross-site scripting. Algumas vulnerabilidades de XSS são causadas por código do lado do servidor que cria de forma insegura o código HTML que forma o site. Outros têm uma causa raiz no cliente, em que o código JavaScript chama funções perigosas com conteúdo controlado pelo usuário.

Para evitar XSS do lado do servidor, não gere HTML concatenando strings. Em vez disso, use bibliotecas de modelos de escape automático contextual seguro, além de uma Política de Segurança de Conteúdo baseada em nonce para reduzir ainda mais os bugs.

Agora, os navegadores também podem ajudar a evitar XSS baseado em DOM do lado do cliente usando Tipos confiáveis.

Introdução à API

Os Trusted Types funcionam bloqueando as seguintes funções de coletor de dados arriscadas. Talvez você já conheça alguns deles, porque os fornecedores de navegadores e frameworks da Web já evitam o uso desses recursos por motivos de segurança.

Os Trusted Types exigem que você processe os dados antes de transmiti-los a essas funções de coletor. Usar apenas uma string falha porque o navegador não sabe se os dados são confiáveis:

O que não fazer
anElement.innerHTML  = location.href;
Com os Tipos Confiáveis ativados, o navegador gera um TypeError e impede o uso de um coletor de XSS do DOM com uma string.

Para indicar que os dados foram processados com segurança, crie um objeto especial, um Tipo confiável.

O que fazer
anElement.innerHTML = aTrustedHTML;
  
Com os Trusted Types ativados, o navegador aceita um objeto TrustedHTML para receptores que esperam snippets HTML. Há também objetos TrustedScript e TrustedScriptURL para outros coletores sensíveis.

Os Tipos confiáveis reduzem significativamente a superfície de ataque do DOM XSS do seu aplicativo. Ele simplifica as análises de segurança e permite aplicar as verificações de segurança baseadas em tipos feitas ao compilar, usar o lint ou agrupar seu código em tempo de execução no navegador.

Como usar tipos confiáveis

Preparar relatórios de violação da Política de Segurança de Conteúdo

Você pode implantar um coletor de relatórios, como o reporting-api-processor ou o go-csp-collector de código aberto, ou usar um dos equivalentes comerciais. Também é possível adicionar registros personalizados e depurar violações no navegador usando um ReportingObserver:

const observer = new ReportingObserver((reports, observer) => {
    for (const report of reports) {
        if (report.type !== 'csp-violation' ||
            report.body.effectiveDirective !== 'require-trusted-types-for') {
            continue;
        }

        const violation = report.body;
        console.log('Trusted Types Violation:', violation);

        // ... (rest of your logging and reporting logic)
    }
}, { buffered: true });

observer.observe();

ou adicionando um listener de eventos:

document.addEventListener('securitypolicyviolation',
    console.error.bind(console));

Adicionar um cabeçalho CSP somente de relatório

Adicione o seguinte cabeçalho de resposta HTTP aos documentos que você quer migrar para Trusted Types:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Agora, todas as violações são informadas a //my-csp-endpoint.example, mas o site continua funcionando. A próxima seção explica como o //my-csp-endpoint.example funciona.

Identificar violações de tipos confiáveis

A partir de agora, sempre que os Trusted Types detectarem uma violação, o navegador vai enviar um relatório para um report-uri configurado. Por exemplo, quando o aplicativo transmite uma string para innerHTML, o navegador envia o seguinte relatório:

{
"csp-report": {
    "document-uri": "https://my.url.example",
    "violated-directive": "require-trusted-types-for",
    "disposition": "report",
    "blocked-uri": "trusted-types-sink",
    "line-number": 39,
    "column-number": 12,
    "source-file": "https://my.url.example/script.js",
    "status-code": 0,
    "script-sample": "Element innerHTML <img src=x"
}
}

Isso significa que, em https://my.url.example/script.js na linha 39, innerHTML foi chamado com a string que começa com <img src=x. Essas informações ajudam a restringir quais partes do código podem estar introduzindo XSS do DOM e precisam ser alteradas.

Corrigir as violações

Há algumas opções para corrigir uma violação de tipo confiável. É possível remover o código que apresentou problemas, usar uma biblioteca, criar uma política de Tipos confiáveis ou, como último recurso, criar uma política padrão.

Reescrever o código problemático

É possível que o código não conforme não seja mais necessário ou possa ser reescrito sem as funções que causam as violações:

O que fazer
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
O que não fazer
el.innerHTML = '<img src=xyz.jpg>';

Usar uma biblioteca

Algumas bibliotecas já geram Trusted Types que podem ser transmitidos para as funções de coleta. Por exemplo, use o DOMPurify para limpar um snippet de HTML, removendo payloads de XSS.

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

O DOMPurify é compatível com Tipos confiáveis e retorna HTML limpo agrupado em um objeto TrustedHTML para que o navegador não gere uma violação.

Criar uma política de tipo confiável

Às vezes, não é possível remover o código que causa a violação, e não há uma biblioteca para limpar o valor e criar um tipo confiável para você. Nesses casos, é possível criar um objeto Trusted Type.

Primeiro, crie uma política. As políticas são fábricas de Tipos confiáveis que aplicam determinadas regras de segurança à entrada delas:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '&lt;')
  });
}

Esse código cria uma política chamada myEscapePolicy que pode produzir objetos TrustedHTML usando a função createHTML(). As regras definidas fazem o escape de HTML dos caracteres < para evitar a criação de novos elementos HTML.

Use a política assim:

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '&lt;img src=x onerror=alert(1)>'

Usar uma política padrão

Às vezes, não é possível mudar o código ofensivo, por exemplo, se você estiver carregando uma biblioteca de terceiros de uma CDN. Nesse caso, use uma política padrão:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  trustedTypes.createPolicy('default', {
    createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
  });
}

A política chamada default é usada sempre que uma string é usada em um gravador que aceita apenas Trusted Type.

Mudar para a aplicação da Política de Segurança de Conteúdo

Quando o aplicativo não produzir mais violações, comece a aplicar Trusted Types:

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Agora, não importa a complexidade do seu aplicativo da Web, a única coisa que pode introduzir uma vulnerabilidade DOM XSS é o código em uma das suas políticas, e você pode restringir ainda mais isso ao limitar a criação de políticas.

Leitura adicional