Как предотвратить XSS атаку. Два уровня защиты

Источник: «How to prevent XSS»
В этой стать мы опишем некоторые общепринятые принципы предотвращения XSS уязвимостей и способы использования различных распространённых технологий защиты от XSS атак.

Предотвращение Межсайтовых сценариев (XSS) обычно достигается за счёт двух уровней защиты:

Кодирование данных на выходе

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

В контексте HTML вы должны преобразовать значения, не внесённые в белый список, в HTML сущности:

В контексте строки JavaScript не буквенно-цифровые значения должны быть экранированы в Unicode:

Иногда нужно применить несколько слоёв кодирования в правильном порядке. Например, чтобы безопасно встроить пользовательский ввод в обработчик события, вам необходимо иметь дело как с контекстом JavaScript, так и с контекстом HTML. Итак, вам нужно сначала экранировать в Unicode ввод, а затем преобразовать в HTML-сущности:

<a href="#" onclick="x='This string needs two layers of escaping'">test</a>

Валидация вводимых данных

Кодирование, вероятно, является самой важной линией защиты от XSS, но этого недостаточно для предотвращения уязвимостей в каждом контексте. Вы также должны как можно более строго проверять входные данные в момент их первого получения от пользователя.

Примеры проверки ввода включают:

В идеале проверка ввода должна работать, блокируя недопустимый ввод. Альтернативный подход, заключающийся в попытке очистить не валидный ввод, чтобы сделать его валидным, более подвержен ошибкам, и его следует по возможности избегать.

Белый список или Чёрный список

Валидация ввода должна использовать белые списки, а не чёрные списки. Например, вместо того, чтобы пытаться составить список всех вредоносных протоколов (javascript, data и т.д.), просто составьте список безопасных протоколов (HTTP, HTTPS) и запретите всё, чего нет в списке. Это гарантирует, что ваша защита не сломается, когда появятся новые вредоносные протоколы, и сделает её менее восприимчивой к атакам пытающимся скрыть недопустимые значения для обхода чёрного списка.

Разрешение безопасного HTML

По возможности следует избегать предоставления пользователям размещать HTML-разметку, но иногда это бывает требование бизнеса. Например, сайт блог может разрешать размещение комментариев, содержащих некоторую ограниченную HTML-разметку.

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

Наименее плохой вариант — использовать библиотеку JavaScript, выполняющую фильтрацию и кодирование в браузере пользователя, например DOMPurify. Другие библиотеки позволяют пользователям предоставлять контент в формате markdown и преобразовывать его в HTML. К сожалению, все эти библиотеки имеют XSS уязвимости появляющиеся время от времени, так что это не идеальное решение. Если вы используете одну из них, вам следует следить за обновлениями безопасности.

Как предотвратить XSS с помощью механизма шаблонов

Многие современные веб-сайты используют механизмы шаблонов на стороне сервере, такие как Twig, Blade и Freemarker, для встраивания динамического содержимого в HTML. Обычно они определяют свою собственную систему экранирования. Например, в Twig вы можете использовать фильтр e() м аргументом определяющим контекст:

{{ user.firstname | e('html') }}

Некоторые механизмы шаблонов, такие как Jinja и React, по умолчанию избегают динамического содержимого, что эффективно предотвращает большинство случаев XSS.

Мы рекомендуем внимательно изучить функции экранирования, когда вы решаете, следует ли использовать данный механизм шаблонов или платформу.

Примечание. Если вы напрямую объединяете пользовательский ввод в строки шаблона, вы будете уязвимы для внедрения шаблона на стороне сервера, что зачастую более серьёзно, чем XSS.

Как предотвратить XSS в PHP

В PHP есть встроенная функция для кодирования сущностей, называемая htmlentities. Вы должны вызывать эту функцию для экранирования ввода, когда находитесь внутри HTML контекста. Функция должна вызываться с тремя аргументами:

Например:

<?php echo htmlentities($input, ENT_QUOTES, 'UTF-8');?>

Когда вы находитесь в контексте строки JavaScript, вам необходимо экранировать ввод Unicode, как уже было описано. К сожалению, PHP не предоставляет API для экранирования Unicode строки. Вот пример кода, как реализовать это в PHP:

