Викторина: Можете ли вы защититься от XSS-уязвимости?
Проверьте свои знания и навыки безопасности в этой викторине! Можете ли вы предотвратить захват злоумышленником контроля над HTML-страницей?
Во всех заданиях вы будете решать один и
тот же вопрос: как правильно отобразить
переменную $str
в HTML-странице, не
создавая XSS-уязвимость.
Основой защиты является escaping, то есть
замена символов со специальными значениями
на соответствующие последовательности.
Например, при выводе строки в HTML-текст, в
которой символ <
имеет специальное
значение (обозначает начало тега), мы
заменяем его на HTML-сущность <
, и
браузер корректно отображает символ
<
.
Будьте бдительны, так как XSS-уязвимость очень серьезна. Она может привести к тому, что злоумышленник получит контроль над страницей или даже учетной записью пользователя. Удачи вам и пусть вам удастся сохранить HTML-страницу в безопасности!
Первая тройка вопросов
Укажите, какие символы и как нужно обрабатывать в первом, втором и третьем примерах:
1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>
Если бы вывод никак не обрабатывался, он
стал бы частью отображаемой страницы. Если
бы злоумышленнику удалось вставить строку
'foo" onclick="evilCode()'
в переменную, а вывод
не был бы обработан, то это привело бы к
выполнению его кода при нажатии на
элемент:
$str = 'foo" onclick="evilCode()'
❌ not treated: <input value="foo" onclick="evilCode()">
✅ treated: <input value="foo" onclick="evilCode()">
Решения для каждого примера:
- символы
<
и&
представляют собой начало HTML-тега и сущности; замените их на<
и&
- символы
"
и&
представляют конец значения атрибута и начало сущности HTML; замените их на"
и&
- символы
'
и&
представляют конец значения атрибута и начало сущности HTML; замените их на'
и&
За каждый правильный ответ вы получите по одному баллу. Конечно, во всех трех случаях вы можете заменить сущностями и другие символы; это не причинит вреда, но это не обязательно.
Вопрос № 4
Далее, какие символы необходимо заменить при отображении переменной в данном контексте?
<input value=<?= $str ?>>
Решение: Как вы можете видеть, здесь не
хватает кавычек. Самый простой способ –
просто добавить кавычки, а затем
экранировать их, как в предыдущем вопросе.
Есть и второе решение, которое заключается
в замене пробелов и всех символов, имеющих
особое значение внутри тега, таких как
>
, /
, =
, и некоторые
другие, на HTML-сущности.
Вопрос № 5
Теперь становится интереснее. С какими персонажами нужно обращаться в данном контексте:
<script>
let foo = '<?= $str ?>';
</script>
Решение: Внутри <script>
правила
экранирования определяются JavaScript. Здесь не
используются сущности HTML, но есть одно
специальное правило. Так какие символы мы
должны экранировать? Внутри строки JavaScript
мы, естественно, экранируем символ '
,
который отделяет ее, с помощью обратной
косой черты, заменяя его на \'
.
Поскольку JavaScript не поддерживает
многострочные строки (за исключением шаблонных
литералов), нам также необходимо
экранировать символы новой строки. Однако
имейте в виду, что помимо обычных символов
\n
и \r
, JavaScript также считает
символы Юникода \u2028
и \u2029
символами новой строки, которые мы также
должны экранировать. Наконец, упомянутое
специальное правило: строка не должна
содержать </script
. Этого можно
избежать, например, заменив его на
<\/script
.
Если вы это знали, поздравляем.
Вопрос № 6
Следующий контекст кажется просто вариацией предыдущего. Как вы думаете, будет ли лечение отличаться?
<p onclick="foo('<?= $str ?>')"></p>
Решение: Здесь снова применяются правила
экранирования для строк JavaScript, но в отличие
от предыдущего контекста, где сущности HTML
не экранировались, здесь они экранируются.
Поэтому сначала мы экранируем строку JavaScript
с помощью обратных слешей, а затем заменим
специальные символы ("
и &
)
на HTML-сущности. Будьте внимательны,
правильный порядок очень важен.
Как видите, один и тот же литерал JavaScript
может быть по-разному закодирован в
элементе <script>
элементе и
по-разному в атрибуте!
Вопрос № 7
Давайте вернемся от JavaScript обратно к HTML. Какие символы нам нужно заменить внутри комментария и как?
<!-- <?= $str ?> -->
Решение: Внутри HTML (и XML) комментария могут
появляться все традиционные специальные
символы, такие как <
, &
,
"
и '
. Что запрещено, и это
может вас удивить, так это пара символов
--
. Экранирование этой
последовательности не предусмотрено,
поэтому вы сами решаете, чем ее заменить. Вы
можете перемежать их пробелами. Или,
например, заменить их на ==
.
Вопрос № 8
Мы приближаемся к концу, поэтому давайте попробуем разнообразить вопрос. Попробуйте подумать о том, что нужно быть осторожным при печати переменной в данном контексте:
<a href="<?= $str ?>">...</a>
Решение: Помимо экранирования, важно
также убедиться, что URL не содержит опасной
схемы типа javascript:
, поскольку URL,
составленный подобным образом, будет
выполнять код злоумышленника при щелчке.
Вопрос № 9
Наконец, угощение для настоящих знатоков.
Это пример приложения, использующего
современный JavaScript-фреймворк, а именно Vue.
Давайте посмотрим, сможете ли вы понять, что
нужно быть осторожным при печати
переменной внутри элемента #app
:
<div id="app">
<?= $str ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
...
})
</script>
Этот код создает приложение Vue, которое
будет выведено в элемент #app
. Vue
интерпретирует содержимое этого элемента
как свой шаблон. А внутри шаблона он интерпретирует
двойные фигурные скобки, которые
представляют собой вывод переменных или
вызов кода JavaScript (например, {{ foo }}
).
Таким образом, внутри элемента #app
,
помимо символов <
и &
, пара
{{
также имеет особое значение,
которое нам нужно заменить на другую
подходящую последовательность, чтобы Vue не
интерпретировал ее как свой собственный
тег. Замена на HTML-сущности в данном случае
не помогает. Как с этим справиться? Есть
один трюк: вставьте пустой HTML-комментарий
между скобками {<!-- -->{
и Vue
проигнорирует эту последовательность.
Результаты викторины
Как вы справились с викториной? Сколько у вас правильных ответов? Если вы правильно ответили хотя бы на 4 вопроса, вы входите в 8% лучших решателей – поздравляем!
Как бы то ни было, обеспечение безопасности вашего сайта требует правильной обработки выходных данных в любых ситуациях.
Если вы были удивлены тем, как много различных контекстов может появиться на типичной HTML-странице, знайте, что мы упомянули далеко не все из них. Это сделало бы викторину намного длиннее. Тем не менее, вам не нужно быть экспертом по экранированию в каждом контексте, если ваша система шаблонов может с этим справиться.
Итак, давайте проверим их.
Как работают системы шаблонов?
Все современные системы шаблонизации имеют функцию autoescaping, которая автоматически экранирует все выводимые переменные. Если они делают это правильно, ваш сайт в безопасности. Если они делают это плохо, сайт подвержен риску XSS-уязвимости со всеми вытекающими отсюда серьезными последствиями.
Мы протестируем популярные системы шаблонизации из вопросов этого теста, чтобы определить эффективность их автоэскейпинга. Начнем обзор систем шаблонов PHP.
Twig ❌
Первой будет система шаблонов Twig (версия 3.5), наиболее
часто используемая в сочетании с
фреймворком Symfony. Мы поручим ей ответить на
все вопросы викторины. Переменная $str
всегда будет заполнена хитрой строкой, и мы
посмотрим, как она справится с выводом.
Результаты вы можете увидеть справа. Вы
также можете изучить его ответы и поведение
на игровой
площадке.
{% set str = "<'\"&" %}
1) <p>{{ str }}</p>
2) <input value="{{ str }}">
3) <input value='{{ str }}'>
{% set str = "foo onclick=evilCode()" %}
4) <input value={{ str }}>
{% set str = "'\"\n\u{2028}" %}
5) <script> let foo = '{{ str }}'; </script>
6) <p onclick="foo('{{ str }}')"></p>
{% set str = "-- ---" %}
7) <!-- {{ str }} -->
{% set str = "javascript:evilCode()" %}
8) <a href="{{ str }}">...</a>
{% set str = "{{ foo }}" %}
9) <div id="app"> {{ str }} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
❌ <input value=foo onclick=evilCode()>
❌ <script> let foo = '"u{2028}; </script>
❌ <p onclick="foo('"u{2028})"></p>
❌ <!-- -- --- -->
❌ <a href="javascript:evilCode()">...</a>
❌ <div id="app"> {{ foo }} </div>
**Твиг провалил шесть из девяти тестов!
К сожалению, автоматическое экранирование в Twig работает только в HTML-тексте и атрибутах, да и то только тогда, когда они заключены в кавычки. Как только кавычки пропадают, Twig не сообщает об ошибке и создает XSS брешь в безопасности.
Это особенно неприятно, потому что именно так записываются значения атрибутов в популярных библиотеках, таких как React или Svelte. Программист, использующий и Twig, и React, может совершенно естественно забыть о кавычках.
Во всех остальных примерах
автозавершение Twig также не работает. В
контекстах (5) и (6) необходимо ручное
экранирование с помощью клавиш
{{ str|escape('js') }}
а в других контекстах Twig
даже не предлагает функции экранирования. В
нем также отсутствует защита от печати
вредоносной ссылки (8) и поддержка шаблонов
Vue (9).
Blade ❌❌
Второй участник – это система шаблонов Blade (версия 10.9), которая тесно интегрирована с Laravel и его экосистемой. И снова мы проверим ее возможности в вопросах нашей викторины. Вы также можете изучить ее ответы на игровой площадке.
@php($str = "<'\"&")
1) <p>{{ $str }}</p>
2) <input value="{{ $str }}">
3) <input value='{{ $str }}'>
@php($str = "foo onclick=evilCode()")
4) <input value={{ $str }}>
@php($str = "'\"\n\u{2028}")
5) <script> let foo = {{ $str }}; </script>
6) <p onclick="foo({{ $str }})"></p>
@php($str = "-- ---")
7) <!-- {{ $str }} -->
@php($str = "javascript:evilCode()")
8) <a href="{{ $str }}">...</a>
@php($str = "{{ foo }}")
9) <div id="app"> {{ $str }} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
❌ <input value=foo onclick=evilCode()>
❌ <script> let foo = '" ; </script>
❌ <p onclick="foo('" )"></p>
❌ <!-- -- --- -->
❌ <a href="javascript:evilCode()">...</a>
❌❌ <div id="app"> <?php echo e(foo); ?> </div>
Лезвие не прошло шесть из девяти тестов!
Результат аналогичен Twig. Опять же,
автоматическое экранирование работает
только в HTML-тексте и атрибутах и только если
они заключены в кавычки. Во всех остальных
примерах автоматическое экранирование Blade
также не работает. В контекстах (5) и (6)
необходимо ручное экранирование с помощью
функции {{ Js::from($str) }}
. Для других
контекстов Blade даже не предлагает функцию
экранирования. В нем также отсутствует
защита от печати вредоносной ссылки (8) или
поддержка шаблонов Vue (9).
Однако, что удивительно, так это
неработающая директива @php
в Blade,
которая приводит к выводу собственного
PHP-кода непосредственно в вывод, как видно
из последней строки.
Латте ✅
Трио завершено системой шаблонов Latte (версия 3.0). Мы протестируем ее автоэскейпинг. Вы также можете изучить ее ответы и поведение на игровой площадке.
{var $str = "<'\"&"}
1) <p>{$str}</p>
2) <input value="{$str}">
3) <input value='{$str}'>
{var $str = "foo onclick=evilCode()"}
4) <input value={$str}>
{var $str = "'\"\n\u{2028}"}
5) <script> let foo = {$str}; </script>
6) <p onclick="foo({$str})"></p>
{var $str = "-- ---"}
7) <!-- {$str} -->
{var $str = "javascript:evilCode()"}
8) <a href="{$str}">...</a>
{var $str = "{{ foo }}"}
9) <div id="app"> {$str} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
✅ <input value="foo onclick=evilCode()">
✅ <script> let foo = "'\"\n\u2028"; </script>
✅ <p onclick="foo("'\"\n\u2028")"></p>
✅ <!-- - - - - - -->
✅ <a href="">...</a>
✅ <div id="app"> {<!-- -->{ foo }} </div>
Латте отлично справился со всеми девятью заданиями!
Он справился с отсутствующими кавычками в
атрибутах HTML, обработал JavaScript как в
<script>
элемент и атрибуты, а также
справился с запрещенной
последовательностью в HTML-комментариях.
Более того, он предотвратил ситуацию, когда нажатие на вредоносную ссылку, предоставленную злоумышленником, могло привести к выполнению его кода. И она справилась с экранированием тегов для Vue.
Бонусный тест
Одной из важнейших возможностей всех
систем шаблонизации является работа с
блоками и связанным с ними наследованием
шаблонов. Поэтому мы дадим всем
протестированным шаблонизаторам еще одно
задание. Мы создадим блок description
,
который выведем в HTML-атрибуте. В реальном
мире определение блока, конечно же, будет
находиться в дочернем шаблоне, а его вывод в
родительском шаблоне, например, в макете.
Это всего лишь упрощенная форма, но ее
достаточно, чтобы проверить
автозакрепление при выводе блоков. Как они
себя показали?
Twig: не удалось ❌ при выводе блоков, символы не экранируются должным образом
{% block description %}
rock n' roll
{% endblock %}
<meta name='description'
content='{{ block('description') }}'>
<meta name='description'
content=' rock n' roll '> ❌
Лезвие: сбой ❌ при выводе блоков, символы неправильно экранируются
@section('description')
rock n' roll
@endsection
<meta name='description'
content='@yield('description')'>
<meta name='description'
content=' rock n' roll '> ❌
Латте: передал ✅ при выводе блоков, он правильно обрабатывал проблемные символы
{block description}
rock n' roll
{/block}
<meta name='description'
content='{include description}'>
<meta name='description'
content=' rock n' roll '> ✅
Почему так много веб-сайтов уязвимы?
Автоскейпинг в таких системах, как Twig, Blade
или Smarty, работает путем простой замены пяти
символов <>"'&
на HTML-сущности и не
различает контекст. Поэтому он работает
только в некоторых ситуациях и не работает
во всех остальных. Наивная автозамена –
это опасная функция, потому что она создает
ложное чувство безопасности..
Поэтому неудивительно, что в настоящее время более 27% веб-сайтов имеют критические уязвимости, в основном XSS (источник: Acunetix Web Vulnerability Report). Как выйти из этой ситуации? Используйте систему шаблонов, которая различает контексты.
Latte – единственная система шаблонизации PHP, которая не воспринимает шаблон как просто строку символов, а понимает HTML. Она знает, что такое теги, атрибуты и т.д. Она различает контексты. И поэтому она правильно экранирует в HTML-тексте, по-разному внутри HTML-тегов, по-разному внутри JavaScript и т.д.
Таким образом, Latte представляет собой единственную безопасную систему шаблонов..
Кроме того, благодаря пониманию HTML, она предлагает замечательные n:attributes, которые нравятся пользователям:.
<ul n:if="$menu">
<li n:foreach="$menu->getItems() as $item">{$item->title}</li>
</ul>
Чтобы оставить комментарий, пожалуйста, войдите в систему