Найкраща практика для вбудовування довільної JSON у DOM?


110

Я думаю про вбудовування довільної JSON у DOM так:

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

Це схоже на те, як можна зберігати довільний HTML-шаблон у DOM для подальшого використання з механізмом шаблонів JavaScript. У цьому випадку ми зможемо пізніше отримати JSON і проаналізувати його:

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

Це працює , але це найкращий спосіб? Чи це порушує будь-яку найкращу практику чи стандарт?

Примітка. Я не шукаю альтернатив для зберігання JSON у DOM, я вже вирішив, що це найкраще рішення для конкретної проблеми, яка у мене є. Я просто шукаю найкращий спосіб це зробити.


1
чому б у вас це не було varв JavaScript?
Криз

@Krizz, він повинен бути частиною статичного документа, щоб він згодом оброблявся складним ланцюжком інкапсульованого javascript. Зберігання його у DOM - це те, що я хочу зробити.
Бен Лі

@Krizz Мені постало подібне завдання. Я хотів розмістити дані на веб-сайті, різному для кожного користувача, не роблячи запит AJAX. Тому я вбудував якийсь PHP в контейнер, зробив щось подібне до того, що у вас є вище, щоб отримати дані в JavaScript.
Патрік Лоріо

2
Я думаю, що ваш оригінальний метод є найкращим насправді. Це на 100% дійсно в HTML5, він виразний, він не створює "підроблених" елементів, які ви просто вилучите або приховете за допомогою CSS; і для цього не потрібно кодування символів. Який мінус?
Джеймі Треворі

22
Якщо у вас </script><script>alert()</script><script>об’єкт JSON має рядок зі значенням , ви отримаєте сюрпризи. Це не є безпечним, якщо ви попередньо не очистите дані.
silviot

Відповіді:


77

Я думаю, що ваш оригінальний метод є найкращим. Специфікація HTML5 навіть адресує це використання:

"При використанні для включення блоків даних (на відміну від скриптів) дані повинні бути вбудовані в рядки, формат даних повинен бути заданий за допомогою атрибута типу, атрибут src не повинен бути вказаний, а вміст елемента сценарію повинен бути відповідати вимогам, визначеним для використовуваного формату. "

Читайте тут: http://dev.w3.org/html5/spec/Overview.html#the-script-element

Ви зробили саме це. Що не любити? Немає кодування символів за потребою даних атрибутів. Ви можете відформатувати, якщо хочете. Це виразно і призначене використання зрозуміло. Це не схоже на злом (наприклад, за допомогою CSS для приховування вашого елемента "перевізника"). Це абсолютно дійсно.


3
Дякую. Цитата із специфікації мене переконала.
Бен Лі

17
Це цілком справедливо, лише якщо ви спершу перевірите та оздоровите об’єкт JSON: ви не можете просто вставляти дані, що походять від користувача. Дивіться мій коментар до питання.
silviot

1
зайве цікаво: яке гарне місце поставити? голова чи тіло, зверху чи знизу?
виклик

1
На жаль, виявляється, що політика CSP може / зупинить усі scriptтеги.
Ларрі К

2
Як ви ефективно захищаєтесь від вбудовування JSON, який містить </script> і, таким чином, дозволяє вводити HTML? Чи є щось тверде / легке або краще використовувати атрибути даних?
jonasfj

23

Як загальний напрямок, я б спробував використовувати атрибути даних HTML5 . Ніщо не завадить вам ввести дійсний JSON. наприклад:

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

Якщо ви використовуєте jQuery, то його отримання буде таким же простим, як:

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

1
Має сенс. Хоча зауважте, що з єдиними котируваннями для ключового імені JSON.parseне вийде (принаймні рідний Google Chrome JSON.parse не буде). Специфікація JSON вимагає подвійних лапок. Але це досить просто виправити за допомогою таких об'єктів ...&lt;unicorns&gt;:....
Бен Лі

4
Хоча одне питання: чи є обмеження у довжині атрибутів у HTML 5?
Бен Лі

Так, це спрацювало б. Ви також можете переключити його, щоб ваш HTML використовував одинарні лапки, а дані JSON використовували подвійні.
Гораціо Альдераан

1
Гаразд, знайшов відповідь на моє запитання: stackoverflow.com/questions/1496096/… - цього достатньо для моїх цілей.
Бен Лі

2
Це не буде працювати для однієї рядка, наприклад, із "I am valid JSON"використанням подвійних лапок для тегу, або одинарних лапок з одинарними цитатами в рядку, наприклад, data-unicorns='"My JSON's string"'оскільки одиничні цитати не уникнуть кодування як JSON.
Роббі Аверилл

13

Цей спосіб вбудовування json в тег скрипта може мати проблему безпеки. Якщо припустити, що дані json походять від введення користувача, можна створити член даних, який фактично вирветься з тегу сценарію та дозволить безпосередньо вводити в dom. Дивіться тут:

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

Ось ін’єкція

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

Просто не обійтися / кодувати.


7
Це правда, але насправді це не є вадою безпеки методу. Якщо ви коли-небудь розміщуєте на своїх сторінках щось, що виник у введенні користувача, ви повинні бути ретельними щодо того, щоб уникнути цього. Цей метод все ще є здоровим, якщо ви вживаєте звичайних запобіжних заходів щодо введення користувачем.
Бен Лі

