Meilleure pratique pour intégrer du JSON arbitraire dans le DOM?

110

Je pense à intégrer JSON arbitraire dans le DOM comme ceci:

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

Ceci est similaire à la façon dont on peut stocker un modèle HTML arbitraire dans le DOM pour une utilisation ultérieure avec un moteur de modèle JavaScript. Dans ce cas, nous pourrions plus tard récupérer le JSON et l'analyser avec:

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

Cela fonctionne , mais est-ce le meilleur moyen? Cela enfreint-il une des meilleures pratiques ou des normes?

Remarque: je ne cherche pas d'alternatives au stockage de JSON dans le DOM, j'ai déjà décidé que c'était la meilleure solution pour le problème particulier que je rencontre. Je cherche juste la meilleure façon de le faire.

Ben Lee
la source
1
pourquoi ne l'avez-vous pas varen javascript?
Krizz
@Krizz, il doit faire partie d'un document statique qu'il est ensuite traité par une chaîne complexe de javascript encapsulé. Je veux le stocker dans le DOM.
Ben Lee
@Krizz m'a posé un problème similaire. Je voulais mettre des données dans un site différent pour chaque utilisateur sans faire de requête AJAX. J'ai donc intégré du PHP dans un conteneur pour faire quelque chose de similaire à ce que vous avez ci-dessus pour obtenir les données en javascript.
Patrick Lorio
2
Je pense que votre méthode originale est la meilleure en fait. C'est 100% valide en HTML5, c'est expressif, ça ne crée pas de "faux" éléments que vous allez simplement supprimer ou masquer avec CSS; et il ne nécessite aucun encodage de caractères. Quel est l'inconvénient?
Jamie Treworgy
22
Si vous avez une chaîne avec la valeur à l' </script><script>alert()</script><script>intérieur de votre objet JSON, vous aurez des surprises. Ce n'est pas sûr sauf si vous nettoyez d'abord les données.
silviot

Réponses:

77

Je pense que votre méthode originale est la meilleure. La spécification HTML5 aborde même cette utilisation:

"Lorsqu'elles sont utilisées pour inclure des blocs de données (par opposition aux scripts), les données doivent être intégrées en ligne, le format des données doit être donné à l'aide de l'attribut type, l'attribut src ne doit pas être spécifié et le contenu de l'élément de script doit conforme aux exigences définies pour le format utilisé. "

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

Vous avez fait exactement cela. Ce qui est de ne pas aimer? Aucun codage de caractères nécessaire avec les données d'attribut. Vous pouvez le formater si vous le souhaitez. C'est expressif et l'utilisation prévue est claire. Cela ne ressemble pas à un hack (par exemple, comme le fait d'utiliser CSS pour masquer votre élément "porteur"). C'est parfaitement valable.

Jamie Treworgy
la source
3
Je vous remercie. La citation de la spécification m'a convaincu.
Ben Lee
17
Ce n'est parfaitement valide que si vous vérifiez et nettoyez d'abord l'objet JSON: vous ne pouvez pas simplement incorporer des données d'origine utilisateur. Voir mon commentaire sur la question.
silviot
1
demande supplémentaire: quel est le bon endroit pour le mettre? tête ou corps, haut ou bas?
challet
1
Malheureusement, il semble que la politique CSP peut / arrêtera toutes les scriptbalises.
Larry K
2
Comment vous protégez-vous efficacement contre l'incorporation de JSON contenant </script> et, par conséquent, autorisant l'injection HTML? Y a-t-il quelque chose de solide / facile, ou vaut-il mieux utiliser des attributs de données?
jonasfj
23

En règle générale, j'essaierais d'utiliser les attributs de données HTML5 à la place. Rien ne vous empêche de mettre en JSON valide. par exemple:

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

Si vous utilisez jQuery, le récupérer est aussi simple que:

var stuff = JSON.parse($('#mydiv').attr('data-unicorns'));
Horatio Alderaan
la source
1
Logique. Cependant, notez qu'avec des guillemets simples pour le nom de la clé, JSON.parsene fonctionnera pas (au moins le Google Chrome JSON.parse natif ne le fera pas). La spécification JSON nécessite des guillemets doubles. Mais c'est assez facile à résoudre en utilisant des entités comme ...&lt;unicorns&gt;:....
Ben Lee
4
Une question cependant: y a-t-il une limite à la longueur des attributs dans HTML 5?
Ben Lee
Oui, cela fonctionnerait. Vous pouvez également l'inverser afin que votre HTML utilise des guillemets simples et que les données JSON utilisent des doubles.
Horatio Alderaan
1
Ok, j'ai trouvé la réponse à ma question: stackoverflow.com/questions/1496096/… - c'est largement suffisant pour mes besoins.
Ben Lee
2
Cela ne fonctionnerait pas pour une seule chaîne, par exemple "I am valid JSON"et en utilisant des guillemets doubles pour la balise, ou des guillemets simples avec des guillemets simples dans la chaîne, par exemple, data-unicorns='"My JSON's string"'car les guillemets simples ne sont pas échappés avec un codage en JSON.
Robbie Averill
13

Cette méthode d'incorporation de json dans une balise de script présente un problème de sécurité potentiel. En supposant que les données json proviennent de l'entrée utilisateur, il est possible de créer un membre de données qui sortira en fait de la balise de script et permettra l'injection directe dans le dom. Vois ici:

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

Voici l'injection

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

Il n'y a tout simplement aucun moyen d'éviter l'échappement / l'encodage.

