Praktik terbaik untuk menyematkan JSON sewenang-wenang di DOM?

110

Saya berpikir untuk menyematkan JSON sewenang-wenang di DOM seperti ini:

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

Ini mirip dengan cara menyimpan template HTML arbitrer di DOM untuk digunakan nanti dengan mesin template JavaScript. Dalam kasus ini, kami nanti dapat mengambil JSON dan menguraikannya dengan:

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

Ini berhasil , tetapi apakah ini cara terbaik? Apakah ini melanggar praktik atau standar terbaik?

Catatan: Saya tidak mencari alternatif untuk menyimpan JSON di DOM, saya sudah memutuskan bahwa itu solusi terbaik untuk masalah khusus yang saya alami. Saya hanya mencari cara terbaik untuk melakukannya.

Ben Lee
sumber
1
mengapa Anda tidak memilikinya sebagai vardalam javascript?
Krizz
@Krizz, itu harus menjadi bagian dari dokumen statis yang kemudian diproses oleh rantai kompleks javascript yang dienkapsulasi. Menyimpannya di DOM adalah yang ingin saya lakukan.
Ben Lee
@ Krizal Saya pernah mengalami masalah serupa. Saya ingin meletakkan data di situs yang berbeda untuk setiap pengguna tanpa melakukan permintaan AJAX. Jadi saya menyematkan beberapa PHP dalam wadah melakukan sesuatu yang mirip dengan apa yang Anda miliki di atas untuk mendapatkan data dalam javascript.
Patrick Lorio
2
Saya pikir metode asli Anda sebenarnya adalah yang terbaik. Ini 100% valid di HTML5, ekspresif, tidak membuat elemen "palsu" yang hanya akan Anda hapus atau sembunyikan dengan CSS; dan tidak memerlukan pengkodean karakter apa pun. Apa kerugiannya?
Jamie Treworgy
22
Jika Anda memiliki string dengan nilai </script><script>alert()</script><script>di dalam objek JSON Anda, Anda akan mendapatkan kejutan. Ini tidak aman kecuali Anda membersihkan data terlebih dahulu.
silviot

Jawaban:

77

Saya pikir metode asli Anda adalah yang terbaik. Spesifikasi HTML5 bahkan membahas penggunaan ini:

"Saat digunakan untuk menyertakan blok data (sebagai lawan dari skrip), data harus disematkan sebaris, format data harus diberikan menggunakan atribut type, atribut src tidak boleh ditentukan, dan konten elemen skrip harus sesuai dengan persyaratan yang ditentukan untuk format yang digunakan. "

Baca di sini: http://dev.w3.org/html5/spec/Overview.html#the-script-element

Anda telah melakukan itu. Apa yang tidak untuk dicintai? Tidak ada pengkodean karakter yang diperlukan dengan data atribut. Anda dapat memformatnya jika Anda mau. Ini ekspresif dan tujuan penggunaannya jelas. Ini tidak terasa seperti hack (misalnya seperti menggunakan CSS untuk menyembunyikan elemen "carrier" Anda). Ini sangat valid.

Jamie Treworgy
sumber
3
Terima kasih. Kutipan dari spesifikasi telah meyakinkan saya.
Ben Lee
17
Ini benar-benar valid hanya jika Anda memeriksa dan membersihkan objek JSON terlebih dahulu: Anda tidak bisa begitu saja menyematkan data yang berasal dari pengguna. Lihat komentar saya tentang pertanyaan itu.
silviot
1
ekstra bertanya-tanya: di mana tempat yang baik untuk meletakkannya? kepala atau badan, atas atau bawah?
palet
1
Sayangnya, tampaknya kebijakan CSP mungkin / akan menghentikan semua scripttag.
Larry K
2
Bagaimana Anda menjaga secara efektif agar tidak menyematkan JSON yang berisi </script> dan, dengan demikian, memungkinkan injeksi HTML? Apakah ada sesuatu yang solid / mudah, atau apakah lebih baik menggunakan atribut data?
jonasfj
23

Sebagai arahan umum, saya akan mencoba menggunakan atribut data HTML5 . Tidak ada yang menghentikan Anda memasukkan JSON yang valid. misalnya:

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

