web-dev-qa-db-fra.com

Assainir l'entrée utilisateur avant de l'ajouter au DOM en Javascript

J'écris le JS pour une application de chat sur laquelle je travaille pendant mon temps libre, et j'ai besoin d'avoir des identifiants HTML qui changent en fonction des données soumises par l'utilisateur. C'est généralement quelque chose d'assez fragile sur le plan conceptuel pour que je ne le tente même pas, mais je ne me vois pas avoir beaucoup de choix cette fois. Ce que je dois faire ensuite est d'échapper à l'ID HTML pour m'assurer qu'il ne permettra pas le XSS ou la rupture du 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 à id pour éviter tout type de problème mentionné ci-dessus? Comme vous pouvez le voir, en ce moment j'utilise la fonction intégrée escape(), mais je ne sais pas à quel point cela est censé être comparé à d'autres alternatives. Je suis surtout habitué à assainir les entrées avant qu'elles ne se retrouvent dans un nœud de texte, pas un identifiant lui-même.

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

Si vous voulez un encodeur 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, même si cela suffit pour placer votre user_id Dans des endroits comme le 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'en fait pas partie, donc escape() ou même encodeURIComponent() n'est pas bon.)

Vous pouvez inventer votre propre schéma de codage pour mettre des caractères dans un ID, 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 toujours un problème si le même user_id Se produit deux fois. Et pour être honnête, le fait de lancer des chaînes HTML est généralement une mauvaise idée. Utilisez plutôt des méthodes DOM et conservez les références JavaScript à chaque élément, vous n'avez donc pas à continuer d'appeler getElementById, ni à 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 framework JS pour réduire la longueur des appels create-set-appends.

ETA:

J'utilise jQuery en ce moment comme cadre

OK, alors considérez les raccourcis de création de 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 donc je ne peux pas savoir si les éléments existent déjà ou non avant d'afficher un message.

Vous pouvez conserver une recherche de user_id Sur les nœuds d'élément (ou les objets wrapper) en JavaScript, pour éviter de placer ces informations dans le DOM lui-même, où les caractères pouvant aller 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 dû au fait que les objets JavaScript ne tout à fait fonctionnent pas comme un mappage de chaînes arbitraires. La chaîne vide et, dans IE, certains Object noms de membres, confondre.)

40
bobince

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

17
codecraig

Vous pouvez utiliser ceci:

function sanitize(string) {
  const map = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#x27;',
      "/": '&#x2F;',
  };
  const reg = /[&<>"'/]/ig;
  return string.replace(reg, (match)=>(map[match]));
}

Voir également OWASP XSS Prevention Cheat Sheet .

9
SilentImp

Vous pouvez utiliser une expression régulière simple pour affirmer que l'ID ne contient que des caractères autorisés, comme ceci:

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

Mon exemple n'autorise que les caractères alphanumériques et les chaînes de longueur 1 à 16, vous devez le modifier pour qu'il corresponde au type d'ID que vous utilisez.

Soit dit en passant, à la ligne 6, il manque une paire de guillemets à la propriété value, 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 du tout nécessaire, ou peut-être pas suffisante. Afin d'effectuer un examen de sécurité approprié, nous aurions besoin de plus d'informations.

En général, à propos des fonctions d'échappement ou d'assainissement intégrées, ne leur faites pas confiance aveuglément. Vous devez savoir exactement ce qu'ils font et vous devez établir que c'est réellement ce dont vous avez besoin. Si ce n'est pas ce dont vous avez besoin, le code est le vôtre, la plupart du temps une simple regex de liste blanche comme celle que je vous ai donnée fonctionne très bien.

9
aaaaaaaaaaaa

Étant donné que le texte que vous échappez apparaîtra dans un attribut HTML, vous devez être sûr d'échapper non seulement aux entités HTML mais également 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, consultez HTML à toute épreuve s'échappant en Javascript .

2
Brandon Mintern

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 de sortie dans les balises HTML.

La seule façon d'éviter les attaques XSS est de tout coder sauf les caractères alphanumériques. Échappez tous les caractères avec ASCII valeurs inférieures à 256 avec le format & # xHH;. Ce qui peut malheureusement 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

1
kozmic