Уязвимости PHP-фреймворков

22.07.2020

10 июня состоялась онлайн-встреча по информационной безопасности Digital Security ON AIR. Записи докладов можно посмотреть на Youtube-канале. Также по материалам докладов мы выпустим цикл статей, и первая из них — об уязвимостях PHP-фреймворков.
Полезного чтения!

Что такое MVC

Большинство PHP фреймворков использует MVC — Model-View-Controller — концепцию разделения проекта на три отдельных компонента:

  • модели, отвечающие за данные
  • представления, отвечающие за интерфейс
  • контроллеры, отвечающие за логику

Cхематичное представление:

Компоненты независимы друг от друга, то есть внесение изменений в один из них не затронет другие.

Полезные уязвимости в PHP

Итак, что же искать в типичном проекте, созданном на PHP-фреймворке? Искать нужно все! Как ни удивительно, встретить можно любую уязвимость, так как ответственность за безопасность лежит в большей степени на разработчике проекта. Прежде всего нужно проверить, не включен ли режим отладки (исследователю это на руку, но если вы забыли выключить его на своем сайте, то поспешите это сделать). Режим отладки сильно поможет как в дальнейшем поиске уязвимостей, так и в их эксплуатации. Кроме режима отладки в любом проекте на PHP обязательно используются операторы сравнения для описания логики приложения. И тут скрывается еще одна уязвимость, типичная для PHP.

Речь идет о type juggling. Она возникает, когда в коде используется оператор сравнения == вместо ===. Оператор сравнения == сравнивает объекты в PHP по-особому, предварительно преобразовывая типы данных, из-за чего логика при сравнении пользовательских данных может быть нарушена. Например, можно войти под учетной записью администратора, используя пароль null. Оператор сравнения === сравнивает объекты без преобразования типов, рекомендуется использовать его в качестве основного оператора сравнения. Примеры сравнения приведены в табличке ниже из доклада о type juggling от OWASP.

Рекомендую ознакомиться и с самим докладом, чтобы подробнее узнать об уязвимости.

Не менее распространенная уязвимость, из-за которой возможны большинство RCE, — это десериализация потенциально опасных данных. Дело в том, что в PHP можно сериализовать любой объект — преобразовать объект в строку, из которой его можно восстановить. Для восстановления строчку нужно десериализовать с помощью функции unserialize(). Уязвимость возникает, когда на вход этой функции подаются данные, контролируемые пользователем. Так он может собрать цепочку из классов, которые приведут к выполнению кода или чтению файлов на сервере. Тут стоит упомянуть phpggc — сборник уже готовых гаджетов (gadgetchains, цепочек из классов) для популярных PHP-фреймворков. Узнать больше об эксплуатации десериализации можно из доклада Павла Топоркова.

Каждый фреймворк состоит из набора пакетов, которые нужно как-то компоновать. На помощь приходит Composer, управляющий всеми зависимостями, который используется во всех фреймворках и сильно упрощает жизнь: все зависимости прописаны в одном файле composer.json. Все зависимые пакеты можно проверить на уязвимости с помощью сайта snyk.io просто передав ему composer.json. Также можно проверить только PHP-уязвимости с помощью специального инструмента — Security Checker.

Laravel

Перейдем к главному — к анализу PHP-фреймворков. Самым популярным PHP-фреймворком считается Laravel: он очень простой и достаточно безопасный сам по себе. Вся его логика описана в контроллерах. Middleware находится рядом с контроллерами и нужна для проверки шаблонных условий: например, является ли пользователь администратором или является ли введенный e-mail валидным. Также middleware может лежать в kernel.php. Все маршруты лежат в одной папке. Web маршруты лежат в web.php, а api маршруты — в api.php. Представления лежат в отдельной папке и обычно являются blade-шаблонами. Конфигурационный файл сайта находится в корне, в файле .env. В нем хранятся учетные данные от базы данных и APP_KEY, включается режим отладки и прописываются другие настройки сервера.

