Теперь более чем когда-либо безопасность вашего приложения рельсов находится под пристальным вниманием. Пользователь Github homakov (Егор Хомаков выявил уязвимость Rails (которая, без сомнения, также живет в приложении рядом с вами) с этим игривым коммитом в самом проекте Rails. Я влюбился в этот коммит исключительно из-за последовавшего мем-фестиваля. Однако Реакция сообщества Rails в целом удивила меня: с момента атаки в блогах было опубликовано бесчисленное множество решений — от банальной до расточительности DSL.
Итак, что случилось?
В случае, если вы пропустили все ура, уязвимость массового назначения существует в Rails или, более конкретно, ActiveRecord начиная с года. Каждый раз, когда вы пишете код в контроллере, например:
model = SomeModel.new(params[:some_model]) | |
if model.save | |
# … | |
end | |
# or | |
model = SomeModel.find(params[:id]) | |
if model.update_attributes(params[:some_model]) | |
# … | |
end |
Вы открыли себя для теперь почти печально известного взлома. Массовое назначение удобно, на самом деле это так удобно, что я содрогаюсь от перспективы обновления атрибутов модели любым другим способом через формы. Я буду первым, кто встанет и признает свой позор (или отсутствие) в том факте, что я написал / сгенерировал подобный код, и он все еще находится в производстве сегодня.
Мой опыт работы с PHP и большинство небольших приложений, которые я написал, были без фреймворка. Я бы повторно использовал класс базы данных, который обрабатывал все основные моменты чтения и записи в базе данных, а затем я просто унаследовал свой код модели от этого. Звучит знакомо? Теперь все это чтение и запись обрабатывались через объекты данных PHP (PDO) . Из-за моего опыта работы с PDO эта статья должна называться «Уязвимость массового назначения в Rails и как PHP PDO убедился, что меня никогда не поймали, как Github», но это граничит со спорным, не говоря уже о нелепости.
PDO 101
Для тех, кто не знает о PDO, это слой абстракции между моим кодом и базой данных. Подобно ActiveRecord
Готовое утверждение выглядит примерно так:
<?php | |
// Assuming we have setup the connection | |
$stmt = $dbh->prepare(«INSERT INTO some_models (first_name, surname) VALUES (:first_name, :surname»); | |
$stmt->bindParam(:first_name, $some_variable); | |
$stmt->bindParam(:surname, $another_variable); | |
$stmt->execute(); | |
?> |
Здесь у нас есть фрагмент функции, которая принимает два параметра ( $some_variable
$another_variable
В Rails или, более конкретно, ActiveRecord
ActiveModel
attr_accessible
Как и в случае с attr_accessible
Такая же защита в приложении Rails будет выглядеть так:
class SomeModel < ActiveRecord::Base attr_accessible :first_name, :surname end
В версиях PHP и Rails, если модель SomeModel
is_admin
Единственное реальное различие, и оно существенное, заключается в том, что мы должны набрать гораздо меньше строк кода. Да, Rails — это фреймворк / эко-система, так что это будет так. Я просто на мгновение пожелал, чтобы весь этот код PDO, когда я писал его много месяцев назад, был таким же кратким.
Дело в том, что в связи с уязвимостью массового присвоения этого никогда бы не произошло, используя простой элементарный доступ к данным, как мы видим в подготовленном PHP-выражении. Есть ли аргумент, что легкость ActiveRecord
Мое мнение таково, что для начинающих рубинистов, конечно (хотя они будут блаженно не осведомлены об этом), и для более опытных среди нас, вероятно. Тем не менее, я позволю вам быть настоящим судьей этого предложения.
Все сделано, не совсем
До этого момента все, что обсуждалось, это механизм размещения данных в базе данных. Это где курение пистолета для этой уязвимости лежит? Мы знаем, как предотвратить это сейчас, мы всегда знали, это даже комментируется в каждом недавнем приложении на Rails, которое мы создаем. В нашем файле config/application.rb
# config.active_record.whitelist_attributes = true
Раскомментировав эту одну строчку, мы в безопасности, фу. Но приложение, вероятно, сломается. Именно в этот момент мы видим настоящих преступников, а они не живут в моделях. Было безумие в создании новых, почти сложных методов защиты от массового назначения с использованием attr_accessible
attr_protected
Следующий код в контроллерах удобен, но неверен.
model = SomeModel.new(params[:some_model]) | |
if model.save | |
# … | |
end | |
# or | |
model = SomeModel.find(params[:id]) | |
if model.update_attributes(params[:some_model]) | |
# … | |
end |
Здесь есть два оскорбительных пункта. Прежде всего, использование update_attributes
save
Но, эй, это Rails, мы делаем такие вещи каждый день, даже не отдавая должного принципам SOLID. В любом случае, мы бы извлекли только такую вещь в модель.
Теперь мы находимся в моём основном понимании этого фрагмента кода. Параметры, передающие params[:whatever]
Это лениво и безрассудно. Мы все ожидаем, что ActiveRecord
Она написала убийство
Прежде всего, я скорее парень из Коломбо (ранние вещи), но я уверен, что почти все, кто читает это, будут думать, что мы уже рассмотрели достаточное средство для принятия хэша params
attr_accessible
Тем не менее, я никогда не использовал attr_accessible
Я знаю, что несколько «быстрых, грязных» приложений по-прежнему подвержены массовым атакам с присвоением, но есть несколько, которые я защитил без использования attr_accessible
attr_protected
Помните, я упоминал, что заголовок статьи должен соответствовать тому, как мой опыт работы с PDO защитит меня от атак на массовые назначения в Rails? Что ж, в тот день, когда я жаждал массового назначения, привязка каждого параметра к подготовленному заявлению PDO в лучшем случае утомительна. Поэтому я написал вспомогательный метод.
<?php | |
// … | |
protected function build_params() { | |
$params = array(); | |
foreach($_POST as $key => $value) { | |
$params[«:$key»] = $value; | |
} | |
return $params; | |
} | |
// … | |
?> |
Метод execute
build_params
<?php | |
// … | |
public function create_some_model($params) { | |
// Database handle setup to $dbh | |
$stmt = $dbh->prepare(«INSERT INTO some_models (first_name, surname) VALUES (:first_name, :surname»); | |
$stmt->execute($params); | |
} | |
?> |
Конечно, это было не без его недостатков. Я снова нахожусь в состоянии быть открытым для массовых атак с присвоением, и каждый элемент (кнопки отправки и т. Д. В форме с именованным атрибутом) передается в подготовленный оператор. Исправить было достаточно легко.
<?php | |
// … | |
protected function build_params() { | |
$whitelist = array(«name», «surname»); | |
$params = array(); | |
foreach($_POST as $key => $value) { | |
if (in_array($key, $whitelist)) { | |
$params[«:$key»] = $value; | |
} | |
} | |
return $params; | |
} | |
// … | |
?> |
У меня снова есть защита от массового назначения, и, что более важно, контроллер отвечает за эту защиту. Когда дело дошло до изучения и использования рельсов, я взял эту простую идиому со мной.
Я собирался опубликовать мой пример кода на Ruby (в основном прямой порт PHP), затем я заметил, что DHH опубликовал суть с той же функциональностью, но более приятной, используя Array#slice
class PostsController < ActionController::Base | |
def create | |
Post.create(post_params) | |
end | |
def update | |
Post.find(params[:id]).update_attributes!(post_params) | |
end | |
private | |
def post_params | |
params[:post].slice(:title, :content) | |
end | |
end |
Это действительно становится вопросом вкуса, веры в то, в чем заключается логика защиты массового назначения. Естественно, я бы никогда не нахмурился, если бы кто-нибудь использовал attr_accessible
На самом деле, мне нравится простота белого списка всех атрибутов в самой модели. Но всегда в глубине души я слышу голос Джима Вейриха, напоминающий мне о принципах SOLID и об одной ответственности, которая часто теряется в Rails для удобства. Я чувствую, что модель не должна нести ответственность за данные, которые она передает. Если контроллер является «связующим звеном» между нашими моделями и представлениями, конечно, в этом и заключается ответственность?
Конечно, я не смог бы закончить эту статью, не указав на некоторые большие ресурсы по безопасности, особенно с Rails. Не стесняйтесь добавлять больше в комментариях.