Разделы портала

Онлайн-тренинги

.
Руководство по XSS, часть 3
06.02.2020 00:00

Авторы: Джейкоб Каллин и Ирен Лобо Валбуэна (Jakob Kallin, Irene Lobo Valbuena)
Оригинал статьи: https://excess-xss.com/
Перевод: Ольга Алифанова

Руководство по XSS, часть 1

Руководство по XXS, часть 2

Предотвращение XSS

Методы предотвращения XSS

Напомним, что XSS-атака – это тип инъекции кода: вредоносный код ошибочно интерпретируется как пользовательский ввод. Чтобы предотвратить этот тип инъекций, необходимо безопасно обращаться с вводом. Для веб-разработки существует два фундаментально разных подхода к безопасной обработке вводимой информации:

  • Шифрование, когда браузер интерпретирует пользовательский ввод только как данные, а не как код.
  • Валидация, фильтрующая пользовательский ввод – браузер интерпретирует его как код, игнорируя вредоносные команды.

Несмотря на то, что эти методы предотвращения XSS в корне различны, у них есть ряд общих характеристик, которые важно понимать при их использовании:

Контекст

Безопасную обработку вводимой информации нужно выполнять по-разному в зависимости от того, на каком месте страницы этот ввод применяется.

Вход/выход

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

Клиент/сервер

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

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

Контекст обработки вводимой информации

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

Контекст

Пример кода

Содержание элемента HTML

<div>userInput</div>

Значение атрибута HTML

<input value="userInput">

Значение запроса URL

http://example.com/?parameter=userInput

Значение CSS

color: userInput

Значение JavaScript

var name = "userInput";

Почему контекст – это важно

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

К примеру, если на каком-то моменте сайт напрямую вставляет пользовательский ввод в атрибут HTML, злоумышленник сможет внедрить вредоносный скрипт, начав свой ввод с кавычки, как показано ниже:

In all of the contexts described, an XSS vulnerability would arise if user input were inserted before first being encoded or validated. An attacker would then be able to inject malicious code by simply inserting the closing delimiter for that context and following it with the malicious code.

Код приложения:

<input value="userInput">

Вредоносная строка:

"><script>...</script><input value="

Результат:

<input value=""><script>...</script><input value="">

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

Обработка ввода на входе/выходе

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

Проблема тут в том, что, как говорилось ранее, пользовательский ввод может осуществляться в различных контекстах на странице. Нет легкого способа определить, в каком контексте будет использована прибывшая на сайт входящая информация, и та же самая пользовательская информация часто может использоваться в разных контекстах. Надежда на обработку ввода на входе с целью предотвращения XSS, следовательно, решение очень хрупкое, подверженное ошибкам. Функция обрезания "магических кавычек" в PHP – пример такого решения.

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

Где нужно безопасно обрабатывать входящую информацию

В большинстве современных веб-приложений пользовательский ввод обрабатывается как серверным, так и клиентским кодом. Чтобы защититься от всех типов XSS, безопасная обработка ввода должна проводиться и на серверной, и на клиентской стороне.

  • Чтобы защититься от традиционных XSS, нужно безопасно обрабатывать ввод на стороне кода сервера. Это делается путем языка, поддерживаемого сервером.
  • Чтобы защититься от XSS на основе DOM, когда сервер не получает вредоносную строку (к примеру, это обсуждавшаяся ранее атака через идентификатор фрагмента), нужно безопасно обрабатывать ввод на стороне кода клиента. Это делается через JavaScript.

Теперь, когда мы пояснили, почему так важны контекст, разница между обработкой на входе и на выходе, и безопасность обработки информации на стороне и сервера, и клиента, перейдем к объяснению, как на самом деле работают два типа безопасной обработки информации (шифрование и валидация).

Шифрование

Шифрование – это способ экранирования пользовательского ввода: браузер интерпретирует его только как данные, а не как код. Наиболее распространенный тип шифрования в веб-разработке – это HTML-экранирование, трансформирующее символы вроде < и > в &lt; и &gt; соответственно.

Псевдокод ниже – это пример того, как пользовательский ввод может быть закодирован с использованием HTML-экранирования, а затем вставлен на страницу серверным скриптом:

print "<html>"
print "Latest comment: "
print encodeHtml(userInput)
print "</html>"
Если пользовательский ввод – это строка <script>...</script>, HTML в результате будет таким:
<html>
Latest comment:
&lt;script&gt;...&lt;/script&gt;
</html>
Так как все символы особого назначения были экранированы, браузер не воспримет такой ввод, как HTML.

Шифрование на клиентской и серверной стороне

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

Шифруя данные на серверной стороне, вы полагаетесь на функции, имеющиеся в языке вашего сервера или фреймворка. Из-за большого количества доступных языков и фреймворков это руководство не покроет в деталях шифрование во всех них. Однако умение работать с функциями шифрования, которые используются в JavaScript на клиентской стороне, пригодится вам при создании серверного кода.

Шифрование на клиентской стороне

Шифруя пользовательский ввод на клиентской стороне при помощи JavaScript, можно воспользоваться рядом встроенных методов и свойств, которые автоматически шифруют всю информацию с учетом контекста:

When encoding user input on the client-side using JavaScript, there are several built-in methods and properties that automatically encode all data in a context-aware manner:

Контекст

Метод/Свойство

Содержание элемента HTML

node.textContent = userInput

Значение атрибута HTML

element.setAttribute(attribute, userInput)

или

element[attribute] = userInput

Значение запроса URL

window.encodeURIComponent(userInput)

Значение CSS

element.style.property = userInput

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

Ограничения шифрования

В некоторых контекстах ввод вредоносных строк возможен даже при наличии шифрования. Яркий пример: пользовательский ввод используется для создания URL, как в примере ниже:

document.querySelector('a').href = userInput

Присвоение значения свойству опорного элемента href автоматически шифрует его, и оно становится просто значением атрибута, но само по себе это не мешает злоумышленнику вставить URL, начинающийся с "javascript:". При нажатии на такую ссылку будет выполнен JavaScript, встроенный в URL.

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

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

Обсудить в форуме