
bluemonday: un veloce sanitizer HTML in golang (ispirato all'OWASP Java HTML Sanitizer) per ripulire il contenuto generato dagli utenti da XSS
bluemonday è un sanitizzatore HTML implementato in Go. È veloce e altamente configurabile.
bluemonday prende in input contenuti generati da utenti non fidati e restituisce HTML che è stato sanitizzato rispetto a una whitelist di elementi e attributi HTML approvati, in modo da poter includere tranquillamente il contenuto nella tua pagina web.
Se accetti contenuti generati dagli utenti e il tuo server usa Go, hai bisogno di bluemonday.
La policy predefinita per i contenuti generati dagli utenti (bluemonday.UGCPolicy().Sanitize()) trasforma questo:
Hello <STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A>World
In un innocuo:
Hello World
E trasforma questo:
<a href="javascript:alert('XSS1')" onmouseover="alert('XSS2')">XSS<a>
In questo:
XSS
Pur continuando a permettere questo:
<a href="http://www.google.com/">
<img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
</a>
Di passare quasi inalterato (ha guadagnato un rel="nofollow, che è positivo per i contenuti generati dagli utenti):
<a href="http://www.google.com/" rel="nofollow">
<img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
</a>
Protegge i siti da attacchi XSS. Ci sono molti vettori per un attacco XSS e il modo migliore per mitigare il rischio è sanitizzare l'input utente rispetto a una lista nota di elementi e attributi HTML sicuri.
Dovresti sempre eseguire bluemonday dopo qualsiasi altra elaborazione.
Se usi blackfriday o Pandoc, bluemonday dovrebbe essere eseguito dopo questi passaggi. Questo garantisce che nessun HTML insicuro venga introdotto successivamente nel tuo processo.
bluemonday è fortemente ispirato sia dall'OWASP Java HTML Sanitizer che dall'HTML Purifier.
Basato su whitelist: devi costruire una policy che descriva gli elementi e gli attributi HTML da permettere (e i pattern regexp degli attributi), oppure usare una delle policy fornite che rappresentano buoni valori predefiniti.
La policy contenente la whitelist viene applicata utilizzando un parser rapido, non validante, forward-only e basato su token, implementato nella libreria Go net/html dal team principale di Go.
Ci aspettiamo di ricevere HTML ben formattato (elementi di chiusura per ogni elemento aperto applicabile, nidificati correttamente) e quindi non ci concentriamo sulla riparazione di HTML mal nidificato o incompleto. Ci concentriamo semplicemente sull'assicurare che qualunque elemento esista sia descritto nella whitelist della policy e che attributi e link siano sicuri per l'uso nella tua pagina web. Vale il GIGO e se gli fornisci HTML scadente, bluemonday non ha il compito di capire come renderlo di nuovo buono.
Sì
Stiamo usando bluemonday in produzione dopo essere migrati dall'ampio e testato OWASP Java HTML Sanitizer.
Stiamo superando la nostra estesa suite di test (inclusi i test AntiSamy e test per qualsiasi problema segnalato). Controlla eventuali problemi irrisolti per vedere se qualcosa potrebbe essere un blocco per te.
Accogliamo con favore pull request e issue per aiutarci a garantire di offrire una protezione completa contro vari attacchi tramite contenuti generati dagli utenti.
Installa usando go get github.com/microcosm-cc/bluemonday
Poi chiamalo:
package main
import (
"fmt"
"github.com/microcosm-cc/bluemonday"
)
func main() {
// Fallo una volta per ogni policy unica e usa la policy per tutta la durata del programma
// La creazione/modifica della policy non è sicura da usare in più goroutine
p := bluemonday.UGCPolicy()
// La policy può poi essere usata per sanitizzare molti input ed è sicuro usarla in più goroutine
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)
}
Offriamo tre modi per chiamare Sanitize:
p.Sanitize(string) string
p.SanitizeBytes([]byte) []byte
p.SanitizeReader(io.Reader) bytes.Buffer
Se sei ossessionato dalle prestazioni, p.SanitizeReader(r).Bytes() restituirà un []byte senza eseguire alcun casting non necessario degli input o output. Tuttavia, la differenza è così trascurabile che non dovresti mai preoccupartene.
Puoi creare le tue policy:
package main
import (
"fmt"
"github.com/microcosm-cc/bluemonday"
)
func main() {
p := bluemonday.NewPolicy()
// Richiede che gli URL siano analizzabili da net/url.Parse e siano:
// mailto: http:// o https://
p.AllowStandardURLs()
// Permettiamo solo <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>`,
)
// Output:
// <a href="http://www.google.com">Google</a>
fmt.Println(html)
}
Forniamo due policy predefinite:
bluemonday.StrictPolicy() che può essere considerata equivalente alla rimozione di tutti gli elementi HTML e dei loro attributi, poiché non ha nulla nella sua whitelist. Un esempio di scenario d'uso potrebbero essere i titoli di post di blog in cui non ci si aspetta affatto tag HTML e, se ci sono, gli elementi e il contenuto degli elementi dovrebbero essere rimossi. Questa è una policy molto restrittiva.bluemonday.UGCPolicy() che permette un'ampia selezione di elementi e attributi HTML sicuri per contenuti generati dagli utenti. Nota che questa policy non permette iframe, object, embed, style, script, ecc. Un esempio di scenario d'uso potrebbero essere i corpi di post di blog in cui ci si aspetta una varietà di formattazione insieme alla possibilità di TABLE e IMG.L'essenza della costruzione di una policy è determinare quali elementi e attributi HTML siano considerati sicuri per il tuo scenario. OWASP fornisce un cheat sheet per la prevenzione XSS per aiutare a spiegare i rischi, ma essenzialmente:
script, style, iframe, object, embed, base che consentono l'esecuzione di codice da parte del client o l'inclusione di contenuti di terze parti che possono eseguire codiceIn pratica, dovresti essere in grado di descrivere quale HTML va bene per il tuo scenario. Se non hai fiducia di poter descrivere la tua policy, considera l'uso di una delle policy fornite, come bluemonday.UGCPolicy().
Per creare una nuova policy:
p := bluemonday.NewPolicy()
Per aggiungere elementi a una policy, aggiungi semplicemente gli elementi:
p.AllowElements("b", "strong")
Oppure usando un regex:
Nota: se un elemento viene aggiunto per nome come sopra, qualsiasi regex corrispondente verrà ignorato
Si raccomanda anche di assicurarsi che più pattern non si sovrappongano, poiché l'ordine di esecuzione non è garantito e potrebbe portare ad alcune regole mancate.
p.AllowElementsMatching(regex.MustCompile(`^my-element-`))
Oppure aggiungi elementi come conseguenza dell'aggiunta di un attributo:
// Nota il pattern raccomandato, vedi la raccomandazione sull'uso di .Matching() più sotto
p.AllowAttrs("nowrap").OnElements("td", "th")
Anche questo supporta una variante con pattern regex:
p.AllowAttrs("nowrap").OnElementsMatching(regex.MustCompile(`^my-element-`))
Gli attributi possono essere aggiunti a tutti gli elementi:
p.AllowAttrs("dir").Matching(regexp.MustCompile("(?i)rtl|ltr")).Globally()
O gli attributi possono essere aggiunti a elementi specifici:
// Non il pattern raccomandato, vedi la raccomandazione sull'uso di .Matching() più sotto
p.AllowAttrs("value").OnElements("li")
È sempre raccomandato che un attributo sia associato a un pattern. L'XSS negli attributi HTML è altrimenti molto facile:
// \p{L} corrisponde a lettere unicode, \p{N} corrisponde a numeri unicode
p.AllowAttrs("title").Matching(regexp.MustCompile(`[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*`)).Globally()
Puoi fermarti in qualsiasi momento e chiamare .Sanitize():
// string htmlIn passata da un POST HTTP
htmlOut := p.Sanitize(htmlIn)
E puoi prendere qualsiasi policy esistente ed estenderla:
p := bluemonday.UGCPolicy()
p.AllowElements("fieldset", "select", "option")
Sebbene sia possibile gestire il CSS inline usando AllowAttrs con una regola Matching, scrivere un'unica espressione regolare monolitica per elaborare in modo sicuro tutto il CSS inline che si desidera permettere non è un compito banale. Invece di tentare di farlo, puoi permettere l'attributo style sugli elementi che desideri e usare policy di stile per controllare e sanitizzare gli stili inline.
È fortemente raccomandato usare Matching (con un'espressione regolare adatta), MatchingEnum o MatchingHandler per assicurarsi che ogni stile soddisfi le tue esigenze, ma sono forniti gestori predefiniti per la maggior parte degli stili più diffusi.
Simile agli attributi, puoi permettere che specifiche proprietà CSS siano impostate inline:
p.AllowAttrs("style").OnElements("span", "p")
// Permetti la proprietà 'color' solo con valori esadecimali RGB(A) validi (su qualsiasi elemento a cui sia permesso l'attributo 'style')
p.AllowStyles("color").Matching(regexp.MustCompile("(?i)^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$")).Globally()
Inoltre, puoi permettere che una proprietà CSS sia impostata solo a un valore consentito:
p.AllowAttrs("style").OnElements("span", "p")
// Permetti che la proprietà 'text-decoration' sia impostata a 'underline', 'line-through' o 'none'
// solo su elementi 'span'
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElements("span")
Oppure puoi specificare elementi basati su un pattern regex:
p.AllowAttrs("style").OnElementsMatching(regex.MustCompile(`^my-element-`))
// Permetti che la proprietà 'text-decoration' sia impostata a 'underline', 'line-through' o 'none'
// solo su elementi 'span'
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElementsMatching(regex.MustCompile(`^my-element-`))
Se hai bisogno di un controllo più specifico, puoi creare un gestore che prenda una stringa e restituisca un bool per validare i valori per una data proprietà. Il parametro stringa è stato convertito in minuscolo e i punti di codice unicode sono stati convertiti.
myHandler := func(value string) bool{
// Valida il tuo input qui
return true
}
p.AllowAttrs("style").OnElements("span", "p")
// Permetti la proprietà 'color' con valori validati dal gestore (su qualsiasi elemento a cui sia permesso l'attributo 'style')
p.AllowStyles("color").MatchingHandler(myHandler).Globally()
I link sono bestie difficili da sanitizzare in modo sicuro e anche uno dei maggiori vettori di attacco per contenuti malevoli.
È possibile fare questo:
p.AllowAttrs("href").Matching(regexp.MustCompile(`(?i)mailto|https?`)).OnElements("a")
Ma questo non ti proteggerà, poiché l'espressione regolare in questo caso è insufficiente per aver impedito a un valore malformato di fare qualcosa di inaspettato.
Forniamo alcune opzioni globali aggiuntive per lavorare in modo sicuro con i link.
RequireParseableURLs garantirà che gli URL siano analizzabili dal pacchetto net/url di Go:
p.RequireParseableURLs(true)
Se hai abilitato URL analizzabili, l'opzione seguente permetterà AllowRelativeURLs. Per impostazione predefinita è disabilitata (bluemonday è uno strumento basato su whitelist... devi dirci esplicitamente di permettere le cose) e quando disabilitata impedirà tutti gli URL locali e relativi allo schema (ad es. href="localpage.html", href="../home.html" e anche href="//www.google.com" sono relativi):
p.AllowRelativeURLs(true)
Se hai abilitato URL analizzabili, puoi permettere gli schemi (comunemente chiamati protocollo quando si pensa a http e https) che sono consentiti. Tieni presente che permettere URL relativi con l'opzione sopra consentirà uno schema vuoto:
p.AllowURLSchemes("mailto", "http", "https")
Indipendentemente dal fatto che tu abbia abilitato URL analizzabili, puoi forzare tutti gli URL ad avere un attributo rel="nofollow". Questo verrà aggiunto se non esiste, ma solo quando l'href è valido:
// Questo si applica agli elementi "a", "area", "link" che hanno un attributo "href"
p.RequireNoFollowOnLinks(true)
Allo stesso modo, puoi forzare tutti gli URL ad avere "noreferrer" nel loro attributo rel.
// Questo si applica agli elementi "a", "area", "link" che hanno un attributo "href"
p.RequireNoReferrerOnLinks(true)
Forniamo un metodo di comodo che applica tutto quanto sopra, ma dovrai comunque permettere gli elementi collegabili affinché le regole URL vengano applicate:
p.AllowStandardURLs()
p.AllowAttrs("cite").OnElements("blockquote", "q")
p.AllowAttrs("href").OnElements("a", "area")
p.AllowAttrs("src").OnElements("img")
Un'ulteriore complessità relativa ai link è l'URI dei dati come definito in RFC2397. L'URI dei dati permette di servire immagini inline usando questo formato:
<img src="data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=">
Abbiamo fornito un helper per verificare il mimetype seguito dal contenuto base64 dei link URI di dati:
p.AllowDataURIImages()
Questo helper abiliterà le immagini GIF, JPEG, PNG e WEBP.
Va notato che esiste un potenziale rischio di sicurezza nell'uso di link URI di dati. Dovresti abilitare i link URI di dati solo se ti fidi già del contenuto.
Abbiamo anche alcune funzionalità per aiutare a gestire i contenuti generati dagli utenti:
p.AddTargetBlankToFullyQualifiedLinks(true)
Questo garantirà che i link anchor <a href="" /> che sono completamente qualificati (la destinazione href include un nome host) riceveranno target="_blank".
Inoltre, qualsiasi link che ha target="_blank" dopo che la policy è stata applicata avrà anche l'attributo rel modificato per aggiungere noopener. Ciò significa che un link potrebbe iniziare come <a href="//host/path"/> e finirà come <a href="//host/path" rel="noopener" target="_blank">. È importante notare che l'aggiunta di noopener è una caratteristica di sicurezza e non un problema. C'è una sfortunata caratteristica dei browser per cui una finestra del browser aperta come risultato di target="_blank" può ancora controllare l'opener (la tua pagina web) e questo protegge da ciò. Il background si trova qui: https://dev.to/ben/the-targetblank-vulnerability-by-example
Forniamo anche alcuni helper per semplificare la costruzione della policy:
// Permette gli attributi "dir", "id", "lang", "title" a livello globale
p.AllowStandardAttributes()
// Permette l'elemento "img" e i suoi attributi standard
p.AllowImages()
// Permette liste ordinate e non ordinate, e anche liste di definizione
p.AllowLists()
// Permette le tabelle HTML e tutti gli elementi applicabili e gli attributi non di stile
p.AllowTables()
Quanto segue non è valido:
// Questo non dice dove sono permessi gli attributi, devi aggiungere
// .Globally() o .OnElements(...)
// Verrà ignorato senza errore.
p.AllowAttrs("value")
// Questo non dice dove sono permessi gli attributi, devi aggiungere
// .Globally() o .OnElements(...)
// Verrà ignorato senza errore.
p.AllowAttrs(
"type",
).Matching(
regexp.MustCompile("(?i)^(circle|disc|square|a|A|i|I|1)$"),
)
Entrambi gli esempi mostrano lo stesso problema: dichiarano attributi ma non specificano poi se sono permessi a livello globale o solo su elementi specifici (e quali elementi). Gli attributi appartengono a uno o più elementi e la policy deve dichiararlo.
Non stiamo ancora includendo strumenti per aiutare a permettere e sanitizzare il CSS. Ciò significa che, a meno che tu non voglia fare il lavoro pesante in una singola espressione regolare (sconsigliato), non dovresti permettere l'attributo "style" da nessuna parte.
Sullo stesso tema, sia <script> che <style> sono considerati dannosi. Questi elementi (e il loro contenuto) non verranno renderizzati per impostazione predefinita e richiedono di impostare esplicitamente p.AllowUnsafe(true). Dovresti essere consapevole che permettere questi elementi vanifica lo scopo di usare un sanitizzatore HTML, poiché permetteresti esplicitamente JavaScript (e qualsiasi XSS scritto in chiaro) e CSS (che può modificare un DOM per inserire JS), e in aggiunta ci sono limitazioni in questa libreria che la rendono non consapevole se l'HTML è strutturato validamente e ciò può permettere a questi elementi di bypassare alcuni dei meccanismi di sicurezza incorporati nello standard del parser HTML WhatWG.
Non è compito di bluemonday riparare il tuo HTML scadente, è solo compito di bluemonday impedire che HTML malevolo passi. Se hai elementi HTML non corrispondenti o nidificazione non conforme, questi rimarranno. Ma se hai HTML ben strutturato, bluemonday non lo romperà.
bluemonday.UGCPolicy()) che incapsula il 90% di ciò che cercano ma fa più del necessario, e rimuovere le cose extra che non vogliono per renderla al 100% ciò che vogliono.table non sia discendente di un caption, che colgroup, thead, tbody, tfoot e tr siano permessi, e che i dati carattere non siano permessi).