web-dev-qa-db-fra.com

Désinfecter/réécrire le code HTML côté client

Je dois afficher les ressources externes chargées via des requêtes entre domaines et m'assurer d'afficher uniquement le contenu "safe". 

Peut utiliser le prototype String # stripScripts pour supprimer des blocs de script. Mais les gestionnaires tels que onclick ou onerror sont toujours là.

Y at-il une bibliothèque qui peut au moins

  • dépouiller les blocs de script,
  • tuer les gestionnaires DOM,
  • supprimer les étiquettes noires (par exemple: embed ou object).

Existe-t-il des liens et des exemples liés à JavaScript?

67
aemkei

Mise à jour 2016: Il existe maintenant un paquet Google Closure basé sur le désinfectant Caja.

Il possède une API plus propre, a été réécrit pour prendre en compte les API disponibles sur les navigateurs modernes et interagit mieux avec Closure Compiler.


Prise sans vergogne: voir caja/plugin/html-sanitizer.js pour un désinfectant html côté client qui a été examiné en détail.

C'est une liste blanche, pas une liste noire, mais les listes blanches sont configurables comme indiqué dans CajaWhitelists


Si vous souhaitez supprimer toutes les balises, procédez comme suit:

var tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';

var tagOrComment = new RegExp(
    '<(?:'
    // Comment body.
    + '!--(?:(?:-*[^->])*--+|-?)'
    // Special "raw text" elements whose content should be elided.
    + '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
    + '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
    // Regular name
    + '|/?[a-z]'
    + tagBody
    + ')>',
    'gi');
function removeTags(html) {
  var oldHtml;
  do {
    oldHtml = html;
    html = html.replace(tagOrComment, '');
  } while (html !== oldHtml);
  return html.replace(/</g, '&lt;');
}

Les gens vous diront que vous pouvez créer un élément et attribuer innerHTML, puis obtenir le innerText ou textContent, puis échapper des entités. Ne faites pas cela. Il est vulnérable à l'injection XSS car <img src=bogus onerror=alert(1337)> exécutera le gestionnaire onerror même si le nœud n'est jamais attaché au DOM.

98
Mike Samuel

Le Google Caja assainisseur HTML peut être préparé "pour le Web" en l'intégrant dans un outil Web . Toutes les variables globales introduites par l’assainisseur seront contenues dans l’agent de travail et le traitement aura lieu dans son propre thread.

Pour les navigateurs qui ne prennent pas en charge Web Workers, nous pouvons utiliser un iframe en tant qu’environnement distinct dans lequel le désinfectant peut fonctionner. Timothy Chien utilise un polyfill cette partie est faite pour nous.

Le projet Caja a une page wiki sur comment utiliser Caja en tant que désinfectant autonome côté client :

  • Extraire la source, puis compiler en exécutant ant
  • Incluez html-sanitizer-minified.js ou html-css-sanitizer-minified.js dans votre page
  • Appelez html_sanitize(...)

Le script de travail doit seulement suivre ces instructions:

importScripts('html-css-sanitizer-minified.js'); // or 'html-sanitizer-minified.js'

var urlTransformer, nameIdClassTransformer;

// customize if you need to filter URLs and/or ids/names/classes
urlTransformer = nameIdClassTransformer = function(s) { return s; };

// when we receive some HTML
self.onmessage = function(event) {
    // sanitize, then send the result back
    postMessage(html_sanitize(event.data, urlTransformer, nameIdClassTransformer));
};

