Статьи

Подводные камни безопасности Rails

PylonAndBigHole_image

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

Мы собираемся быстро погрузиться в эти ловушки и посмотреть, как их предотвратить.

Секретный токен

При создании нового приложения Rails генерирует случайный secret_token используемый для проверки целостности файла cookie сеанса. Звучит хорошо, так в чем проблема?

Ну, secret_token находится в файле в нашем коде, что означает, что есть много шансов для него в нашем контроле версий. Нам бы не хотелось, чтобы хакер (или злой бывший коллега!), Получивший доступ к нашему коду, также имел доступ к любым учетным данным или информации, которые могут дать ему больше контроля.

В Rails данные cookie десериализуются с помощью Marshal.load , который может выполнять произвольный код ruby . Это означает, что если злоумышленник знает наш secret_token , он может легко создать действительный файл cookie с любыми данными, которые ему нужны, и может выполнить какой-нибудь неприятный код на наших серверах.

Как предотвратить это

К счастью, исправить эту проблему довольно просто. Просто используйте переменную ENV для secret_token . Таким образом, вы можете запустить его в производство, и у каждого разработчика будет свой.

Для этого вам нужно изменить строку в config/initializers/secret_token.rb на:

 MyRailsApp::Application.config.secret_token = ENV['SECRET_TOKEN'] 

И не забудьте установить переменную ENV перед запуском вашего приложения, иначе это вызовет ошибку.

Совет : Для локальной разработки вы можете иметь .env который будет автоматически загружаться такими инструментами, как .env или dot-env . Рекомендуется env.example файл env.example в вашем контроле версий, чтобы было легко узнать, какие переменные ENV нужны. Если вы используете pow, вы также можете поместить его в свой .powenv .

Перегибы в дикой природе

ActiveSupport::Inflectors предоставляет несколько действительно удобных методов для работы со строками. Например, чтобы получить объект из строки, вы можете сделать:

 "Hash".constantize #=> Hash 

С его помощью очень часто можно получить объекты класса из params, например:

 klass = params[:class].constantize 

Это более рискованно, чем кажется. Злоумышленник может воспользоваться этим фрагментом кода и проверить, используем ли мы определенный гем или требуем определенную библиотеку, просто выполнив запрос к этому действию с параметром класса (например: /faulty/action?class=Mysql2 ) , Если класс или модуль загружен, приложение, вероятно, ответит HTTP-статусом 200 и 500, если это не так.

Еще хуже, если код также имеет что-то вроде этого:

 object = klass.new(params[:name]) 

Правильный набор параметров, таких как { class: 'File', name: '/etc/hosts' } может подвергнуть нашу файловую систему злоумышленникам или позволить выполнить произвольный код.

Это может произойти не только с constantize , но и со многими другими ActiveSupport::Inflector которые можно использовать для получения таблицы или частичного имени.

Как предотвратить это

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

В таких случаях вы можете захотеть внести в белый список некоторые допустимые классы или параметры, чтобы предотвратить нежелательное выполнение кода или утечку информации.

Параметры ведения журнала

Ваше приложение может не только выявить уязвимости, но и журналы. Да, эти полезные и, казалось бы, безвредные журналы могут содержать конфиденциальные данные.
По умолчанию Rails фильтрует любой параметр, который соответствует регулярному выражению /password/ из [FILTERED] , заменяя его на [FILTERED] .

Это действительно хорошее значение по умолчанию, но вы можете добавить любой другой чувствительный параметр в этот фильтр.

Как предотвратить это

Добавьте любое конфиденциальное имя параметра (например, токен, код, возможно, адрес электронной почты?) В файл config/application.rb . Конфигурация файла по умолчанию выглядит примерно так:

 # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] 

Этот фильтр может работать даже с Proc или Regexp . Строки и символы автоматически преобразуются в регулярные выражения, которые будут соответствовать любой части имени параметра.

