Sobre el mecanismo React para evitar la posibilidad de inyección JSON para XSS y sobre cómo evitar vulnerabilidades comunes.
Puede pensar que está escribiendo JSX:
<marquee bgcolor="#ffa7c4">hi</marquee>
Pero en realidad estás llamando a la función:
React.createElement( 'marquee', { bgcolor: '#ffa7c4' }, 'hi' )
Y esta función le devuelve un objeto normal llamado elemento React. En consecuencia, después de atravesar todos los componentes, se obtiene un árbol de objetos similares:
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
Si utilizó React anteriormente, puede estar familiarizado con los campos tipo, accesorios, clave y referencia. Pero, ¿cuál es el tipo de propiedad $$typeof
? ¿Y por qué tiene el símbolo Symbol()
como su valor?
Antes de que las bibliotecas de IU se hicieran populares, para mostrar la entrada del cliente en el código de la aplicación, se generó una línea que contenía marcado HTML y se insertó directamente en el DOM, a través de innerHTML:
const messageEl = document.getElementById('message'); messageEl.innerHTML = '<p>' + message.text + '</p>';
Este mecanismo funciona bien a menos que message.text
esté configurado en <img src onerror="stealYourPassword()">
. Por consiguiente, concluimos que no necesita interpretar todas las entradas del cliente como marcado HTML.
Para protegerse contra tales ataques, puede usar API seguras como document.createTextNode()
o textContent
, que no interpretan el texto. Y como medida adicional, escapa de las cadenas reemplazando los caracteres potencialmente peligrosos como <
, >
por otros seguros.
Sin embargo, la probabilidad de error es alta, ya que es difícil rastrear todos los lugares donde utiliza la información registrada por el usuario en su página. Esta es la razón por la cual las bibliotecas modernas como React funcionan de manera segura con cualquier texto predeterminado:
<p> {message.text} </p>
Si message.text
es una cadena maliciosa con una <img>
, no se convertirá en una <img>
real. React escapa del contenido de texto y luego lo agrega al DOM. Por lo tanto, en lugar de ver la <img>
, simplemente ve su marcado como una cadena.
Para mostrar HTML arbitrario dentro de un elemento React, debe usar la siguiente construcción: dangerouslySetInnerHTML={{ __html: message.text }}
. El diseño es intencionalmente incómodo. Debido a su absurdo, se vuelve más notable y llama la atención al ver el código.
¿Esto significa que React es completamente seguro? No Existen muchos métodos de ataque conocidos basados en HTML y DOM. Los atributos de etiqueta merecen especial atención. Por ejemplo, si escribe <a href={user.website}>
, puede sustituir un código malicioso como un enlace de texto: 'javascript: stealYourPassword()'
.
En la mayoría de los casos, la presencia de vulnerabilidades en el lado del cliente es el resultado de problemas en el lado del servidor, y debe solucionarse en primer lugar.
Sin embargo, la visualización segura de contenido de texto personalizado es una primera línea de defensa razonable que refleja muchos ataques potenciales.
Basado en consideraciones previas, podemos concluir que el siguiente código debería ser completamente seguro:
Pero ese tampoco es el caso. Y aquí nos acercamos a explicar la presencia de $$typeof
en el elemento React.
Como explicamos anteriormente, los elementos React son objetos simples:
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
Por lo general, un elemento React se crea llamando a la función React.createElement()
, pero puede crearlo inmediatamente con un literal, como acabo de hacer anteriormente.
Supongamos que almacenamos en el servidor una cadena que el usuario nos envió previamente y cada vez que la mostramos en el lado del cliente. Pero alguien, en lugar de una cadena, nos envió JSON:
let expectedTextButGotJSON = { type: 'div', props: { dangerouslySetInnerHTML: { __html: '/* */' }, },
Es decir, de repente, en lugar de la cadena esperada, el valor de la variable expectedTextButGotJSON
resultó ser JSON. Que será procesado por React como un literal y, por lo tanto, ejecutará código malicioso.
React 0.13 es vulnerable a un ataque tipo XSS, pero a partir de la versión 0.14, cada elemento está marcado con un símbolo:
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
Dicha protección funciona porque los caracteres no son un valor JSON válido. Por lo tanto, incluso si el servidor tiene una vulnerabilidad potencial y devuelve JSON en lugar de texto, JSON no puede contener Symbol.for('response.element')
. React comprueba el elemento por element.$$typeof
y se niega a procesar el elemento si falta o no es válido.
La principal ventaja de Symbol.for()
es que los símbolos son globales entre contextos porque usan un registro global. Esto garantiza el mismo valor de retorno incluso en un iframe. E incluso si hay varias copias de React en la página, aún podrán "coincidir" a través de un solo valor de $$typeof
.
¿Qué pasa con los navegadores que no admiten caracteres?
Por desgracia, no podrán implementar la protección adicional que se discutió anteriormente, pero los elementos React seguirán conteniendo la propiedad $$typeof
para mantener la coherencia, pero será solo un número: 0xeac7
.
¿Por qué exactamente 0xeac7
? Porque parece reaccionar.