Как передавать данные с сервера Node / Express клиенту (JavaScript), разрешая очищенный HTML, но предотвращая XSS?

Каким будет лучший способ передать данные из экспресс-бэкэнда клиенту (JavaScript), чтобы можно было отображать данные в DOM, используя какой-то вид рендеринга на стороне клиента, при этом разрешая очищенный белый список HTML и по-прежнему предотвращая XSS?

Скажем, например, метод рендеринга ответа узла выглядит так:

res.render('index', {
    data : {
        foo: '<a href="myhomepage">foo</p>'
    }
}); 

И по какой-то причине в него входят неэкранированные символы. Обычно включение его в шаблон html / ejs было бы тривиальным, например:

<script>
    myVar = JSON.parse('<%- JSON.stringify(data) %>');
</script> 

Но он подавляется первой двойной кавычкой тега привязки: Unexpected token h in JSON at position 18

Мы определенно хотим разрешить строки с символами HTML (полужирным шрифтом, якорными ссылками и т. Д.), Но хотим удалить теги сценария и другие подобные опасные теги.

Есть ли простой способ добиться этого? Или нужно будет пройти все уровни данных, передаваемых методу рендеринга, и запустить все строковые ключи через какой-то движок дезинфицирующего средства XSS?


person Afs35mm    schedule 30.10.2017    source источник
comment
Не уверен, почему он работает с JSON-тегом, но я бы передал данные через DOMPurify, прежде чем добавлять их в DOM.   -  person Erlend    schedule 31.10.2017


Ответы (1)


Если вам нужно разрешить ненадежный HTML-код, его можно очистить с помощью внешних библиотек. Я не использовал его, но упомянутый в комментарии DOMPurify выглядит неплохо. Это можно сделать либо на сервере, либо на клиенте перед добавлением содержимого myVar в DOM.

Другая проблема - передача данных с сервера клиенту. Это отдельная проблема. JSON.stringify создает строку JavaScript, но она анализируется дважды. Один раз механизмом JavaScript в браузере, когда скрипт загружен, и еще раз JSON.parse. Это приводит к тому, что экранированные кавычки не экранируются до достижения JSON.parse, и он интерпретирует их неверно.

If data is:

{ foo: '<a href="myhomepage">foo</p>' }

Тогда JSON.stringify произведет:

{"foo":"<a href=\"myhomepage\">foo</p>"}

Он вставляется на страницу html и становится:

myVar = JSON.parse('{"foo":"<a href=\"myhomepage\">foo</p>"}');

Здесь последовательности \" интерпретируются синтаксическим анализатором JavaScript как кавычки, так что это то же самое, что:

myVar = JSON.parse('{"foo":"<a href="myhomepage">foo</p>"}');

Кавычки href теперь интерпретируются как закрытые кавычки для свойства, а не как открытые кавычки для атрибута.

Вы можете это исправить, но есть еще одна проблема с JSON.parse. Он не учитывает окружающий HTML-контекст. Поскольку он находится внутри тегов <script>, то, если данные:

{ foo: '</script><script>alert(1)</script>' }

Это создаст HTML:

<script>
    myVar = JSON.parse('{"foo":"</script><script>alert(1)</script>"}');
</script>

Он содержит допустимую строку JavaScript. Он не выходит за пределы строки, а вместо этого выходит непосредственно из тега сценария и повторно входит с новым сценарием, что приводит к проблеме XSS.

Вместо этого вам нужно что-то, что экранирует метасимволы HTML и JS. Что-то вроде этого должно получиться:

myVar = JSON.parse(unescape('<%- escape(JSON.stringify(data)) %>'));
person fgb    schedule 31.10.2017
comment
Я тоже этого ожидал, но все это задыхается от JSON.parse('{"foo":"<a href=\"myhomepage\">foo</p>"}');. Если вы просто введете это в консоль, появится ошибка VM5523:1 Uncaught SyntaxError: Unexpected token m in JSON at position 17, потому что это недопустимый JSON. - person Afs35mm; 01.11.2017