Best practice per incorporare JSON arbitrario nel DOM?


110

Sto pensando di incorporare JSON arbitrario nel DOM in questo modo:

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

Questo è simile al modo in cui si potrebbe memorizzare un modello HTML arbitrario nel DOM per un uso successivo con un motore di modelli JavaScript. In questo caso, potremmo successivamente recuperare il JSON e analizzarlo con:

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

Funziona , ma è il modo migliore? Ciò viola qualche best practice o standard?

Nota: non sto cercando alternative all'archiviazione di JSON nel DOM, ho già deciso che è la soluzione migliore per il particolare problema che sto riscontrando. Sto solo cercando il modo migliore per farlo.


1
perché non lo avrai come varin javascript?
Krizz

@Krizz, deve essere parte di un documento statico che viene successivamente elaborato da una complessa catena di javascript incapsulato. Memorizzarlo nel DOM è quello che voglio fare.
Ben Lee,

@ Krizz mi è stato posto un problema simile. Volevo inserire i dati in un sito diverso per ogni utente senza fare una richiesta AJAX. Quindi ho incorporato un po 'di PHP in un contenitore fatto qualcosa di simile a quello che hai sopra per ottenere i dati in javascript.
Patrick Lorio

2
Penso che il tuo metodo originale sia il migliore in realtà. È valido al 100% in HTML5, è espressivo, non crea elementi "falsi" che dovrai semplicemente rimuovere o nascondere con CSS; e non richiede alcuna codifica dei caratteri. Qual è lo svantaggio?
Jamie Treworgy,

22
Se hai una stringa con il valore </script><script>alert()</script><script>all'interno del tuo oggetto JSON, avrai sorprese. Questo non è sicuro a meno che non si disinfettino prima i dati.
silviot

Risposte:


77

Penso che il tuo metodo originale sia il migliore. La specifica HTML5 affronta anche questo uso:

"Quando vengono utilizzati per includere blocchi di dati (al contrario degli script), i dati devono essere incorporati in linea, il formato dei dati deve essere fornito utilizzando l'attributo type, l'attributo src non deve essere specificato e il contenuto dell'elemento script deve conformi ai requisiti definiti per il formato utilizzato. "

Leggi qui: http://dev.w3.org/html5/spec/Overview.html#the-script-element

Hai fatto esattamente questo. Cosa non amare? Nessuna codifica dei caratteri secondo necessità con i dati degli attributi. Puoi formattarlo se vuoi. È espressivo e la destinazione d'uso è chiara. Non sembra un hack (ad es. Come usare i CSS per nascondere il tuo elemento "vettore"). È perfettamente valido.


3
Grazie. La citazione dalla specifica mi ha convinto.
Ben Lee,

17
È perfettamente valido solo se controlli e disinfetti prima l'oggetto JSON: non puoi semplicemente incorporare i dati di origine dell'utente. Vedi il mio commento sulla domanda.
silviot

1
chiedendosi extra: qual è il buon posto per metterlo? testa o corpo, sopra o sotto?
challet

1
Sfortunatamente, sembra che il criterio CSP possa / interromperà tutti i scripttag.
Larry K

2
Come ti difendi efficacemente dall'incorporare JSON che contiene </script> e, quindi, consente l'iniezione di HTML? C'è qualcosa di solido / facile o è meglio usare gli attributi dei dati?
jonasfj

23

Come direzione generale, proverei invece a utilizzare gli attributi dei dati HTML5 . Non c'è niente che ti impedisca di inserire un JSON valido. per esempio:

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

Se stai usando jQuery, recuperarlo è facile come:

var stuff = JSON.parse($('#mydiv').attr('data-unicorns'));

1
Ha senso. Anche se JSON.parsetieni presente che con virgolette singole per il nome della chiave, non funzionerà (almeno il JSON.parse nativo di Google Chrome non lo farà). La specifica JSON richiede virgolette doppie. Ma è abbastanza facile da risolvere usando entità come ...&lt;unicorns&gt;:....
Ben Lee,

4
Una domanda però: esiste un limite alla lunghezza degli attributi in HTML 5?
Ben Lee,

Sì, funzionerebbe. Puoi anche cambiarlo in modo che il tuo HTML utilizzi virgolette singole e i dati JSON utilizzino doppie.
Horatio Alderaan,

1
Ok, ho trovato la risposta alla mia domanda: stackoverflow.com/questions/1496096/… - questo è abbastanza per i miei scopi.
Ben Lee,

2
Questo non funzionerebbe per una singola stringa, ad es. "I am valid JSON"E utilizzando virgolette doppie per il tag, o virgolette singole con virgolette singole nella stringa, ad es. data-unicorns='"My JSON's string"'Poiché le virgolette singole non sono sottoposte a escape con la codifica come JSON.
Robbie Averill

13

Questo metodo di incorporamento di json in un tag di script presenta un potenziale problema di sicurezza. Supponendo che i dati json abbiano avuto origine dall'input dell'utente, è possibile creare un membro dati che in effetti uscirà dal tag script e consentirà l'iniezione diretta nel dom. Vedere qui:

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

Ecco l'iniezione

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

Non c'è proprio modo di sfuggire / codificare.


7
Questo è vero, ma non è davvero un difetto di sicurezza del metodo. Se metti nelle tue pagine qualcosa che ha avuto origine dall'input dell'utente, devi essere diligente nell'evaderlo. Questo metodo è valido fino a quando si adottano le normali misure precauzionali relative all'input dell'utente.
Ben Lee

