Бдительная «Лида»: автоматизация тестирования безопасности
Добрый день, уважаемые читатели. Меня зовут Виктор Буров, я разработчик в ISPsystem. В прошлом посте я рассказывал об инструменте для создания автотестов, сегодня поделюсь опытом автоматизации тестирования безопасности.
Сначала уязвимости в продуктах у нас искал отдельный сотрудник. Ручное тестирование занимало много времени и не гарантировало, что будут найдены все уязвимости. Выяснив основные закономерности тестирования, мы пришли к выводу, что его можно автоматизировать. Тогда мы решили написать утилиту, которая облегчит жизнь тестировщика, сэкономит его время и позволит проверять продукты после каждого изменения. Так как тестировщика звали Лида, новое приложение мы назвали в её честь. Вообще, у нас в компании это стало традицией — называть инструменты тестирования именами тестировщиц.
Проанализировав утилиты поиска уязвимостей, я пришёл к выводу, что им всем необходимо указывать функции для вызова и используемые параметры. Мы вновь решили воспользоваться преимуществами унифицированного интерфейса и сформулировали требования к «Лиде».
Требования на старте:
- Автоматическое построение списков функций.
- Автозаполнение параметров.
- Выполнение запросов к API.
- Анализ вывода данных после выполнения функций.
- Поиск уязвимостей в данных.
- Формирование отчётов.
- Гибкие настройки.
Реализовать всё это оказалось непросто.
Реализация
Обход форм и списков
Чтобы найти уязвимости в функции, её надо выполнить, передав необходимые параметры. Наш интерфейс строится на основе списков и форм, поэтому автоматизировать сбор данных можно за счёт обработки xml-документов, описывающих структуру элементов интерфейса.
Я решил начать обход с главного меню, рекурсивно заходя во все вложенные списки. «Лида» открывает список первого уровня. Как правило, в нём есть несколько кнопок, вызывающих какие-то функции.
Если кнопка открывает форму, то результатом вызова будет xml-документ с узлами, содержащими информацию о полях: имя, валидатор, диапазон допустимых значений. На основе этой информации генерируются значения полей. Например, для int будет сгенерировано число. Если валидатора нет, генерируется случайная строка. После заполнения всех полей формы отправляется запрос.
Если функция является списком, то он будет открыт и для его элементов будут вызваны функции, привязанные к кнопкам.
При проверке списков возникает проблема — все списки должны иметь набор записей, который обеспечит доступность нажатия всех кнопок в списке.
Поиск SQL-инъекций
SQL-инъекции — наверное, одна из самых распространенных проблем для приложений, в том числе и для наших. Многие вызовы функций порождают выполнение различных запросов к СУБД. Ошибка возникает, когда параметры, пришедшие извне, подставляются в тело запроса «как есть». Последствия таких ошибок могут быть печальными: от несанкционированного получения данных до удаления таблиц.
Чтобы начать поиск SQL-инъекций, я организовал вывод всех SQL-запросов в файл. После выполнения функции приложение ищет значения переданных параметров в получившихся SQL-запросах.
Можно было воспользоваться журналом самого SQL-сервера. Но в нашем случае существует только один метод для выполнения запросов и добавить в него логирование было несложно. Благодаря этому мы точно знаем, какой вызов породил тот или иной запрос.
Когда значение передаваемого параметра найдено, утилита передаёт в этот параметр значение, содержащее одинарную кавычку. Если кавычка найдена в той же последовательности, значит, параметр не экранируется — мы нашли место для SQL-инъекций.
Анализ системных вызовов
Аналогичная проблема возникает, когда мы выполняем какие-либо системные вызовы. Их я решил искать при помощи strace и подобрал для него оптимальные параметры запуска. Пример запуска ISPmanager:
strace -f -s 1024 -e trace=file,process bin/core ispmgr
Так же как и с SQL инъекциями, «Лида» выполняет функцию и анализирует вывод strace на предмет попадания в функции open, unlink, rmdir, chmod chown, chflags, mknod, mkfifo, fcntl, symlink, link, execve, mkdir значений переданных параметров.
Если параметр найден, утилита передаёт в него значение, содержащее, к примеру, путь с переходом в директорию выше. Если оно попадает как есть, то потенциальная уязвимость найдена.
Очень полезным оказался анализ функции execve. Он позволяет определить, в какой функции не экранируются аргументы запуска исполняемых файлов. Эта ошибка очень дорогая, ведь через нее можно получить root-доступ к серверу, просто сменив пароль.
При нахождении пользователями уязвимости в наших продуктах, компания выплачивает денежное вознаграждение, размер которого зависит от категории уязвимости. Данный подход может обходится дешевле, чем поиск ошибок собственными силами (тестировщик может ошибку и не найти, а зарплату получит).
Еще интересная проверка: проверка порядка вызова функций stat и других. Часто сначала проверяют доступ через stat, а затем какие-либо небезопасные действия, что оставляет возможность для подмены. Но такое мы не автоматизировали.
Проверка доступа к чужим объектам
Доступ к чужим объектам мы проверяем из-под пользователя для сущностей другого пользователя и администратора. В этом режиме мы проверяем возможность чтения, изменения и просмотра списков элементов чужого пользователя.
«Лида» обходит все доступные от имени владельца или администратора функции и запоминает их элементы. Затем вызывает эти же функции с элементами из-под другого пользователя. В идеальной ситуации ответом должна быть ошибка типа Access или Missed. Если такой ошибки не получено, следовательно, с большой вероятностью можно прочитать данные чужого пользователя.
В некоторых случаях отсутствие ошибки не означает, что можно получить прямой доступ к объектам другого пользователя. В начало таких функций мы добавляем проверку на права доступа к элементу. Так проверяется не только безопасность, но и корректность ответов сервера.
Валидация API
Валидация нашего API является дополнительным бонусом проверки функций на уязвимости. Анализируя отчеты о работе Лиды мы научились возвращать правильные типы ошибок, сделали наше API более удобным и логичным. В результате, как и с магнитофоном, мы получили не только проверку безопасности, но и в очередной раз проверили наш API на «логичность».
Трудности
Ложные срабатывания
Утилита может работать со всеми нашими продуктами, следовательно, она проверяет много разных функций. В основном ложные срабатывания появлялись в режиме проверки доступа к чужим объектам. Это связано с особенностями работы кнопок и функций.
Ложные срабатывания были самой большой проблемой «Лиды». Казалось, что она полностью отлажена, но при проверках на разных панелях возникали все новые ложные срабатывания. В итоге было несколько этапов их исправления.
Создание сущностей
Основная часть действий в панели выполняется над какой-либо сущностью (доменное имя, база данных и т. д.). Чтобы добиться максимальной автоматизации, «Лида» должна была создавать эти сущности автоматически. Но на практике это оказалось труднореализуемо. Иногда валидация выполняется в коде, поэтому не всегда удаётся подставить значение параметра автоматически. Вторая причина — зависимые сущности. Например, для создания почтового ящика необходимо создать почтовый домен.
Поэтому мы решили не биться за полную автоматизацию. Перед запуском тестировщик создаёт сущности вручную и делает снимок машины, так как после проверки сущности будут изменены. Это позволяет не пропустить проверку группы функций в случае неуспешного создания сущности.
Вызов деструктивных функций
Практически в каждом списке есть функции удаления или выключения сущности. Если выполнять их в порядке следования, то сущность будет удалена до выполнения других функций. Я определяю такие функции и выполняю после других. Дополнительно добавил ключ, который запрещает выполнение таких функций.
Некоторые функции перезагружают сервер. Их приходится отслеживать и добавлять в список игнорируемых.
Из-за особенностей логики работы некоторые функции перезапускают панель. Во время тестирования безопасности это приводит к тому, что панель запускается без трассировки SQL-запросов или strace — дальнейшая проверка становится бессмысленной. Приходится это отслеживать и заново запускать панель в режиме трассировки.
Проверка зависимых параметров
На формах есть поля ввод текста, чекбоксы, выпадающие списки, от значений которых зависит доступность значений других полей. На каждое значение зависимого поля может приходиться отдельный участок кода, следовательно, части когда могут остаться непроверенными.
Для решения этой проблемы я добавил алгоритмы анализа зависимых полей и проверку всех комбинаций зависимых элементов управления.
Проверка функций, недоступных в интерфейсе
Служебные функции недоступны для перехода, но могут содержать уязвимости. Чтобы определить и проверить их, у нас есть специальная функция, возвращающая список всех зарегистрированных функций. Этот список мы сравниваем со списком проверенных функций. Для служебных функций нет метаданных, поэтому проверить обрабатываемые внутри них параметры невозможно.Чтобы хоть как-то проверить такие функции, я передаю в них наши стандартные параметры elid, plid и другие.
Заключение
Мы включили «Лиду» в каждую ночную сборку в Jenkins. По итогу её работы формируется отчет о проверке, информация о подозрительной функции в нем выводится со всеми параметрами.
Закрытую разработчиком задачу теперь проверяет не только тестировщик, но и «Лида». Тестировщик обрабатывает полученный отчёт: копирует параметры подозрительной функции в браузер и анализирует поведение и лог панели. Если уязвимость подтверждается, регистрирует ошибку разработчику.