Главная » Компьютер, железо » Объявление activate php. Использование библиотеки TGM Plugin Activation в своих темах WordPress. С чего все началось

Объявление activate php. Использование библиотеки TGM Plugin Activation в своих темах WordPress. С чего все началось

Сегoдня мы рассмотрим эксплуатацию критической 1day-уязвимости в популярной CMS Joomla, которая прогремела на просторах интернета в конце октября. Речь пойдет об уязвимостях с номерами CVE-2016-8869 , CVE-2016-8870 и CVE-2016-9081 . Все три происходят из одного кусочка кода, который пять долгих лет томился в недрах фреймворка в ожидании своего часа, чтобы затем вырваться на свободу и принести с собой хаос, взломанные сайты и слезы ни в чем не повинных пользователей этой Joomla. Лишь самые доблестные и смелые разработчики, чьи глаза красны от света мониторов, а клавиатуры завалены хлебными крошками, смогли бросить вызов разбушевавшейся нечисти и возложить ее голову на алтарь фиксов.

WARNING

Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.

С чего все началось

6 октября 2016 года Дэмис Пальма (Demis Palma) создал топик на Stack Exchange , в котором поинтересовался: а почему, собственно, в Joomla версии 3.6 существуют два метода регистрации пользователей с одинаковым названием register() ? Первый находится в контроллере UsersControllerRegistration , а второй - в UsersControllerUser . Дэмис хотел узнать, используется ли где-то метод UsersControllerUser::register() , или это лишь эволюционный анахронизм, оставшийся от старой логики. Его беспокоил тот факт, что, даже если этот метод не используется никаким представлением, он может быть вызван при помощи сформированного запроса. На что получил ответ от девелопера под ником itoctopus, подтвердившего: проблема действительно существует. И направил отчет разработчикам Joomla.

Далее события развивались самым стремительным образом. 18 октября разработчики Joomla принимают репорт Дэмиса, который к тому времени набросал PoC, позволяющий регистрировать пользователя. Он опубликовал заметку на своем сайте , где в общих чертах рассказал о найденной проблеме и мыслях по этому поводу. В этот же день выходит новая версия Joomla 3.6.3, которая все еще содержит уязвимый код.

После этого Давиде Тампеллини (Davide Tampellini) раскручивает баг до состояния регистрации не простого пользователя, а администратора. И уже 21 октября команде безопасности Joomla прилетает новый кейс. В нем речь уже идет о повышении привилегий . В этот же день на сайте Joomla появляется анонс о том, что во вторник, 25 октября, будет выпущена очередная версия с порядковым номером 3.6.3, которая исправляет критическую уязвимость в ядре системы.

25 октября Joomla Security Strike Team находит последнюю проблему, которую создает обнаруженный Дэмисом кусок кода. Затем в главную ветку официального репозитория Joomla пушится коммит от 21 октября с неприметным названием Prepare 3.6.4 Stable Release , который фиксит злосчастный баг.

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

27 октября исследователь Гарри Робертс (Harry Roberts) выкладывает в репозиторий Xiphos Research готовый эксплоит , который может загружать PHP-файл на сервер с уязвимой CMS.

Детали

Что ж, с предысторией покончено, переходим к самому интересному - разбору уязвимости. В качестве подопытной версии я установил Joomla 3.6.3, поэтому все номера строк будут актуальны именно для этой версии. А все пути до файлов, которые ты увидишь далее, будут указываться относительно корня установленной CMS.

Благодаря находке Дэмиса Пальмы мы знаем, что есть два метода, которые выполняют регистрацию пользователя в системе. Первый используется CMS и находится в файле /components/com_users/controllers/registration.php:108 . Второй (тот, что нам и нужно будет вызвать), обитает в /components/com_users/controllers/user.php:293 . Посмотрим на него поближе.

