Как вставить произвольный JSON в тег HTML script

Я хочу сохранить содержимое JSON в источнике документа HTML внутри тега скрипта.

Содержимое этого JSON зависит от ввода, введенного пользователем, поэтому необходимо тщательно обработать эту строку для XSS.

Я прочитал две концепции здесь, на SO.

1. Замените все вхождения тега </script в <\/script или замените все </ на стороне сервера <\/.

Код выглядит следующим образом (с использованием Python и jinja2 в качестве примера):

// view
data = {
    'test': 'asdas</script><b>as\'da</b><b>as"da</b>',
}

context_dict = {
    'data_json': json.dumps(data, ensure_ascii=False).replace('</script', r'<\/script'),
}

// template
<script>
    var data_json = {{ data_json | safe }};
</script>

// js
access it simply as window.data_json object

2. Кодируйте данные в виде строки JSON в кодировке HTML-сущности и unescape + анализируйте их на стороне клиента. Unescape от этого ответа: qaru.site/info/65908/...

// view
context_dict = {
    'data_json': json.dumps(data, ensure_ascii=False),
}

// template
<script>
    var data_json = '{{ data_json }}'; // encoded into HTML entities, like &lt; &gt; &amp;
</script>

// js
function htmlDecode(input) {
  var doc = new DOMParser().parseFromString(input, "text/html");
  return doc.documentElement.textContent;
}

var decoded = htmlDecode(window.data_json);
var data_json = JSON.parse(decoded);

Этот метод не работает, поскольку \" в источнике сценария становится " в переменной JS. Кроме того, он создает намного больший HTML-документ, а также не очень удобочитаемый для человека, поэтому я бы остановился на первом, если это не означает огромный риск для безопасности.

Есть ли угроза безопасности при использовании первой версии? Достаточно ли санировать JSON-кодированную строку с помощью .replace('</script', r'<\/script')?

Ссылка на SO:
Лучший способ сохранить JSON в атрибуте HTML?
Зачем разбивать скрипт & lt; script & gt; тег при записи с помощью document.write()?
Тег скрипта в строке JavaScript
Дезинфицировать & lt; script & gt; содержимое элемента
Escape & lt;/в содержимом тега скрипта

Несколько замечательных внешних ресурсов по этой проблеме:
Реализация фильтра Flask tojson source
Rail json_escape метод справки и источника
5-летнее обсуждение в Django тикете и предложенном коде

Ответ 1

Прежде всего, ваша паранойя обоснована.

  • HTML-парсер может быть обманут закрывающим тегом script (лучше предположить любым закрывающим тегом)
  • JS-парсер может быть обманут обратными косыми чертами и кавычками (с очень плохим кодировщиком).

Да, было бы намного "безопаснее" кодировать все символы, которые могли бы смутить различные используемые партизаны. Сохранение его правдоподобности может противоречить вашей парадигме безопасности.

Примечание. Результат кодирования JSON String должен быть каноническим и OFC, а не сломанным, как в parsable. JSON является подмножеством JS и, таким образом, может быть JS разборчивым без какого-либо риска. Итак, все, что вам нужно сделать, это убедиться, что экземпляр HTML-Parser, который извлекает JS-код, не обманут вашими пользовательскими данными.

Таким образом, реальная ловушка является гнездом обоих парсеров. На самом деле, я хотел бы попросить вас добавить что-то подобное в отдельный запрос. Таким образом, вы полностью избегаете этого сценария.

Предполагая, что всевозможные стили и исправления ошибок могут произойти в таком парсере, может быть, что другие теги (открытые или закрытые) могут достичь аналогичного успеха.

Как и в: предлагая парсеру, что тег script закончился неявно.

Таким образом, желательно кодировать косую черту и все привязки тегов (/, & ​​lt;, > ), а не только закрытие script -tag в любом обратимом методе, который вы выберете, до тех пор, пока это не будет путайте HTML-Parser:

  • Лучший выбор - base64 (но вы хотите более читаемый)
  • HTML будут делать, хотя путают людей:)
  • Выполнение собственного экранирования также будет работать, просто избегайте отдельных символов, а не фрагмента </script

В заключение, да, это, вероятно, лучше всего с некоторыми изменениями, но учтите, что вы уже в один шаг от "безопасного", попробовав что-то вроде этого, вместо того, чтобы загружать JSON через XHR или по крайней мере, используя строгую строчную кодировку, такую ​​как base64.

P.S.: Если вы можете узнать у других людей код, кодирующий строки, которые хороши, но вы не должны прибегать к функциям "библиотеки" или другим людям, если они не делают именно то, что вам нужно. Так что напишите и тщательно протестируйте свой собственный (de/en) кодер и узнайте, что эта ловушка была запечатана.

Ответ 2

Вот как я справился с относительно небольшой частью этой проблемы - проблемой кодирования с сохранением JSON в элементе сценария. Краткий ответ: вы должны экранировать < или /, так как вместе они заканчивают элемент скрипта - даже внутри строкового литерала JSON. Вы не можете HTML-кодировать сущности для элемента скрипта. Вы можете JavaScript-обратную косую черту-избежать косой черты. Я предпочел JavaScript-hex-экранировать меньше, чем угловая скобка, как \u003C.

.replace('<', r'\u003C')

Я столкнулся с этой проблемой, пытаясь передать JSON из результатов oembed. Некоторые из них содержат теги закрытия скрипта (без упоминания Twitter по имени).

json_for_script = json.dumps(data).replace('<', r'\u003C');

Это превращает data = {'test': 'foo </script> bar'}; в

'{"test": "foo \\u003C/script> bar"}'

это допустимый JSON, который не завершает элемент скрипта.

Я понял эту маленькую жемчужину внутри движка шаблонов Jinja. Это то, что запускается, когда вы используете {{data|tojson}} фильтр.

def htmlsafe_json_dumps(obj, dumper=None, **kwargs):
    """Works exactly like :func:'dumps' but is safe for use in ''<script>''
    tags.  It accepts the same arguments and returns a JSON string.  Note that
    this is available in templates through the ''|tojson'' filter which will
    also mark the result as safe.  Due to how this function escapes certain
    characters this is safe even if used outside of ''<script>'' tags.
    The following characters are escaped in strings:
    -   ''<''
    -   ''>''
    -   ''&''
    -   '''''
    This makes it safe to embed such strings in any place in HTML with the
    notable exception of double quoted attributes.  In that case single
    quote your attributes or HTML escape it in addition.
    """
    if dumper is None:
        dumper = json.dumps
    rv = dumper(obj, **kwargs) \
        .replace(u'<', u'\\u003c') \
        .replace(u'>', u'\\u003e') \
        .replace(u'&', u'\\u0026') \
        .replace(u"'", u'\\u0027')
    return Markup(rv)

(Вы могли бы использовать \x3C вместо \xu003C, и это работало бы в элементе скрипта, потому что это допустимый JavaScript. Но с тем же успехом можно придерживаться допустимого JSON.)