h, потому что рельсы всегда будут делать это за вас.Тем не менее, всё не так просто, как кажется на первый взгяд. Рассмотрим следующий код:
В примере выше мы имеем несколько различных случаев, подразумевающих использование HTML тегов. В первом случае Rails не должен фильтровать тегПривет <strong>друзья</strong>!
<%= tag(:p, some_text) %>
<%= some_text %>
<strong>, окружающий слово друзья, т.к. результатом будет явно не то, что вводил пользователь. Во втором случае Rails должен отфильтровать some_text внутри тега <p> (но не весь тег <p>). Наконец, в третьем случае, some_text в некотором родительском теге должен быть отфильтрован.В случае, если
some_text выглядит как результатом будет:Привет <strong>друзья</strong>!
<p><script>evil_js</script></p>
<script>evil_js</script>Для того, чтобы это работало повсеместно в приложениях 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 не фильтрует никакие части шаблона вне
<% %>, так-как справляется с этим в момент компиляции, результируя, опять-же, в отсутствии воздействия на производительность
+ для строк; влиял даже если приложение использовало хелпер raw, или на любую другую конкатенацию вне <% %> внутри шаблона.Тем не менее, я хотел бы выразить личную благодарность Майклу Козиарски, предоставившему черновую реализацию своей идеи. Она работала, продемонстрировала концепцию, и сообщество вдоволь её оттестировало. В конце концов, она дала хорошее начало.

