Тест: Чи можете ви захиститися від XSS вразливостей?
Перевірте свої знання та навички безпеки в цьому тесті! Чи можете ви перешкодити зловмиснику отримати контроль над HTML-сторінкою?
У всіх завданнях ви вирішуватимете одне й
те саме питання: як правильно відобразити
змінну $str
на HTML-сторінці, не
створюючи при цьому XSS-уразливості.
Основою захисту є екранування, тобто
заміна символів зі спеціальним значенням
на відповідні послідовності. Наприклад, при
виведенні в 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 також розглядає символи Unicode
\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 ✅
Трійку замикає система шаблонування 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>
Latte відмінно впоралася з усіма дев'ятьма завданнями!
Він впорався з відсутніми лапками в
атрибутах HTML, обробив JavaScript як в елементі
<script>
як в елементі, так і в
атрибутах, а також впорався із забороненою
послідовністю в HTML-коментарях.
Більше того, він запобіг ситуації, коли перехід за шкідливим посиланням, наданим зловмисником, міг призвести до виконання його коду. І він впорався з екрануванням тегів для Vue.
Бонусний тест
Однією з важливих можливостей усіх систем
шаблонів є робота з блоками та пов'язане з
цим успадкування шаблонів. Тому ми дамо
всім протестованим системам шаблонів ще
одне завдання. Ми створимо блок
description
, який виведемо в атрибуті HTML. У
реальному світі визначення блоку, звичайно,
буде знаходитися в дочірньому шаблоні, а
його виведення – в батьківському шаблоні,
наприклад, в макеті. Це лише спрощена форма,
але її достатньо, щоб протестувати
автообтікання при виведенні блоків. Як вони
себе показали?
Гілка: не вдалося ❌ при виведенні блоків символи не екрануються належним чином
{% 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 '> ❌
Latte: пройдено ✅ при виведенні блоків коректно обробляв проблемні символи
{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:атрибути, які так подобаються користувачам:.
<ul n:if="$menu">
<li n:foreach="$menu->getItems() as $item">{$item->title}</li>
</ul>
Щоб залишити коментар, будь ласка, увійдіть до системи