Допустим, у нас есть 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-код (например, ворующий куки у пользователей или делающий что-то на сайте от их имени).