La nouvelle API Sanitizer vise à créer un processeur robuste permettant d'insérer en toute sécurité des chaînes arbitraires dans une page.
Les applications traitent constamment des chaînes non approuvées, mais l'affichage sécurisé de ce contenu dans un document HTML peut s'avérer délicat. Sans précautions suffisantes, il est facile de créer accidentellement des opportunités de script intersites (XSS) que des pirates informatiques malveillants peuvent exploiter.
Pour atténuer ce risque, la nouvelle proposition d'API Sanitizer vise à créer un processeur robuste permettant d'insérer de manière sécurisée des chaînes arbitraires dans une page. Cet article présente l'API et explique comment l'utiliser.
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())
Échapement de l'entrée utilisateur
Lorsque vous insérez des entrées utilisateur, des chaînes de requête, le contenu de cookies, etc. dans le DOM, les chaînes doivent être correctement échappées. Une attention particulière doit être accordée à la manipulation du DOM via .innerHTML
, où les chaînes non échappées sont une source typique d'attaque XSS.
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.innerHTML = user_input
Si vous échappez les caractères spéciaux HTML dans la chaîne d'entrée ci-dessus ou l'étendez à l'aide de .textContent
, alert(0)
ne sera pas exécuté. Toutefois, comme l'<em>
ajouté par l'utilisateur est également développé en tant que chaîne, cette méthode ne peut pas être utilisée pour conserver la décoration du texte en HTML.
La meilleure solution consiste à nettoyer les données, et non à les échapper.
Nettoyer les entrées utilisateur
Différence entre l'échappement et la validation
L'échappement consiste à remplacer les caractères HTML spéciaux par des entités HTML.
La validation consiste à supprimer les parties sémantiquement dangereuses (telles que l'exécution de script) des chaînes HTML.
Exemple
Dans l'exemple précédent, <img onerror>
entraîne l'exécution du gestionnaire d'erreurs, mais si le gestionnaire onerror
était supprimé, il serait possible de l'étendre de manière sécurisée dans le DOM tout en laissant <em>
intact.
// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`
Pour effectuer une bonne validation, il est nécessaire d'analyser la chaîne d'entrée en tant que code HTML, d'omettre les balises et les attributs considérés comme dangereux, et de conserver les éléments inoffensifs.
La spécification proposée de l'API Sanitizer vise à fournir ce traitement en tant qu'API standard pour les navigateurs.
API Sanitizer
L'API Sanitizer est utilisée de la manière suivante:
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>
Toutefois, { sanitizer: new Sanitizer() }
est l'argument par défaut. Il peut donc être comme ci-dessous.
$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>
Notez que setHTML()
est défini sur Element
. Étant donné qu'il s'agit d'une méthode de Element
, le contexte à analyser s'explique de lui-même (<div>
dans ce cas). L'analyse est effectuée une fois en interne, et le résultat est directement développé dans le DOM.
Pour obtenir le résultat du nettoyage sous forme de chaîne, vous pouvez utiliser .innerHTML
à partir des résultats setHTML()
.
const $div = document.createElement('div')
$div.setHTML(user_input)
$div.innerHTML // <em>hello world</em><img src="">
Personnaliser via la configuration
L'API Sanitizer est configurée par défaut pour supprimer les chaînes qui déclencheraient l'exécution du script. Toutefois, vous pouvez également ajouter vos propres personnalisations au processus de nettoyage via un objet de configuration.
const config = {
allowElements: [],
blockElements: [],
dropElements: [],
allowAttributes: {},
dropAttributes: {},
allowCustomElements: true,
allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)
Les options suivantes spécifient comment le résultat de la purification doit traiter l'élément spécifié.
allowElements
: noms des éléments que le nettoyeur doit conserver.
blockElements
: noms des éléments que le nettoyeur doit supprimer, tout en conservant leurs enfants.
dropElements
: noms des éléments que le nettoyeur doit supprimer, ainsi que leurs enfants.
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>
Vous pouvez également contrôler si le nettoyeur autorise ou refuse les attributs spécifiés à l'aide des options suivantes:
allowAttributes
dropAttributes
Les propriétés allowAttributes
et dropAttributes
requièrent des listes de correspondances d'attributs, c'est-à-dire des objets dont les clés sont des noms d'attributs et les valeurs des listes d'éléments cibles ou du caractère générique *
.
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
permet d'autoriser ou de refuser les éléments personnalisés. Si elles sont autorisées, d'autres configurations pour les éléments et les attributs s'appliquent toujours.
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>
Surface d'API
Comparaison avec DomPurify
DOMPurify est une bibliothèque bien connue qui offre une fonctionnalité de désinfection. La principale différence entre l'API Sanitizer et DOMPurify est que DOMPurify renvoie le résultat de la désinfection sous forme de chaîne, que vous devez écrire dans un élément DOM via .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="">`
DOMPurify peut servir de solution de secours lorsque l'API Sanitizer n'est pas implémentée dans le navigateur.
L'implémentation de DOMPurify présente quelques inconvénients. Si une chaîne est renvoyée, la chaîne d'entrée est analysée deux fois, par DOMPurify et .innerHTML
. Cette double analyse gaspille du temps de traitement, mais peut également entraîner des failles intéressantes lorsque le résultat de la deuxième analyse est différent du premier.
Le code HTML a également besoin d'un contexte pour être analysé. Par exemple, <td>
a du sens dans <table>
, mais pas dans <div>
. Étant donné que DOMPurify.sanitize()
n'accepte qu'une chaîne en tant qu'argument, le contexte d'analyse devait être deviné.
L'API Sanitizer améliore l'approche DOMPurify et est conçue pour éliminer le besoin d'une double analyse et clarifier le contexte d'analyse.
État de l'API et compatibilité avec les navigateurs
L'API Sanitizer est en cours de discussion dans le processus de normalisation, et Chrome est en train de l'implémenter.
Étape | État |
---|---|
1. Créer un message d'explication | Fin |
2. Créer un brouillon de spécification | Fin |
3. Recueillir des commentaires et itérer sur la conception | Fin |
4. Essai Origin Trial de Chrome | Fin |
5. Lancer | Intent to Ship sur M105 |
Mozilla: considère cette proposition comme méritant d'être prototypée et l'implémente activement.
WebKit: consultez la réponse sur la liste de diffusion WebKit.
Activer l'API Sanitizer
Activation via about://flags
ou option de la CLI
Chrome
Chrome est en train d'implémenter l'API Sanitizer. Dans Chrome 93 ou version ultérieure, vous pouvez tester ce comportement en activant l'indicateur about://flags/#enable-experimental-web-platform-features
. Dans les versions antérieures de Chrome Canary et du canal de développement, vous pouvez l'activer via --enable-blink-features=SanitizerAPI
et l'essayer dès maintenant. Consultez les instructions pour exécuter Chrome avec des indicateurs.
Firefox
Firefox implémente également l'API Sanitizer en tant que fonctionnalité expérimentale. Pour l'activer, définissez l'indicateur dom.security.sanitizer.enabled
sur true
dans about:config
.
Détection de fonctionnalités
if (window.Sanitizer) {
// Sanitizer API is enabled
}
Commentaires
Si vous essayez cette API et que vous avez des commentaires à nous faire, nous serions ravis de les recevoir. Partagez vos commentaires sur les problèmes GitHub de l'API Sanitizer et discutez avec les auteurs de la spécification et les personnes intéressées par cette API.
Si vous constatez des bugs ou un comportement inattendu dans l'implémentation de Chrome, signalez-les. Sélectionnez les composants Blink>SecurityFeature>SanitizerAPI
et partagez des informations pour aider les implémentateurs à suivre le problème.
Démo
Pour voir l'API Sanitizer en action, consultez le bac à sable de l'API Sanitizer de Mike West:
Références
- Spécification de l'API HTML Sanitizer
- Dépôt WICG/sanitizer-api
- Questions fréquentes sur l'API Sanitizer
- Documentation de référence de l'API HTML Sanitizer sur MDN
Photo de Towfiqu barbhuiya sur Unsplash.