Безопасность Ruby
Язык программирования Ruby большой и сложный, и новички, и опытные разработчики Ruby часто сталкиваются с множеством проблем безопасности.
Этот документ призван обсудить многие из этих проблем и предложить более безопасные альтернативы, где это возможно.
Пожалуйста, ознакомьтесь с полным списком общеизвестных CVEs и правильного способа сообщения о уязвимости безопасности по адресу: www.ruby-lang.org/en/security/ Японская версия находится здесь: www.ruby-lang.org/ja/security/
Уязвимости в области безопасности следует сообщать по электронной почте на адрес security@ruby-lang.org (открытый ключ PGP), который является частным списком рассылки. Сообщенные проблемы будут опубликованы после исправления.
$SAFE
Ruby предоставляет механизм для ограничения операций, которые может выполнять код Ruby, в виде переменной $SAFE.
Однако, $SAFE не предоставляет безопасную среду для выполнения недоверенного кода.
Если вам необходимо выполнить недоверенный код, вы должны использовать механизм изоляции на уровне операционной системы. В Linux для изоляции потенциально вредоносного кода можно использовать ptrace или LXC. Аналогичные механизмы существуют на всех основных операционных системах.
Marshal.load
Модуль Marshal Ruby предоставляет методы для сериализации и десериализации деревьев объектов Ruby в двоичный формат данных и обратно.
Никогда не используйте Marshal.load для десериализации недоверенных или предоставленных пользователем данных. Поскольку Marshal может десериализовать почти любой объект Ruby и имеет полный контроль над переменными экземпляра, существует возможность создания вредоносной полезной нагрузки, которая выполнит код вскоре после десериализации.
Если вам необходимо десериализовать недоверенные данные, вы должны использовать JSON, так как он может возвращать только «примитивные» типы, такие как строки, массивы, хэши, числа и nil. Если вам нужно десериализовать другие классы, вы должны обрабатывать это вручную. Никогда не десериализуйте в указанный пользователем класс.
YAML
YAML — популярный удобочитаемый формат сериализации данных, используемый во многих программах Ruby для конфигурации и сохранения данных деревьев объектов Ruby в базах данных.
Аналогично Marshal, он может десериализовать в произвольные классы Ruby. Например, следующие данные YAML создадут объект ERB при десериализации:
!ruby/object:ERB src: puts `uname`
Из-за этого многие соображения безопасности, применимые к Marshal, также применимы к YAML. Не используйте YAML для десериализации недоверенных данных.
Символы
Символы часто рассматриваются как синтаксический сахар для простых строк, но они играют гораздо более важную роль. Реализация MRI Ruby использует символы в качестве имен методов, переменных и констант. Причина этого в том, что символы — это просто целые числа с присоединенными к ним именами, поэтому их быстрее искать в хэш-таблицах.
Начиная с версии 2.2, большинство символов могут быть удалены сборщиком мусора; эти символы называются смертными символами. Большинство символов, которые вы создаете (например, вызывая to_sym), являются смертными.
Бессмертные символы, с другой стороны, никогда не будут удалены сборщиком мусора. Они создаются при модификации кода:
-
определение метода (например, с помощью
define_method), -
установка переменной экземпляра (например, с помощью
instance_variable_set), -
создание переменной или константы (например, с помощью
const_set)
Расширения C, которые не были обновлены и до сих пор вызывают `SYM2ID`, создают бессмертные символы. Ошибки в 2.2.0: send и +__send__+ также создавали бессмертные символы, а вызов методов с ключевыми аргументами также мог создавать некоторые.
Не создавайте бессмертные символы из входных данных пользователя. В противном случае это позволит пользователю осуществить атаку типа отказ в обслуживании против вашего приложения, засыпав его уникальными строками, что приведет к неограниченному росту памяти до тех пор, пока процесс Ruby не будет убит или система не замедлится до полной остановки.
Хотя вызов этих методов с входными данными пользователя не рекомендуется, уязвимые ранее методы, такие как to_sym, respond_to?, method, instance_variable_get, const_get, и т.д. больше не представляют угрозы.
Регулярные выражения
Синтаксис регулярных выражений Ruby имеет некоторые незначительные отличия по сравнению с другими языками. В Ruby якоря ^ и $ не относятся к началу и концу строки, а к началу и концу строки.
Это означает, что если вы используете регулярное выражение, например, /^[a-z]+$/ для ограничения строки только буквами, злоумышленник может обойти эту проверку, передав строку, содержащую букву, затем перевод строки и любую строку по своему выбору.
Если вы хотите сопоставить начало и конец всей строки в Ruby, используйте якоря \A и \z.
eval
Никогда не передавайте недоверенные или контролируемые пользователем входные данные в eval.
Если вы не реализуете REPL, как irb или pry, eval практически наверняка не то, что вам нужно. Не пытайтесь отфильтровать входные данные пользователя перед передачей их в eval — этот подход чреват опасностями и, скорее всего, приведет к серьезной уязвимости удаленного выполнения кода в вашем приложении.
send
«Глобальные функции» в Ruby (puts, exit, и т.д.) на самом деле являются частными методами экземпляра Object. Это означает, что их можно вызвать с помощью send, даже если вызов send имеет явный получатель.
Например, следующий фрагмент кода выводит «Hello world» в терминал:
1.send(:puts, "Hello world")
Никогда не вызывайте send с входными данными пользователя в качестве первого параметра. Это может привести к уязвимости от отказа в обслуживании:
foo.send(params[:bar]) # params[:bar] is "exit!"
Если злоумышленник может контролировать первые два аргумента send, возможно удаленное выполнение кода:
# params is { :a => "eval", :b => "...ruby code to be executed..." }
foo.send(params[:a], params[:b])
При отправке вызова метода на основе входных данных пользователя тщательно проверяйте имя метода. Если возможно, проверяйте его по белому списку безопасных имен методов.
Обратите внимание, что использование public_send также опасно, так как send само по себе является публичным:
1.public_send("send", "eval", "...ruby code to be executed...")
DRb
Так как DRb позволяет удаленным клиентам вызывать произвольные методы, он не подходит для использования с недоверенными клиентами.
При использовании DRb, старайтесь избегать его экспонирования в сети, если это возможно. Если это невозможно и вам необходимо экспонировать DRb во внешнюю сеть, вы обязательно должны настроить соответствующую политику безопасности с DRb::ACL.
Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.