Best Practice zum Einbetten von beliebigem JSON in das DOM?

110

Ich denke darüber nach, beliebigen JSON wie folgt in das DOM einzubetten:

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

Dies ähnelt der Art und Weise, wie eine beliebige HTML-Vorlage im DOM zur späteren Verwendung mit einer JavaScript-Vorlagen-Engine gespeichert werden kann. In diesem Fall können wir den JSON später abrufen und analysieren mit:

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

Das funktioniert , aber ist es der beste Weg? Verstößt dies gegen bewährte Verfahren oder Standards?

Hinweis: Ich suche nicht nach Alternativen zum Speichern von JSON im DOM. Ich habe bereits entschieden, dass dies die beste Lösung für das jeweilige Problem ist. Ich suche nur nach dem besten Weg, es zu tun.

Ben Lee
quelle
1
warum hast du es nicht als varin Javascript?
Krizz
@Krizz, es muss Teil eines statischen Dokuments sein, das später von einer komplexen Kette von eingekapseltem Javascript verarbeitet wird. Ich möchte es im DOM speichern.
Ben Lee
@Krizz Ich hatte ein ähnliches Problem. Ich wollte Daten für jeden Benutzer auf eine andere Site stellen, ohne eine AJAX-Anfrage zu stellen. Also habe ich etwas PHP in einen Container eingebettet und etwas Ähnliches wie oben gemacht, um die Daten in Javascript zu erhalten.
Patrick Lorio
2
Ich denke, Ihre ursprüngliche Methode ist die beste. Es ist zu 100% in HTML5 gültig, es ist ausdrucksstark und es werden keine "falschen" Elemente erstellt, die Sie nur mit CSS entfernen oder ausblenden. und es erfordert keine Zeichenkodierung. Was ist der Nachteil?
Jamie Treworgy
22
Wenn Sie eine Zeichenfolge mit dem Wert </script><script>alert()</script><script>in Ihrem JSON-Objekt haben, erhalten Sie Überraschungen. Dies ist nur dann sicher, wenn Sie die Daten zuerst bereinigen.
Silviot

Antworten:

77

Ich denke, Ihre ursprüngliche Methode ist die beste. Die HTML5-Spezifikation befasst sich sogar mit dieser Verwendung:

"Bei Verwendung zum Einschließen von Datenblöcken (im Gegensatz zu Skripten) müssen die Daten inline eingebettet werden, das Format der Daten muss mit dem Attribut type angegeben werden, das Attribut src darf nicht angegeben werden und der Inhalt des Skriptelements muss angegeben werden den für das verwendete Format definierten Anforderungen entsprechen. "

Lesen Sie hier: http://dev.w3.org/html5/spec/Overview.html#the-script-element

Du hast genau das getan. Was ist nicht zu lieben? Keine Zeichenkodierung nach Bedarf mit Attributdaten. Sie können es formatieren, wenn Sie möchten. Es ist ausdrucksstark und der Verwendungszweck ist klar. Es fühlt sich nicht wie ein Hack an (z. B. wie bei der Verwendung von CSS, um Ihr "Träger" -Element zu verbergen). Es ist vollkommen gültig.

Jamie Treworgy
quelle
3
Danke dir. Das Zitat aus der Spezifikation hat mich überzeugt.
Ben Lee
17
Dies ist nur dann vollkommen gültig, wenn Sie zuerst das JSON-Objekt überprüfen und bereinigen: Sie können nicht einfach Benutzerursprungsdaten einbetten. Siehe meinen Kommentar zur Frage.
Silviot
1
extra fragen: was ist der gute Ort, um es auszudrücken? Kopf oder Körper, oben oder unten?
Challet
1
Leider scheint es , dass die CSP-Richtlinie alle scriptTags stoppen kann / wird .
Larry K
2
Wie schützen Sie sich effektiv vor dem Einbetten von JSON, das </ script> enthält und somit die HTML-Injection ermöglicht? Gibt es etwas Festes / Einfaches oder ist es besser, Datenattribute zu verwenden?
Jonasfj
23

Generell würde ich versuchen, stattdessen HTML5-Datenattribute zu verwenden. Nichts hindert Sie daran, gültiges JSON einzugeben. z.B:

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

Wenn Sie jQuery verwenden, ist das Abrufen so einfach wie:

