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