O scripting em vários locais 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 retirado do fragmento
de URL) chegam a um sink, 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 a introduzam acidentalmente nos apps. Os Tipos confiáveis oferecem as ferramentas para escrever, revisar a segurança e manter os aplicativos livres de vulnerabilidades DOM XSS, tornando as funções de API da Web perigosas seguras por padrão. Os tipos confiáveis 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 script entre sites. Algumas vulnerabilidades XSS são causadas por código do lado do servidor que cria de forma não segura o código HTML que forma o site. Outras 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 no servidor, não gere HTML concatenando strings. Use bibliotecas de modelos de escape automático contextual seguro em vez disso, com 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 baseados em DOM do lado do cliente usando Tipos confiáveis.
Introdução à API
Os tipos confiáveis bloqueiam as seguintes funções de sink de risco. Talvez você já reconheça alguns deles, porque os fornecedores de navegadores e frameworks da Web já impedem que você use esses recursos por motivos de segurança.
- Manipulação de script:
<script src>
e definir o conteúdo de texto dos elementos<script>
. - Como gerar HTML a partir de uma string:
- Como executar conteúdo de plug-ins:
- Compilação de código JavaScript no ambiente de execução:
eval
setTimeout
setInterval
new Function()
Os tipos confiáveis exigem que você processe os dados antes de transmiti-los a essas funções de sumidouro. O uso de apenas uma string falha porque o navegador não sabe se os dados são confiáveis:
anElement.innerHTML = location.href;
Para indicar que os dados foram processados com segurança, crie um objeto especial, um tipo confiável.
anElement.innerHTML = aTrustedHTML;
Os Tipos confiáveis reduzem significativamente a superfície de ataque do XSS do DOM do seu aplicativo. Ele simplifica as análises de segurança e permite que você aplique as verificações de segurança baseadas em tipo feitas ao compilar, analisar ou agrupar seu código no momento da execução, no navegador.
Como usar os tipos confiáveis
Preparar-se para relatórios de violação da Política de Segurança de Conteúdo
É possível implantar um coletor de relatórios, como o reporting-api-processor de código aberto ou o go-csp-collector, ou usar um dos equivalentes comerciais. Também é possível adicionar violações de registro e depuração personalizadas 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 para relatórios
Adicione o cabeçalho de resposta HTTP a seguir aos documentos que você quer migrar para Tipos confiáveis:
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
//my-csp-endpoint.example
funciona.
Identificar violações de tipos confiáveis
A partir de agora, sempre que os Tipos confiáveis 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 indica que, em https://my.url.example/script.js
na linha 39, innerHTML
foi
chamado com a string começando com <img src=x
. Essas informações devem ajudar
você 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 Tipo confiável ou, como último recurso, criar uma política padrão.
Reescrever o código com problema
É 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:
el.textContent = ''; const img = document.createElement('img'); img.src = 'xyz.jpg'; el.appendChild(img);
el.innerHTML = '<img src=xyz.jpg>';
Usar uma biblioteca
Algumas bibliotecas já geram Tipos confiáveis que podem ser transmitidos para as funções de sink. Por exemplo, use o DOMPurify para higienizar um snippet de HTML, removendo payloads de XSS.
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});
O DOMPurify oferece suporte a tipos confiáveis
e retorna HTML higienizado 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á biblioteca para limpar o valor e criar um tipo confiável para você. Nesses casos, você pode criar um objeto de tipo confiável.
Primeiro, crie uma política. As políticas são fábricas de Tipos confiáveis que aplicam determinadas regras de segurança na entrada:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
createHTML: string => string.replace(/\</g, '<')
});
}
Esse código cria uma política chamada myEscapePolicy
, que pode produzir objetos TrustedHTML
usando a função createHTML()
. As regras definidas escapam de caracteres <
do HTML para evitar a criação de novos elementos HTML.
Use a política da seguinte maneira:
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<img src=x onerror=alert(1)>'
Usar uma política padrão
Às vezes, não é possível mudar o código com problema, por exemplo, se você estiver carregando uma biblioteca de terceiros de um 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 default
é usada sempre que uma string é usada em um sink que aceita apenas
o tipo confiável.
Ativar a Política de Segurança de Conteúdo
Quando o aplicativo não produzir mais violações, você poderá começar a aplicar tipos confiáveis:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
Agora, não importa o quão complexo seja seu aplicativo da Web, a única coisa que pode introduzir uma vulnerabilidade XSS do DOM é o código em uma das suas políticas. Você pode bloquear ainda mais isso limitando a criação de políticas.