JSON не є частиною HTML, HTML-аналізатор просто продовжує працювати. Це те саме, що коли JSON був би частиною текстового абзацу або діва-елемента. HTML-вихід із вмісту у вашій програмі. Крім того, ви також можете уникнути косої риски. Хоча JSON цього не вимагає, він терпить непотрібні косої риски. Які можна використовувати її для того, щоб зробити її безпечною для вбудовування. PHP json_encode робить це за замовчуванням.
Тімо Тіхоф

7

Дивіться Правило № 3.1 на шахрайському аркуші запобігання XSS OWASP.

Скажіть, що ви хочете включити цей JSON в HTML:

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

Створіть прихований <div>у HTML. Далі, уникнути свого JSON, кодуючи небезпечні сутності (наприклад, &, <,>, ", ', і /) та вставте його всередину елемента.

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

Тепер ви можете отримати доступ до нього, прочитавши textContentелемент за допомогою JavaScript і проаналізувавши його:

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

Я вірю, що це найкраща і безпечна відповідь. Зауважте, що багато поширених символів JSON втечуть, а певні символи отримують подвійне втечу, наприклад, внутрішні лапки в об'єкті {name: 'Dwayne "The Rock" Johnson'}. Але, мабуть, найкраще використовувати цей підхід, оскільки ваша бібліотека фреймворків / шаблонів, ймовірно, вже містить безпечний спосіб кодування HTML. Альтернативою було б використовувати base64, який є одночасно HTML безпечним та безпечним для введення в рядок JS. Кодувати / декодувати в JS легко за допомогою btoa () / atob (), і вам, мабуть, легко зробити серверну сторону.
sstur

Ще безпечнішим методом було б використання семантично правильного <data>елемента та включення в valueатрибут даних JSON . Тоді вам потрібно лише уникати лапок, &quotякщо ви використовуєте подвійні лапки, щоб укласти дані, або &#39;якщо ви використовуєте одинарні лапки (що, мабуть, краще).
Рунар Берг

5

Я б запропонував помістити JSON в вбудований сценарій з функцією зворотного виклику (вид JSONP ):

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

Якщо сценарій виконання завантажується після документа, ви можете зберігати його десь, можливо, з додатковим аргументом ідентифікатора: someCallback("stuff", { ... });


@BenLee це має працювати дуже добре, з єдиним недоліком потрібно визначити функцію зворотного виклику. Інше запропоноване рішення розбивається на спеціальні символи HTML (наприклад, &) та цитати, якщо вони є у вашому JSON.
копія

Це відчуває себе краще, тому що вам не потрібен запит дому, щоб знайти дані
Jaseem

@copy Це рішення все ще потребує втечі (просто іншого виду), дивіться відповідь MadCoder. Просто залиште його тут для повноти.
пвгоран

2

Моєю рекомендацією було б зберігати дані JSON у зовнішніх .jsonфайлах, а потім отримувати ці файли через Ajax. Ви не вводите код CSS та JavaScript на веб-сторінку (вбудовану), то чому б це робити з JSON?


12
Ви не розміщуєте CSS та Javascript в Інтернеті на веб-сторінці, тому що вона зазвичай використовується між іншими сторінками. Якщо ці дані генеруються сервером явно для цього контексту, вбудовувати його набагато ефективніше, ніж ініціювати інший запит на те, що неможливо кешувати.
Джеймі Треворі

Це тому, що я оновлюю застарілу систему, яка була розроблена погано, і замість того, щоб переробляти всю систему, мені потрібно лише виправити одну частину. Зберігання JSON у DOM - найкращий спосіб виправити цю частину. Також я згоден з тим, що сказав @jamietre.
Бен Лі

@jamietre Зауважте, що ОП заявило, що ця рядок JSON потрібна лише пізніше . Питання в тому, чи потрібно це завжди, або лише в деяких випадках. Якщо він потрібен лише в деяких випадках, то має сенс мати його у зовнішньому файлі та завантажувати його лише умовно.
Šime Vidas

2
Я погоджуюсь, що існує багато "що, якщо", що може нахилити шкалу так чи інакше. Але загалом кажучи, якщо ви знаєте, коли сторінка надається, що вам потрібно - навіть якщо тільки можливо - часто краще відправити її відразу. Мовляв, якби у мене з’явилися деякі інформаційні поля, які починають згортатися, я зазвичай хотів би включити їхній вміст в рядок, щоб вони миттєво розширювалися. Накладні витрати нового запиту - це багато в порівнянні з накладними витратами на кілька додаткових даних про існуючий, і це створює більш чуйну роботу користувачів. Я впевнений, що є точка перерви.
Джеймі Треворі

2

HTML5 включає <data>елемент збереження машиночитаних даних. В якості альтернативи, можливо, більш безпечної <script type="application/json">ви можете включити свої дані JSON всередині valueатрибута цього елемента.

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>

У цьому випадку вам потрібно замінити всі одиничні лапки на &#39;або з, &quot;якщо ви вирішите додавати значення подвійними лапками. В іншому випадку ваші ризики XSS- атаки, як і інші відповіді.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.