Статьи

Массовое назначение Rails: перспектива PHP

Теперь более чем когда-либо безопасность вашего приложения рельсов находится под пристальным вниманием. Пользователь 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();
?>

view raw
example.php
hosted with ❤ by GitHub

Здесь у нас есть фрагмент функции, которая принимает два параметра ( $some_variable$another_variable

В Rails или, более конкретно, ActiveRecordActiveModelattr_accessible Как и в случае с attr_accessible

Такая же защита в приложении Rails будет выглядеть так:

 class SomeModel < ActiveRecord::Base attr_accessible :first_name, :surname end 

В версиях PHP и Rails, если модель SomeModelis_admin Единственное реальное различие, и оно существенное, заключается в том, что мы должны набрать гораздо меньше строк кода. Да, Rails — это фреймворк / эко-система, так что это будет так. Я просто на мгновение пожелал, чтобы весь этот код PDO, когда я писал его много месяцев назад, был таким же кратким.

Дело в том, что в связи с уязвимостью массового присвоения этого никогда бы не произошло, используя простой элементарный доступ к данным, как мы видим в подготовленном PHP-выражении. Есть ли аргумент, что легкость ActiveRecord Мое мнение таково, что для начинающих рубинистов, конечно (хотя они будут блаженно не осведомлены об этом), и для более опытных среди нас, вероятно. Тем не менее, я позволю вам быть настоящим судьей этого предложения.

Все сделано, не совсем

До этого момента все, что обсуждалось, это механизм размещения данных в базе данных. Это где курение пистолета для этой уязвимости лежит? Мы знаем, как предотвратить это сейчас, мы всегда знали, это даже комментируется в каждом недавнем приложении на Rails, которое мы создаем. В нашем файле config/application.rb

 # config.active_record.whitelist_attributes = true 

Раскомментировав эту одну строчку, мы в безопасности, фу. Но приложение, вероятно, сломается. Именно в этот момент мы видим настоящих преступников, а они не живут в моделях. Было безумие в создании новых, почти сложных методов защиты от массового назначения с использованием attr_accessibleattr_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

view raw
ok_code.rb
hosted with ❤ by GitHub

Здесь есть два оскорбительных пункта. Прежде всего, использование update_attributessave Но, эй, это Rails, мы делаем такие вещи каждый день, даже не отдавая должного принципам SOLID. В любом случае, мы бы извлекли только такую ​​вещь в модель.

Теперь мы находимся в моём основном понимании этого фрагмента кода. Параметры, передающие params[:whatever] Это лениво и безрассудно. Мы все ожидаем, что ActiveRecord

Она написала убийство

Прежде всего, я скорее парень из Коломбо (ранние вещи), но я уверен, что почти все, кто читает это, будут думать, что мы уже рассмотрели достаточное средство для принятия хэша paramsattr_accessible Тем не менее, я никогда не использовал attr_accessible Я знаю, что несколько «быстрых, грязных» приложений по-прежнему подвержены массовым атакам с присвоением, но есть несколько, которые я защитил без использования attr_accessibleattr_protected

Помните, я упоминал, что заголовок статьи должен соответствовать тому, как мой опыт работы с PDO защитит меня от атак на массовые назначения в Rails? Что ж, в тот день, когда я жаждал массового назначения, привязка каждого параметра к подготовленному заявлению PDO в лучшем случае утомительна. Поэтому я написал вспомогательный метод.

<?php
// …
protected function build_params() {
$params = array();
foreach($_POST as $key => $value) {
$params[«:$key»] = $value;
}
return $params;
}
// …
?>

Метод executebuild_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

view raw
dhh.rb
hosted with ❤ by GitHub

Это действительно становится вопросом вкуса, веры в то, в чем заключается логика защиты массового назначения. Естественно, я бы никогда не нахмурился, если бы кто-нибудь использовал attr_accessible На самом деле, мне нравится простота белого списка всех атрибутов в самой модели. Но всегда в глубине души я слышу голос Джима Вейриха, напоминающий мне о принципах SOLID и об одной ответственности, которая часто теряется в Rails для удобства. Я чувствую, что модель не должна нести ответственность за данные, которые она передает. Если контроллер является «связующим звеном» между нашими моделями и представлениями, конечно, в этом и заключается ответственность?

Конечно, я не смог бы закончить эту статью, не указав на некоторые большие ресурсы по безопасности, особенно с Rails. Не стесняйтесь добавлять больше в комментариях.