Всего было найдено четыре довольно критичных уязвимости в Laravel:

  1. CVE-2017-9303 — специфичная утечка учетных данных
  2. CVE-2017-16894 — утечка файла .env через прямое обращение к нему
  3. CVE-2018-6330 — error-based SQLI в GET-параметре dhx_user (пример вызова версии базы данных представлен на рисунке)
  4. CVE-2018-15133 — полноценная RCE через десериализацию хедера
  5. Рассмотрим возможность получения RCE.

    Демо-видео

    В качестве примера был создан блог, уязвимый к этой CVE. Эксплуатация данной уязвимости возможна при соблюдении двух условий: нужно знать APP_KEY из .env, и сервер должен принимать POST-запросы. Если версия Laravel очень старая, то можно воспользоваться имеющейся уязвимостью CVE-2017-16894 и достать .env

    Второй способ — вызвать ошибку, чтобы перейти в режим отладки, и из него получить необходимый APP_KEY. Далее генерируем заголовок со встроенной полезной нагрузкой. Если отправить POST запрос на сервер с таким заголовком, то полезная нагрузка десериализуется на сервере, что спровоцирует выполнение кода. В данном случае выполнится команда, которую мы первым аргументом передали в скрипт. Скрипт состоит из двух частей: скрипта подписи заголовка, доступного по ссылке, и phpggc-модуля, с помощью которого генерируется полезная нагрузка. Остается отправить POST-запрос с этим заголовком и получить результат выполнения команды на сервере.

    Symfony

    В PHP-фреймворке Symfony маршруты располагаются в директории config. Контроллеры находятся в директории src, представления — в директории templates.

    У Symfony на cvedetails можно найти наибольшее количество CVE по сравнению с другими фреймворками. Среди них есть и пара очень старых RCE. Одна возможна из-за того, что при эксплуатации XSS в тэге script в параметре language можно указать язык PHP, и тогда PHP-код, указанный внутри тэга, выполнится на сервере. Вторая RCE, еще более старая, позволяет поместить PHP-код в специально сконфигурированный yaml-файл. При парсинге yaml-файла PHP-код выполнится на сервере.

    Интересна история с обходом аутентификации. Сразу оговоримся, что CVE 2016 и 2018 года относятся к одному и тому же модулю аутентификации через LDAP. В 2016 году обнаружили, что процесс аутентификации можно обойти, используя пустой пароль (CVE-2016-2403). Уязвимость исправили, но допустили ошибку.

    В 2017 году в другом модуле аутентификации нашли ту же проблему (CVE-2017-11365). В этот раз действительно исправили: введенный пользователем пароль сверяется не только с пустой строкой, но и с null. Это необходимо, потому что в языке PHP null не считается за пустую строку.

    В 2018 году все-таки нашли ошибку в исправлении 2016 года (да-да, и такое бывает), которая позволяла обойти аутентификацию с помощью null, и закрыли ее (CVE-2018-11407).

    Вот так можно было 2 года обходить LDAP с помощью null.

    За последний год нашли еще несколько разнообразных уязвимостей на любой вкус. Некоторые из них легко эксплуатировать, например, уязвимости, заключающиеся в возможности header injection. Другие — например RCE — эксплуатировать довольно сложно. Они новые, и модулей для них в phpggc нет, а работать с классами во фреймворках нужно уметь.

    Yii

    Перейдем к фреймворку Yii. У него все не так, как у других, поэтому рассмотрим его более внимательно. В Yii помимо типичных для MVC моделей, представлений и контроллеров, есть виджеты, модули и фильтры. Модули — это законченные приложения со своими данными, интерфейсами, логикой. В отличие от самих приложений, модули не могут быть развернуты отдельно: они должны находиться внутри приложений.

    Фильтр — та же middleware, которая используется для шаблонных действий. Например, чтобы проверить, является ли пользователь администратором или является ли введенный e-mail валидным. Виджеты служат для создания сложных настраиваемых элементов пользовательского интерфейса. Например, с помощью виджета можно сгенерировать интерфейс выбора дат. Кроме того, в Yii нет конкретного файла с маршрутами. Все дело в том, что он сам строит маршрутизацию внутри сайта, используя контроллеры и действия. Соответственно, нам нужно либо вручную искать все контроллеры и все действия, либо писать чудо-скрипт, который сгенерирует файл с маршрутами.

    Из уязвимостей следует отметить три CVE:

    1. CVE-2014-4672 — RCE, позволяющая выполнять любые PHP-методы
    2. CVE-2018-6010 — утечка информации через ошибки
    3. CVE-2018-7269 — SQLi

    Остановимся на первых двух поподробнее.

    Демо-видео

    Утечка информации возникает, когда режим отладки выключен и исключения ловятся стандартной функцией. Так вышло, что при переходе на страницу About срабатывает исключение, режим отладки выключен, и мы не можем получить никакой дополнительной информации из этой ошибки. Но если на эту же страницу обратиться при помощи ajax-запроса, то внезапно мы получаем содержимое конфигурационного файла. Как же так? Все дело в том, что при ajax-запросе выдается сообщение, прописанное в исключении. Для наглядности мы поместили туда конфигурационный файл сайта. Однако, через такую уязвимость могут просочиться достаточно чувствительные данные (пароли или ключи), которые помогут при исследовании сайта.

    Демо-видео

    Если вам повезло встретить проект, использующий Yii версии 1.1.14 и виджет CDetailView, в который передаются пользовательские данные, то вы сможете выполнять любой метод на сервере. На Github'е пишут, что таким образом можно выполнить любой PHP-файл в системе, но когда я попробовал это сделать, он не проходил условие, прописанное в функции run класса CDetailView, если не был явно подключен в коде.

    
    $value=is_callable($attribute['value']) ? call_user_func($attribute['value'],$this->data) : $attribute['value'];
    

    Так мы передаем название класса и метода в запросе и видим содержимое конфигурационного файла сайта при помощи заранее добавленной секретной функции. Это все происходит потому, что параметр value проверяется функцией is_callable(), и если результат сравнения положительный, то он вызывается функцией call_user_func().

    CakePHP

    Фреймворк CakePHP структурно мало чем отличается от Laravel. Понятно, где искать модели, представления и контроллеры.

    В нем так бы и были только лишь древние уязвимости (CVE-2010-4335, CVE-2012-4399), если бы не недавняя CVE-2019-11458 на десериализацию, при помощи которой можно перезаписывать файлы на сервере. Гаджетов пока что нет, поэтому будем ждать обновления phpggc.

    Codeigniter

    И, наконец, Codeigniter. Структура приложения понятная, все компоненты находятся на своих местах.

    Из интересных уязвимостей можно выделить две старых (CVE-2014-8684, CVE-2016-10131) и одну относительно новую (CVE-2017-1000247). Первая из них настолько старая, что для нее есть модуль для Metasploit. Вторая встречается не только в этом фреймворке, но и во многих других PHP-фреймворках и PHP-приложениях. Более свежая CVE 2017 года — обычная header injection в функции set_status_header(). Рассмотрим поподробнее две старые уязвимости.

    Демо-видео

    Первая уязвимость связана с сессией: зная ключ шифрования можно расшифровать cookie-файл и поставить себе права админа. Кроме того, можно загрузить полезную нагрузку, которая выполнится на сервере. Все это возможно из-за слабой криптографии (всего лишь XOR) и использования дефолтного ключа во многих проектах. В данном случае, эксплуатируя уязвимость, мы получаем права админа.

    Демо-видео

    В этом примере рассмотрим вторую уязвимость — в функции mail(). Она передает параметры в программу Sendmail, пятый аргумент которой опционален. Этим пятым аргументом в нее можно передать адрес отправителя, чтобы почтовый сервер получил сообщение об ошибке, если сообщение не будет доставлено. Но передает он его в Sendmail с помощью флага -f, благодаря чему можно вставить через пробел свои аргументы и получить RCE. Существует много техник эксплуатации, но обычно используется два основных метода. В первом случае при помощи флага -С (использовать другой конфигурационный файл) читаем содержимое любого файла на сервере; его можно сохранить в какой-нибудь файл при помощи флага -Х (записывать весь траффик). Это нужно для того, чтобы открыть файл напрямую через сайт, записав содержимое файла в корень веб-сервера. Во втором случае с помощью флага -OQueueDirectory перемещаем письмо в очередь в указанную папку и сохраняем его полностью с PHP-кодом внутри тела письма. Далее выполняем его, например, добавив в корень веб-сервера.

    Заключение

    Подводя итог, стоит сказать, что PHP-фреймворки просты и лаконичны. Наиболее критичные уязвимости существуют в старых версиях фреймворков, так что необходимо регулярно обновлять их, иначе пентест продукта может сразу выявить парочку RCE. Я разобрал и перечислил далеко не все уязвимости, и далеко не во всех фреймворках. Кто знает, сколько еще удивительных уязвимостей они хранят в себе. Увлекательных пентестов!

    Обсудить статью можно на habr

    Автор: Владимир Волков, пентестер Digital Security.