Вы также можете добавить фильтры для каждой среды. Просто добавьте аналогичную строку в инициализатор среды.

XSS

Rails отлично справляется с предотвращением межсайтового скриптинга из коробки. Руководство по безопасности подробно объясняет проблему.

Начиная с Rails 3, весь вывод в представление по умолчанию экранируется. Это замечательно, но все еще много распространенных случаев, когда XSS-уязвимости могут быть представлены.

Примеры

Скажем, вы хотите создать помощника, который обернет некоторый текст в тег HTML. Вы можете испытать желание сделать что-то вроде:

 def emphasize(text) "<em>#{text}</em>".html_safe end 

html_safe необходим для того, чтобы представление правильно отображало теги. Но непреднамеренно мы ввели уязвимость XSS: все, что находится в text аргументе, будет также рассматриваться как html_safe . Это означает, что если бы text был чем-то вроде <script>alert("oops")</script> , в результате вы получите <em><script>alert("oops")</script></em> . Пользователь не увидит содержимое, а тег script будет выполнен браузером. Это не то, что мы хотели бы случиться.

Для предотвращения этого есть пара вариантов. Вы можете явно экранировать text переменную:

 def safe_emphasize(text) "<em>#{h text}</em>".html_safe end 

Или вы можете использовать Rails ‘ content_tag helper:

 def another_safe_emphasize(text) content_tag :em, text end 

В обоих случаях содержимое будет экранировано, и в результате будет получен <em>&lt;script&gt;alert("oops")&lt;/script&gt;</em> , который будет отображать текст для пользователя и выигранный скрипт не выполняется браузером.

Менее распространенный случай

Еще один подводный камень — использование предоставленных пользователем данных в части href помощника link_to . Например:

 <%= link_to "Website", user.website %> 

В этом случае, если пользователь устанавливает что-то вроде javascript:alert("oops") качестве атрибута своего веб-сайта, этот код javascript будет выполнен. К сожалению, исправить это дело не так просто. Никакой встроенный помощник не удаляет код javascript: .

Как предотвратить это

  • При создании HTML-тегов из ваших помощников всегда старайтесь использовать встроенный помощник content_tag .
  • Если по какой-то причине вам нужно использовать строки, взгляните на некоторые полезные методы, такие как html_escape и strip_tags .
  • Всегда дважды проверяйте, используете ли вы предоставленные пользователем данные в качестве атрибута ссылки href . Возможно, вы захотите создать некоторый базовый помощник или проверку для предотвращения уязвимостей.

Отказ в обслуживании

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

 User.where(:name => { 'foo' => 'bar' }) 

строка ‘foo’ будет преобразована в символ.

Краткое объяснение

В Ruby символы никогда не собираются мусором после их создания. Символ всегда указывает на один и тот же адрес памяти — вот что делает его таким хорошим для повторяющихся хеш-ключей. Они не используют новую память или должны быть собраны мусором!

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

Вернуться к DoS

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

Если мы конвертируем пользовательский ввод в символы, тщательно разработанный запрос может привести к тому, что наше приложение создаст огромное количество символов, что приведет к отказу в обслуживании.

Как предотвратить это

  • Избегайте использования to_sym при to_sym пользователем. Если нет лучшего варианта, вы можете захотеть ввести белый список перед преобразованием его в символы.
  • Такое поведение наблюдается в отчетах контроллеров, уделяя особое внимание этим случаям.
  • Многие библиотеки используют to_sym под капотом, вы можете перепроверить это перед отправкой вводимых пользователем данных.

SQL-инъекция

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

Большинство встроенных в Rails методов запросов выполняют санацию перед генерацией SQL-запроса. Большинство из них. Это означает, что есть некоторые, которые этого не делают. Чтобы сделать это более сложным, есть способы избежать санитарии на тех, которые делают.

Простой случай

Например, запрос типа

 @posts = Post.where(user_id: params[:user_id]) 

