В начале 2012 года разработчик по имени Егор Хомаков воспользовался дырой в безопасности в Github (приложение Rails), чтобы получить коммитный доступ к проекту Rails .
Его целью было в основном указать на общую проблему безопасности со многими приложениями Rails, которая возникает в результате функции, известной как массовое назначение (и делала это довольно громко). В этой статье мы рассмотрим, что такое массовое назначение, как это может быть проблемой, и что вы можете сделать с этим в своих собственных приложениях.
Что такое массовое назначение?
Для начала давайте сначала посмотрим, что означает массовое назначение и почему оно существует. В качестве примера представим, что в нашем приложении есть следующий класс User
:
1
2
3
|
# Assume the following fields: [:id, :first, :last, :email]
class User < ActiveRecord::Base
end
|
Массовое назначение позволяет нам устанавливать сразу несколько атрибутов:
1
2
3
4
5
|
attrs = {:first => «John», :last => «Doe», :email => «[email protected]»}
user = User.new(attrs)
user.first #=> «John»
user.last #=> «Doe»
user.email #=> «[email protected]»
|
Без удобства массового присваивания нам пришлось бы написать оператор присваивания для каждого атрибута, чтобы достичь того же результата. Вот пример:
1
2
3
4
5
6
7
8
|
attrs = {:first => «John», :last => «Doe», :email => «[email protected]»}
user = User.new
user.first = attrs[:first]
user.last = attrs[:last]
user.email = attrs[:email]
user.first #=> «John»
user.last #=> «Doe»
user.email #=> «[email protected]»
|
Очевидно, это может стать утомительным и болезненным; поэтому мы кланяемся у ног лени и говорим: да, массовое задание — это хорошо.
(Потенциальная) проблема с массовым присвоением
Одна проблема с острыми инструментами заключается в том, что вы можете порезаться ими.
Но ждать! Одна проблема с острыми инструментами заключается в том, что вы можете порезаться ими. Массовое назначение не является исключением из этого правила.
Предположим теперь, что наше маленькое воображаемое приложение приобрело способность стрелять ракетами. Поскольку мы не хотим, чтобы мир превратился в пепел, мы добавляем логическое поле разрешений в модель User
чтобы решить, кто может запускать ракеты.
1
2
3
4
5
|
class AddCanFireMissilesFlagToUsers < ActiveRecord::Migration
def change
add_column :users, :can_fire_missiles, :boolean, :default => false
end
end
|
Давайте также предположим, что у нас есть способ для пользователей редактировать свои контактные данные: это может быть где-то форма, которая доступна пользователю с текстовыми полями для имени пользователя, его фамилии и адреса электронной почты.
Наш друг Джон Доу решает изменить свое имя и обновить свою учетную запись электронной почты. Когда он отправляет форму, браузер выдаст запрос, подобный следующему:
1
|
PUT http://missileapp.com/users/42?user[first]=NewJohn&user[email][email protected]
|
Действие update
в UsersController
может выглядеть примерно так:
1
2
3
4
5
6
7
8
|
def update
user = User.find(params[:id])
if user.update_attributes(params[:user]) # Mass assignment!
redirect_to home_path
else
render :edit
end
end
|
Учитывая наш пример запроса, хэш params
будет выглядеть примерно так:
1
2
3
|
{:id => 42, :user => {:first => «NewJohn», :email => «[email protected]»}
# :id — parsed by the router
# :user — parsed from the incoming querystring
|
Теперь давайте скажем, что NewJohn становится немного хитрым. Вам не обязательно нужен браузер для выдачи HTTP-запроса, поэтому он пишет скрипт, который выдает следующий запрос:
1
|
PUT http://missileapp.com/users/42?user[can_fire_missiles]=true
|
Поля, такие как
:admin
:public_key
:owner
и:public_key
, довольно легко угадать.
Когда этот запрос достигает нашего действия по update
, вызов update_attributes
увидит {:can_fire_missiles => true}
и даст NewJohn возможность запускать ракеты! Горе стало нам.
Именно так Егор Хомаков предоставил себе доступ к проекту Rails. Поскольку Rails настолько :public_key
такие поля, как :admin
:public_key
:owner
и :public_key
довольно легко угадать. Кроме того, если нет средств защиты, вы можете получить доступ к вещам, которые вы не должны касаться.
Как справиться с массовым заданием
Так как же нам защитить себя от бессмысленных массовых назначений? Как мы можем предотвратить неосторожный запуск «Нью-Джонс мира» наших ракет?
К счастью, Rails предоставляет несколько инструментов для решения проблемы: attr_protected
и attr_accessible
.
attr_protected
: attr_protected
список
Используя attr_protected
, вы можете указать, какие поля никогда не могут быть назначены массово :
1
2
3
|
class User < ActiveRecord::Base
attr_protected :can_fire_missiles
end
|
Теперь любая попытка массового присвоения атрибута can_fire_missiles
потерпит неудачу.
attr_accessible
: Белый список
Проблема с attr_protected
заключается в том, что слишком легко забыть добавить недавно реализованное поле в список.
Здесь и вступает attr_accessible
. Как вы уже догадались, это противоположность attr_protected
: перечисляйте только те атрибуты, которые вы хотите назначить массово.
Таким образом, мы можем переключить наш класс User
на такой подход:
1
2
3
|
class User < ActiveRecord::Base
attr_accessible :first, :last, :email
end
|
Здесь мы явно перечисляем то, что может быть назначено по массе. Все остальное будет запрещено. Преимущество здесь в том, что, если мы, скажем, добавим флаг admin
в модель User
, он автоматически будет защищен от массового назначения.
Как правило, вы должны предпочесть attr_accessible
attr_protected
, так как это поможет вам ошибиться в случае осторожности.
Массовое назначение ролей
В Rails 3.1 введено понятие массового назначения «ролей». Идея состоит в том, что вы можете указать разные attr_protected
и attr_accessible
для разных ситуаций.
01
02
03
04
05
06
07
08
09
10
|
class User < ActiveRecord::Base
attr_accessible :first, :last, :email # :default role
attr_accessible :can_fire_missiles, :as => :admin # :admin role
end
user = User.new({:can_fire_missiles => true}) # uses the :default role
user.can_fire_missiles #=> false
user2 = User.new({:can_fire_missiles => true}, :as => :admin)
user.can_fire_missiles #=> true
|
Конфигурация всего приложения
Вы можете контролировать поведение массового назначения в своем приложении, отредактировав параметр config.active_record.whitelist_attributes
в файле config/application.rb
.
Если установлено значение false
, защита массового присвоения будет активирована только для моделей, в которых вы указали список attr_protected
или attr_accessible
.
Если установлено значение true
, массовое назначение будет невозможно для всех моделей, если они не задают список attr_protected
или attr_accessible
. Обратите внимание, что эта опция включена по умолчанию из Rails 3.2.3 и выше.
взыскательность
Начиная с Rails 3.2, дополнительно есть опция конфигурации для контроля строгости защиты массовых назначений: config.active_record.mass_assignment_sanitizer
.
Если установлено значение :strict
, оно будет вызывать ActiveModel::MassAssignmentSecurity::Error
каждый раз, когда ваше приложение пытается массово назначить то, что не должно. Вам нужно будет обработать эти ошибки явно. Начиная с версии 3.2, этот параметр установлен для вас в средах разработки и тестирования (но не в рабочей среде), предположительно, чтобы помочь вам отследить возможные проблемы массового назначения.
Если не установлен, он будет молча обрабатывать защиту от массовых назначений — это означает, что он будет устанавливать только те атрибуты, для которых он предназначен, но не вызовет ошибку.
Rails 4 сильных параметра: другой подход
Безопасность массовых назначений действительно связана с обработкой ненадежного ввода.
Инцидент с Хомаковым положил начало разговору о защите массовых назначений в сообществе Rails (и далее на других языках); был задан интересный вопрос: относится ли защита массовых назначений к уровню модели?
Некоторые приложения имеют сложные требования авторизации. Попытка разобраться со всеми особыми случаями на уровне модели может начать казаться неуклюжей и чрезмерно сложной, особенно если вы повсеместно выполняете roles
гипсовой повязки.
Ключевым моментом здесь является то, что безопасность массовых назначений действительно связана с обработкой ненадежного ввода. Когда Rails-приложение получает пользовательский ввод на уровне контроллера, разработчики начали задумываться о том, не лучше ли решить эту проблему вместо моделей ActiveRecord.
Результатом этого обсуждения является гем Strong Parameters , доступный для использования с Rails 3, и по умолчанию в следующем выпуске Rails 4.
Предполагая, что наше ракетное приложение работает на Rails 3, вот как мы можем обновить его для использования с параметром stong:
Добавьте драгоценный камень
Добавьте следующую строку в Gemfile:
1
|
gem strong_parameters
|
Отключить защиту массовых назначений на основе модели
В config/application.rb
:
1
|
config.active_record.whitelist_attributes = false
|
Расскажите об этом моделям
1
2
3
|
class User < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
end
|
Обновите контроллеры
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
class UsersController < ApplicationController
def update
user = User.find(params[:id])
if user.update_attributes(user_params) # see below
redirect_to home_path
else
render :edit
end
end
private
# Require that :user be a key in the params Hash,
# and only accept :first, :last, and :email attributes
def user_params
params.require(:user).permit(:first, :last, :email)
end
end
|
Теперь, если вы попытаетесь что-то вроде user.update_attributes(params)
, вы получите ошибку в вашем приложении. Сначала вы должны вызвать permit
для хэша params
с ключами, которые разрешены для определенного действия.
Преимущество этого подхода заключается в том, что вы должны четко указать, какой вклад вы принимаете в тот момент, когда вы имеете дело с вводом.
Примечание. Если бы это было приложение на Rails 4, нам понадобился бы код контроллера; функциональность сильных параметров будет включена по умолчанию. В результате вам не понадобится включение в модель или отдельный гем в Gemfile.
Завершение
Массовое назначение может быть невероятно полезной функцией при написании кода на Rails. На самом деле, практически невозможно написать разумный код Rails без него. К сожалению, бессмысленное массовое задание также чревато опасностями.
Надеемся, что теперь вы оснащены необходимыми инструментами для безопасной навигации в водах массового назначения. Вот меньше ракет!