Это последняя часть нашего исследования безопасности Rails. На прошлой неделе мы рассмотрели проблемы массового назначения и внедрения SQL-кода , предварительно изучив риски угона сеанса . На этой неделе мы рассмотрим риски для кэширования, защиты от XSS и пару других советов по безопасности Rails.
Риск кеширования
Кэширование фрагментов — это одна из самых важных вещей, которую вы можете сделать для повышения производительности. Кэши вложенных фрагментов, срок годности которых истекает, являются мощными и красивыми, но при этом существует большой риск: как сделать так, чтобы люди видели только то, что им предназначено? Это поймало меня несколько раз и может быть ужасно выслеживать.
Большая опасность — встраивание пользовательского кода в шаблоны, которые в конечном итоге кэшируются. Я приведу пример из реального мира, который чуть не застал нас врасплох. На Meducation у пользователя есть профиль с различной информацией о них. Раньше у нас был профиль на странице профиля, который был виден только администраторам, который отображал адрес электронной почты пользователя и другую информацию — главным образом в случае, если нам нужно было связаться с ними. У нас был такой шаблон:
%h2= @user.name | |
=render «users/information« | |
=render «users/media_files« | |
=render «users/wall« |
… с частичным, как это:
.bio= @user.bio | |
.university | |
.field University | |
.value= @user.university | |
.university_year | |
.field Year | |
.value= @user.university_year | |
—if current_user.is_admin? | |
%h3 Admin Information: | |
.field Email | |
.value= @user.email | |
/… |
Функциональность здесь довольно стандартная, и хотя она не особенно элегантна, она довольно безопасна. Проблема возникла, когда мы решили добавить кеширование в show.html.erb
=cache «user_#{@user.id}_#{@user.updated_at}_#{@user.media_files_updated_at}_#{@user.wall_updated_at}« do | |
%h2= @user.name | |
=cache «user_#{@user.id}_information_#{@user.updated_at}« do | |
=render «users/information« | |
=cache «user_#{@user.id}_media_files_#{@user.media_files_updated_at}« do | |
=render «users/media_files« | |
=cache «user_#{@user.id}_wall_#{@user.wall_updated_at}« do | |
=render «users/wall« |
Это приятное вложенное кэширование повышает производительность нашего приложения, но неожиданно мы ввели огромную дыру в безопасности: наш код только для администраторов кэшируется. В зависимости от того, какой пользователь отображает этот код первым, либо все увидят код администратора, либо никто не увидит код администратора. Оба эти сценария довольно плохие.
Проблема здесь в том, что мы не учли пользователя, который просматривает данные, а только данные, которые отображаются. Если ваш код содержит какую-либо ссылку на current_user
, Там нет волшебной палочки, чтобы решить эту проблему, вы просто должны быть очень осторожны и осторожны при кэшировании данных. Правило, которое я обычно использую, состоит в том, что если есть только один или два вызова методов current_user
?
методы, затем я включу их в ключ кеша, в противном случае я не буду его кешировать (и вообще решу, что это запах кода и реорганизует представление!). В этом сценарии я бы изменил код на:
=cache «#{current_user.is_admin?}_user_#{@user.id}_#{@user.updated_at}_#{@user.media_files_updated_at}_#{@user.wall_updated_at}« do | |
%h2= @user.name | |
=cache «#{current_user.is_admin?}_user_#{@user.id}_information_#{@user.updated_at}« do | |
=render «users/information« | |
=cache «user_#{@user.id}_media_files_#{@user.media_files_updated_at}« do | |
=render «users/media_files« | |
=cache «user_#{@user.id}_wall_#{@user.wall_updated_at}« do | |
=render «users/wall« |
В заключение, в реальном коде вы хотите извлечь эти ключи кеша во вспомогательные методы. Код выше довольно сложен для чтения и поддержки.
Защита от атак XSS (межсайтовый скриптинг)
Атаки XSS — одни из самых опасных атак, с которыми вы столкнетесь. Rails 3 защищает нас от базового XSS, но, как всегда, легко сломать защиту Rails, если вы не будете осторожны.
Атаки XSS вовлекают злонамеренного пользователя, внедряющего код, который позже обрабатывает ваше приложение. Давайте рассмотрим простой пример:
Допустим, у вас есть сайт, на котором размещены блоги. Пользователи могут создавать сообщения с заголовком и некоторым содержанием. Ваш show.html.erb
<h2><%= @blog_post.title %></h2> | |
<div id=»content«><%= @blog_post.content %></div> |
На прошлой неделе на вашем сайте кто-то опубликовал пост в блоге, который грустит обо мне плохо. Я решил взять это на себя, вызвав некоторый хаос. Для этого я создаю новую запись в блоге, но вместо того, чтобы дать вам некоторую интересную информацию о том, какой кофе я пил сегодня, я ввожу некоторый HTML-код, содержащий Javascript. Например:
<script> | |
setInterval(function() { | |
alert(«I’m annoying!!!») | |
}, 50) | |
</script> |
В Rails 2 ваш вид будет отображать ваш шаблон шоу следующим образом:
<h2>My First Blog Post</h2> | |
<div id=»content«> | |
<script> | |
setInterval(function() { | |
alert(«I’m annoying!!!») | |
}, 50) | |
</script> | |
</div> |
Это было явно ОЧЕНЬ плохо. Окно оповещения раздражает, но если бы я захотел, я мог бы написать JavaScript, который угонял сессии, публиковал формы, используя одноразовый authenticity_token, или наносил всевозможные другие реальные повреждения.
Как вы, наверное, догадались, Rails 3 добавил защиту, чтобы остановить это, так что шаблон теперь будет отображаться так:
<h2>My First Blog Post</h2> | |
<div id=»content»> | |
<script> | |
setInterval(function() { | |
alert(«I’m annoying!!!») | |
}, 50) | |
</script> | |
</div> |
В чем проблема, спросите вы?
Ну, есть две большие проблемы. Один ловит неосторожных, второй может поймать опытного разработчика.
Первая проблема проста. Что произойдет, если вы захотите разрешить пользователю вводить HTML-код, например, полужирный, курсив и т. Д.? Многие пользователи на этом этапе просто решают прекратить экранирование HTML и использовать ключевое слово raw, например:
<h2><%= @blog_post.title %></h2> | |
<div id=»content«><%= raw @blog_post.content %></div> |
Внезапно, вы отменили всю тяжелую работу Rails, и хаос может обуздать. НИКОГДА не используйте raw
. Это относится к обычному тексту, изображениям srcs, css, чему угодно. Даже если данные содержат только интерполированный пользовательский ввод, не рискуйте использовать raw
Если вы хотите разрешить использование HTML-тегов в своих представлениях, используйте Rails, предоставляющий метод sanitize
Для вашего блога вы должны изменить свой show.html.erb
<h2><%= @blog_post.title %></h2> | |
<div id=»content«><%= sanitize(@blog_post.content, tags: tags %w(b strong i em)) %></div> |
Или используйте Markdown. RedCarpet предоставляет простое и надежное решение, и зачастую пользователям проще, чем пытаться писать в HTML. Если контент, который пишут ваши пользователи, носит технический характер, подумайте об использовании уценки Github .
Второй, немного более сложный пример — когда вы создаете вспомогательный метод. Допустим, мы хотим создать некоторую функциональность, которая упаковывает некоторые данные в таблицу:
module DataHelper | |
# Expects a nested array such as: | |
# [[1,’Jez’,’iHiD’], [2,’Bob’,'<b>xyz</b>’]] | |
# | |
# and outputs: | |
# <table> | |
# <tr><td>1</td><td>Jez</td><td>iHiD</td></tr> | |
# <tr><td>2</td><td>Bob</td><td><b>xyz</b></td></tr> | |
# </table> | |
def data_to_table(data) | |
output = «<table>» | |
data.each do |row| | |
output << «<tr>» | |
row.each do |cell| | |
output << «<td>#{cell}</td>» | |
end | |
output = «</tr>» | |
end | |
output << «</table>» | |
output.html_safe | |
end | |
end |
Чтобы заставить Rails выводить этот контент в виде HTML, мы использовали метод html_safe
Это помечает контент как «безопасный буфер», который Rails может выводить без экранирования. Однако, делая это, мы также убрали защитную защиту от данных, и мы снова уязвимы для XSS.
Наша первая попытка изменить это, чтобы сделать это безопасным, — это прямой доступ к данным пользователя с помощью h
Например:
module DataHelper | |
... | |
output << «<td>#{h cell}</td>» | |
... | |
end |
Однако этот код все еще громоздок, подвержен ошибкам и сложен в обслуживании. Следует избегать создания контента с использованием конкатенации строк в Rails. Вместо этого используйте tag
content_tag
Теперь мы можем изменить метод на это:
module DataHelper | |
def data_to_table(data) | |
content_tag :table do | |
rows = data.map do |row| | |
content_tag :tr do | |
cells = row.map do |cell| | |
content_tag :td, cell | |
end | |
safe_join(cells) | |
end | |
end | |
safe_join(rows) | |
end | |
end | |
end |
Этот код использует safe_join
Вы не можете использовать обычное join
html_safe
Альтернатива, которая мне очень нравится, — это использовать reduce
module DataHelper | |
def data_to_table(data) | |
content_tag :table do | |
data.map do |row| | |
content_tag :tr do | |
row.map {|cell| content_tag :td, cell}.reduce(:<<) | |
end | |
end.reduce(:<<) | |
end | |
end | |
end |
Избежать XSS не сложно — просто будьте осторожны при работе с внешними данными.
Пара советов.
Есть три последние вещи, которые я хочу упомянуть.
Во-первых, что касается ведения журнала. Будьте осторожны с тем, что хранится в ваших журналах. Журналы — это просто текстовые файлы без какой-либо защиты, и если они попадают в дикую природу и содержат конфиденциальную информацию, у вас могут возникнуть большие проблемы. По умолчанию Rails удаляет пароли из ваших логов. Вы также можете легко удалить другие конфиденциальные данные, такие как адреса электронной почты. В вашем config.rb просто отредактируйте строку параметров фильтра следующим образом:
config.filter_parameters += [:password, :email] |
Во-вторых, атаки типа «отказ в обслуживании» становятся все более распространенными. Невозможно полностью защитить от них, но можно уменьшить риск, перемещая все как можно более асинхронно. Сохранение жизненного цикла вашего запроса как можно более коротким является жизненно важным как для производительности, так и для защиты от атак DOS. Перемещайте все, что можете, в асинхронные задачи с помощью метода, такого как delayed_job .
Наконец, считайте безопасность подобной луку. Добавьте как можно больше слоев, зная, что некоторые из них, вероятно, будут пробиты, но вы можете сохранить ядро в безопасности. Следите за своим трафиком и журналами, внимательно ищите возможные атаки и отслеживайте исключения, чтобы увидеть, что пытаются сделать хакеры. Продолжайте улучшать свою безопасность и проверяйте свое приложение и обновляйте свои драгоценные камни.
Не доверяйте данным!
Тема этой серии была простой: не доверяйте данным. Не верьте, что данные принадлежат тому, от кого, как вы думаете, они есть, не доверяйте тому, что данные могут сделать с вашим приложением, и не верьте тому, что может произойти, когда вы их визуализируете. Запомните это правило, и вы будете на пути к написанию более безопасных приложений. Удачи!