JSON non fa parte dell'HTML, il parser HTML continua a funzionare. È lo stesso di quando il JSON sarebbe parte di un paragrafo di testo o di un elemento div. Esegui l'escape HTML del contenuto nel tuo programma. Inoltre, puoi anche sfuggire alle barre. Sebbene JSON non lo richieda, tollera le barre non necessarie. Che può essere utilizzato allo scopo di renderlo sicuro da incorporare. Il json_encode di PHP lo fa per impostazione predefinita.
Timo Tijhof

7

Vedi la regola # 3.1 nel cheat sheet di OWASP sulla prevenzione XSS.

Supponi di voler includere questo JSON in HTML:

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

Crea un nascosto <div>in HTML. Successivamente, esegui l'escape del tuo JSON codificando entità non sicure (ad esempio, &, <,>, ", 'e, /) e inseriscilo all'interno dell'elemento.

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

Ora puoi accedervi leggendo textContentl'elemento dell'elemento utilizzando JavaScript e analizzandolo:

var text = document.querySelector('#init_data').textContent;
var json = JSON.parse(text);
console.log(json); // {html: "<script>alert("XSS!");</script>"}

Credo che questa sia la risposta migliore e più sicura. Si noti che molti caratteri JSON comuni vengono sottoposti a escape e ad alcuni caratteri viene eseguito un double escape, come le virgolette interne nell'oggetto {name: 'Dwayne "The Rock" Johnson'}. Ma probabilmente è ancora meglio usare questo approccio poiché la tua libreria di framework / modelli probabilmente include già un modo sicuro per eseguire la codifica HTML. Un'alternativa sarebbe usare base64, che è sia HTML sicuro che sicuro da inserire all'interno di una stringa JS. È facile codificare / decodificare in JS usando btoa () / atob () ed è probabilmente facile per te farlo lato server.
sstur

Un metodo ancora più sicuro sarebbe utilizzare l' <data>elemento semanticamente corretto e includere i dati JSON valuenell'attributo. Quindi devi solo eseguire l'escape delle virgolette con &quotse usi le virgolette doppie per racchiudere i dati, o &#39;se usi le virgolette singole (che è probabilmente meglio).
Rúnar Berg

5

Suggerirei di inserire JSON in uno script inline con una funzione di callback (tipo JSONP ):

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

Se lo script in esecuzione viene caricato dopo il documento, puoi memorizzarlo da qualche parte, possibilmente con un argomento identificativo aggiuntivo: someCallback("stuff", { ... });


@ BenLee dovrebbe funzionare molto bene, con l'unico svantaggio di dover definire la funzione di callback. L'altra soluzione suggerita si interrompe su caratteri HTML speciali (ad esempio &) e virgolette, se li hai nel tuo JSON.
copia il

È meglio perché non è necessaria una query dom per trovare i dati
Jaseem

@copy Questa soluzione deve ancora sfuggire (solo un tipo diverso), vedere la risposta di MadCoder. Lasciandolo qui per completezza.
pvgoran

2

Il mio consiglio sarebbe di mantenere i dati JSON in .jsonfile esterni e quindi recuperare quei file tramite Ajax. Non inserisci codice CSS e JavaScript nella pagina web (inline), quindi perché dovresti farlo con JSON?


12
Non metti CSS e Javascript in linea in una pagina web perché di solito è condiviso tra altre pagine. Se i dati in questione vengono generati dal server esplicitamente per questo contesto, incorporarli è molto più efficiente rispetto all'avvio di un'altra richiesta per qualcosa che non può essere memorizzato nella cache.
Jamie Treworgy,

È perché sto apportando aggiornamenti a un sistema legacy che è stato progettato in modo inadeguato, e invece di riprogettare l'intero sistema, devo sistemare solo una parte. La memorizzazione di JSON nel DOM è il modo migliore per correggere questa parte. Inoltre, sono d'accordo con ciò che ha detto @jamietre.
Ben Lee,

@jamietre Nota che l'OP ha dichiarato che questa stringa JSON è necessaria solo in seguito . La domanda è se è necessario sempre o solo in alcuni casi. Se è necessario solo in alcuni casi, ha senso averlo in un file esterno e caricarlo solo in modo condizionale.
Šime Vidas

2
Sono d'accordo sul fatto che ci sono molti "e se" che potrebbero far pendere la bilancia in un modo o nell'altro. Ma in generale se sai quando la pagina viene renderizzata di cosa avrai bisogno, anche se solo possibilmente, è spesso meglio inviarlo subito. Ad esempio, se avessi alcune caselle di informazioni che iniziano a comprimersi, di solito vorrei includere i loro contenuti in linea in modo che si espandano istantaneamente. Il sovraccarico di una nuova richiesta è molto rispetto al sovraccarico di un po 'di dati extra su una esistente e crea un'esperienza utente più reattiva. Sono sicuro che ci sia un punto di rottura.
Jamie Treworgy,

2

HTML5 include un <data>elemento per mantenere i dati leggibili dalla macchina. Come alternativa, forse più sicura, <script type="application/json">potresti includere i tuoi dati JSON all'interno valuedell'attributo di quell'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>

In questo caso è necessario sostituire tutte le virgolette singole con &#39;o con &quot;se si sceglie di racchiudere il valore tra virgolette doppie. Altrimenti rischi che attacchi XSS come altre risposte hanno suggerito.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.