Manipulation DOM sécurisée avec l'API Sanitizer

La nouvelle API Sanitizer vise à créer un processeur robuste pour insérer en toute sécurité des chaînes arbitraires dans une page.

Jack J
Jack J

Les applications traitent constamment des chaînes non fiables, mais afficher ce contenu en toute sécurité dans un document HTML peut s'avérer délicat. Sans les précautions nécessaires, il est facile de créer accidentellement des scripts intersites (XSS) que des pirates informatiques malveillants pourraient exploiter.

Pour atténuer ce risque, la nouvelle proposition de l'API Sanitizer vise à créer un processeur robuste pour insérer en toute sécurité des chaînes arbitraires dans une page. Cet article présente l'API et explique son utilisation.

// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())

Échapper l'entrée utilisateur

Lorsque vous insérez dans le DOM des entrées utilisateur, des chaînes de requête, des contenus de cookies, etc., ces 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 sans échappement 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 ci-dessus ou si vous la développez à l'aide de .textContent, alert(0) ne sera pas exécuté. Toutefois, comme <em> ajouté par l'utilisateur est également développé sous forme de chaîne en l'état, cette méthode ne peut pas être utilisée pour conserver la mise en forme du texte en HTML.

La meilleure chose à faire ici n'est pas d'échapper, mais de nettoyer.

Nettoyer les entrées utilisateur

La différence entre échapper et nettoyer

L'échappement consiste à remplacer les caractères HTML spéciaux par des entités HTML.

Le nettoyage consiste à supprimer les parties sémantiquement dangereuses (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 a été supprimé, il serait possible de le développer 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 un nettoyage correct, il est nécessaire d'analyser la chaîne d'entrée au format HTML, d'omettre les balises et les attributs considérés comme dangereux, et de garder ceux qui sont inoffensifs.

La spécification d'API Sanitizer proposée vise à fournir un tel traitement comme une 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>

Cependant, { sanitizer: new Sanitizer() } est l'argument par défaut. comme ci-dessous.

$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>

Notez que setHTML() est défini sur Element. Comme il s'agit d'une méthode de Element, le contexte à analyser est explicite (<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 susceptibles de déclencher l'exécution du script. Cependant, 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 la manière dont le résultat de nettoyage 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 autorisera ou refusera les attributs spécifiés à l'aide des options suivantes:

  • allowAttributes
  • dropAttributes

Les propriétés allowAttributes et dropAttributes nécessitent 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 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 permet d'autoriser ou de refuser les éléments personnalisés. Si elles sont autorisées, les autres configurations des éléments et des attributs continuent de s'appliquer.

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 propose une fonctionnalité de nettoyage. La principale différence entre l'API Sanitizer et DOMPurify est que DOMPurify renvoie le résultat du nettoyage 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 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 causées par des cas où le résultat de la deuxième analyse est différent de la première.

Le code HTML a également besoin de contexte pour être analysé. Par exemple, <td> a du sens dans <table>, mais pas dans <div>. Comme DOMPurify.sanitize() n'accepte 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 pour 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 vidéo explicative Fin
2. Créer un brouillon de spécification Fin
3. Recueillir les commentaires et améliorer la conception Fin
4. Phase d'évaluation de Chrome Fin
5. Lancer Intention de livraison sur M105

Mozilla: considère cette proposition comme utile de procéder à un prototypage et la met en œuvre activement.

WebKit: consultez la réponse dans la liste de diffusion WebKit.

Activer l'API Sanitizer

Navigateurs pris en charge

  • Chrome: non compatible. <ph type="x-smartling-placeholder">
  • Edge: non compatible. <ph type="x-smartling-placeholder">
  • Firefox: derrière un drapeau.
  • Safari: non compatible. <ph type="x-smartling-placeholder">

Source

Activation via about://flags ou l'option 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 de la version en 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'option dom.security.sanitizer.enabled sur true dans about:config.

Détection de caractéristiques

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. Faites-nous part de vos commentaires sur les problèmes GitHub liés à l'API Sanitizer, et discutez-en avec les auteurs des spécifications et les personnes intéressées par cette API.

Si vous rencontrez des bugs ou des comportements inattendus dans l'implémentation de Chrome, signalez un bug. Sélectionnez les composants Blink>SecurityFeature>SanitizerAPI et partagez les détails pour aider les responsables de la mise en œuvre à suivre le problème.

Démo

Pour voir l'API Sanitizer en action, consultez le Sanitizer API Playground de Mike West:

Références


Photo de Towfiqu barbhuiya sur Unsplash.