¿Las mejores prácticas para incrustar JSON arbitrario en el DOM?

110

Estoy pensando en incrustar JSON arbitrario en el DOM de esta manera:

<script type="application/json" id="stuff">
    {
        "unicorns": "awesome",
        "abc": [1, 2, 3]
    }
</script>

Esto es similar a la forma en que se puede almacenar una plantilla HTML arbitraria en el DOM para su uso posterior con un motor de plantillas JavaScript. En este caso, más tarde podríamos recuperar el JSON y analizarlo con:

var stuff = JSON.parse(document.getElementById('stuff').innerHTML);

Esto funciona , pero ¿es la mejor manera? ¿Esto viola alguna de las mejores prácticas o estándar?

Nota: No estoy buscando alternativas para almacenar JSON en el DOM, ya he decidido que es la mejor solución para el problema particular que tengo. Solo estoy buscando la mejor manera de hacerlo.

Ben Lee
fuente
1
¿Por qué no lo tendrá como varen javascript?
Krizz
@Krizz, debe ser parte de un documento estático que luego es procesado por una compleja cadena de javascript encapsulado. Almacenarlo en el DOM es lo que quiero hacer.
Ben Lee
@Krizz Me plantearon un problema similar. Quería poner datos en un sitio diferente para cada usuario sin hacer una solicitud AJAX. Así que inserté algo de PHP en un contenedor y hice algo similar a lo que tienes arriba para obtener los datos en javascript.
Patrick Lorio
2
Creo que tu método original es el mejor en realidad. Es 100% válido en HTML5, es expresivo, no crea elementos "falsos" que simplemente eliminarás u ocultarás con CSS; y no requiere ninguna codificación de caracteres. ¿Cuál es la desventaja?
Jamie Treworgy
22
Si tiene una cadena con el valor </script><script>alert()</script><script>dentro de su objeto JSON, obtendrá sorpresas. Esto no es seguro a menos que primero desinfecte los datos.
silviot

Respuestas:

77

Creo que tu método original es el mejor. La especificación HTML5 incluso aborda este uso:

"Cuando se utiliza para incluir bloques de datos (a diferencia de scripts), los datos deben estar incrustados en línea, el formato de los datos debe proporcionarse mediante el atributo type, el atributo src no debe especificarse y el contenido del elemento script debe cumplir con los requisitos definidos para el formato utilizado ".

Lea aquí: http://dev.w3.org/html5/spec/Overview.html#the-script-element

Has hecho exactamente eso. que es no amar? Sin codificación de caracteres según sea necesario con datos de atributos. Puede formatearlo si lo desea. Es expresivo y el uso previsto es claro. No se siente como un truco (por ejemplo, como usar CSS para ocultar su elemento "portador"). Es perfectamente válido.

Jamie Treworgy
fuente
3
Gracias. La cita de la especificación me ha convencido.
Ben Lee
17
Es perfectamente válido solo si verifica y desinfecta el objeto JSON primero: no puede simplemente incrustar datos de origen del usuario. Vea mi comentario sobre la pregunta.
silviot
1
extra preguntando: ¿cuál es el buen lugar para ponerlo? cabeza o cuerpo, arriba o abajo?
desafío
1
Desafortunadamente, parece que la política de CSP puede detener / detendrá todosscript etiquetas.
Larry K
2
¿Cómo se protege eficazmente contra la incrustación de JSON que contiene </script> y, por lo tanto, permite la inyección de HTML? ¿Hay algo sólido / fácil o es mejor usar atributos de datos?
jonasfj
23

Como dirección general, intentaría usar atributos de datos HTML5 en su lugar. No hay nada que le impida poner un JSON válido. p.ej:

<div id="mydiv" data-unicorns='{"unicorns":"awesome", "abc":[1,2,3]}' class="hidden"></div>

Si está utilizando jQuery, recuperarlo es tan fácil como:

var stuff = JSON.parse($('#mydiv').attr('data-unicorns'));
Horacio Alderaan
fuente
1
Tiene sentido. Aunque tenga en cuenta que con comillas simples para el nombre de la clave, JSON.parseno funcionará (al menos el JSON.parse nativo de Google Chrome no lo hará). La especificación JSON requiere comillas dobles. Pero eso es bastante fácil de arreglar usando entidades como ...&lt;unicorns&gt;:....
Ben Lee
4
Sin embargo, una pregunta: ¿Existe algún límite para la longitud de los atributos en HTML 5?
Ben Lee
Sí, eso funcionaría. También puede cambiarlo para que su HTML use comillas simples y los datos JSON usen doble.
Horatio Alderaan
1
Ok, encontré la respuesta a mi pregunta: stackoverflow.com/questions/1496096/… - esto es suficiente para mis propósitos.
Ben Lee
2
Esto no funcionaría para una sola cadena, por ejemplo, "I am valid JSON"usando comillas dobles para la etiqueta, o comillas simples con comillas simples en la cadena, por ejemplo, data-unicorns='"My JSON's string"'ya que las comillas simples no se escapan con la codificación como JSON.
Robbie Averill
13

Este método de incrustar json en una etiqueta de script tiene un problema de seguridad potencial. Suponiendo que los datos json se originaron a partir de la entrada del usuario, es posible crear un miembro de datos que, de hecho, salga de la etiqueta del script y permita la inyección directa en el dom. Mira aquí:

http://jsfiddle.net/YmhZv/1/

Aquí está la inyección

<script type="application/json" id="stuff">
{
    "unicorns": "awesome",
    "abc": [1, 2, 3],
    "badentry": "blah </script><div id='baddiv'>I should not exist.</div><script type="application/json" id='stuff'> ",
}
</script>

Simplemente no hay forma de escapar / codificar.

MadCoder
fuente
7
Esto es cierto, pero en realidad no es una falla de seguridad del método. Si alguna vez coloca algo que se originó en la entrada del usuario en sus páginas, debe ser diligente para evitarlo. Este método sigue siendo válido siempre que tome las medidas de precaución normales con respecto a la entrada del usuario.
Ben Lee
JSON no es parte de HTML, el analizador de HTML sigue funcionando. Es lo mismo que cuando JSON sería parte de un párrafo de texto o elemento div. HTML: escape del contenido de su programa. Además, también puede escapar de las barras. Si bien JSON no requiere esto, tolera barras diagonales innecesarias. Que se puede utilizar con el fin de que sea seguro para incrustar. El json_encode de PHP hace esto de forma predeterminada.
Timo Tijhof
7

Consulte la Regla n. ° 3.1 en la hoja de trucos de prevención XSS de OWASP.

Supongamos que desea incluir este JSON en HTML:

{
    "html": "<script>alert(\"XSS!\");</script>"
}