Jika Anda menggunakan jQuery, mengambilnya semudah:

var stuff = JSON.parse($('#mydiv').attr('data-unicorns'));
Horatio Alderaan
sumber
1
Masuk akal. Meskipun perhatikan bahwa dengan tanda kutip tunggal untuk nama kunci, JSON.parsetidak akan berfungsi (setidaknya Google Chrome JSON.parse asli tidak akan berfungsi). Spesifikasi JSON membutuhkan tanda kutip ganda. Tapi itu cukup mudah untuk diperbaiki menggunakan entitas seperti ...&lt;unicorns&gt;:....
Ben Lee
4
Namun satu pertanyaan: Apakah ada batasan panjang atribut dalam HTML 5?
Ben Lee
Ya, itu akan berhasil. Anda juga dapat mengubahnya sehingga HTML Anda menggunakan tanda kutip tunggal dan data JSON menggunakan tanda kutip ganda.
Horatio Alderaan
1
Oke, temukan jawaban untuk pertanyaan saya: stackoverflow.com/questions/1496096/… - ini cukup banyak untuk tujuan saya.
Ben Lee
2
Ini tidak akan berfungsi untuk string tunggal, misalnya "I am valid JSON"dan menggunakan tanda kutip ganda untuk tag, atau tanda kutip tunggal dengan tanda kutip tunggal dalam string, misalnya data-unicorns='"My JSON's string"'karena tanda kutip tunggal tidak di-escape dengan pengkodean sebagai JSON.
Robbie Averill
13

Metode penyematan json dalam tag skrip ini memiliki potensi masalah keamanan. Dengan asumsi data json berasal dari input pengguna, dimungkinkan untuk membuat anggota data yang akan keluar dari tag skrip dan memungkinkan injeksi langsung ke dom. Lihat disini:

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

Ini suntikannya

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

Tidak ada cara untuk keluar / encoding.

MadCoder
sumber
7
Ini benar, tetapi ini sebenarnya bukan kesalahan keamanan dari metode ini. Jika Anda pernah meletakkan sesuatu yang berasal dari input pengguna ke halaman Anda, Anda harus rajin untuk menghindarinya. Metode ini masih terdengar selama Anda melakukan tindakan pencegahan normal terkait masukan pengguna.
Ben Lee
JSON bukan bagian dari HTML, pengurai HTML terus berjalan. Ini sama seperti ketika JSON akan menjadi bagian dari paragraf teks atau elemen div. HTML-escape konten dalam program Anda. Selain itu, Anda juga dapat menghindari garis miring. Meskipun JSON tidak memerlukan ini, ia mentolerir garis miring yang tidak diperlukan. Yang bisa digunakan untuk tujuan membuatnya aman untuk disematkan. Json_encode PHP melakukan ini secara default.
Timo Tijhof
7

Lihat Aturan # 3.1 di lembar contekan pencegahan XSS OWASP.

Katakanlah Anda ingin memasukkan JSON ini dalam HTML:

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

