Лучшая практика для встраивания произвольного 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
@Krizz, он должен быть частью статического документа, который позже будет обрабатываться сложной цепочкой инкапсулированных javascript. Я хочу сохранить его в DOM.
Бен Ли
@Krizz Мне поставили аналогичную задачу. Я хотел разместить данные на сайте, разные для каждого пользователя, без запроса AJAX. Итак, я встроил PHP в контейнер и сделал что-то похожее на то, что вы сделали выше, чтобы получить данные в javascript.
Патрик Лорио,
2
Я думаю, что ваш оригинальный метод на самом деле лучший. Он на 100% действителен в HTML5, выразителен, он не создает «фальшивых» элементов, которые вы просто удалите или скроете с помощью CSS; и не требует кодировки символов. Какая обратная сторона?
Джейми Трюорги
22
Если у вас есть строка со значением </script><script>alert()</script><script>внутри вашего объекта JSON, вы получите сюрпризы. Это небезопасно, если вы сначала не очистите данные.
silviot

Ответы:

77

Я думаю, что ваш оригинальный метод лучше всего. Спецификация HTML5 даже рассматривает это использование:

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

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

Вы сделали именно это. Что не любить? Для данных атрибутов не требуется кодировки символов. Вы можете отформатировать его, если хотите. Он выразительный, и предполагаемое использование ясное. Это не похоже на взлом (например, как при использовании CSS для скрытия вашего элемента «носитель»). Это совершенно верно.

Джейми Треворги
источник
3
Спасибо. Цитата из спецификации меня убедила.
Бен Ли
17
Это совершенно справедливо только в том случае, если вы сначала проверите и продезинфицируете объект JSON: вы не можете просто встроить данные пользователя. См. Мой комментарий к вопросу.
silviot
1
дополнительно интересно: как это хорошее место поставить? голова или тело, верх или низ?
Challet
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>

Нет никакого способа избежать экранирования / кодирования.

MadCoder
источник
7
Это правда, но на самом деле это не недостаток безопасности метода. Если вы когда-либо помещаете на свои страницы что-то, что возникло в результате пользовательского ввода, вы должны стараться избегать этого. Этот метод работает до тех пор, пока вы принимаете обычные меры предосторожности в отношении ввода данных пользователем.
Бен Ли
JSON не является частью HTML, парсер HTML просто продолжает работать. Это то же самое, что если бы JSON был частью текстового абзаца или элемента div. HTML-экранирование содержимого вашей программы. Кроме того, вы можете избегать косой черты. Хотя JSON этого не требует, он допускает ненужные косые черты. Что может быть использовано ею для обеспечения безопасности встраивания. PHP json_encode делает это по умолчанию.
Timo Tijhof
7

См. Правило № 3.1 в шпаргалке OWASP по предотвращению XSS.

Допустим, вы хотите включить этот 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>элемента и включение данных JSON в valueатрибут. Тогда вам нужно только избегать кавычек с помощью, &quotесли вы используете двойные кавычки для заключения данных или &#39;если вы используете одинарные кавычки (что, вероятно, лучше).
Rúnar Berg
5

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

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

Если исполняемый скрипт загружается после документа, вы можете где-нибудь его сохранить, возможно, с дополнительным аргументом идентификатора: someCallback("stuff", { ... });

копия
источник
@BenLee, он должен работать очень хорошо, с единственным недостатком - необходимость определять функцию обратного вызова. Другое предлагаемое решение разбивается на специальные символы HTML (например, &) и кавычки, если они есть в вашем JSON.
копия
Это лучше, потому что вам не нужен запрос dom для поиска данных
Jaseem
@copy Это решение все еще требует экранирования (просто другого типа), см. ответ MadCoder. Просто оставим это здесь для полноты картины.
pvgoran
2

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

Шиме Видас
источник
12
Вы не помещаете CSS и Javascript встроенными в веб-страницу, потому что они обычно используются другими страницами. Если данные, о которых идет речь, генерируются сервером явно для этого контекста, их внедрение намного эффективнее, чем инициирование другого запроса для чего-то, что не может быть кэшировано.
Джейми Трюорги
Это потому, что я делаю обновления устаревшей системы, которая была плохо спроектирована, и вместо того, чтобы перепроектировать всю систему, мне нужно просто исправить одну часть. Хранение JSON в DOM - лучший способ исправить эту часть. Кроме того, я согласен с тем, что сказал @jamietre.
Бен Ли
@jamietre Обратите внимание, что OP заявил, что эта строка JSON понадобится только позже . Вопрос в том, нужно ли это всегда или только в отдельных случаях. Если он нужен только в некоторых случаях, тогда имеет смысл разместить его во внешнем файле и загружать только условно.
Шиме Видас
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- атак, как предлагали другие ответы.

Рунар Берг
источник