
CVE-2024-11972 в Hunk Companion <1.9.0 позволяет неаутентифицированным атакующим эксплуатировать небезопасные конечные точки REST API и устанавливать уязвимые плагины, что подвергает риску RCE, SQLi, XSS и бэкдоры.
CVE-2024-11972 — это критическая уязвимость в плагине WordPress Hunk Companion в версиях ниже 1.9.0. Эта ошибка позволяет неаутентифицированным злоумышленникам использовать неправильно авторизованные конечные точки REST API, что даёт им возможность устанавливать и активировать плагины из репозитория WordPress.org, в том числе устаревшие или имеющие известные уязвимости. Эксплуатация этой уязвимости может привести к серьёзным угрозам безопасности, таким как удалённое выполнение кода, SQL-инъекции, XSS или создание бэкдоров в админке.
Типичный пример злоупотребления этой уязвимостью — с помощью плагина WP Query Console. После установки через этот эксплойт плагин предоставляет консольный интерфейс внутри WordPress, где пользователи могут выполнять SQL-запросы к базе данных сайта, что позволяет похитить данные, создать бэкдор и полностью скомпрометировать базу данных.
Необходимые библиотеки Python -> argparse, requests, urljoin
options:
-h, --help show this help message and exit
-u URL, --url URL Base URL of the WordPress site (default: http://localhost/wordpress/).
-p PLUGIN, --plugin PLUGIN
Plugin name to install (default: classic-editor).
<вставьте URL WordPress> -p <вставьте имя плагина>Уязвимая конечная точка /wp-json/hc/v1/themehunk-import была первоначально обнаружена Даниэлем Родригесом при анализе журналов доступа в ходе расследования.
Используя эту информацию, мы можем заглянуть внутрь исходного кода конечной точки импорта.
В файле /import/core/class-installation.php видим следующую строку:
204 | $temp_file = download_url('https://downloads.wordpress.org/plugin/'.$slug.'.zip');
Класс HUNK_COMPANION_SITES_BUILDER_SETUP управляет установкой и активацией плагинов и тем WordPress, обрабатывая как бесплатные, так и премиум-типы на основе входных параметров.
Он динамически проверяет, установлен ли плагин или тема, загружает и распаковывает необходимые файлы, если они отсутствуют, и активирует их с помощью основных функций WordPress.
Эта жёстко заданная функциональность позволяет плагину загружать любой плагин из репозитория WordPress, даже те, что были удалены или сняты с поддержки, давая злоумышленникам возможность использовать уязвимые плагины для эксплуатации.
Чтобы глубже разобраться в проблеме, заглянем в /import/app/app.php:
сравним различные исправления, введённые в версиях 1.8.0 | 1.8.7 | 1.9.0, поскольку каждая добавляла новый уровень безопасности:
register_rest_route( 'hc/v1', 'themehunk-import', array(
'methods' => 'POST',
'callback' => array( $this, 'tp_install' ),
'permission_callback' => '__return_true',
) );
permission_callback всегда возвращает true, что позволяет любому пользователю или неаутентифицированному лицу получить доступ к конечной точке с помощью POST-запроса.
Поскольку отсутствует какая-либо проверка nonce или процесс аутентификации, злоумышленник может обойти необходимые разрешения и напрямую установить нужный плагин.
В версии 1.8.7 были введены несколько улучшений безопасности для устранения недостатков предыдущих версий,
но конечная точка /hc/v1/themehunk-import и общая реализация всё ещё имеют существенные изъяны:
public function register_routes() {
register_rest_route( 'hc/v1', 'themehunk-import', array(
'methods' => 'POST',
'callback' => array( $this, 'tp_install' ),
'permission_callback' => function () {
// Проверяем, вошёл ли пользователь в систему
if ( ! is_user_logged_in() ) {
return new WP_REST_Response( 'Unauthorized: User not logged in', 401 );
}
// Отладка: логируем роль и возможности пользователя, чтобы увидеть, что у него есть
$current_user = wp_get_current_user();
// error_log( 'Current user: ' . $current_user->user_login );
// error_log( 'User roles: ' . implode( ', ', $current_user->roles ) );
// error_log( 'User capabilities: ' . print_r( $current_user->allcaps, true ) );
// Убеждаемся, что пользователь имеет возможность 'install_plugins'
if ( ! current_user_can( 'install_plugins' ) ) {
return new WP_REST_Response( 'Unauthorized: Insufficient capabilities', 401 );
}
// Получаем nonce из заголовка запроса
$nonce = $request->get_header('X-WP-Nonce');
// Проверяем nonce
if ( ! wp_verify_nonce( $nonce, 'hc_import_nonce' ) ) {
return new WP_REST_Response( 'Unauthorized: Invalid nonce', 401 );
}
return true; // Доступ разрешён
Хотя эта версия включает проверки того, что пользователь вошёл в систему (is_user_logged_in()) и подтверждает, что у него есть соответствующие возможности (current_user_can('install_plugins')), отправляя ответ 401 Unauthorized, если это не так, она всё же упускает основную проблему, которая остаётся нерешённой.
Логика обратного вызова WordPress в отношении permission_callback работает следующим образом:

То есть корректным ответом может быть одно из следующих значений: true | false | WP_Error.
Проблема в том, что возвращаемый WP_REST_Response не является логическим значением или WP_Error. Поскольку WordPress не интерпретирует его как отказ (false) или ошибку (WP_Error()), он может неявно принять его и предоставить доступ неаутентифицированному пользователю, что по сути не решает основную проблему.
Версия 1.9.0 успешно решила проблему, сделав уязвимость больше не эксплуатируемой:
public function register_routes() {
register_rest_route( 'hc/v1', 'themehunk-import', array(
'methods' => 'POST',
'callback' => array( $this, 'tp_install' ),
'permission_callback' => function () {
// Проверяем, вошёл ли пользователь в систему
if ( ! is_user_logged_in() ) {
return false;
}
// Отладка: логируем роль и возможности пользователя, чтобы увидеть, что у него есть
$current_user = wp_get_current_user();
// error_log( 'Current user: ' . $current_user->user_login );
// error_log( 'User roles: ' . implode( ', ', $current_user->roles ) );
// error_log( 'User capabilities: ' . print_r( $current_user->allcaps, true ) );
// Убеждаемся, что пользователь имеет возможность 'install_plugins'
if ( ! current_user_can( 'install_plugins' ) ) {
return false;
}
// Получаем nonce из заголовка запроса
$nonce = $request->get_header('X-WP-Nonce');
// Проверяем nonce
if ( ! wp_verify_nonce( $nonce, 'hc_import_nonce' ) ) {
return false;
}
return true; // Доступ разрешён
В этой версии мы видим, что возвращается корректное значение false, если пользователь не вошёл в систему или не имеет соответствующих разрешений, что гарантирует правильный отказ permission_callback в несанкционированном доступе, делая эксплойт больше не жизнеспособным.