286: /** 287: * Method to register a user. 288: * 289: * @return boolean 290: * 291: * @since 1.6 292: */ 293: public function register() 294: { 295: JSession::checkToken("post") or jexit(JText::_("JINVALID_TOKEN")); ... 300: // Get the form data. 301: $data = $this->input->post->get("user", array(), "array"); ... 315: $return = $model->validate($form, $data); 316: 317: // Check for errors. 318: if ($return === false) 319: { ... 345: // Finish the registration. 346: $return = $model->register($data);

Здесь я оставил только интересные строки. Полную версию уязвимого метода можно посмотреть в репозитории Joomla.

Разберемся, что происходит при обычной регистрации пользователя: какие данные отправляются и как они обрабатываются. Если регистрация пользователей включена в настройках, то форму можно найти по адресу http://joomla.local/index.php/component/users/?view=registration .


Легитимный запрос на регистрацию пользователя выглядит как на следующем скриншоте.


За работу с пользователями отвечает компонент com_users . Обрати внимание на параметр task в запросе. Он имеет формат $controller.$method . Посмотрим на структуру файлов.

Имена скриптов в папке controllers соответствуют названиям вызываемых контроллеров. Так как в нашем запросе сейчас $controller = "registration" , то вызовется файл registration.php и его метод register() .

Внимание, вопрос: как передать обработку регистрации в уязвимое место в коде? Ты наверняка уже догадался. Имена уязвимого и настоящего методов совпадают (register), поэтому нам достаточно поменять название вызываемого контроллера. А где у нас находится уязвимый контроллер? Правильно, в файле user.php . Получается $controller = "user" . Собираем все вместе и получаем task = user.register . Теперь запрос на регистрацию обрабатывается нужным нам методом.


Второе, что нам нужно сделать, - это отправить данные в правильном формате. Тут все просто. Легитимный register() ждет от нас массив под названием jform , в котором мы передаем данные для регистрации - имя, логин, пароль, почту (см. скриншот с запросом).

  • /components/com_users/controllers/registration.php: 124: // Get the user data. 125: $requestData = $this->input->post->get("jform", array(), "array");

Наш подопечный получает эти данные из массива с именем user .

  • /components/com_users/controllers/user.php: 301: // Get the form data. 302: $data = $this->input->post->get("user", array(), "array");

Поэтому меняем в запросе имена всех параметров с jfrom на user .

Третий наш шаг - это нахождение валидного токена CSRF, так как без него никакой регистрации не будет.

  • /components/com_users/controllers/user.php: 296: JSession::checkToken("post") or jexit(JText::_("JINVALID_TOKEN"));

Он выглядит как хеш MD5, а взять его можно, например, из формы авторизации на сайте /index.php/component/users/?view=login .


Теперь можно создавать пользователей через нужный метод. Если все получилось, то поздравляю - ты только что проэксплуатировал уязвимость CVE-2016-8870 «отсутствующая проверка разрешений на регистрацию новых пользователей».

Вот как она выглядит в «рабочем» методе register() из контроллера UsersControllerRegistration:

  • /components/com_users/controllers/registration.php: 113: // If registration is disabled - Redirect to login page. 114: if (JComponentHelper::getParams("com_users")->get("allowUserRegistration") == 0) 115: { 116: $this->setRedirect(JRoute::_("index.php?option=com_users&view=login", false)); 117: 118: return false; 119: }

А так в уязвимом:

  • /components/com_users/controllers/user.php:

Ага, никак.

Чтобы понять вторую, гораздо более серьезную проблему, отправим сформированный нами запрос и проследим, как он выполняется на различных участках кода. Вот кусок, который отвечает за проверку отправленных пользователем данных в рабочем методе:

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «сайт», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score!

Создаем собственную страницу регистрации для мультисайта взамен стандартной wp-signup.php .

В обычной установке WordPress страницу регистрации (авторизации, сброса пароля) выводит файл wp-login.php .

  • /wp-login.php - авторизация
  • /wp-login.php?action=register - регистрация
  • /wp-login.php?action=lostpassword - сброс пароля

Для мультисайта в wp-login.php есть отдельные условия. Так, при переходе по ссылке /wp-login.php?action=register на мультисайте, WordPress сделает редирект на страницу /wp-signup.php . Во многих темах страница выглядит не очень привлекательно, поэтому мы сделаем свою собственную.

Основной сайт сети

По умолчанию, WordPress открывает страницу регистрации (wp-signup.php) на основном домене (сайте) сети. Тем не менее, можно сделать отдельную страницу регистрации для каждого сайта сети, даже если у них разные темы. Мы будем рассматривать случай, когда на всех сайтах сети есть своя собственная страница регистрации, но используется одинаковая тема и сайты различаются лишь языком. Если используются разные темы, потребуется написать больше кода.

functions.php?

Нет. Имя этого файла, кажется, упоминается в любой статье про WordPress. В нашем случае, с учетом того, что функционал регистрации рассчитан на несколько сайтов, имеет смысл вынести его в MU-плагины, которые загружаются при открытии любого сайта.

Лирическое отступление

Стоит отметить, что MU-плагины загружаются раньше обычных плагинов и до полной загрузки ядра WordPress, поэтому вызов некоторых функций может привести к фатальным ошибкам в PHP. Подобная «ранняя» загрузка имеет и свои плюсы. Скажем внутри любой темы нельзя цепляться к некоторым экшенам, которые срабатывают еще до загрузки файла functions.php из темы. Примером этого могут служить экшены из плагина Jetpack вида jetpack_module_loaded_related-posts (related-posts - название модуля) с помощью которых возможно отслеживать активность модулей в Jetpack. К этому экшену невозможно «прицепиться» из файла темы, потому что экшен уже сработал до загрузки темы - плагины загружаются раньше тем. Взглянуть на общую картинку порядка загрузки WordPress можно на странице Action Reference в кодексе .

Порядок в файлах

MU-плагины могут содержать любое количество файлов и любую стуктуру, которая покажется вам логичной. Я придерживаюсь примерно такой иерархии:

|-mu-plugins |-|-load.php |-|-|-selena-network |-|-|-|-signup |-|-|-|-|-plugin.php |-|-|-|-|-... |-|-|-|-jetpack |-|-|-|-|-plugin.php

В файле load.php подключаются все необходимые «плагины» для нашей сети:

// Load Traslates for all addons load_muplugin_textdomain ("selena_network", "/selena-network/languages/"); // Network Signup require WPMU_PLUGIN_DIR . "/selena-network/signup/plugin.php"; // Another plugins // require WPMU_PLUGIN_DIR ...

Внутри папки selena-network хранятся папки плагинов, в каждой есть свой plugin.php , которые мы и подключаем в load.php . Это дает гибкость и возможность быстро отключать и включать некоторые вещи.

Адрес страницы регистрации

Чтобы указать адрес страницы регистрации, используется фильтр wp_signup_location . Его можно найти внутри файла wp-login.php и именно он отвечает за редирект на wp-signup.php .

Case "register" : if (is_multisite()) { wp_redirect(apply_filters("wp_signup_location", network_site_url("wp-signup.php"))); exit;

Добавим свою функцию в mu-plugins/selena-network/signup/plugin.php , которая будет отдавать адрес страницы регистрации на текущем сайте:

Function selena_network_signup_page ($url) { return home_url () . "/signup/"; } add_filter ("wp_signup_location", "selena_network_signup_page", 99);

selena_network - префикс, который я использую в именах всех функций внутри MU-плагинов на своем сайте для избежания коллизий, его следует заменить на свой собственный уникальный префикс. Приоритет добавления фильтра 99, потому что некоторые плагины, например bbPress и BuddyPress могут перезаписать этот адрес на свой собственный (MU-плагины загружаются раньше, чем обычные плагины, см. выше). Обратите внимание, что используется home_url() , вместо network_site_url() , чтобы оставить посетителя на том же домене. В качестве адреса можно использовать любой URL.

Создание страницы

Теперь создадим страницу с адресом site.com/signup/ через обычный интерфейс, а в папке дочерней темы шаблон для нашей новой страницы - page-signup.php . Вместо слова «signup» можно использовать уникальный ID.

Внутри нового шаблона необходимо выполнить вызов функции selena_network_signup_main() , которая будет выводить форму регистрации.

Стоит заметить, что весь процесс с шаблонами не обязателен и вместо этого можно создать свой шорткод, который будет также вызывать функцию selena_network_signup_main() .

wp-signup.php и wp-activate.php

Теперь займемся созданием функции, которая будет выводить форму регистрации. Для этого скопируем файлы wp-signup.php и wp-activate.php из корня WordPress в mu-plugings/selena-network/signup/ (и не забываем их подключить внутри mu-plugins/selena-network/signup/plugin.php). Дальнейшие манипуляции с файлами крайне сложно и долго описывать, поэтому прийдется сделать их самостоятельно. Я лишь опишу что именно надо сделать и опубликую исходные файлы своего проекта:

  1. В начале файла удалить все require , вызов функций и прочий код вне функций.
  2. Переименовать все функции, добавив к именам уникальные префиксы.
  3. Нижнюю часть кода wp-signup.php обернуть в функцию selena_network_signup_main и в ее самом начале написать global $active_signup; .
  4. Заменить верстку на свою собственную в нужных местах.

Внутри wp-activate.php необходимо сделать примерно тоже самое:

  1. Удалить весь код вне функций, обернуть верстку в отдельную функцию.
  2. Изменить верстку в местах, где это необходимо.

Файл wp-activate.php отвечает за страницу активации аккаунта. Как и со страницей регистрации для нее необходимо создать отдельный шаблон, внутри которого вызывать функцию из файла wp-activate.php .

Отправляем письма активации

Страница регистрации отправляет посетителю письмо со ссылкой на активацию аккаунта. По умолчанию этим занимается функция wpmu_signup_user_notification() из файла ms-functions.php . Ее функционал можно заимствовать для своей функции. Причина, по которой необходимо отказаться от использования этой функции - она отправляет ссылку активации аккаунта с wp-activate.php . «Выключить» же эту функцию можно с помощью фильтра wpmu_signup_user_notification отдавая по нему false (если этого не cделать, письмо активации будет отправляться дважды, окей, на самом деле два разных письма).

Function armyofselenagomez_wpmu_signup_user_notification($user, $user_email, $key, $meta = array()) { // ... // Код из функции wpmu_signup_user_notification() wp_mail($user_email, wp_specialchars_decode($subject), $message, $message_headers); return false; } add_filter("wpmu_signup_user_notification", "armyofselenagomez_wpmu_signup_user_notification", 10, 4);

В результате страница регистрации в теме Селена стала выглядеть намного чище и аккуратней.

Заключение

В интернете множество других не очень правильных способов того, как сделать тоже самое - редиректы Apache, AJAX-формы, которые не будут работать без Java Script и т. п. Все это мне не очень понравилось, поэтому я постарался сделать это максимально правильно на своем собственном сайте.

Замечу, что править файлы следует осторожно и стараться не сильно отходить от исходных, чтобы в дальнешйем, в случае если WordPress изменит файлы wp-signup.php и wp-activate.php , их проще было сравнивать между собой для поиска изменений.

Не забывайте смотреть в исходный код всех описанных выше функций, чтобы полностью разобраться с тем, что и как происходит внутри кода.

Бонус. Защита от спамеров

Даже самые маленькие сайты на WordPress часто подвергаются налету спам-регистраций. Можно писать бесконечные условия для фильтрации ботов, зачастую больше похожие на попытку создать искусственный интеллект 🙂 В случае мультисайта мне очень помог обычный редирект в Apache, с помощью которого при открытии /wp-signup.php и /wp-acitvate.php я попросил выдавать 404 (я не эксперт по настройке Apache, поэтому мои правила могут быть не очень правильными).

RewriteEngine On RewriteBase / RewriteRule ^wp-signup\.php - RewriteRule ^wp-activate\.php - # BEGIN WordPress # Правила от WordPress по умолчанию не трогаем:) # ... # END WordPress

P. S. Я стараюсь максимально детально описывать некоторые сторонние вещи, потому что когда начинал я, порой некому было подсказать и объяснить многие вещи. Также я считаю, что подобные небольшие наводки на другие материалы кого-нибудь подтолкнут к изучению чего-то нового и расширению своей области знаний. В записях RewriteRule используются регулярные выражения, они совсем не сложные, например, символ ^ означает начало строки.

Темы обычно не являются функциональными, однако иной раз нам, разработчикам, требуется внедрить некоторые возможности в нашу тему, чтобы сделать ее чуть лучше и удобнее.

В этом руководстве мы рассмотрим термин «территория плагинов», а также научимся использовать фантастический инструмент, написанный Томасом Гриффином: библиотеку TGM Plugin Activation.

Функциональность темы: вторжение на территорию плагинов

Темы предназначены для того, чтобы изменить дизайн веб-сайта WordPress. В идеале тема должна затрагивать только визуальный аспект. Однако в нашем «золотом веке» WordPress разработчики плагинов зачастую включают в свои темы функциональные особенности, которые позволяют сохранить конкурентоспособность на рынке.

Это вторжение на территорию плагинов. Мы можем представить «территорию плагинов», как некоторые функциональные участки кода. Любой фрагмент кода, меняющий функциональность вашего сайта, должен быть представлен в виде плагина, если указанный код не заложен в ядро WordPress.

Я уже сформулировал ранее в одной из своих статей эмпирическое правило «территории плагинов:

Если особенность связана с визуальным представлением сайта, то в таком случае она должна быть включена в тему; если же она связана с функциональностью, то тогда она должна быть представлена в виде отдельного плагина.

Довольно простое правило. Люди по-прежнему стараются прописать на уровне кода функциональные фрагменты в своих темах, однако каталоги тем (такие как WordPress.org или ThemeForest) не принимают темы, которые вторгаются на «территорию плагинов». Таким образом, предложение функциональности в темах стало определенной проблемой.

К счастью, есть простое решение, которое не идет вразрез с правилом «территории плагинов».

Введение в библиотеку TGM Plugin Activation

Настройка TGM Plugin Activation

Обратите внимание на функцию tgmpa() с двумя параметрами в самом конце кода. Второй параметр – это переменная $config, которая также является массивом, как и $plugins. Как и следует из ее названия, вы можете настраивать библиотеку TGM Plugin Activation с помощью данного массива. Переменная принимает и свой собственный набор опций:

  • id (string) – уникальный id для библиотеки TGM Plugin Activation в вашей теме. Это очень важно: если другие плагины также используют TGM Plugin Activation, разные ID предотвратят возможные конфликты.
  • default_path (string) – дефолтный абсолютный путь для плагинов в вашей теме. Когда вы установите его, вы сможете использовать название ZIP-файла в качестве значения параметра source для вашего плагина.
  • menu (string) – слаг меню для страницы установки плагинов.
  • has_notices (boolean) – если задан в true, администраторские уведомления будут выдаваться для требуемых/рекомендованных плагинов.
  • dismissible (boolean) – если задан в true, пользователь может «закрыть» уведомления.
  • dismiss_msg (string) – если опция dismissible задана в false, данное сообщение будет показано над администраторским уведомлением.
  • is_automatic (boolean) – если задано в true, плагины будут активированы после того, как пользователь согласится их установить.
  • message (string) – дополнительный HTML, выводимый перед таблицей плагинов.
  • strings (array) – массив, который включает в себя выводимые сообщения. Вы можете задавать их как транслируемые строки. Посмотрите файл example.php, чтобы увидеть полный список всех сообщений.
"mytheme-tgmpa", // your unique TGMPA ID "default_path" => get_stylesheet_directory() . "/lib/plugins/", // default absolute path "menu" => "mytheme-install-required-plugins", // menu slug "has_notices" => true, // Show admin notices "dismissable" => false, // the notices are NOT dismissable "dismiss_msg" => "I really, really need you to install these plugins, okay?", // this message will be output at top of nag "is_automatic" => true, // automatically activate plugins after installation "message" => "", // message to output right before the plugins table "strings" => array(); // The array of message strings that TGM Plugin Activation uses); ?>

Заключение

Как вы можете видеть, предложить функциональность в темах WordPress возможно – вы просто должны думать в первую очередь о пользователях, которые могут переключиться с одной темы на другую. Библиотека TGM Plugin Activation предлагает действительно умный способ для этого.

Что вы думаете по поводу данного инструмента? Использовали ли вы его когда-либо, планируете ли вы его использовать в будущем? Делитесь своими мыслями!

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

Создание URL

Предположим, что у нас на сайте есть система аутентификации пользователей. После регистрации мы просим пользователя пройти процедуру верификации email-а. Для создания подобных ссылок можем воспользоваться специальным параметром token. Пример подобной ссылки:

Http://example.com/activate?token=ee97780...

Без базы данных тут нам не обойтись, поэтому давайте посмотрим на таблицу, с которой будем работать.

CREATE TABLE pending_users (token CHAR(40) NOT NULL, username VARCHAR(45) NOT NULL, tstamp INTEGER UNSIGNED NOT NULL, PRIMARY KEY(token));

В таблице будем хранить 3 поля: токен, имя пользователя и время. Для генерации токена мы воспользуемся функцией sha1(), которая выдаёт строку из 40 символов. Поле tstamp будет хранить время генерации токена для того, чтобы мы могли отследить ссылки с истёкшим сроком.

Существует множество способов генерации токена, однако в этом уроке мы воспользуемся функциями uniqid() и sha1(). Независимо от способа генерации токена, убедитесь что генерируемые значения будут разными и вероятность появления дубликатов минимальна.

$token = sha1(uniqid($username, true));

В качестве параметра функция uniqid() принимает строку, а на выходе даёт уникальный идентификатор, основанный на переданном аргументе и текущем времени. Также, в качестве второго аргумента, данная функция принимает булево значение, которое даст сигнал uniqid прибавить несколько дополнительных символов для увеличения вероятности уникальности значения. Функция sha1 принимает уникальный идентификатор и создаёт хэш.

После работы данных двух функций, у нас будет уникальный токен, который можем использовать для генерации url адресов. Теперь нам нужно занести его в базу:

$query = $db->prepare("INSERT INTO pending_users (username, token, tstamp) VALUES (?, ?, ?)"); $query->execute(array($username, $token, $_SERVER["REQUEST_TIME"]));

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

Теперь, когда у нас есть вся необходимая информация, можем создать временный url адрес:

$url = "http://example.com/activate.php?token=$token";

$message = <<

Проверка

Теперь нам нужен скрипт, благодаря которому мы будем осуществлять проверку. Всё что нам нужно сделать, это сравнить токен из url адреса и токен из базы. Если такой имеется, и время его жизни не истекло, то всё ОК.

// получаем токен if (isset($_GET["token"]) && preg_match("/^{40}$/i", $_GET["token"])) { $token = $_GET["token"]; } else { throw new Exception("токен не валиден."); } // проверяем токен $query = $db->prepare("SELECT username, tstamp FROM pending_users WHERE token = ?"); $query->execute(array($token)); $row = $query->fetch(PDO::FETCH_ASSOC); $query->closeCursor(); if ($row) { extract($row); } else { throw new Exception("токен не валиден."); } // активируем пользовательский аккаунт // ... // удаляем токен из базы $query = $db->prepare("DELETE FROM pending_users WHERE username = ? AND token = ? AND tstamp = ?",); $query->execute(array($username, $token, $tstamp));

Также нам нужно предусмотреть проверку токенов, время жизни которых истекло:

// 1 день в секундах = 60 секунд * 60 минут * 24 часа $delta = 86400; // проверка if ($_SERVER["REQUEST_TIME"] - $tstamp > $delta) { throw new Exception("время жизни токена истекло."); } // активируем пользовательский аккаунт // ...

Таким образом, у нас будут осуществляться две проверки: одна на валидность токена, другая на время его существования.

Итог

Данный метод можно применить не только для активации учётных записей пользователей, но и в других нуждах: к примеру, для предоставления однократного или временного доступа к какому-то ресурсу или услуге.

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



Предыдущая статья: Следующая статья:

© 2015 .
О сайте | Контакты
| Карта сайта