<?php function jsEscape($str) {
$output = '';
$str = str_split($str);
for($i=0;$i<count($str);$i++) {
$chrNum = ord($str[$i]);
$chr = $str[$i];
if($chrNum === 226) {
if(isset($str[$i+1]) && ord($str[$i+1]) === 128) {
if(isset($str[$i+2]) && ord($str[$i+2]) === 168) {
$output .= '\u2028';
$i += 2;
continue;
}
if(isset($str[$i+2]) && ord($str[$i+2]) === 169) {
$output .= '\u2029';
$i += 2;
continue;
}
}
}
switch($chr) {
case "'":
case '"':
case "\n";
case "\r";
case "&";
case "\\";
case "<":
case ">":
$output .= sprintf("\\u%04x", $chrNum);
break;
default:
$output .= $str[$i];
break;
}
}
return $output;
}
?>

Пример использования jsEscape в PHP:

<script>x = '<?php echo jsEscape($_GET['x'])?>';</script>

В качестве альтернативы вы можете использовать механизм шаблонов.

Как предотвратить XSS на стороне клиента в JavaScript

Для экранирования пользовательского ввода в HTML а контексте JavaScript, вам нужен собственный кодировщик HTML, поскольку JavaScript не предоставляет API для кодирования HTML. Вот пример кода JavaScript преобразующий строку в HTML-сущности:

function htmlEncode(str){
return String(str).replace(/[^\w. ]/gi, function(c){
return '&#'+c.charCodeAt(0)+';';
});
}

Данную функцию можно использовать следующим образом:

<script>document.body.innerHTML = htmlEncode(untrustedValue)</script>

Если ваш ввод находится внутри строки JavaScript, вам нужен кодировщик выполняющий Unicode экранирование. Вот пример Unicode кодировщика:

function jsEscape(str){
return String(str).replace(/[^\w. ]/gi, function(c){
return '\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4);
});
}

Вы можете использовать его следующим способом:

<script>document.write('<script>x="'+jsEscape(untrustedValue)+'";<\/script>')</script>

Как предотвратить XSS в jQuery

Наиболее распространённая форма XSS в jQuery — передача пользовательского ввода селектору jQuery. Веб-разработчики часто использовали location.hash и передавали его селектору, что вызывало XSS, поскольку jQuery отображал HTML. Разработчики jQuery распознали эту проблему и исправили логику селектора для проверки, начинается ли ввод с хэша. Теперь jQuery будет отображать HTML только в том случае, если первым символом является <. Если вы передаёте ненадёжные данные в селектор jQuery, убедитесь, что вы правильно экранируете значение, используя функцию jsEscape приведённую выше.

Смягчение XSS с помощью Политики Безопасности Контента (CSP)

Политика безопасности контента (CSP) — последняя линия защиты от межсайтовых сценариев. Если ваша защита от XSS не работает, вы можете использовать CSP для смягчения XSS, ограничивая действия злоумышленника.

CSP позволяет управлять различными вещами, например, можно ли загружать внешние сценарии и будут ли выполняться встроенные сценарии. Чтобы развернуть CSP, необходимо включить заголовок HTTP ответа с именем Content-Security-Policy и значением содержащим вашу политику.

Пример CSP выглядит следующим образом:

default-src 'self'; script-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'none';

Эта политика указывает, что такие ресурсы, как изображения и скрипты, могут загружаться только из того же источника, что и главная страница. Таким образом, даже если злоумышленник сможет успешно внедрить полезную нагрузку XSS, он сможет загружать ресурсы только из текущего источника. Это значительно снижает вероятность того, что злоумышленник сможет воспользоваться XSS-уязвимостью.

Если вам потребуется загрузка внешних ресурсов, убедитесь, что вы разрешаете только сценарии, которые не помогают злоумышленнику использовать ваш сайт. Например, если вы внесёте ы белый список определённые домены, злоумышленник сможет загрузить любой сценарий с этих доменов. По возможности старайтесь размещать ресурсы на собственном домене.

Если это невозможно, вы можете использовать политику хэша или основанную на nonce для разрешения сценариев в разных доменах. Nonce — случайная строка, добавляемая в качестве атрибута сценария или ресурса, которые будут исполняться только в том случае, если случайная строка совпадает со сгенерированной сервером. Злоумышленник не может угадать рандомизированную строку и, следовательно, не может вызвать сценарий или ресурс с допустимым одноразовым номером, поэтому ресурс не будет выполнен.

Дополнительные материалы

Предыдущая Статья

Laravel: Eloquent Наблюдатели не выполняются при массовых событиях

Следующая Статья

Eloquent: Оптимизация подсчёта Моделей по Отношениям