Crea un oculto <div>en HTML. A continuación, escapa de tu JSON codificando entidades no seguras (por ejemplo, &, <,>, ", 'y, /) y colócalo dentro del elemento.

<div id="init_data" style="display:none">
        {&#34;html&#34;:&#34;&lt;script&gt;alert(\&#34;XSS!\&#34;);&lt;/script&gt;&#34;}
</div>

Ahora puede acceder a él leyendo el textContentdel elemento usando JavaScript y analizándolo:

var text = document.querySelector('#init_data').textContent;
var json = JSON.parse(text);
console.log(json); // {html: "<script>alert("XSS!");</script>"}
Mateo
fuente
Creo que esta es la mejor y más segura respuesta. Tenga en cuenta que muchos caracteres JSON comunes se escapan y que ciertos caracteres se escapan dos veces, como las comillas internas del objeto {name: 'Dwayne "The Rock" Johnson'}. Pero probablemente sea mejor usar este enfoque, ya que su marco / biblioteca de plantillas probablemente ya incluye una forma segura de codificar HTML. Una alternativa sería usar base64, que es HTML seguro y seguro para colocar dentro de una cadena JS. Es fácil de codificar / decodificar en JS usando btoa () / atob () y probablemente sea fácil para usted hacerlo en el lado del servidor.
sstur
Un método aún más seguro sería utilizar el <data>elemento semánticamente correcto e incluir los datos JSON en el valueatributo. Entonces solo necesita escapar de las comillas &quotsi usa comillas dobles para encerrar los datos, o &#39;si usa comillas simples (que probablemente sea mejor).
Rúnar Berg
5

Sugeriría poner JSON en un script en línea con una función de devolución de llamada (tipo de JSONP ):

<script>
someCallback({
    "unicorns": "awesome",
    "abc": [1, 2, 3]
});
</script>

Si el script de ejecución se carga después del documento, puede almacenarlo en algún lugar, posiblemente con un argumento identificador adicional: someCallback("stuff", { ... });

Copiar
fuente
@BenLee debería funcionar muy bien, con la única desventaja de tener que definir la función de devolución de llamada. La otra solución sugerida se divide en caracteres HTML especiales (por ejemplo &) y comillas, si los tiene en su JSON.
copiar
Esto se siente mejor porque no necesita una consulta dom para encontrar los datos
Jaseem
@copy Esta solución aún necesita escapar (solo un tipo diferente), vea la respuesta de MadCoder. Dejándolo aquí para completarlo.
pvgoran
2

Mi recomendación sería mantener los datos JSON en .jsonarchivos externos y luego recuperar esos archivos a través de Ajax. No colocas código CSS y JavaScript en la página web (en línea), entonces, ¿por qué lo harías con JSON?

Šime Vidas
fuente
12
No coloca CSS y Javascript en línea en una página web porque generalmente se comparte entre otras páginas. Si el servidor genera los datos en cuestión explícitamente para este contexto, incrustarlos es mucho más eficiente que iniciar otra solicitud de algo que no se puede almacenar en caché.
Jamie Treworgy
Es porque estoy actualizando un sistema heredado que se diseñó mal y, en lugar de rediseñar todo el sistema, solo necesito arreglar una pieza. Almacenar JSON en DOM es la mejor manera de arreglar esta parte. Además, estoy de acuerdo con lo que dijo @jamietre.
Ben Lee
@jamietre Tenga en cuenta que el OP indicó que esta cadena JSON solo se necesita más adelante . La pregunta es si es necesario siempre o solo en algunos casos. Si solo es necesario en algunos casos, entonces tiene sentido tenerlo en un archivo externo y solo cargarlo condicionalmente.
Šime Vidas
2
Estoy de acuerdo en que hay muchos "qué pasaría si" que podrían inclinar la balanza de una forma u otra. Pero en términos generales, si sabe cuándo se procesa la página lo que va a necesitar, aunque sea solo posible, a menudo es mejor enviarlo de inmediato. Por ejemplo, si tuviera algunos cuadros de información que comienzan a colapsar, generalmente me gustaría incluir su contenido en línea para que se expandan instantáneamente. La sobrecarga de una nueva solicitud es mucho en comparación con la sobrecarga de un poco de datos adicionales en una existente, y crea una experiencia de usuario más receptiva. Estoy seguro de que hay un punto de quiebre.
Jamie Treworgy
2

HTML5 incluye un <data>elemento para mantener los datos legibles por máquina. Como alternativa, quizás más segura, <script type="application/json">podría incluir sus datos JSON dentro del valueatributo de ese elemento.

const jsonData = document.querySelector('.json-data');
const data = JSON.parse(jsonData.value);

console.log(data)
<data class="json-data" value='
  {
    "unicorns": "awesome",
    "abc": [1, 2, 3],
    "careful": "to escape &#39; quotes"
  }
'></data>

En este caso, debe reemplazar todas las comillas simples con &#39;o con &quot;si opta por incluir el valor entre comillas dobles. De lo contrario, corre el riesgo de ataques XSS como han sugerido otras respuestas.

Rúnar Berg
fuente