Les applications traitent constamment des chaînes non fiables, mais il peut être difficile d'afficher ce contenu de manière sécurisée dans un document HTML. Sans une attention suffisante, vous risquez de créer des opportunités de scripts intersites (XSS) que des pirates malveillants pourraient exploiter.
Pour atténuer ce risque, la nouvelle proposition d'API Sanitizer vise à créer un processeur robuste pour que les chaînes arbitraires puissent être insérées de manière sécurisée dans une page.
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())
Échapper les entrées utilisateur
Lorsque vous insérez des entrées utilisateur, des chaînes de requête, des contenus 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 avec .innerHTML, où les chaînes non échappées sont une source typique de 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 ou si vous l'étendez à l'aide de .textContent, alert(0) n'est pas exécuté. Toutefois, étant donné que <em> ajouté par l'utilisateur est également étendu en tant que chaîne, cette méthode ne peut pas être utilisée pour conserver la décoration de texte en HTML.
La meilleure chose à faire ici n'est pas d'échapper, mais de désinfecter.
Désinfecter les entrées utilisateur
L'échappement consiste à remplacer les caractères HTML spéciaux par des entités HTML.
La désinfection consiste à supprimer les parties sémantiquement nuisibles (telles que l'exécution de scripts) 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 en toute sécurité 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 désinfecter correctement, il est nécessaire d'analyser la chaîne d'entrée en tant que HTML, d'omettre les balises et les attributs considérés comme nuisibles, et de conserver ceux qui sont inoffensifs.
La spécification proposée de l'API Sanitizer vise à fournir un tel traitement en tant qu'API standard pour les navigateateurs.
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.
$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>
Il convient de noter que setHTML() est défini sur Element. Étant donné qu'il s'agit d'une méthode de Element, le contexte à analyser est explicite (<div> dans ce cas), l'analyse est effectuée une seule fois en interne et le résultat est directement étendu dans le DOM.
Pour obtenir le résultat de la désinfection 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 avec la configuration
L'API Sanitizer est configurée par défaut pour supprimer les chaînes qui déclencheraient l'exécution de scripts. Toutefois, vous pouvez également ajouter vos propres personnalisations au processus de désinfection avec 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 désinfection doit traiter l'élément spécifié.
allowElements : noms des éléments que le désinfectant doit conserver.
blockElements : noms des éléments que le désinfectant doit supprimer, tout en conservant leurs enfants.
dropElements : noms des éléments que le désinfectant 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 désinfectant autorise ou refuse les attributs spécifiés avec les options suivantes :
allowAttributesdropAttributes
Les propriétés allowAttributes et dropAttributes attendent des listes de correspondance d'attributs, c'est-à-dire des objets dont les clés sont des noms d'attributs et les valeurs sont des listes d'éléments cibles ou le 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 est l'option permettant d'autoriser ou de refuser les éléments personnalisés. S'ils sont autorisés, 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 des fonctionnalités 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 avec .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 repli 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 vulnérabilités intéressantes causées par des cas où le résultat de la deuxième analyse est différent de la première.
Le HTML a également besoin d'un contexte pour être analysé. Par exemple, <td> est logique dans <table>, mais pas dans <div>. Étant donné que DOMPurify.sanitize() ne prend qu'une chaîne comme argument, le contexte d'analyse a dû ê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 une explication | Fin |
| 2. Créer un brouillon de spécification | Fin |
| 3. Recueillir des commentaires et itérer la conception | Fin |
| 4. Essai Origin Trial Chrome | Fin |
| 5. Lancement | Intention de livraison sur M105 |
Mozilla : considère que cette proposition mérite d'être prototypée et l'implémente activement.
WebKit : consultez la réponse sur la liste de diffusion WebKit.
Activer l'API Sanitizer
Browser Support
Chrome est en train d'implémenter l'API Sanitizer. Dans Chrome 93 ou version ultérieure, vous pouvez essayer le comportement en activant l'indicateur about://flags/#enable-experimental-web-platform-features. Dans les versions antérieures de Chrome Canary et de la version en développement, vous pouvez l'activer avec --enable-blink-features=SanitizerAPI. 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, n'hésitez pas à nous en faire part. Partagez vos réflexions 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 trouvez des bugs ou un comportement inattendu dans l'implémentation de Chrome, signalez-le. Sélectionnez les composants Blink>SecurityFeature>SanitizerAPI et partagez des informations pour aider les implémenteurs à suivre le problème.
Démo
Pour voir l'API Sanitizer en action, consultez le terrain de jeu de l'API Sanitizer de Mike West :