дезинфицирует свои параметры. Но отвлеченный разработчик может написать что-то вроде этого:

 @posts = Post.where("user_id = '#{params[:user_id]}'") 

Все будет работать, как и ожидалось, но уязвимость только что была введена. ActiveRecord не пропустит эту часть запроса, позволяя злоумышленнику использовать тщательно разработанный параметр user_id для доступа ко всем записям сообщений (это может быть очень плохо, если вы имеете дело с конфиденциальной информацией). Параметр user_id ' OR 1 -- выдаст этот запрос:

 SELECT * FROM accounts WHERE user_id = '' OR 1 --' 

Он будет соответствовать всем записям, и злоумышленник будет иметь к ним доступ.

Всегда лучше использовать условия хеширования. Если вам нужно использовать строку для установки какого-либо сложного условия SQL, всегда используйте интерполяцию Rails:

 @posts = Post.where("user_id = ? AND posted_at > ?", params[:user_id], params[:date]) 

Это будет работать точно так же, как в первом примере: user_id параметров user_id и date .

Менее распространенный случай

Это был относительно простой сценарий. Но настоящей ловушкой являются те методы, которые не дезинфицируют свои аргументы и не документируют их.

Вот обширный список этих методов. Это даже указывает на несколько вещей, которые следует иметь в виду с теми, которые действительно дезинфицируют.

Например, метод lock принимает любой sql в качестве аргумента.

 User.where(id: params[:id]).lock(params[:lock]) 

Если злоумышленник предоставляет тщательно разработанный параметр lock такой как " OR 1=1" , приложение выполнит запрос, подобный следующему:

 SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 OR 1=1 

Это вернуло бы всех пользователей вместо того, у которого был запрошенный id .

Подобные атаки и форма вредоносных параметров могут сильно различаться в зависимости от ядра СУБД. Обязательно используйте один и тот же механизм базы данных во всех своих средах и прочитайте их документацию.

Как предотвратить это

  • Прежде всего, обязательно прочтите Руководство по безопасности Rails и помните о методах, перечисленных здесь .
  • Всегда пытаться использовать условия хеширования или использовать ? интерполяция в другом случае. Никогда не указывайте только одну строку для условий запроса .
  • Двойная проверка при написании необычного запроса. Возможно, вы захотите попробовать отправить какой-нибудь SQL-запрос самостоятельно, чтобы проверить, не вышел ли он.

Жемчужина, чтобы держать нас в безопасности

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

Чтобы упростить нашу жизнь (или дать нам больше поводов для беспокойства?), Есть Brakeman . Brakeman — это сканер безопасности для приложений Ruby on Rails. Он работает, просматривая исходный код, а не взаимодействуя с приложением. Это имеет некоторые плюсы и минусы, которые хорошо объяснены в их документации .

Как только вы запустите brakeman , он выдаст хороший отчет со списком предупреждений с указанием достоверности, файла, типа и описания:

 +------------+---------------------------------------------------------+----------------------+--------------------------------------------+ | Confidence | Template | Warning Type | Message | +------------+---------------------------------------------------------+----------------------+--------------------------------------------+ | High | posts/show | Cross Site Scripting | Unsafe parameter value in link_to href ... | | Weak | dashboard/index (DashboardController#index) | Cross Site Scripting | Symbol conversion from unsafe string ... | +------------+---------------------------------------------------------+----------------------+--------------------------------------------+ 

Здесь сообщения обрезаны, но Brakeman выдает действительно полезные предупреждающие сообщения, включая строку файла и контекст.

Совет : я рекомендую вывести отчет в файл HTML (с помощью опции -o report.html ), чтобы получить полную информацию.

В Brakeman также есть плагин Jenkins, если вы хотите сканировать свой код в среде CI. Это действительно полезный и простой способ следить за безопасностью.

Подводя итоги

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

Дополнительные ресурсы