🐛 Ловушки безопасности Ruby и как их избежать
Продолжая большую тему ошибок в коде, разберем, как с этим обстоят дела в Ruby. На этот раз речь пойдет о самых страшных багах, приводящих к проблемам с информационной безопасностью.
Перевод публикуется с сокращениями, автор оригинальной статьи Dhruv.
Ruby – универсальный язык, сочетающий в себе простоту синтаксиса и мощные функции. Благодаря популярности фреймворка Rails, Ruby входит в десятку лучших современных языков. Однако с таким комьюнити разработчиков существует множество распространенных ошибок, ведущих к серьезным последствиям.
Острые ножи
С момента создания Ruby обрел репутацию языка программирования, который обладает ориентированными на счастье разработчиков функциями. Тем не менее он имеет особенность в виде «острых ножей» в своем ящике функций, и только вопрос времени, когда от них кто-то серьезно пострадает.
«Острые ножи» – это все доступное в Ruby метапрограммирование, и тот нюанс, что все открыто для изменения/переопределения в любой момент. Дальше станет понятнее. Рассмотрим ошибки и их решения.
Небезопасная десериализация
Выполнять все, что пользователь собирался отправлять – плохая мысль. А вот сериализация/десериализация пользовательского ввода не кажется такой уж плохой идеей, т. к. никакой код при этом не выполняется.
Основанная на Psych встроенная библиотека YAML Ruby поддерживает сериализацию пользовательских типов данных в YAML и обратно:
Десериализация этого YAML возвращает исходный тип данных:
Как вы можете видеть,
строка -- !ruby/object:
Set
описывает, как повторно создавать экземпляры объектов из их текстовых
представлений. Это открывает множество векторов атаки, которые могут перерасти
в RCE.
Решение
Решение заключается в
использовании безопасной загрузки. Используйте функцию YAML::safe_load
вместо
YAML::load
. Она полностью блокирует загрузку пользовательских классов:
Стандартные типы вроде хэшей и массивов все еще могут быть сериализованы в документы YAML и десериализованы из них, как и раньше.
IO hijacking
Если вашему приложению необходимо читать или записывать пользовательские файлы с диска или делать запросы через API к пользовательским URL-адресам, вы можете написать следующий код:
У пользователя запрашивается имя файла, а потом его содержимое выводится построчно. Здесь есть ошибка.
Функция Kernel::open
из приведенного выше фрагмента кода имеет еще одну
дополнительную функцию, которая используется для порождения процессов. Посмотрите,
что происходит, когда вы передаете начинающиеся с символа конвейера ( | ) входные данные:
Она выполняет команду date и передает выходные данные обратно Ruby. Злоумышленник будет выполнять команды гораздо более разрушительные, чем в этом коде. В нашем примере базовое RCE – удаленный пользователь, выполняющий код на сервере с полными привилегиями, доступными вашему веб-приложению.
Никогда не запускайте код от ненадежного источника, такого как пользователь, но если все-таки придется – ограничьте доступ к выбранным ресурсам.
Решение
Никогда не используйте
Kernel::open
! Альтернативой является File::open
, URL::open
или
IO::open
, в зависимости от контекста. Попробуем еще раз, на этот раз с
File::open
вместо Kernel::open
. Такой вариант гораздо более безопасен, поскольку он не дает доступа к shell:
SQL injection
Инъекция происходит, когда пользовательский ввод из фронтенда без проверок и очистки применяется непосредственно в SQL-запросе на бекенде.
Допустим, у вас есть столбец users, в котором выполняется поиск по введенному в форму имени. Вот, как бы это выглядело в Ruby:
Это небезопасно. Рассмотрим следующий ввод от злоумышленника:
Запрос ложный, как видно в первой строке и крашится из-за вредоносного кода, как показано во второй строке. WHERE всегда true и возвращает каждого пользователя из таблицы:
Решение
Используйте один из следующих двух способов:
Оба подхода предназначены для очистки значения перед генерацией запроса и поэтому невосприимчивы для этой атаки.
Автоинкремент ID
Когда вы создаете модель Rails, автоматически создается id – автоинкрементное целочисленное поле. По большей части этого достаточно. Целочисленные идентификаторы имеют преимущество: поскольку они постоянно увеличиваются, нет никаких шансов на коллизию. Что произойдет, если пострадает безопасность:
- Масштабирование может стать сложнее. Если множество серверов работают независимо друг от друга, вполне вероятно, что они могут назначить один и тот же ID разным ресурсам.
- Станет возможным определить, сколько у вас объектов. Можно зарегистрироваться и увидеть собственный идентификатор, чтобы узнать количество клиентов и даже оценить бизнес-показатели.
- Можно будет перебирать и красть данные. Злоумышленник сможет вывести весь список и получить все ограниченные ресурсы.
- Возможна утечка конфиденциальной информации. Незарегистрированные ресурсы, видимые только держателям ссылок, сломались бы из-за целочисленных ID.
Решение
Используйте UUID. С Rails сделать это будет очень просто. После несложного изменения все ваши модели будут использовать UUID в качестве первичных ключей:
Если вы все-таки хотите использовать целые числа, берите большие, выбранные случайным образом. Посмотрим на структуру URL-адресов ролика из YouTube. Так выглядит его URL-адрес:
Идентификатор видео – это
случайное число между $0$ и $64^{11}$
(более 73 квинтиллионов чисел), закодированное
в Base64. Не совсем UUID, но и не целочисленное поле с автоинкрементом.
Инструменты в помощь
Ключевой вывод из описанных выше примеров – никогда не доверять юзеру. Данные пользователя не должны быть напрямую сериализованы/десериализованы, оценены или обработаны. В качестве дополнительных мер предосторожности подойдет специальный софт.
Статические анализаторы кода
Инструменты анализа кода, такие как линтеры и сканеры уязвимостей, могут найти множество просчетов до того, как те начнут эксплуатироваться. RuboCop уведомит вас о проблемах безопасности, вроде небезопасной десериализации или IO hijacking, а также предложит варианты исправления.
Brakeman – сканер уязвимостей, который может найти SQL-инъекцию среди других ошибок безопасности. Оба инструмента являются бесплатными и поставляются с открытыми исходными текстами.
DeepSource – комплексное ПО, способное сканировать код, на каждом коммите, анализировать результаты работы линтеров/анализаторов и автоматически устранять множество проблем.
Для большинства языков программирования это решение имеет собственные пользовательские анализаторы, которые постоянно совершенствуются и обновляются. А еще оно элементарно устанавливается – нужно всего лишь добавить .deepsource.toml файл в корень репозитория и DeepSource готов:
Заключение
Проблемы с безопасностью – самые опасные и дорогостоящие. Будьте предельно внимательны и учитывайте все вышесказанное в своих проектах. Обязательно продолжайте изучать язык программирования, ошибки других разработчиков и нашу статью про ошибки новичков. Удачи!