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


    10 июня компания Digital Security провела онлайн-встречу по информационной безопасности Digital Security ON AIR. Записи докладов можно посмотреть на Youtube-канале.


    По материалам докладов мы выпустим цикл статей, и первая из них — об уязвимостях PHP-фреймворков уже ждет под катом.


    Что такое MVC


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


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

    image


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


    Полезные уязвимости в 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:


    • CVE-2017-9303 — специфичная утечка учетных данных;
    • CVE-2017-16894 — утечка файла .env через прямое обращение к нему (http://example.com/.env);
    • CVE-2018-6330 — error-based SQLI в GET-параметре dhx_user (пример вызова версии базы данных представлен на рисунке ниже);
    • CVE-2018-15133 — полноценная RCE через десериализацию хедера.

    Рассмотрим возможность получения 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 нет, а работать с классами во фреймворках надо уметь. Так что если встретились с Symfony, уязвимым к этим багам, желаю удачи :)


    Yii


    Перейдем к фреймворку Yii. У него все не так, как у других, поэтому рассмотрим его более внимательно.


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



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


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


    • CVE-2014-4672 — RCE, позволяющая выполнять любые PHP-методы;
    • CVE-2018-6010 — утечка информации через ошибки;
    • 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. Я разобрал и перечислил далеко не все уязвимости, и далеко не во всех фреймворках. Кто знает, сколько еще удивительных уязвимостей они хранят в себе. Увлекательных пентестов!


    Первоисточник

    Digital Security
    Безопасность как искусство

    Комментарии 20

      +4
      По поводу Yii неправильный тест, да там было отключено отображение ошибок в самом PHP, но константа
      YII_DEBUG=true

      Это видно по значку в правом нижнем углу экрана, а этот режим как раз и служит для отображения ошибок разработчику. В production всегда должно быть YII_DEBUG=false, и это уже ошибка разработчика если он такое допустил.

      И ко всему тот, кто использует в PHP суперглобальный массив $_GLOBALS сразу теряется доверие, это никак не влияет на тест, но говорит о том, что человек не очень то разбирается в фреймворке, и возможно в PHP.
        +4
        Вы безусловно правы, значок есть. Но вы же не видите исходный код приложения. А в исходном коде прописана как раз таки константа YII_DEBUG=false. Если бы она имела значение true, то сообщение об ошибке при запросе было бы подробным, а так просто выскакивает ошибка 500 без информации. Однако, также прописана константа YII_ENV=dev, она как раз таки и отрисовывет значок в правом нижнем углу. Демо версия показывает уязвимость так, как и должна. Тем более, уязвимость не была бы уязвимостью, если бы дело было в том, что в дебаг режиме отображаются ошибки :) Использование массива $_GLOBALS очень некрасиво, но зато понятно абсолютно всем, даже тем, кто не разбирается в PHP.
        +4
        Аналогично и про Laravel, как я понимаю. Уязвимость получения .env файла мне кажется к фреймворку вообще отношения не имеет. Этого файла в принципе не должно быть на сервере.

        нужно знать APP_KEY из .env


        Если мы можем видеть env переменные, то у нас сильно больше проблем, так как там данные доступа к БД и тому подобное)
          0
          Этого файла в принципе не должно быть на сервере.
          А где он должен быть?
            0

            Нигде, эти «секреты» должны передаваться через переменные окружения, а потом конфиг должен быть закеширован в виде огромного массива со статическими значениями https://laravel.com/docs/7.x/configuration#configuration-caching


            Насколько помню файлик будет лежать в bootstrap/cache

              +1
              Кеширование — само собой. А вот зачем передавать через переменные окружения — вот это уже для меня загадка. Особенно учитывая что вебсервер смотрит на папку public, а .env лежит на уровень выше неё. Если же мы говорим о получении полного доступа к файловой системе сервера — то мы уже и cache файл сможем почитать точно так же, как .env.
                0
                ну технически окружение у процесса есть всегда, а вот файл еще вычитать надо, потратив на это ресурс. во-вторых так проще запускать во всяких докерах. есть один и тот же образ с одной и той же фс без .env, а настройки задаются при запуске образа. да хоть через тот же --env-file=.env, но приложение про него не узнает — образ остается неизменным, что более гибко. можно конечно смаунтить .env в образ, но тоже не думаю что это удобно и производительно.

                опять же в случае работы через переменные окружения не все фреймворки будут уязвимыми от утечки кэша. симфони например не кэширует env переменные при дампе кэша, насколько я знаю
              0
              Верно ответили ниже, следует устанавливать переменные окружения. Этот файл стоит использовать только для локальной разработки
                0
                А можно упоминание об этом где-нибудь в ларавель документации? Не вижу смысла не использовать этот файл на продакшне. Да, естественно, не хранить его в гите. Но тем не менее.
                  0
                  Пробежался по доке, не нашел. Был уверен, что это упоминание есть. Могу разве что вспомнить один из факторов 12factor приложений. Что, конечно, тоже не является истиной в последней инстанции.
                    0
                    Можете почитать обсуждение здесь → https://github.com/symfony/symfony/issues/25643.
                0
                Этого файла в принципе не должно быть на сервере.

                Не давайте вредных советов — этого файла просто не должно быть в веб доступе.
                  +1
                  Как я выше сказал, я был уверен, что это офф рекомендация. Для себя все же считаю, что лучше его вообще не помещать на сервер, а использовать переменные окружения.
                  Впрочем, убедиться, что он не доступен для веб-сервера, должно быть достаточно для большинства
                    0
                    Да и вредным этот совет можно назвать с большой натяжкой
                +6
                Т.е. сначала делаем из сервера проходной двор, а потом обвиняем фреймворки в дырявости?
                  +2
                  Большинство PHP фреймворков использует MVC — Model-View-Controller — концепцию

                  Поправка: не использует, а может использовать. Но как правило в админках, фронт обычно делается в рамках отдельных проектов на JS уже лет семь.


                  Одна возможна из-за того, что при эксплуатации XSS в тэге script в параметре language можно указать язык PHP, и тогда PHP-код, указанный внутри тэга, выполнится на сервере.

                  Что, простите?

                    0
                    Наверное речь об этом: <script language="php">, на очень древних проектах, которые дешевле просто «похоронить как есть», чем как-то переностить на 7.2+ версии
                      0
                      Так и есть, речь идет о CVE-2015-2308.
                        0

                        v1ru55 pOmelchenko
                        Почему 7.2, если этот функционал был доступен только до версии 5.6 включительно, которая умерла в 2018 году окончательно, при этом тег по умолчанию включен не был, а включить его можно было даже тогда только самостоятельно?


                        Раньше была потенциально, но не из-за фреймворка, а из-за целенаправленных действий эникейщика для её включения. Естественно про неё знали люди, связанные с разработкой и администрированием, но не включали.

                          0

                          Потому что 7.1 и старше уже официально не поддерживаются, при этом 7.2 хоть и получает патчи безопасности, но в ноябре и она тоже прекратит поддерживаться.


                          https://www.php.net/supported-versions.php


                          По этому, можно было бы сказать о том, что «проект дорого переводить на 7+», но в комментариях о проблемах безопасности лучше уточнять, как мне кажется, что если и переезжать, то на актуальные версии. Это то что касается самого языка.


                          А по поводу эникейщиков это да. Фрэймворк может быть и без уязвимостей. Но это никак не защитит от эникейщика который может целенаправленно, по не знанию, включать что-то что станет той самой дырой, через которую и получится нанести какой либо ущерб.


                          Выводом этого трэда можно сделать следующее: обновляйтесь, при этом читайте что приедет в обновлении. Там могут быть как патчи безопасности, так и ломающие какое-то поведение. При этом следите за обновлением не только языка и библиотек в проекте, но и за остальными компонентами окружения. Потому что сюрприз может приехать с любой стороны.

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

                  Самое читаемое