
bluemonday: um sanitizador HTML rápido em golang (inspirado no OWASP Java HTML Sanitizer) para limpar conteúdo gerado por usuários contra XSS
bluemonday é um sanitizador HTML implementado em Go. É rápido e altamente configurável.
bluemonday recebe conteúdo gerado por utilizadores não confiáveis como entrada e devolve HTML que foi sanitizado com base numa lista de permissões de elementos e atributos HTML aprovados, para que possa incluir o conteúdo com segurança na sua página web.
Se aceita conteúdo gerado por utilizadores e o seu servidor usa Go, você precisa do bluemonday.
A política padrão para conteúdo gerado por utilizadores (bluemonday.UGCPolicy().Sanitize()) transforma isto:
Hello <STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A>World
Num inofensivo:
Hello World
E transforma isto:
<a href="javascript:alert('XSS1')" onmouseover="alert('XSS2')">XSS<a>
Nisto:
XSS
Enquanto ainda permite isto:
<a href="http://www.google.com/">
<img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
</a>
Passe quase inalterado (ganhou um rel="nofollow" que é algo bom para conteúdo gerado por utilizadores):
<a href="http://www.google.com/" rel="nofollow">
<img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
</a>
Protege sites de ataques XSS. Existem muitos vetores para um ataque XSS e a melhor forma de mitigar o risco é sanitizar a entrada do utilizador contra uma lista segura conhecida de elementos e atributos HTML.
Deve sempre executar o bluemonday após qualquer outro processamento.
Se utiliza blackfriday ou Pandoc, o bluemonday deve ser executado após esses passos. Isso garante que nenhum HTML inseguro seja introduzido posteriormente no seu processo.
bluemonday é fortemente inspirado tanto pelo OWASP Java HTML Sanitizer quanto pelo HTML Purifier.
Baseado em lista de permissões, você precisa construir uma política descrevendo os elementos e atributos HTML a permitir (e os padrões regexp dos atributos), ou usar uma das políticas fornecidas que representam boas predefinições.
A política que contém a lista de permissões é aplicada usando um analisador rápido, não validante, apenas para a frente, baseado em tokens, implementado na biblioteca Go net/html pela equipa central do Go.
Esperamos receber HTML bem formatado (elementos de fecho para cada elemento de abertura aplicável, aninhamento correto) e, portanto, não nos concentramos em reparar HTML mal aninhado ou incompleto. Focamo-nos simplesmente em garantir que quaisquer elementos que existam estão descritos na lista de permissões da política e que os atributos e links são seguros para uso na sua página web. GIGO aplica-se; se fornecer HTML ruim, o bluemonday não tem a tarefa de descobrir como torná-lo bom novamente.
Sim
Estamos a usar bluemonday em produção após migrar do amplamente utilizado e testado OWASP Java HTML Sanitizer.
Estamos a passar no nosso extenso conjunto de testes (incluindo testes AntiSamy, bem como testes para quaisquer problemas levantados). Verifique se há problemas não resolvidos para ver se algo pode ser um bloqueador para si.
Convidamos pull requests e issues para nos ajudar a garantir que oferecemos proteção abrangente contra vários ataques através de conteúdo gerado por utilizadores.
Instale usando go get github.com/microcosm-cc/bluemonday
Depois chame:
package main
import (
"fmt"
"github.com/microcosm-cc/bluemonday"
)
func main() {
// Faça isso uma vez para cada política única e use a política durante a vida do programa
// A criação/edição de políticas não é segura para uso em múltiplas goroutines
p := bluemonday.UGCPolicy()
// A política pode então ser usada para sanitizar muitas entradas e é seguro usar a política em múltiplas goroutines
html := p.Sanitize(
`<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
)
// Saída:
// <a href="http://www.google.com" rel="nofollow">Google</a>
fmt.Println(html)
}
Oferecemos três formas de chamar Sanitize:
p.Sanitize(string) string
p.SanitizeBytes([]byte) []byte
p.SanitizeReader(io.Reader) bytes.Buffer
Se é obcecado por desempenho, p.SanitizeReader(r).Bytes() devolverá um []byte sem realizar qualquer conversão desnecessária das entradas ou saídas. Embora a diferença seja tão insignificante que nunca deverá precisar se preocupar.
Pode construir as suas próprias políticas:
package main
import (
"fmt"
"github.com/microcosm-cc/bluemonday"
)
func main() {
p := bluemonday.NewPolicy()
// Exigir que os URLs sejam analisáveis por net/url.Parse e que sejam:
// mailto: http:// ou https://
p.AllowStandardURLs()
// Só permitimos <p> e <a href="">
p.AllowAttrs("href").OnElements("a")
p.AllowElements("p")
html := p.Sanitize(
`<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
)
// Saída:
// <a href="http://www.google.com">Google</a>
fmt.Println(html)
}
Fornecemos duas políticas padrão:
bluemonday.StrictPolicy() que pode ser considerada equivalente a remover todos os elementos HTML e seus atributos, pois não tem nada na sua lista de permissões. Um cenário de uso exemplo seriam títulos de posts de blog onde tags HTML não são esperadas e, se estiverem, os elementos e o conteúdo dos elementos devem ser removidos. Esta é uma política muito rigorosa.bluemonday.UGCPolicy() que permite uma ampla seleção de elementos e atributos HTML que são seguros para conteúdo gerado por utilizadores. Note que esta política não permite iframes, object, embed, styles, script, etc. Um cenário de uso exemplo seriam corpos de posts de blog onde é esperada uma variedade de formatação, juntamente com a possibilidade de TABLEs e IMGs.A essência da construção de uma política é determinar quais elementos e atributos HTML são considerados seguros para o seu cenário. A OWASP fornece uma folha de dicas de prevenção de XSS para ajudar a explicar os riscos, mas essencialmente:
script, style, iframe, object, embed, base que permitem a execução de código pelo cliente ou a inclusão de conteúdo de terceiros que pode executar códigoBasicamente, deve ser capaz de descrever qual HTML é adequado para o seu cenário. Se não tem confiança de que pode descrever a sua política, considere usar uma das políticas fornecidas, como bluemonday.UGCPolicy().
Para criar uma nova política:
p := bluemonday.NewPolicy()
Para adicionar elementos a uma política, adicione apenas os elementos:
p.AllowElements("b", "strong")
Ou usando uma regex:
Nota: se um elemento for adicionado pelo nome como mostrado acima, qualquer regex correspondente será ignorada
Também é recomendado garantir que múltiplos padrões não se sobreponham, pois a ordem de execução não é garantida e pode resultar em algumas regras serem ignoradas.
p.AllowElementsMatching(regex.MustCompile(`^my-element-`))
Ou adicione elementos em virtude de adicionar um atributo:
// Note o padrão recomendado, veja a recomendação sobre usar .Matching() abaixo
p.AllowAttrs("nowrap").OnElements("td", "th")
Novamente, isto também suporta uma alternativa de correspondência de padrão regex:
p.AllowAttrs("nowrap").OnElementsMatching(regex.MustCompile(`^my-element-`))
Os atributos podem ser adicionados a todos os elementos:
p.AllowAttrs("dir").Matching(regexp.MustCompile("(?i)rtl|ltr")).Globally()
Ou os atributos podem ser adicionados a elementos específicos:
// Não é o padrão recomendado, veja a recomendação sobre usar .Matching() abaixo
p.AllowAttrs("value").OnElements("li")
É sempre recomendado que um atributo seja feito para corresponder a um padrão. XSS em atributos HTML é muito fácil de outra forma:
// \p{L} corresponde a letras unicode, \p{N} corresponde a números unicode
p.AllowAttrs("title").Matching(regexp.MustCompile(`[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*`)).Globally()
Pode parar a qualquer momento e chamar .Sanitize():
// string htmlIn passada de um HTTP POST
htmlOut := p.Sanitize(htmlIn)
E pode pegar em qualquer política existente e estendê-la:
p := bluemonday.UGCPolicy()
p.AllowElements("fieldset", "select", "option")
Embora seja possível lidar com CSS inline usando AllowAttrs com uma regra Matching, escrever uma única expressão regular monolítica para processar com segurança todo o CSS inline que deseja permitir não é uma tarefa trivial. Em vez de tentar fazê-lo, pode permitir o atributo style nos elementos que desejar e usar políticas de estilo para controlar e sanitizar estilos inline.
É fortemente recomendado que use Matching (com uma expressão regular adequada),
MatchingEnum, ou MatchingHandler para garantir que cada estilo corresponda às suas necessidades,
mas handlers padrão são fornecidos para a maioria dos estilos amplamente utilizados.
Semelhante aos atributos, pode permitir que propriedades CSS específicas sejam definidas inline:
p.AllowAttrs("style").OnElements("span", "p")
// Permitir a propriedade 'color' apenas com valores hexadecimais RGB(A) válidos (em qualquer elemento que tenha permissão para ter um atributo 'style')
p.AllowStyles("color").Matching(regexp.MustCompile("(?i)^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$")).Globally()
Além disso, pode permitir que uma propriedade CSS seja definida apenas com um valor permitido:
p.AllowAttrs("style").OnElements("span", "p")
// Permitir que a propriedade 'text-decoration' seja definida como 'underline', 'line-through' ou 'none'
// apenas em elementos 'span'
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElements("span")
Ou pode especificar elementos com base numa correspondência de padrão regex:
p.AllowAttrs("style").OnElementsMatching(regex.MustCompile(`^my-element-`))
// Permitir que a propriedade 'text-decoration' seja definida como 'underline', 'line-through' ou 'none'
// apenas em elementos 'span'
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElementsMatching(regex.MustCompile(`^my-element-`))
Se precisar de uma verificação mais específica, pode criar um handler que recebe uma string e devolve um bool para validar os valores de uma determinada propriedade. O parâmetro string foi convertido para minúsculas e os code points unicode foram convertidos.
myHandler := func(value string) bool{
// Valide a sua entrada aqui
return true
}
p.AllowAttrs("style").OnElements("span", "p")
// Permitir a propriedade 'color' com valores validados pelo handler (em qualquer elemento que tenha permissão para ter um atributo 'style')
p.AllowStyles("color").MatchingHandler(myHandler).Globally()
Links são bestas difíceis de sanitizar com segurança e também um dos maiores vetores de ataque para conteúdo malicioso.
É possível fazer isto:
p.AllowAttrs("href").Matching(regexp.MustCompile(`(?i)mailto|https?`)).OnElements("a")
Mas isso não o protegerá, pois a expressão regular é insuficiente neste caso para evitar que um valor malformado faça algo inesperado.
Fornecemos algumas opções globais adicionais para trabalhar com segurança com links.
RequireParseableURLs garantirá que os URLs são analisáveis pelo pacote net/url do Go:
p.RequireParseableURLs(true)
Se ativou URLs analisáveis, a seguinte opção permitirá AllowRelativeURLs. Por padrão, isto está desativado (bluemonday é uma ferramenta de lista de permissões... precisa de nos dizer explicitamente para permitir coisas) e quando desativado, impedirá todos os URLs locais e relativos ao esquema (ou seja, href="localpage.html", href="../home.html" e até href="//www.google.com" são relativos):
p.AllowRelativeURLs(true)
Se ativou URLs analisáveis, pode permitir os esquemas (comumente chamados de protocolo quando se pensa em http e https) que são permitidos. Tenha em mente que permitir URLs relativos na opção acima permitirá um esquema em branco:
p.AllowURLSchemes("mailto", "http", "https")
Independentemente de ter ativado URLs analisáveis, pode forçar todos os URLs a terem um atributo rel="nofollow". Isto será adicionado se não existir, mas apenas quando o href for válido:
// Isto aplica-se aos elementos "a" "area" "link" que têm um atributo "href"
p.RequireNoFollowOnLinks(true)
Da mesma forma, pode forçar todos os URLs a terem "noreferrer" no seu atributo rel.
// Isto aplica-se aos elementos "a" "area" "link" que têm um atributo "href"
p.RequireNoReferrerOnLinks(true)
Fornecemos um método de conveniência que aplica tudo o acima, mas ainda precisará de permitir os elementos linkáveis para que as regras de URL sejam aplicadas:
p.AllowStandardURLs()
p.AllowAttrs("cite").OnElements("blockquote", "q")
p.AllowAttrs("href").OnElements("a", "area")
p.AllowAttrs("src").OnElements("img")
Uma complexidade adicional relacionada a links é a data URI conforme definido em RFC2397. A data URI permite que imagens sejam servidas inline usando este formato:
<img src="data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=">
Fornecemos um helper para verificar o tipo MIME seguido pelo conteúdo base64 de links data URI:
p.AllowDataURIImages()
Esse helper ativará imagens GIF, JPEG, PNG e WEBP.
Deve notar-se que existe um potencial risco de segurança com o uso de links data URI. Deve ativar links data URI apenas se já confiar no conteúdo.
Também temos algumas funcionalidades para ajudar a lidar com conteúdo gerado por utilizadores:
p.AddTargetBlankToFullyQualifiedLinks(true)
Isto garantirá que links de âncora <a href="" /> que são totalmente qualificados (o destino href inclui um nome de host) recebam target="_blank" adicionado.
Além disso, qualquer link que tenha target="_blank" após a política ter sido aplicada também terá o atributo rel ajustado para adicionar noopener. Isto significa que um link pode começar como <a href="//host/path"/> e terminará como <a href="//host/path" rel="noopener" target="_blank">. É importante notar que a adição de noopener é uma funcionalidade de segurança e não um problema. Existe uma funcionalidade infeliz em navegadores onde uma janela do navegador aberta como resultado de target="_blank" ainda pode controlar o abridor (a sua página web) e isto protege contra isso. O contexto por trás disto pode ser encontrado aqui: https://dev.to/ben/the-targetblank-vulnerability-by-example
Também incluímos alguns ajudantes para simplificar a construção de políticas:
// Permite os atributos "dir", "id", "lang", "title" globalmente
p.AllowStandardAttributes()
// Permite o elemento "img" e os seus atributos padrão
p.AllowImages()
// Permite listas ordenadas e não ordenadas, e também listas de definição
p.AllowLists()
// Permite tabelas HTML e todos os elementos aplicáveis e atributos não relacionados a estilo
p.AllowTables()
Os seguintes são inválidos:
// Isto não diz onde os atributos são permitidos, precisa de adicionar
// .Globally() ou .OnElements(...)
// Isto será ignorado sem erro.
p.AllowAttrs("value")
// Isto não diz onde os atributos são permitidos, precisa de adicionar
// .Globally() ou .OnElements(...)
// Isto será ignorado sem erro.
p.AllowAttrs(
"type",
).Matching(
regexp.MustCompile("(?i)^(circle|disc|square|a|A|i|I|1)$"),
)
Ambos os exemplos exibem o mesmo problema: declaram atributos mas não especificam se são permitidos globalmente ou apenas em elementos específicos (e quais elementos). Os atributos pertencem a um ou mais elementos, e a política precisa de declarar isto.
Ainda não incluímos nenhuma ferramenta para ajudar a permitir e sanitizar CSS. O que significa que, a menos que deseje fazer o trabalho pesado numa única expressão regular (desaconselhável), não deve permitir o atributo "style" em nenhum lugar.
No mesmo tema, tanto <script> como <style> são considerados prejudiciais. Estes elementos (e o seu conteúdo) não serão renderizados por padrão, e exigem que defina explicitamente p.AllowUnsafe(true). Deve estar ciente de que permitir estes elementos derrota o propósito de usar um sanitizador HTML, pois estaria a permitir explicitamente JavaScript (e qualquer XSS escrito claramente) e CSS (que pode modificar um DOM para inserir JS), e adicionalmente, mas limitações nesta biblioteca significam que ela não está ciente se o HTML está validamente estruturado e isso pode permitir que estes elementos contornem alguns dos mecanismos de segurança incorporados no padrão do analisador HTML WhatWG.
Não é trabalho do bluemonday consertar o seu HTML ruim, é meramente trabalho do bluemonday impedir que HTML malicioso passe. Se tiver elementos HTML desemparelhados, ou aninhamento não conforme de elementos, estes permanecerão. Mas se tiver HTML bem estruturado, o bluemonday não o quebrará.
bluemonday.UGCPolicy()) que encapsula 90% do que procuram mas faz mais do que precisam, e removessem as coisas extra que não querem para torná-la 100% do que queremtable não seja descendente de um caption, que colgroup, thead, tbody, tfoot e tr sejam permitidos, e que dados de caracteres não sejam permitidos)