Buat yang tersembunyi <div>di HTML. Selanjutnya, keluar dari JSON Anda dengan mengenkode entitas yang tidak aman (misalnya, &, <,>, ", ', dan, /) dan letakkan di dalam elemen.

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

Sekarang Anda dapat mengaksesnya dengan membaca textContentelemen menggunakan JavaScript dan menguraikannya:

var text = document.querySelector('#init_data').textContent;
var json = JSON.parse(text);
console.log(json); // {html: "<script>alert("XSS!");</script>"}
Matthew
sumber
Saya yakin ini adalah jawaban terbaik dan teraman. Perhatikan bahwa banyak karakter JSON umum yang di-escape, dan karakter tertentu di-escape ganda, seperti tanda kutip bagian dalam pada objek {name: 'Dwayne "The Rock" Johnson'}. Tapi mungkin masih lebih baik menggunakan pendekatan ini karena framework / template library Anda kemungkinan sudah menyertakan cara yang aman untuk melakukan encoding HTML. Alternatifnya adalah menggunakan base64 yang merupakan HTML aman dan aman untuk dimasukkan ke dalam string JS. Sangat mudah untuk menyandikan / mendekode di JS menggunakan btoa () / atob () dan mungkin mudah bagi Anda untuk melakukan sisi server.
sstur
Metode yang lebih aman adalah dengan menggunakan <data>elemen yang benar secara semantik dan menyertakan data JSON dalam valueatribut. Kemudian Anda hanya perlu mengosongkan tanda kutip &quotjika Anda menggunakan tanda kutip ganda untuk menyertakan data, atau &#39;jika Anda menggunakan tanda kutip tunggal (mungkin lebih baik).
Rúnar Berg
5

Saya menyarankan untuk memasukkan JSON ke dalam skrip inline dengan fungsi callback (jenis JSONP ):

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

Jika skrip pelaksana dimuat setelah dokumen, Anda dapat menyimpannya di suatu tempat, mungkin dengan argumen pengenal tambahan: someCallback("stuff", { ... });

menyalin
sumber
@BenLee itu harus bekerja dengan sangat baik, dengan satu-satunya kelemahan harus mendefinisikan fungsi callback. Solusi lain yang disarankan istirahat pada karakter HTML khusus (misalnya &) dan tanda kutip, jika Anda memilikinya di JSON Anda.
salin
Ini terasa lebih baik karena Anda tidak memerlukan kueri dom untuk menemukan data
Jaseem
@copy Solusi ini masih perlu keluar (hanya jenis yang berbeda), lihat jawaban MadCoder. Biarkan saja di sini untuk kelengkapan.
pvgoran
2

Rekomendasi saya adalah menyimpan data JSON di .jsonfile eksternal , dan kemudian mengambil file tersebut melalui Ajax. Anda tidak memasukkan kode CSS dan JavaScript ke halaman web (sebaris), jadi mengapa Anda melakukannya dengan JSON?

Šime Vidas
sumber
12
Anda tidak menempatkan CSS dan Javascript sebaris di halaman web karena biasanya dibagikan di antara halaman lain. Jika data yang dipermasalahkan dibuat oleh server secara eksplisit untuk konteks ini, menyematkannya jauh lebih efisien daripada memulai permintaan lain untuk sesuatu yang tidak dapat disimpan dalam cache.
Jamie Treworgy
Itu karena saya membuat pembaruan pada sistem lama yang dirancang dengan buruk, dan daripada mendesain ulang seluruh sistem, saya hanya perlu memperbaiki satu bagian. Menyimpan JSON di DOM adalah cara terbaik untuk memperbaiki bagian yang satu ini. Juga, saya setuju dengan apa yang dikatakan @jamietre.
Ben Lee
@jamietre Perhatikan bahwa OP menyatakan bahwa string JSON ini hanya diperlukan nanti . Pertanyaannya adalah apakah itu selalu dibutuhkan, atau hanya dalam beberapa kasus. Jika hanya diperlukan dalam beberapa kasus, maka masuk akal untuk memilikinya di file eksternal dan hanya memuatnya secara bersyarat.
Šime Vidas
2
Saya setuju bahwa ada banyak "bagaimana seandainya" yang bisa mengubah skala ke satu atau lain cara. Tetapi secara umum jika Anda tahu kapan halaman dirender apa yang akan Anda butuhkan - meskipun hanya mungkin - sering kali lebih baik untuk segera mengirimkannya. Seperti, jika saya memiliki beberapa kotak informasi yang mulai menciut, saya biasanya ingin memasukkan isinya sebaris sehingga mereka berkembang secara instan. Overhead permintaan baru jauh dibandingkan dengan overhead dari sedikit data tambahan pada yang sudah ada, dan ini menciptakan pengalaman pengguna yang lebih responsif. Saya yakin ada break point.
Jamie Treworgy
2

HTML5 menyertakan <data>elemen untuk menjaga data yang dapat dibaca mesin. Sebagai alternatif — mungkin lebih aman — <script type="application/json">Anda dapat menyertakan data JSON Anda di dalam valueatribut elemen tersebut.

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>

Dalam hal ini, Anda perlu mengganti semua tanda kutip tunggal dengan &#39;atau dengan &quot;jika Anda memilih untuk menyertakan nilai dengan tanda kutip ganda. Jika tidak, risiko Anda terkena serangan XSS seperti jawaban lain yang disarankan.

Rúnar Berg
sumber