Desinfectar la entrada del usuario antes de agregarla al DOM en Javascript


Estoy escribiendo el JS para una aplicación de chat en la que estoy trabajando en mi tiempo libre, y necesito tener identificadores HTML que cambien de acuerdo con los datos enviados por el usuario. Por lo general, esto es algo conceptualmente lo suficientemente inestable como para que ni siquiera lo intente, pero no me veo teniendo muchas opciones esta vez. Lo que necesito hacer entonces es escapar del ID HTML para asegurarme de que no permita XSS o romper HTML.

Aquí está el código:

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>';

¿Cuál sería la mejor manera de escapar id para evitar cualquier tipo de problema mencionado anteriormente? Como puedes ver, ahora mismo estoy usando la función incorporada escape(), pero no estoy seguro de lo bueno que se supone que debe ser comparado con otras alternativas. Estoy acostumbrado a desinfectar la entrada antes de que vaya a un nodo de texto, no a un id en sí.

Author: sth, 2010-05-08

6 answers

Nunca use escape(). No tiene nada que ver con la codificación HTML. Es más como codificación de URL, pero ni siquiera es correctamente eso. Es una extraña codificación no estándar disponible solo en JavaScript.

Si quieres un codificador HTML, tendrás que escribirlo tú mismo ya que JavaScript no te da uno. Por ejemplo:

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

Sin embargo, mientras que esto es suficiente para poner su user_id en lugares como el input value, no es suficiente para id porque los IDs solo pueden usar una selección limitada de caracter. (Y % no está entre ellos, así que escape() o incluso encodeURIComponent() no es bueno.)

Usted podría inventar su propio esquema de codificación para poner cualquier carácter en un ID, por ejemplo:

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

Pero todavía tienes un problema si el mismo user_id ocurre dos veces. Y para ser honesto, todo el asunto con lanzar cadenas HTML suele ser una mala idea. Utilice métodos DOM en su lugar, y conserve las referencias de JavaScript a cada elemento, por lo que no tiene que seguir llamando a getElementById, o preocuparse por cómo las cadenas arbitrarias se insertan en los IDs.

Eg.:

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;
}

También podría usar una función de conveniencia o un framework JS para reducir la longitud de las llamadas create-set-appends allí.

ETA:

Estoy usando jQuery en este momento como un framework

OK, entonces considere los accesos directos de creación de jQuery 1.4, por ejemplo.:

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

El problema que tengo ahora mismo es que uso JSONP para agregar elementos y eventos a una página, no se puede saber si los elementos ya existen o no antes de mostrar un mensaje.

Puede mantener una búsqueda de user_id a nodos de elementos (u objetos wrapper) en JavaScript, para guardar poniendo esa información en el propio DOM, donde los caracteres que pueden ir en un id están restringidos.

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);
}

(El prefijo _map_ se debe a que los objetos JavaScript no funcionan bastante como una asignación de cadenas arbitrarias. La cadena vacía y, en IE, algunos nombres de miembros Object, confunden se.)

 34
Author: bobince,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-08 16:32:52

Otro enfoque que me gusta es usar las capacidades nativas del DOM: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript

 15
Author: codecraig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-07 11:28:21

Podría usar una expresión regular simple para afirmar que el id solo contiene caracteres permitidos, como así:

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

Mi ejemplo solo permite caracteres alfanuméricos, y cadenas de longitud 1 a 16, debe cambiarlo para que coincida con el tipo de ID que utiliza.

Por cierto, en la línea 6, a la propiedad value le falta un par de comillas, un error fácil de cometer cuando cita en dos niveles.

No puedo ver su flujo de datos real, dependiendo del contexto, esta comprobación puede no ser en todos serán necesarios, o puede que no sea suficiente. Para hacer una revisión de seguridad adecuada necesitaríamos más información.

En general, sobre las funciones integradas de escape o desinfección, no confíes en ellas ciegamente. Tienes que saber exactamente lo que hacen, y tienes que establecer que eso es realmente lo que necesitas. Si no es lo que necesitas, el código es tuyo, la mayoría de las veces una simple expresión regular de lista blanca como la que te di funciona bien.

 8
Author: aaaaaaaaaaaa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-08 14:00:50

También puedes usar esto:

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

La documentación de OWASP sugiere mapear: https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting) _Prevention_Cheat_Sheet

 5
Author: SilentImp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-12 13:02:31

Dado que el texto que está escapando aparecerá en un atributo HTML, debe asegurarse de escapar no solo de las entidades HTML sino también de los atributos 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];
    });
}

Entonces, su código de escape se convierte en var user_id = escapeHTML(id, true).

Para obtener más información, consulte Escape de HTML infalible en Javascript.

 2
Author: Brandon Mintern,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-06 23:35:02

Debe tomar precauciones adicionales al usar datos suministrados por el usuario en atributos HTML. Porque los atributos tienen muchos más vectores de ataque que la salida dentro de etiquetas HTML.

La única manera de evitar ataques XSS es codificar todo excepto caracteres alfanuméricos. Escape todos los caracteres con valores ASCII inferiores a 256 con el formato & # xHH;. Lo que desafortunadamente puede causar problemas en su escenario, si está utilizando clases CSS y javascript para obtener esos elementos.

OWASP tiene un buena descripción de cómo mitigar el atributo 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
Author: kozmic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-22 19:38:47