
bluemonday: un saneador de HTML en golang rápido (inspirado en el OWASP Java HTML Sanitizer) para limpiar contenido generado por el usuario de XSS.
bluemonday es un sanitizador HTML implementado en Go. Es rápido y altamente configurable.
bluemonday toma contenido generado por usuarios no confiable como entrada, y devolverá HTML saneado contra una lista blanca de elementos y atributos HTML aprobados para que puedas incluir el contenido de forma segura en tu página web.
Si aceptas contenido generado por usuarios, y tu servidor usa Go, necesitas bluemonday.
La política predeterminada para contenido generado por usuarios (bluemonday.UGCPolicy().Sanitize()) convierte esto:
Hello <STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A>World
En un inofensivo:
Hello World
Y esto:
<a href="javascript:alert('XSS1')" onmouseover="alert('XSS2')">XSS<a>
En esto:
XSS
Mientras que aún permite esto:
<a href="http://www.google.com/">
<img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
</a>
Para que pase casi sin alteraciones (gana un rel="nofollow", algo bueno para el contenido generado por usuarios):
<a href="http://www.google.com/" rel="nofollow">
<img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
</a>
Protege a los sitios de ataques XSS. Existen muchos vectores para un ataque XSS y la mejor manera de mitigar el riesgo es sanear la entrada del usuario contra una lista conocida y segura de elementos y atributos HTML.
Siempre debes ejecutar bluemonday después de cualquier otro procesamiento.
Si usas blackfriday o Pandoc, bluemonday debe ejecutarse después de estos pasos. Esto garantiza que no se introduzca HTML inseguro más adelante en tu proceso.
bluemonday está fuertemente inspirado tanto en el OWASP Java HTML Sanitizer como en el HTML Purifier.
Basado en lista blanca: necesitas construir una política que describa los elementos y atributos HTML a permitir (y los patrones regexp de los atributos), o usar una de las políticas incluidas que representan buenos valores predeterminados.
La política que contiene la lista blanca se aplica mediante un analizador rápido, no validante, de solo avance y basado en tokens, implementado en la librería Go net/html por el equipo principal de Go.
Esperamos recibir HTML bien formateado (elementos de cierre para cada elemento abierto aplicable, con el anidamiento correcto), por lo que no nos centramos en reparar HTML mal anidado o incompleto. Nos centramos simplemente en garantizar que los elementos que existan estén descritos en la lista blanca de la política y que los atributos y enlaces sean seguros para usar en tu página web.
GIGO aplica aquí: si le das HTML malo, bluemonday no tiene la tarea de descubrir cómo volver a hacerlo bueno.
Sí
Estamos usando bluemonday en producción, tras migrar desde el OWASP Java HTML Sanitizer, ampliamente utilizado y probado en campo.
Estamos superando nuestra amplia suite de pruebas (incluidas las pruebas de AntiSamy y pruebas para cualquier problema reportado). Revisa los problemas no resueltos para ver si algo podría ser un bloqueo para ti.
Invitamos a enviar pull requests e issues para ayudarnos a garantizar que ofrecemos una protección integral contra diversos ataques mediante contenido generado por usuarios.
Instala usando go get github.com/microcosm-cc/bluemonday
Luego llámalo:
package main
import (
"fmt"
"github.com/microcosm-cc/bluemonday"
)
func main() {
// Do this once for each unique policy, and use the policy for the life of the program
// Policy creation/editing is not safe to use in multiple goroutines
p := bluemonday.UGCPolicy()
// The policy can then be used to sanitize lots of input and it is safe to use the policy in multiple goroutines
html := p.Sanitize(
`<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
)
// Output:
// <a href="http://www.google.com" rel="nofollow">Google</a>
fmt.Println(html)
}
Ofrecemos tres maneras de llamar a Sanitize:
p.Sanitize(string) string
p.SanitizeBytes([]byte) []byte
p.SanitizeReader(io.Reader) bytes.Buffer
Si te obsesiona el rendimiento, p.SanitizeReader(r).Bytes() devolverá un []byte sin realizar ninguna conversión innecesaria de las entradas o salidas. Aunque la diferencia es tan insignificante que nunca deberías necesitar preocuparte por ello.
Puedes construir tus propias políticas:
package main
import (
"fmt"
"github.com/microcosm-cc/bluemonday"
)
func main() {
p := bluemonday.NewPolicy()
// Require URLs to be parseable by net/url.Parse and either:
// mailto: http:// or https://
p.AllowStandardURLs()
// We only allow <p> and <a href="">
p.AllowAttrs("href").OnElements("a")
p.AllowElements("p")
html := p.Sanitize(
`<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
)
// Output:
// <a href="http://www.google.com">Google</a>
fmt.Println(html)
}
Incluimos dos políticas predeterminadas:
bluemonday.StrictPolicy(), que puede considerarse equivalente a eliminar todos los elementos HTML y sus atributos, ya que no tiene nada en su lista blanca. Un ejemplo de escenario de uso serían los títulos de publicaciones de blog, donde no se esperan etiquetas HTML en absoluto y, si las hay, tanto los elementos como el contenido de los elementos deben eliminarse. Esta es una política muy estricta.bluemonday.UGCPolicy(), que permite una amplia selección de elementos y atributos HTML seguros para contenido generado por usuarios. Ten en cuenta que esta política no permite iframes, object, embed, estilos, script, etc. Un ejemplo de escenario de uso serían los cuerpos de publicaciones de blog, donde se espera una variedad de formatos junto con la posibilidad de tablas e imágenes.La esencia de construir una política es determinar qué elementos y atributos HTML se consideran seguros para tu escenario. OWASP proporciona una chuleta de prevención de XSS para ayudar a explicar los riesgos, pero esencialmente:
script, style, iframe, object, embed, base, que permiten que el cliente ejecute código o que se incluya contenido de terceros que pueda ejecutar códigoBásicamente, deberías poder describir qué HTML es aceptable para tu escenario. Si no tienes confianza en que puedes describir tu política, considera usar una de las políticas incluidas, como bluemonday.UGCPolicy().
Para crear una nueva política:
p := bluemonday.NewPolicy()
Para añadir elementos a una política, añade solo los elementos:
p.AllowElements("b", "strong")
O usando una regex:
Nota: si un elemento se añade por nombre como se muestra arriba, cualquier regex que coincida será ignorada
También se recomienda asegurarse de que varios patrones no se superpongan, ya que el orden de ejecución no está garantizado y puede provocar que algunas reglas no se apliquen.
p.AllowElementsMatching(regex.MustCompile(`^my-element-`))
O añade elementos en virtud de añadir un atributo:
// Note the recommended pattern, see the recommendation on using .Matching() below
p.AllowAttrs("nowrap").OnElements("td", "th")
De nuevo, esto también admite una alternativa de coincidencia de patrón regex:
p.AllowAttrs("nowrap").OnElementsMatching(regex.MustCompile(`^my-element-`))
Los atributos se pueden añadir a todos los elementos:
p.AllowAttrs("dir").Matching(regexp.MustCompile("(?i)rtl|ltr")).Globally()
O los atributos se pueden añadir a elementos específicos:
// Not the recommended pattern, see the recommendation on using .Matching() below
p.AllowAttrs("value").OnElements("li")
Siempre se recomienda que un atributo deba coincidir con un patrón. De lo contrario, el XSS en atributos HTML es muy fácil:
// \p{L} matches unicode letters, \p{N} matches unicode numbers
p.AllowAttrs("title").Matching(regexp.MustCompile(`[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*`)).Globally()
Puedes detenerte en cualquier momento y llamar a .Sanitize():
// string htmlIn passed in from a HTTP POST
htmlOut := p.Sanitize(htmlIn)
Y puedes tomar cualquier política existente y extenderla:
p := bluemonday.UGCPolicy()
p.AllowElements("fieldset", "select", "option")
Aunque es posible manejar CSS en línea usando AllowAttrs con una regla Matching, escribir una única expresión regular monolítica para procesar de forma segura todo el CSS en línea que deseas permitir no es una tarea trivial. En lugar de intentar hacerlo, puedes permitir el atributo style en los elementos que desees y usar políticas de estilo para controlar y sanear los estilos en línea.
Se recomienda encarecidamente que uses Matching (con una expresión regular adecuada), MatchingEnum o MatchingHandler para asegurarte de que cada estilo se ajusta a tus necesidades, aunque se proporcionan manejadores predeterminados para los estilos más utilizados.
De manera similar a los atributos, puedes permitir que se establezcan propiedades CSS específicas en línea:
p.AllowAttrs("style").OnElements("span", "p")
// Allow the 'color' property with valid RGB(A) hex values only (on any element allowed a 'style' attribute)
p.AllowStyles("color").Matching(regexp.MustCompile("(?i)^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$")).Globally()
Además, puedes permitir que una propiedad CSS se establezca solo a un valor permitido:
p.AllowAttrs("style").OnElements("span", "p")
// Allow the 'text-decoration' property to be set to 'underline', 'line-through' or 'none'
// on 'span' elements only
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElements("span")
O puedes especificar elementos basándote en una coincidencia de patrón regex:
p.AllowAttrs("style").OnElementsMatching(regex.MustCompile(`^my-element-`))
// Allow the 'text-decoration' property to be set to 'underline', 'line-through' or 'none'
// on 'span' elements only
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElementsMatching(regex.MustCompile(`^my-element-`))
Si necesitas una comprobación más específica, puedes crear un manejador que reciba una cadena y devuelva un bool para validar los valores de una propiedad determinada. El parámetro de cadena se ha convertido a minúsculas y los puntos de código unicode se han convertido.
myHandler := func(value string) bool{
// Validate your input here
return true
}
p.AllowAttrs("style").OnElements("span", "p")
// Allow the 'color' property with values validated by the handler (on any element allowed a 'style' attribute)
p.AllowStyles("color").MatchingHandler(myHandler).Globally()
Los enlaces son bestias difíciles de sanear de forma segura y también uno de los mayores vectores de ataque para contenido malicioso.
Es posible hacer esto:
p.AllowAttrs("href").Matching(regexp.MustCompile(`(?i)mailto|https?`)).OnElements("a")
Pero eso no te protegerá, ya que la expresión regular es insuficiente en este caso para evitar que un valor malformado haga algo inesperado.
Proporcionamos algunas opciones globales adicionales para trabajar de forma segura con enlaces.
RequireParseableURLs garantizará que las URLs sean analizables por el paquete net/url de Go:
p.RequireParseableURLs(true)
Si has habilitado las URLs analizables, la siguiente opción será AllowRelativeURLs. Por defecto está deshabilitada (bluemonday es una herramienta de lista blanca... necesitas decirnos explícitamente que permitamos cosas) y cuando está deshabilitada impedirá todas las URLs locales y relativas al esquema (es decir, href="localpage.html", href="../home.html" e incluso href="//www.google.com" son relativas):
p.AllowRelativeURLs(true)
Si has habilitado las URLs analizables, puedes permitir los esquemas (comúnmente llamados protocolo cuando se piensa en http y https) que están permitidos. Ten en cuenta que permitir URLs relativas en la opción anterior permitirá un esquema en blanco:
p.AllowURLSchemes("mailto", "http", "https")
Independientemente de si has habilitado las URLs analizables, puedes forzar que todas las URLs tengan un atributo rel="nofollow". Esto se añadirá si no existe, pero solo cuando el href sea válido:
// This applies to "a" "area" "link" elements that have a "href" attribute
p.RequireNoFollowOnLinks(true)
De manera similar, puedes forzar que todas las URLs tengan "noreferrer" en su atributo rel.
// This applies to "a" "area" "link" elements that have a "href" attribute
p.RequireNoReferrerOnLinks(true)
Proporcionamos un método de conveniencia que aplica todo lo anterior, pero aún necesitarás permitir los elementos enlazables para que se apliquen las reglas de URL:
p.AllowStandardURLs()
p.AllowAttrs("cite").OnElements("blockquote", "q")
p.AllowAttrs("href").OnElements("a", "area")
p.AllowAttrs("src").OnElements("img")
Una complejidad adicional con respecto a los enlaces es la URI de datos, tal como se define en RFC2397. La URI de datos permite que las imágenes se sirvan en línea usando este formato:
<img src="data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=">
Hemos proporcionado un asistente para verificar el mimetype seguido del contenido base64 de las URIs de datos:
p.AllowDataURIImages()
Ese asistente habilitará imágenes GIF, JPEG, PNG y WEBP.
Cabe señalar que existe un posible riesgo de seguridad con el uso de URIs de datos. Solo debes habilitar las URIs de datos si ya confías en el contenido.
También tenemos algunas funciones para ayudar a lidiar con contenido generado por usuarios:
p.AddTargetBlankToFullyQualifiedLinks(true)
Esto garantizará que los enlaces ancla <a href="" /> que estén completamente calificados (el destino del href incluye un nombre de host) reciban target="_blank".
Además, cualquier enlace que tenga target="_blank" después de aplicar la política también tendrá ajustado el atributo rel para añadir noopener. Esto significa que un enlace puede comenzar como <a href="//host/path"/> y terminará como <a href="//host/path" rel="noopener" target="_blank">. Es importante señalar que la adición de noopener es una característica de seguridad y no un problema. Los navegadores tienen una característica desafortunada por la cual una ventana del navegador abierta como resultado de target="_blank" aún puede controlar al opener (tu página web), y esto protege contra eso. Los antecedentes se pueden encontrar aquí: https://dev.to/ben/the-targetblank-vulnerability-by-example
También incluimos algunos asistentes para simplificar la construcción de políticas:
// Permits the "dir", "id", "lang", "title" attributes globally
p.AllowStandardAttributes()
// Permits the "img" element and its standard attributes
p.AllowImages()
// Permits ordered and unordered lists, and also definition lists
p.AllowLists()
// Permits HTML tables and all applicable elements and non-styling attributes
p.AllowTables()
Lo siguiente es inválido:
// This does not say where the attributes are allowed, you need to add
// .Globally() or .OnElements(...)
// This will be ignored without error.
p.AllowAttrs("value")
// This does not say where the attributes are allowed, you need to add
// .Globally() or .OnElements(...)
// This will be ignored without error.
p.AllowAttrs(
"type",
).Matching(
regexp.MustCompile("(?i)^(circle|disc|square|a|A|i|I|1)$"),
)
Ambos ejemplos muestran el mismo problema: declaran atributos pero no especifican si se permiten globalmente o solo en elementos específicos (y en cuáles). Los atributos pertenecen a uno o más elementos, y la política necesita declarar esto.
Aún no incluimos herramientas para ayudar a permitir y sanear CSS. Lo que significa que, a menos que desees hacer el trabajo pesado en una sola expresión regular (desaconsejable), no deberías permitir el atributo "style" en ningún lugar.
En la misma línea, tanto <script> como <style> se consideran dañinos. Estos elementos (y su contenido) no se renderizarán por defecto, y requieren que establezcas explícitamente p.AllowUnsafe(true). Debes ser consciente de que permitir estos elementos anula el propósito de usar un sanitizador HTML, ya que estarías permitiendo explícitamente JavaScript (y cualquier XSS escrito directamente) y CSS (que puede modificar un DOM para insertar JS); además, las limitaciones de esta librería significan que no es consciente de si el HTML está estructurado válidamente, y eso puede permitir que estos elementos eviten algunos de los mecanismos de seguridad integrados en el estándar de análisis HTML de WhatWG.
No es trabajo de bluemonday arreglar tu HTML defectuoso; el trabajo de bluemonday es meramente evitar que el HTML malicioso pase. Si tienes elementos HTML no coincidentes o un anidamiento de elementos no conforme, eso permanecerá. Pero si tienes HTML bien estructurado, bluemonday no lo romperá.
bluemonday.UGCPolicy()) que encapsula el 90% de lo que buscan pero hace más de lo que necesitan, y eliminar lo extra que no desean para que sea 100% lo que quieren.table no sea descendiente de caption, que se permitan colgroup, thead, tbody, tfoot y tr, y que no se permitan datos de caracteres)