Как стать автором
Обновить

Защита от XSS в Rails 3

Время на прочтение4 мин
Количество просмотров3.4K
Автор оригинала: Yehuda Katz
Скорее всего вы уже знаете о том, что в Rails 3 по умолчанию добавлена защита от XSS атак. Это значит, что отныне вам никогда не придется вручную фильтровать ввод пользователя используя хелпер h, потому что рельсы всегда будут делать это за вас.

Тем не менее, всё не так просто, как кажется на первый взгяд. Рассмотрим следующий код:
Привет <strong>друзья</strong>!

<%= tag(:p, some_text) %>
<%= some_text %>

В примере выше мы имеем несколько различных случаев, подразумевающих использование HTML тегов. В первом случае Rails не должен фильтровать тег <strong>, окружающий слово друзья, т.к. результатом будет явно не то, что вводил пользователь. Во втором случае Rails должен отфильтровать some_text внутри тега <p> (но не весь тег <p>). Наконец, в третьем случае, some_text в некотором родительском теге должен быть отфильтрован.

В случае, если some_text выглядит как результатом будет:
Привет <strong>друзья</strong>!
 
<p>&lt;script&gt;evil_js&lt;/script&gt;</p>
&lt;script&gt;evil_js&lt;/script&gt;
Для того, чтобы это работало повсеместно в приложениях Rails, мы в внедрили новый подход под названием html_safe. Так, если строка являеться html_safe (что определяется посредством вызова метода html_safe? на строке), ERB оставляет её нетронутой. Если же строка не является html_safe, ERB, прежде чем вставить её на страницу, произведет фильтрацию.

def tag(name, options = nil, open = false, escape = true)
"<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
end
Здесь Rails создает тег, указывая teg_options отфильтровать содержимое, после чего помечает все тело тега как безопасное. В результате <p> и </p> останутся не нетронутыми, в то время как вставленный пользователем контент будет подвержен фильтрации.

В первой реализации, в плагине Koz’а rails-xss, указанные условия выполнялись путём добавления специального флага ко всем строкам. Приложение Rails помечало нужные строки как безопасные, в то время как сам Rails переопределял методы + и <<, чтобы, в случае изменения строки, соответственно помечать результирующую.

Тем не менее, в ходе моего последнего тестирования производительности рельсов, я заметил, что переопределение каждой конкатенации строк выливается в довольно сильное падение производительности. Мало того, это падение линейно зависело от количества вставок <%= %> в шаблоне, т.е. большие шаблоны не поглощали стоимость (как если бы это был один вызов <%= %> на шаблон), а лишь увеличивали её.

Поразмыслив над проблемой больше, стало ясно (что позже подтвердили Koz, Jeremy и Evan Phoenix из Rubunius’а), что мы могли бы реализовать в точности такие же возможности более производительным путём, при этом даже с меньшим влиянием на Ruby API. Так как проблема сама по себе достаточно сложна, я не буду вдаваться в детали, описывая старую реализацию, а объясню как нужно использовать новую XSS защиту. Если вам довелось раньше использовать плагин Koz’а или вы уже работаете с предварительным релизами Rails, то заметите, что сегоднешний коммит изменяет не многое.

Safe Buffer


В Rails 3, буфер ERB является экземпляром ActiveSupport::SafeBuffer. SafeBuffer наследуется от String’а, переопределяя +, concat и << таким образом, что:
  • если другая строка безопасна (т.е. является SafeBuffer’ом), буфер просто выполняет конкатенацию
  • если другая строка небезопасна (т.е. является простым String’ом), буфер выполняет фильтрацию, а затем конкатенацию
Вызов html_safe на простой строке возвращает SafeBuffer-обертку. Так как SafeBuffer наследуется от сроки, Ruby создает обертку чрезвычайно эффективно (лишь делая общим внутреннее хранилище char*).

В результате такой реализации, я начинал видеть вот такой способ записи:
buffer << other_string.html_safe
Здесь Rails создает новый SafeBuffer для other_string, затем передает его в метод << исходного SafeBuffer’а, который (метод) проверяет, является ли новый SafeBuffer безопасным. Для таких случаев был написан safe_concat — новый метод для буфера, который использует оригинальный метод concat, исключая необходимость создавать новый SafeBuffer и проверять его на безопасность.

Подобным образом, concat и safe_concat в ActionView являются прокси-методами для concat и safe_buffer над буфером, поэтому можно использовать safe_concat в хелпере, если у вас есть HTML текст, который необходимо объединить с буфером без проверок и фильтрации.

ERB внутренне использует safe_concat везде на шаблоне вне тегов <% %>. Это значит, что, в моем сегодняшнем коммите, код XSS защиты никак не влияет на производительность в таких случаях (т.е. сканируя, по сути, весь ваш plain text).

Наконец, ERB теперь знает о хелпере raw, поэтому если вы напишете что-то вроде <%= raw some_stuff %>, ERB незаметно использует safe_concat, пропустив создание SafeBuffer’a и проверок html_safety.

Выводы


Вкратце, «XSS защита» означает следующее:
  • если в <%= %> подается обычная строка, Rails всегда её отфильтрует
  • если в <%= %> подается SafeBuffer, Rails её не фильтрует. Чтобы получить SafeBuffer из строки, необходимо вызвать на ней метод html_safe. Система XSS защиты несет крайне малое влияние на быстродействие, ограничивающееся лишь вызовом методом html_safe?
  • если в <%= %> передать хелпер raw, то Rails обнаружит это на этапе компиляции шаблона, никак не воздействуя на производительность (т.е. в этом случае не происходят никакие проверки на html_safe)
  • во время выполнения Rails не фильтрует никакие части шаблона вне <% %>, так-как справляется с этим в момент компиляции, результируя, опять-же, в отсутствии воздействия на производительность
Для сравнения, в исходной реализации XSS влиял на каждую конкатенацию или + для строк; влиял даже если приложение использовало хелпер raw, или на любую другую конкатенацию вне <% %> внутри шаблона.

Тем не менее, я хотел бы выразить личную благодарность Майклу Козиарски, предоставившему черновую реализацию своей идеи. Она работала, продемонстрировала концепцию, и сообщество вдоволь её оттестировало. В конце концов, она дала хорошее начало.
Теги:
Хабы:
Всего голосов 31: ↑26 и ↓5+21
Комментарии21

Публикации