Допустим, у нас есть JS скрипт, вставленный в страницу, и нам надо из серверного языка вроде PHP передать в него какое-то значение: число, строку, массив или что-то еще. Вот код, который это делает:
<script>
var userName = <?= json_encode($userName); ?>;
var userId = <?= json_encode($userId); ?>;
...json_encode генерирует JSON-объект из переданных данных. А так как JSON - это (почти) подмножество языка Яваскрипт, то (почти) любой JSON-код является корректным Яваскрипт-выражением. Таким образом, что бы мы не передавали в качестве $userName, синтаксис JS скрипта не будет нарушен.
(почти) здесь относится к паре экзотических символов которые недопустимы в JS строках, но допустимы в JSON:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
the Unicode line separator (U+2028) and paragraph separator (U+2029) characters are permitted;
Если эти символы встретятся в $userName, и без изменений попадут в Яваскрипт-код, произойдет ошибка синтаксиса. К счастью, json_encode() кодирует эти символы как \u2028, а не вставляет, как есть, и ошибки не происходит.
Также, теоретически, в $userName может встретиться последовательность </ (или </script>), которая закрывает тег <script> и таким образом позволяет вставить произвольный HTML-код. К счастью, json_encode() экранирует слеш / как \/ и </script> превращается в <\/script>, что предотвращает эту уязвимость.
Мы намеренно не используем здесь htmlspecialchars(), который надо использовать при подстановке переменных в HTML-код. Дело в том, что в HTML-тегах script и style содержимое воспринимается как есть и HTML-мнемоники вроде " не воспринимаются как кавычка. Если бы мы написали htmlspecialchars(json_encode($x)), то могли бы получить некорректный JS код вроде
var userName = "Ivan";Если не использовать json_encode(), то получается риск допустить ошибку или оставить уязвимость. Например, JS строки не могут содержать перенос строки и мы должны бы были за этим следить. Также, в JS строке не должна встречаться последовательность символов </script> или </. json_encode() решает эти проблемы.
В случае использования шаблонизатора twig с автоэкранированием надо добавить фильтр raw, чтобы он не пытался применить функцию htmlspecialchars():
<script>
var userName = {{ userName | json_encode | raw }};Стоит вынести все переменные с подстановками в самое начало JS скрипта, а не раскидывать их по коду.
Неправильный код создает такие возможности атаки:
- можно нарушить синтаксис JS и тем самым предотвратить выполнение JS скрипта
- если удастся подставить значение
</script>в строку, можно закрыть тег<script>и вставить за ним произвольный HTML код.
Вот пример неправильного кода:
<script>
var userName = '<?= $userName ?>';Если злоумышленнику удастся подставить в $userName последовательность '; alert("hello!");var t = ' то получится инъекция и злоумышленник сможет выполнить на странице произвольный JS-код (например, ворующий куки у пользователей или делающий что-то на сайте от их имени).