(Un peu plus de code est nécessaire pour que la bibliothèque simworker fonctionne, mais ce n'est pas important pour cette discussion.)

Démo: https://dl.dropbox.com/u/291406/html-sanitize/demo.html

38
Jeffery To

Ne faites jamais confiance au client. Si vous écrivez une application serveur, supposez que le client soumettra toujours des données non hygiéniques et malveillantes. C'est une règle de base qui vous évitera des ennuis. Si vous le pouvez, je vous conseillerais de procéder à toutes les validations et à toutes les opérations d’assainissement dans le code serveur, ce que vous savez (dans une mesure raisonnable) ne sera pas manipulé. Vous pourriez peut-être utiliser une application Web côté serveur comme proxy pour votre code côté client, qui va chercher de la tierce partie et effectue l'assainissement avant de l'envoyer au client lui-même?

[modifier] Je suis désolé, j'ai mal compris la question. Cependant, je maintiens mes conseils. Vos utilisateurs seront probablement plus en sécurité si vous désinfectez le serveur avant de le leur envoyer.

14
Sean Edwards

Vous ne pouvez pas anticiper tous les types étranges de balisages malformés qu'un navigateur puisse trébucher quelque part pour échapper à la liste noire, alors évitez toute liste noire. Il y a beaucoup plus de structures que vous pourriez avoir besoin de supprimer que le script/embed/object et les gestionnaires.

Au lieu de cela, essayez d'analyser le code HTML en éléments et attributs dans une hiérarchie, puis exécutez tous les noms d'éléments et d'attributs sur une liste blanche aussi minime que possible. Vérifiez également les attributs d'URL que vous laissez passer dans une liste blanche (rappelez-vous, il existe des protocoles plus dangereux que le javascript :).

Si le format d’entrée est bien XHTML, la première partie de ce qui précède est beaucoup plus facile.

Comme toujours avec la désinfection HTML, si vous pouvez trouver un autre moyen d'éviter de le faire, faites-le plutôt. Il y a beaucoup, beaucoup de trous potentiels. Si les principaux services de messagerie Web trouvent encore des exploits après tant d'années, qu'est-ce qui vous fait penser que vous pouvez faire mieux?

12
bobince

Maintenant que tous les principaux navigateurs prennent en charge les iframes en mode bac à sable, il existe un moyen beaucoup plus simple de sécuriser I think. J'adorerais que cette réponse puisse être examinée par des personnes plus familières avec ce type de problème de sécurité.

NOTE: Cette méthode ne fonctionnera certainement pas dans IE 9 et versions antérieures. Voir ce tableau pour les versions de navigateur prenant en charge le sandboxing.

L'idée est de créer une iframe cachée avec JavaScript désactivé, d'y coller votre code HTML non approuvé et de le laisser l'analyser. Ensuite, vous pouvez parcourir l’arborescence DOM et copier les balises et attributs considérés comme sûrs.

Les listes blanches montrées ici ne sont que des exemples. La meilleure solution pour une liste blanche dépend de l’application. Si vous avez besoin d'une stratégie plus sophistiquée que les listes blanches d'étiquettes et d'attributs, cette méthode peut être gérée par cette méthode, mais pas par cet exemple de code.

var tagWhitelist_ = {
  'A': true,
  'B': true,
  'BODY': true,
  'BR': true,
  'DIV': true,
  'EM': true,
  'HR': true,
  'I': true,
  'IMG': true,
  'P': true,
  'SPAN': true,
  'STRONG': true
};

var attributeWhitelist_ = {
  'href': true,
  'src': true
};

function sanitizeHtml(input) {
  var iframe = document.createElement('iframe');
  if (iframe['sandbox'] === undefined) {
    alert('Your browser does not support sandboxed iframes. Please upgrade to a modern browser.');
    return '';
  }
  iframe['sandbox'] = 'allow-same-Origin';
  iframe.style.display = 'none';
  document.body.appendChild(iframe); // necessary so the iframe contains a document
  iframe.contentDocument.body.innerHTML = input;

  function makeSanitizedCopy(node) {
    if (node.nodeType == Node.TEXT_NODE) {
      var newNode = node.cloneNode(true);
    } else if (node.nodeType == Node.ELEMENT_NODE && tagWhitelist_[node.tagName]) {
      newNode = iframe.contentDocument.createElement(node.tagName);
      for (var i = 0; i < node.attributes.length; i++) {
        var attr = node.attributes[i];
        if (attributeWhitelist_[attr.name]) {
          newNode.setAttribute(attr.name, attr.value);
        }
      }
      for (i = 0; i < node.childNodes.length; i++) {
        var subCopy = makeSanitizedCopy(node.childNodes[i]);
        newNode.appendChild(subCopy, false);
      }
    } else {
      newNode = document.createDocumentFragment();
    }
    return newNode;
  };

  var resultElement = makeSanitizedCopy(iframe.contentDocument.body);
  document.body.removeChild(iframe);
  return resultElement.innerHTML;
};

Vous pouvez l'essayer ici .

Notez que je refuse les attributs de style et les balises dans cet exemple. Si vous les autorisez, vous voudrez probablement analyser le CSS et vous assurer qu'il est sûr pour vos besoins.

J'ai testé cela sur plusieurs navigateurs modernes (Chrome 40, Firefox 36 Beta, IE 11, Chrome pour Android) et sur un ancien (IE 8) afin de m'assurer qu'il était sauvegardé avant d'exécuter des scripts. Je serais intéressé de savoir s'il y a des navigateurs qui ont des problèmes avec cela, ou des cas Edge que je néglige.

9
aldel

Donc, nous sommes en 2016 et je pense que beaucoup d’entre nous utilisons maintenant des modules npm dans notre code. sanitize-html semble être l’option principale sur npm, bien qu’il existe autres .

D’autres réponses à cette question apportent une contribution précieuse à la manière d’adopter votre propre stratégie, mais c’est un problème assez complexe, pour lequel des solutions communautaires bien testées constituent probablement la meilleure solution.

Exécutez ceci sur la ligne de commande pour installer: npm install --save sanitize-html

ES5: var sanitizeHtml = require('sanitize-html'); // ... var sanitized = sanitizeHtml(htmlInput);

ES6: import sanitizeHtml from 'sanitize-html'; // ... let sanitized = sanitizeHtml(htmlInput);

7
ericsoco
String.prototype.sanitizeHTML=function (white,black) {
   if (!white) white="b|i|p|br";//allowed tags
   if (!black) black="script|object|embed";//complete remove tags
   var e=new RegExp("(<("+black+")[^>]*>.*</\\2>|(?!<[/]?("+white+")(\\s[^<]*>|[/]>|>))<[^<>]*>|(?!<[^<>\\s]+)\\s[^</>]+(?=[/>]))", "gi");
   return this.replace(e,"");
}

-black liste -> complète supprimer balise et contenu

-blanc liste -> conserver les balises 

-les autres tags sont supprimés mais le contenu des tags est conservé

-tous les attributs des balises de liste blanche (les autres) sont supprimés

5
neu-rah

La bibliothèque Google Caja suggérée ci-dessus était bien trop complexe à configurer et à inclure dans mon projet pour une application Web (donc s'exécutant sur le navigateur). Étant donné que nous utilisons déjà le composant CKEditor, j’ai eu recours à sa fonction de nettoyage HTML et d’ajout de listes blanches, qui est beaucoup plus facile à configurer. Ainsi, vous pouvez charger une instance de CKEditor dans un iframe caché et faire quelque chose comme:

CKEDITOR.instances['myCKEInstance'].dataProcessor.toHtml(myHTMLstring)

Cela dit, d'accord, si vous n'utilisez pas CKEditor dans votre projet, cela risque d'être un peu exagéré, car le composant lui-même fait environ un demi-mégaoctet (réduit), mais si vous avez les sources, vous pouvez peut-être isoler le code la liste blanche (CKEDITOR.htmlParser?) et la rendre beaucoup plus courte.

http://docs.ckeditor.com/#!/api

http://docs.ckeditor.com/#!/api/CKEDITOR.htmlDataProcessor

1
AsGoodAsItGets

[Avertissement: je suis l'un des auteurs]

Nous avons écrit une bibliothèque open source "uniquement sur le Web" (c'est-à-dire "nécessite un navigateur"), https://github.com/jitbit/HtmlSanitizer qui supprime tous les tags/attributes/styles à l'exception des "listes blanches".

Usage:

var input = HtmlSanitizer.SanitizeHtml("<script> Alert('xss!'); </scr"+"ipt>");

P.S. Fonctionne beaucoup plus rapidement qu'une solution "pure JavaScript", car il utilise le navigateur pour analyser et manipuler DOM. Si vous êtes intéressé par une solution "JS pure", veuillez essayer https://github.com/punkave/sanitize-html (non affilié)

1
Alex

Je recommande de couper les cadres de votre vie, cela vous faciliterait excessivement les choses à long terme.

cloneNode: le clonage d'un nœud copie tous ses attributs et leurs valeurs mais ne PAS copie les écouteurs d'événement.

https://developer.mozilla.org/en/DOM/Node.cloneNode

Ce qui suit n’a pas été testé, bien que j’utilise depuis longtemps des trotteurs d’arbres et qu’ils constituent l’une des parties de JavaScript les plus sous-évaluées. Voici une liste des types de nœuds que vous pouvez explorer. J'utilise généralement SHOW_ELEMENT ou SHOW_TEXT.

http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal.html#Traversal-NodeFilter

function xhtml_cleaner(id)
{
 var e = document.getElementById(id);
 var f = document.createDocumentFragment();
 f.appendChild(e.cloneNode(true));

 var walker = document.createTreeWalker(f,NodeFilter.SHOW_ELEMENT,null,false);

 while (walker.nextNode())
 {
  var c = walker.currentNode;
  if (c.hasAttribute('contentEditable')) {c.removeAttribute('contentEditable');}
  if (c.hasAttribute('style')) {c.removeAttribute('style');}

  if (c.nodeName.toLowerCase()=='script') {element_del(c);}
 }

 alert(new XMLSerializer().serializeToString(f));
 return f;
}


function element_del(element_id)
{
 if (document.getElementById(element_id))
 {
  document.getElementById(element_id).parentNode.removeChild(document.getElementById(element_id));
 }
 else if (element_id)
 {
  element_id.parentNode.removeChild(element_id);
 }
 else
 {
  alert('Error: the object or element \'' + element_id + '\' was not found and therefore could not be deleted.');
 }
}
0
John