var stuff = JSON.parse($('#mydiv').attr('data-unicorns'));
Horatio Alderaan
quelle
1
Macht Sinn. Beachten Sie jedoch, dass mit einfachen Anführungszeichen für den Schlüsselnamen JSON.parsenicht funktioniert (zumindest die native Google Chrome JSON.parse nicht). Die JSON-Spezifikation erfordert doppelte Anführungszeichen. Aber das ist einfach genug, um es mit Entitäten wie zu beheben ...&lt;unicorns&gt;:....
Ben Lee
4
Eine Frage: Gibt es eine Begrenzung für die Länge von Attributen in HTML 5?
Ben Lee
Ja, das würde funktionieren. Sie können es auch umschalten, sodass in Ihrem HTML-Code einfache Anführungszeichen und in den JSON-Daten doppelte Anführungszeichen verwendet werden.
Horatio Alderaan
1
Ok, habe die Antwort auf meine Frage gefunden: stackoverflow.com/questions/1496096/… - das ist genug für meine Zwecke.
Ben Lee
2
Dies würde für eine einzelne Zeichenfolge nicht funktionieren, z. B. "I am valid JSON"und doppelte Anführungszeichen für das Tag oder einfache Anführungszeichen mit einfachen Anführungszeichen in der Zeichenfolge, z. B. data-unicorns='"My JSON's string"'da einfache Anführungszeichen bei der Codierung als JSON nicht maskiert werden.
Robbie Averill
13

Diese Methode zum Einbetten von json in ein Skript-Tag weist ein potenzielles Sicherheitsproblem auf. Unter der Annahme, dass die JSON-Daten aus Benutzereingaben stammen, ist es möglich, ein Datenelement zu erstellen, das tatsächlich aus dem Skript-Tag ausbricht und eine direkte Injektion in den Dom ermöglicht. Siehe hier:

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

Hier ist die Injektion

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

An Escape / Codierung führt kein Weg vorbei.

MadCoder
quelle
7
Dies ist wahr, aber es ist nicht wirklich eine Sicherheitslücke der Methode. Wenn Sie jemals etwas, das aus Benutzereingaben stammt, in Ihre Seiten einfügen, müssen Sie sorgfältig vorgehen, um es zu umgehen. Diese Methode ist immer noch sinnvoll, solange Sie die normalen Vorsichtsmaßnahmen in Bezug auf Benutzereingaben treffen.
Ben Lee
JSON ist nicht Teil von HTML, der HTML-Parser macht einfach weiter. Es ist dasselbe, als wenn der JSON Teil eines Textabsatzes oder eines div-Elements wäre. HTML-Escape den Inhalt in Ihrem Programm. Darüber hinaus können Sie auch Schrägstrichen entkommen. JSON benötigt dies zwar nicht, toleriert jedoch nicht benötigte Schrägstriche. Was sie verwendet werden kann, um das Einbetten sicher zu machen. PHPs json_encode macht dies standardmäßig.
Timo Tijhof
7

Siehe Regel Nr. 3.1 im XSS-Präventions-Spickzettel von OWASP.

Angenommen, Sie möchten diesen JSON in HTML aufnehmen:

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