MadCoder
la source
7
C'est vrai, mais ce n'est pas vraiment une faille de sécurité de la méthode. Si jamais vous mettez quelque chose qui provient d'une entrée utilisateur dans vos pages, vous devez être diligent pour y échapper. Cette méthode est toujours valable tant que vous prenez les mesures de précaution normales concernant l'entrée de l'utilisateur.
Ben Lee
JSON ne fait pas partie du HTML, l'analyseur HTML continue simplement. C'est la même chose que lorsque le JSON ferait partie d'un paragraphe de texte ou d'un élément div. HTML-échappez au contenu de votre programme. De plus, vous pouvez également échapper des barres obliques. Bien que JSON ne l'exige pas, il tolère les barres obliques inutiles. Ce qui peut être utilisé dans le but de le rendre sûr à intégrer. Json_encode de PHP le fait par défaut.
Timo Tijhof le
7

Voir la règle n ° 3.1 dans la feuille de triche de prévention XSS de l'OWASP.

Supposons que vous souhaitiez inclure ce JSON dans HTML:

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

Créez un caché <div>en HTML. Ensuite, échappez à votre JSON en encodant des entités non sécurisées (par exemple, &, <,>, ", ', et, /) et placez-le dans l'élément.

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

Vous pouvez maintenant y accéder en lisant le textContentde l'élément en utilisant JavaScript et en l'analysant:

var text = document.querySelector('#init_data').textContent;
var json = JSON.parse(text);
console.log(json); // {html: "<script>alert("XSS!");</script>"}
Matthieu
la source
Je pense que c'est la meilleure et la plus sûre réponse. Notez que de nombreux caractères JSON courants sont échappés et que certains caractères sont échappés deux fois, tels que les guillemets internes de l'objet {name: 'Dwayne "The Rock" Johnson'}. Mais il est probablement toujours préférable d'utiliser cette approche car votre framework / bibliothèque de modèles inclut probablement déjà un moyen sûr de faire l'encodage HTML. Une alternative serait d'utiliser base64 qui est à la fois sûr HTML et sûr à insérer dans une chaîne JS. Il est facile d'encoder / décoder en JS en utilisant btoa () / atob () et c'est probablement facile pour vous de le faire côté serveur.
sstur le
Une méthode encore plus sûre consisterait à utiliser l' <data>élément sémantiquement correct et à inclure les données JSON dans l' valueattribut. Ensuite, vous n'avez besoin d'échapper les guillemets avec &quotsi vous utilisez des guillemets doubles pour entourer les données, ou &#39;si vous utilisez des guillemets simples (ce qui est probablement mieux).
Rúnar Berg
5

Je suggérerais de mettre JSON dans un script en ligne avec un rappel de fonction (sorte de JSONP ):

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

Si le script en cours d'exécution est chargé après le document, vous pouvez le stocker quelque part, éventuellement avec un argument d'identifiant supplémentaire: someCallback("stuff", { ... });

copie
la source
@BenLee cela devrait très bien fonctionner, avec le seul inconvénient d'avoir à définir la fonction de rappel. L'autre solution suggérée utilise des caractères HTML spéciaux (par exemple &) et des guillemets, si vous en avez dans votre JSON.
copie le
Cela se sent mieux car vous n'avez pas besoin d'une requête dom pour trouver les données
Jaseem
@copy Cette solution doit encore s'échapper (juste un type différent), voir la réponse de MadCoder. Il suffit de le laisser ici pour être complet.
pvgoran
2

Ma recommandation serait de conserver les données JSON dans des .jsonfichiers externes , puis de récupérer ces fichiers via Ajax. Vous ne mettez pas de code CSS et JavaScript sur la page Web (en ligne), alors pourquoi le feriez-vous avec JSON?

Šime Vidas
la source
12
Vous ne mettez pas CSS et Javascript en ligne dans une page Web car ils sont généralement partagés entre d'autres pages. Si les données en question sont générées explicitement par le serveur pour ce contexte, leur intégration est beaucoup plus efficace que le lancement d'une autre requête pour quelque chose qui ne peut pas être mis en cache.
Jamie Treworgy
C'est parce que je mets à jour un système hérité qui a été mal conçu, et plutôt que de reconcevoir tout le système, je dois juste réparer une partie. Le stockage de JSON dans le DOM est le meilleur moyen de résoudre cette partie. De plus, je suis d'accord avec ce que @jamietre a dit.
Ben Lee
@jamietre Notez que l'OP a déclaré que cette chaîne JSON n'est nécessaire que plus tard . La question est de savoir si cela est toujours nécessaire, ou seulement dans certains cas. Si cela n'est nécessaire que dans certains cas, il est logique de l'avoir dans un fichier externe et de le charger uniquement de manière conditionnelle.
Šime Vidas
2
Je conviens qu'il existe de nombreux «et si» qui pourraient faire pencher la balance d'une manière ou d'une autre. Mais d'une manière générale, si vous savez quand la page est rendue de quoi vous allez avoir besoin - même si c'est possible - il est souvent préférable de l'envoyer tout de suite. Par exemple, si j'avais des boîtes d'informations qui commençaient à s'effondrer, j'aimerais généralement inclure leur contenu en ligne pour qu'elles se développent instantanément. La surcharge d'une nouvelle demande est beaucoup comparée à la surcharge d'un peu de données supplémentaires sur une demande existante, et cela crée une expérience utilisateur plus réactive. Je suis sûr qu'il y a un point de rupture.
Jamie Treworgy
2

HTML5 comprend un <data>élément permettant de conserver les données lisibles par machine. Comme alternative, peut-être plus sûre, <script type="application/json">vous pouvez inclure vos données JSON dans l' valueattribut de cet élément.

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>

Dans ce cas, vous devez remplacer tous les guillemets simples par &#39;ou par &quot;si vous choisissez de mettre la valeur entre guillemets doubles. Sinon, votre risque d' attaques XSS comme d'autres réponses l'ont suggéré.

Rúnar Berg
la source