wordpress / code review

CVE-2025-15285

Missing Authorization no SEO Flow by LupsOnline ≤ 2.2.1

O que me chamou atenção nessa falha foi que o plugin não esqueceu de criar uma função de autenticação. A função existe, calcula o HMAC e percebe quando o valor enviado está errado. O problema é mais simples: o retorno dela é ignorado.

Começando pelas rotas REST

Algumas rotas sensíveis são registradas com:

'permission_callback' => '__return_true'

Isso permite que a requisição chegue ao callback. Por si só, isso não prova a vulnerabilidade, então fui seguir o código da rota de blog até addBlog().

Onde a proteção quebra

public function addBlog($request) {
    $params = $this->getParams($request);

    if (!$params["title"])
        return new WP_Error(400, "Param 'title' missing");

    $this->checkBlogAuthentication($params);

    return $this->admin->saveBlog($params);
}

À primeira vista, checkBlogAuthentication() parece proteger a operação. A parte importante é ver o que ela devolve quando o HMAC não bate.

$ourhash = hash_hmac(
    'sha256',
    $title . $siteUrl,
    get_option("lupsOnlineLinkNetwerkApiKey")
);

if ($ourhash !== $hash)
    return new WP_Error(401, "Authentication error");

Um hash incorreto gera WP_Error. Só que addBlog() não salva esse retorno e também não usa is_wp_error(). Depois da chamada, o código simplesmente continua para saveBlog().

request ↓ addBlog() ↓ checkBlogAuthentication() ↓ HMAC inválido ↓ WP_Error ↓ retorno ignorado ↓ saveBlog() ↓ wp_insert_post()

Do input até o sink

O request vira $params e esses valores seguem até operações sensíveis do WordPress. Entre os sinks encontrados estão:

wp_insert_post()
wp_update_post()
wp_delete_post()
wp_insert_term()

O ponto do code review foi verificar se uma falha na autenticação realmente interrompia o caminho até essas funções. Nessa versão, não interrompe.

Teste no laboratório

Para confirmar a hipótese, usei um hash deliberadamente incorreto e uma requisição sem sessão WordPress. Um valor simples para esse tipo de teste é:

{
  "title": "CVE 2025 15285 Test",
  "hash": "deadbeef"
}

A parte importante não é só receber HTTP 200. Depois da requisição é preciso confirmar se o post foi realmente criado ou alterado. Para exclusão, usei conteúdo criado especificamente para o próprio teste.

Como eu corrigiria

Uma correção direta é fazer o erro controlar o fluxo:

$authentication = $this->checkBlogAuthentication($params);

if (is_wp_error($authentication)) {
    return $authentication;
}

return $this->admin->saveBlog($params);

Outra opção seria colocar a checagem no permission_callback, evitando que o callback que altera o conteúdo seja chamado quando a validação falhar.

O que eu tiro dessa análise

Durante code review, ver uma função com nome como checkAuthentication() não basta. Vale seguir o retorno até o fim e perguntar o que acontece no caminho de erro. Nesse caso, a autenticação falha corretamente e mesmo assim a operação continua.

Ver a PoC interativa e a captura do laboratório →

← voltar