Erstellen Sie ein <div>in HTML versteckt . Als nächstes entkommen Sie Ihrem JSON, indem Sie unsichere Entitäten (z. B. &, <,>, ", 'und, /) codieren und in das Element einfügen.

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

Jetzt können Sie darauf zugreifen, indem Sie textContentdas Element mit JavaScript lesen und analysieren:

var text = document.querySelector('#init_data').textContent;
var json = JSON.parse(text);
console.log(json); // {html: "<script>alert("XSS!");</script>"}
Matthew
quelle
Ich glaube, das ist die beste und sicherste Antwort. Beachten Sie, dass viele gängige JSON-Zeichen maskiert werden und bestimmte Zeichen doppelt maskiert werden, z. B. die inneren Anführungszeichen im Objekt {name: 'Dwayne "The Rock" Johnson'}. Es ist jedoch wahrscheinlich immer noch am besten, diesen Ansatz zu verwenden, da Ihre Framework- / Vorlagenbibliothek wahrscheinlich bereits eine sichere Methode zur HTML-Codierung enthält. Eine Alternative wäre die Verwendung von base64, das sowohl HTML-sicher als auch sicher in eine JS-Zeichenfolge eingefügt werden kann. Es ist einfach, in JS mit btoa () / atob () zu codieren / decodieren, und es ist wahrscheinlich einfach für Sie, serverseitig zu arbeiten.
sstur
Eine noch sicherere Methode wäre, das semantisch korrekte <data>Element zu verwenden und die JSON-Daten in das valueAttribut aufzunehmen. Dann müssen Sie die Anführungszeichen nur dann umgehen, &quotwenn Sie doppelte Anführungszeichen verwenden, um die Daten einzuschließen, oder &#39;wenn Sie einfache Anführungszeichen verwenden (was wahrscheinlich besser ist).
Rúnar Berg
5

Ich würde vorschlagen, JSON in ein Inline-Skript mit einem Funktionsrückruf (eine Art JSONP ) einzufügen :

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

Wenn das ausführende Skript nach dem Dokument geladen wird, können Sie es irgendwo speichern, möglicherweise mit einem zusätzlichen Bezeichnerargument: someCallback("stuff", { ... });

Kopieren
quelle
@ BenLee sollte es sehr gut funktionieren, mit dem einzigen Nachteil, die Rückruffunktion definieren zu müssen. Die andere vorgeschlagene Lösung enthält spezielle HTML-Zeichen (z. B. &) und Anführungszeichen, sofern diese in Ihrem JSON enthalten sind.
Kopie
Dies fühlt sich besser an, weil Sie keine Dom-Abfrage benötigen, um die Daten zu finden
Jaseem
@copy Diese Lösung muss noch entkommen (nur eine andere Art), siehe MadCoders Antwort. Lassen Sie es der Vollständigkeit halber hier.
Pvgoran
2

Meine Empfehlung wäre, JSON-Daten in externen .jsonDateien zu behalten und diese Dateien dann über Ajax abzurufen. Sie fügen keinen CSS- und JavaScript-Code auf die Webseite (Inline) ein. Warum sollten Sie dies mit JSON tun?

Šime Vidas
quelle
12
Sie fügen CSS und Javascript nicht inline in eine Webseite ein, da diese normalerweise von anderen Seiten geteilt wird. Wenn die betreffenden Daten vom Server explizit für diesen Kontext generiert werden, ist das Einbetten weitaus effizienter als das Initiieren einer weiteren Anforderung für etwas, das nicht zwischengespeichert werden kann.
Jamie Treworgy
Das liegt daran, dass ich Aktualisierungen an einem Legacy-System vornehme, das schlecht entworfen wurde, und anstatt das gesamte System neu zu gestalten, muss ich nur einen Teil reparieren. Das Speichern von JSON im DOM ist der beste Weg, um diesen einen Teil zu beheben. Ich stimme auch dem zu, was @jamietre gesagt hat.
Ben Lee
@jamietre Beachten Sie, dass das OP angegeben hat, dass diese JSON-Zeichenfolge erst später benötigt wird . Die Frage ist, ob es immer oder nur in einigen Fällen benötigt wird. Wenn es nur in einigen Fällen benötigt wird, ist es sinnvoll, es in einer externen Datei zu haben und es nur bedingt zu laden.
Šime Vidas
2
Ich bin damit einverstanden, dass es viele "Was wäre wenn" gibt, die die Waage in die eine oder andere Richtung kippen könnten. Aber im Allgemeinen ist es oft besser, sie sofort zu versenden, wenn Sie wissen, wann die Seite gerendert wird, was Sie benötigen - auch wenn dies nur möglich ist. Wenn ich einige Informationsfelder hätte, die zusammengeklappt werden, würde ich normalerweise gerne deren Inhalt inline einfügen, damit sie sofort erweitert werden. Der Overhead einer neuen Anforderung ist im Vergleich zum Overhead einiger zusätzlicher Daten bei einer vorhandenen Anforderung sehr hoch und sorgt für eine reaktionsschnellere Benutzererfahrung. Ich bin sicher, dass es einen Haltepunkt gibt.
Jamie Treworgy
2

HTML5 enthält ein <data>Element zum Speichern maschinenlesbarer Daten. Als - vielleicht sicherere - Alternative zu <script type="application/json">Ihnen könnten Sie Ihre JSON-Daten in das valueAttribut dieses Elements aufnehmen.

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 diesem Fall müssen Sie alle einfachen Anführungszeichen durch &#39;oder durch ersetzen, &quot;wenn Sie den Wert in doppelte Anführungszeichen setzen möchten. Andernfalls hat Ihr Risiko XSS- Angriffe wie andere Antworten vorgeschlagen.

Rúnar Berg
quelle