Os aplicativos lidam com strings não confiáveis o tempo todo, mas renderizar esse conteúdo com segurança como parte de um documento HTML pode ser complicado. Sem cuidado suficiente, você pode criar oportunidades para scripting em vários sites (XSS) que invasores maliciosos podem explorar.
Para reduzir esse risco, a nova proposta da API Sanitizer visa criar um processador robusto para que strings arbitrárias sejam inseridas com segurança em uma página.
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())
Fazer escape de entradas do usuário
Ao inserir entradas do usuário, strings de consulta, conteúdo de cookies e muito mais no DOM, as strings precisam ser escapadas corretamente. É preciso prestar atenção especial à manipulação do DOM com .innerHTML, em que strings sem escape são uma fonte típica de XSS.
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.innerHTML = user_input
Se você fizer o escape de caracteres especiais HTML na string de entrada ou a expandir usando .textContent, alert(0) não será executado. No entanto, como <em> adicionado pelo usuário também é expandido como uma string, esse método não pode ser usado para manter a decoração de texto em HTML.
O melhor a fazer aqui não é escapar, mas higienizar.
Limpar entrada do usuário
O escape se refere à substituição de caracteres HTML especiais por entidades HTML.
A sanitização se refere à remoção de partes semanticamente prejudiciais (como execução de script) de strings HTML.
Exemplo
No exemplo anterior, <img onerror> faz com que o gerenciador de erros seja executado, mas se o gerenciador onerror fosse removido, seria possível expandir com segurança no DOM, deixando <em> intacto.
// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`
Para limpar corretamente, é necessário analisar a string de entrada como HTML, omitir tags e atributos considerados prejudiciais e manter os inofensivos.
A especificação proposta da API Sanitizer (link em inglês) tem como objetivo fornecer esse processamento como uma API padrão para navegadores.
API Sanitizer
A API Sanitizer é usada da seguinte maneira:
const $div = document.querySelector('div')
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.setHTML(user_input, { sanitizer: new Sanitizer() }) // <div><em>hello world</em><img src=""></div>
No entanto, { sanitizer: new Sanitizer() } é o argumento padrão.
$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>
Vale lembrar que setHTML() é definido em Element. Como é um método de Element, o contexto a ser analisado é autoexplicativo (<div> neste caso), a análise é feita uma vez internamente, e o resultado é expandido diretamente no DOM.
Para receber o resultado da limpeza como uma string, use .innerHTML dos resultados setHTML().
const $div = document.createElement('div')
$div.setHTML(user_input)
$div.innerHTML // <em>hello world</em><img src="">
Personalizar com configuração
A API Sanitizer é configurada por padrão para remover strings que acionariam a execução de scripts. No entanto, também é possível adicionar suas próprias personalizações ao processo de higienização com um objeto de configuração.
const config = {
allowElements: [],
blockElements: [],
dropElements: [],
allowAttributes: {},
dropAttributes: {},
allowCustomElements: true,
allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)
As opções a seguir especificam como o resultado da higienização deve tratar o elemento especificado.
allowElements: nomes de elementos que o higienizador precisa manter.
blockElements: nomes dos elementos que o higienizador precisa remover, mantendo os filhos.
dropElements: nomes dos elementos que o higienizador precisa remover, junto com os filhos deles.
const str = `hello <b><i>world</i></b>`
$div.setHTML(str)
// <div>hello <b><i>world</i></b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: [ "b" ]}) })
// <div>hello <b>world</b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ "b" ]}) })
// <div>hello <i>world</i></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: []}) })
// <div>hello world</div>
Você também pode controlar se o higienizador vai permitir ou negar atributos especificados com as seguintes opções:
allowAttributesdropAttributes
As propriedades allowAttributes e dropAttributes esperam listas de correspondência de atributos, ou seja, objetos cujas chaves são nomes de atributos e cujos valores são listas de elementos de destino ou o caractere curinga *.
const str = `<span id=foo class=bar style="color: red">hello</span>`
$div.setHTML(str)
// <div><span id="foo" class="bar" style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["span"]}}) })
// <div><span style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["p"]}}) })
// <div><span>hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["*"]}}) })
// <div><span style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({dropAttributes: {"id": ["span"]}}) })
// <div><span class="bar" style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// <div>hello</div>
allowCustomElements é a opção para permitir ou negar elementos personalizados. Se eles forem permitidos, outras configurações para elementos e atributos ainda serão aplicadas.
const str = `<custom-elem>hello</custom-elem>`
$div.setHTML(str)
// <div></div>
const sanitizer = new Sanitizer({
allowCustomElements: true,
allowElements: ["div", "custom-elem"]
})
$div.setHTML(str, { sanitizer })
// <div><custom-elem>hello</custom-elem></div>
Superfície da API
Comparação com o DomPurify
DOMPurify é uma biblioteca conhecida que oferece funcionalidade de limpeza. A principal diferença entre a API Sanitizer e o DOMPurify é que o DOMPurify retorna o resultado da sanitização como uma string, que você precisa gravar em um elemento DOM com .innerHTML.
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sanitized
// `<em>hello world</em><img src="">`
O DOMPurify pode servir como substituto quando a API Sanitizer não é implementada no navegador.
A implementação do DOMPurify tem algumas desvantagens. Se uma string for retornada, a string de entrada será analisada duas vezes, pelo DOMPurify e pelo .innerHTML. Essa análise dupla desperdiça tempo de processamento, mas também pode levar a vulnerabilidades interessantes causadas por casos em que o resultado da segunda análise é diferente da primeira.
O HTML também precisa de contexto para ser analisado. Por exemplo, <td> faz sentido em <table>, mas não em <div>. Como DOMPurify.sanitize() só aceita uma string como argumento, o contexto de análise precisou ser adivinhado.
A API Sanitizer melhora a abordagem do DOMPurify e foi projetada para eliminar a necessidade de análise dupla e esclarecer o contexto de análise.
Status da API e suporte para navegadores
A API Sanitizer está em discussão no processo de padronização, e o Chrome está em processo de implementação.
| Etapa | Status |
|---|---|
| 1. Criar explicação | Concluído |
| 2. Criar rascunho de especificação | Concluído |
| 3. Coletar feedback e iterar o design | Concluído |
| 4. Teste de origem do Chrome | Concluído |
| 5. Lançamento | Intenção de envio no M105 |
Mozilla: considera que essa proposta vale a pena ser prototipada e está implementando-a ativamente.
WebKit: confira a resposta na lista de e-mails do WebKit (em inglês).
Como ativar a API Sanitizer
Browser Support
O Chrome está implementando a API Sanitizer. No Chrome 93 ou em versões mais recentes, você pode testar o comportamento ativando a flag about://flags/#enable-experimental-web-platform-features. Em versões anteriores do Chrome Canary e do Canal de Desenvolvedor, é possível ativar com --enable-blink-features=SanitizerAPI. Confira as instruções de como executar o Chrome com flags.
Firefox
O Firefox também implementa a API Sanitizer como um recurso experimental. Para ativar, defina a flag dom.security.sanitizer.enabled como true em about:config.
Detecção de recursos
if (window.Sanitizer) {
// Sanitizer API is enabled
}
Feedback
Se você testar essa API e tiver algum feedback, envie para nós. Compartilhe suas ideias sobre os problemas do GitHub da API Sanitizer e converse com os autores da especificação e pessoas interessadas nessa API.
Se você encontrar bugs ou comportamentos inesperados na implementação do Chrome, registre um bug para informar o problema. Selecione os componentes Blink>SecurityFeature>SanitizerAPI e compartilhe detalhes para ajudar os implementadores a rastrear o problema.
Demonstração
Para conferir a API Sanitizer em ação, acesse o Sanitizer API Playground (link em inglês) de Mike West: