Désinfection de l'entrée des utilisateurs avant de l'ajouter au DOM en Javascript

J'écris le JS pour une application de discussion sur laquelle je travaille pendant mon temps libre et je dois avoir des identifiants HTML qui changent en fonction des données soumises par les utilisateurs. Il s'agit généralement d'un point de vue conceptuellement plus fragile que je ne l'essaierais même pas, mais je ne me vois pas avoir beaucoup de choix cette fois-ci. Ce que je dois faire ensuite, c'est échapper à l'identifiant HTML pour s'assurer qu'il ne permettra pas à XSS ou à la rupture du code HTML.

Voici le code:

var user_id = escape(id) var txt = '<div class="chut">'+ '<div class="log" id="chut_'+user_id+'"></div>'+ '<textarea id="chut_'+user_id+'_msg"></textarea>'+ '<label for="chut_'+user_id+'_to">To:</label>'+ '<input type="text" id="chut_'+user_id+'_to" value='+user_id+' readonly="readonly" />'+ '<input type="submit" id="chut_'+user_id+'_send" value="Message"/>'+ '</div>'; 

Quelle serait la meilleure façon d'échapper à l' id pour éviter tout type de problème mentionné ci-dessus? Comme vous pouvez le voir, en ce moment, j'utilise la fonction d' escape() intégrée escape() , mais je ne suis pas sûr de savoir si cela est censé être comparé à d'autres solutions. Je suis surtout habitué à désinfecter les entrées avant de passer dans un nœud de texte, pas un identifiant lui-même.

Ne jamais utiliser escape() . Cela n'a rien à voir avec l'encodage HTML. C'est plus comme un codage d'URL, mais ce n'est même pas le cas. C'est un codage bizarre non standard disponible uniquement en JavaScript.

Si vous voulez un codeur HTML, vous devrez l'écrire vous-même car JavaScript ne vous en donne pas. Par exemple:

 function encodeHTML(s) { return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;'); } 

Cependant, si cela suffit pour mettre votre user_id dans des endroits comme la input value , ce n'est pas suffisant pour id car les ID ne peuvent utiliser qu'une sélection limitée de caractères. (Et % n'est pas parmi eux, alors escape() ou même encodeURIComponent() n'est pas bon.)

Vous pourriez inventer votre propre programme de codage pour mettre n'importe quel caractère dans un identifiant, par exemple:

 function encodeID(s) { if (s==='') return '_'; return s.replace(/[^a-zA-Z0-9.-]/g, function(match) { return '_'+match[0].charCodeAt(0).toString(16)+'_'; }); } 

Mais vous avez encore un problème si le même user_id survient deux fois. Et pour être honnête, le tout avec lancer des chaînes HTML est généralement une mauvaise idée. Utilisez plutôt les méthodes DOM et conservez les références JavaScript à chaque élément, afin de ne pas continuer à appeler getElementById ou de vous soucier de la façon dont les chaînes arbitraires sont insérées dans les ID.

par exemple.:

 function addChut(user_id) { var log= document.createElement('div'); log.className= 'log'; var textarea= document.createElement('textarea'); var input= document.createElement('input'); input.value= user_id; input.readonly= True; var button= document.createElement('input'); button.type= 'button'; button.value= 'Message'; var chut= document.createElement('div'); chut.className= 'chut'; chut.appendChild(log); chut.appendChild(textarea); chut.appendChild(input); chut.appendChild(button); document.getElementById('chuts').appendChild(chut); button.onclick= function() { alert('Send '+textarea.value+' to '+user_id); }; return chut; } 

Vous pouvez également utiliser une fonction de commodité ou un cadre JS pour réduire la longévité de la création-set-ajoute les appels là-bas.

ETA:

J'utilise jQuery en ce moment comme cadre

OK, alors considérez les raccourcis de création jQuery 1.4, par exemple:

 var log= $('<div>', {className: 'log'}); var input= $('<input>', {readOnly: true, val: user_id}); ... 

Le problème que j'ai en ce moment est que j'utilise JSONP pour ajouter des éléments et des événements à une page, et je ne peux pas savoir si les éléments existent déjà ou pas avant d'afficher un message.

Vous pouvez garder une recherche de user_id sur des noeuds d'éléments (ou des objets wrapper) en JavaScript, pour sauvegarder la mise en place de cette information dans le DOM lui-même, où les caractères pouvant être utilisés dans un id sont restreints.

 var chut_lookup= {}; ... function getChut(user_id) { var key= '_map_'+user_id; if (key in chut_lookup) return chut_lookup[key]; return chut_lookup[key]= addChut(user_id); } 

(Le préfixe _map_ est parce que les objets JavaScript ne fonctionnent pas comme un mappage de chaînes arbitraires. La chaîne vide et, dans IE, certains noms d' Object , confondent.)

Une autre approche que j'aime est d'utiliser les capacités DOM natives: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript

Vous pouvez utiliser une expression régulière simple pour affirmer que l'identifiant ne contient que des caractères autorisés, de la manière suivante:

 if(id.match(/^[0-9a-zA-Z]{1,16}$/)){ //The id is fine } else{ //The id is illegal } 

Mon exemple ne permet que des caractères alphanumériques, et des chaînes de longueur 1 à 16, vous devriez le changer pour correspondre au type d'identifiants que vous utilisez.

Soit dit en passant, à la ligne 6, la propriété de valeur manque une paire de guillemets, une erreur facile à faire lorsque vous citez sur deux niveaux.

Je ne peux pas voir votre flux de données réel, selon le contexte, cette vérification peut ne pas être nécessaire, ou peut-être pas suffisant. Afin de faire une évaluation de sécurité appropriée, nous aurions besoin de plus d'informations.

En général, à propos des fonctions d'échappement ou de désinfection intégrées, ne les croyez pas aveuglément. Vous devez savoir exactement ce qu'ils font, et vous devez établir que c'est en fait ce dont vous avez besoin. Si ce n'est pas ce dont vous avez besoin, le code vous-même, la plupart du temps, un simple regex de liste blanche comme celui que je vous ai donné fonctionne très bien.

Vous devez prendre des précautions supplémentaires lorsque vous utilisez des données fournies par l'utilisateur dans des attributs HTML. Parce que les attributs ont beaucoup plus de vecteurs d'attaque que la sortie dans les balises HTML.

La seule façon d'éviter les attaques XSS est de coder tout sauf les caractères alphanumériques. Échappez tous les caractères avec des valeurs ASCII inférieures à 256 avec le & # xHH; format. Ce qui, malheureusement, peut causer des problèmes dans votre scénario, si vous utilisez des classes CSS et javascript pour récupérer ces éléments.

OWASP a une bonne description de la façon d'atténuer l'attribut HTML XSS:

http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.233_-_JavaScript_Escape_Before_Inserting_Untrusted_Data_into_HTML_JavaScript_Data_Values

Étant donné que le texte que vous éloignez apparaîtra dans un attribut HTML, vous devez être sûr d'échapper non seulement aux entités HTML mais aussi aux attributs HTML:

 var ESC_MAP = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }; function escapeHTML(s, forAttribute) { return s.replace(forAttribute ? /[&<>'"]/g : /[&<>]/g, function(c) { return ESC_MAP[c]; }); } 

Ensuite, votre code d'échappement devient var user_id = escapeHTML(id, true) .

Pour plus d'informations, voir Foolproof HTML échappement en Javascript .