Содержание статьи
- Поднимаем WordPress
- Плагины
- Скрипт для массового скачивания
- Отбор кандидатов
- Semgrep
- Запуск Semgrep на плагине WordPress
- Формат SARIF
- Основные паттерны
- LFI
- Arbitrary file upload
- Переименование файла
- RCE
- Unsafe deserialization
- SQL-инъекции
- Update Option
- AJAX и REST без проверки прав
- PHP-код без проверки среды
- Оформление отчета
- Заключение
Поднимаем WordPress
Для поиска уязвимостей в WordPress совершенно точно понадобится иметь живую тестовую установку WordPress, которую можно со спокойной душой сломать и не жалеть. Проще всего это сделать, используя Docker: один контейнер с базой и один с WordPress, в котором заранее будет настроен Xdebug.
info
Все примеры в статье сделаны на Linux, но на Windows все тоже должно заработать — для этого понадобится WSL2 или Docker Desktop.
Для начала создаем папку для тестов и скачиваем туда последнюю версию WordPress:
mkdir wordpresslab
cd wordpresslab
wget https://wordpress.org/latest.zip
unzip latest.zip
После распаковки переименовываем wordpress в wp, эта папка нам понадобится позже при создании контейнера.
Дальше создадим в нашей папке файл docker-compose., чтобы одной командой можно было поднять сразу весь тестовый стенд. В нем описаны база данных и сам контейнер с WordPress. Секреты от базы данных тоже описаны в этом файле, позже они будут нужны для настройки WordPress. Поскольку наш тестовый инстанс не будет смотреть в сеть, да и чувствительных данных там не предвидится, придумывать сложный пароль кажется лишним, так что я везде задал его как wordpress.
version: '3.3'services: db: image: docker.io/bitnami/mariadb:10.3-debian-10 restart: on-failure environment: MARIADB_USER: wordpress MARIADB_PASSWORD: wordpress MARIADB_ROOT_PASSWORD: wordpress MARIADB_DATABASE: wordpress wp: depends_on: - db image: wordpressxdebug build: context: . dockerfile: Dockerfile volumes: - ./wp:/var/www/html ports: - 8080:80 restart: on-failure extra_hosts: - "host.docker.internal:host-gateway" command: sh -c "chmod -R 777 /var/www/html && apache2-foreground" environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpressПорт 8080 можно поменять на любой удобный. Здесь же папка ./, в которую мы шагом раньше качали чистый WordPress, монтируется внутрь контейнера.
Теперь перейдем к самому образу WordPress. Создаем рядом файл Dockerfile со следующим содержимым:
FROM wordpress:latestENV XDEBUG_PORT 9000ENV XDEBUG_IDEKEY dockerRUN pecl install "xdebug" \ && docker-php-ext-install pdo pdo_mysql \ && docker-php-ext-enable xdebug pdo pdo_mysql
RUN echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/xdebug.ini && \ echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/xdebug.ini && \ echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/xdebug.ini && \ echo "xdebug.client_port=${XDEBUG_PORT}" >> /usr/local/etc/php/conf.d/xdebug.ini && \ echo "xdebug.idekey=${XDEBUG_IDEKEY}" >> /usr/local/etc/php/conf.d/xdebug.ini && \ echo "xdebug.log=/tmp/xdebug.log" >> /usr/local/etc/php/conf.d/xdebug.ini
К официальному образу WordPress тут добавляется Xdebug, что позволит нам на живую препарировать и сам WordPress, и его плагины.
Кроме добавления Xdebug в контейнер, нужно настроить еще ответную часть со стороны IDE. Я использую Visual Studio Code, куда прикручен сам отладчик Xdebug. Для настройки в папке с WordPress создаем папку . и в ней файл launch.:
{ "version": "0.2.0", "configurations": [ { "name": "Listen for XDebug", "type": "php", "request": "launch", "port": 9000, "pathMappings": { "/var/www/html": "${workspaceRoot}/wp", } } ] }pathMappings тут указывает, что путь / внутри контейнера соответствует папке wp на хосте. Если будешь менять какие‑либо папки в стенде — не забудь дописать их в этот файл.
Теперь, когда все готово, можно собрать образ и запустить контейнеры. Благодаря Docker это делается всего одной командой:
docker compose up
После запуска открываем в браузере адрес нашего тестового стенда и настраиваем самый обычный WordPress. Параметры базы нужно переписать из docker-compose., а адрес хоста с базой задать как db (имя контейнера базы). Никаких ., . или подобного — только само имя!
info
Чтобы не возникло проблем с Burp Suite, для подключения к WordPress лучше использовать именно IP из локальной сети, а не localhost.
После окончания установки сам фронтенд сайта будет доступен на /, а админка — на /. Рекомендую проконтролировать установку, чтобы дальше не было сюрпризов: проверь, что сайт открывается и работает, в админку можно нормально залогиниться, а исходники лежат в ./ и видны в VS Code.
Теперь нужно убедиться, что сам Xdebug работает и его можно использовать.
Открываем проект в VS Code из корня с нашим тестовым проектом, где лежат wp, docker-compose. и .:
code .На вкладке Run and Debug выбираем конфигурацию Listen for XDebug и запускаем ее кнопкой Start Debugging. Теперь отладчик будет принимать все входящие подключения от Xdebug, который находится в контейнере WordPress.

Чтобы не лезть сразу в ядро WordPress, давай потренируемся на кошках тестовом скрипте, на примере которого рассмотрим основные возможности Xdebug. Вот листинг test.:
<?php$requestId = bin2hex(random_bytes(4));$action = $_GET['action'] ?? 'view';$userId = (int)($_GET['user'] ?? 0);$noteTitle = trim($_POST['title'] ?? '');$noteBody = trim($_POST['body'] ?? '');$note = [ 'id' => $requestId, 'user_id' => $userId, 'title' => $noteTitle, 'body' => $noteBody,];log_request($action, $note);if ($action === 'save') { $filename = build_filename($userId, $noteTitle); save_note($filename, $note); echo "OK";} else { echo "Nothing to do";}function log_request(string $action, array $note): void { $log = [ 'time' => date('c'), 'action' => $action, 'note_title' => $note['title'], 'note_len' => strlen($note['body']), ]; file_put_contents(__DIR__ . '/debug.log', json_encode($log) . PHP_EOL, FILE_APPEND);}function build_filename(int $userId, string $title): string { $safeTitle = preg_replace('~[^a-z0-9_-]+~i', '-', $title); return __DIR__ . '/data/' . $userId . '-' . $safeTitle . '.txt';}function save_note(string $filename, array $note): void { if (!is_dir(dirname($filename))) { mkdir(dirname($filename), 0777, true); } $payload = $note['title'] . "\n\n" . $note['body']; file_put_contents($filename, $payload);}Функциональность максимально простая: создаем пользовательские заметки и сохраняем их в файл. На основе этого скрипта мы рассмотрим следующее:
- Как проверять суперглобальные переменные.
- Как отслеживать изменения переменных по ходу работы скрипта.
- Как изучать стек вызовов для нахождения точки входа.
Ставим брейкпоинт на строке с $action и делаем запрос на наш скрипт:
curl 'http://<ip>:8080/test.php?action=save&user=42' --data "title=note&body=hello"После запроса VS Code автоматически подсветит, на какой строке остановился отладчик. На вкладке Variables можно детально изучить, что и в какой переменной сейчас находится, а еще эту информацию можно получить, просто встав курсором на нужную переменную: попробуй навести курсор на $action или $requestId и посмотреть, какие данные у них сейчас внутри.
Теперь перейдем в Call Tract. Убери брейкпоинт с $action и поставь внутри любой функции — например, на строку с созданием файла в file_put_contents. Опять выполни запрос и жди, когда сработает брейкпоинт. После этого обрати внимание на панель Call Stack, в которой можно видеть весь стек вызовов:
- Вызов
test..php ({ main} ) - Далее
save_note(.) - И уже внутри остановка на
file_put_contents(.)
В настоящих плагинах WordPress это будет выглядеть примерно так же — ты сможешь изучать цепочку вызовов от точки входа, даже если это будет AJAX-обработчик или REST endpoint до конкретной функции, на которой будет стоять точка останова.
Плагины
Сами плагины можно скачивать из официального магазина WordPress. Для начала багхантинга можно найти несколько не очень популярных плагинов и скачать их вручную, но если ты планируешь массово их изучать и искать баги, то лучше, конечно, скачать как можно больше плагинов за раз и исследовать их все.
Сделать это можно разными способами:
- Экспортировать плагины из SVN-репозитория WordPress.
- Написать парсер для магазина и скачивать плагины по ключевым словам. Я же обычно скачиваю их напрямую из магазина, потому что там сразу можно найти описание и количество активных установок плагинов.
- Использовать сторонние инструменты или зеркала.
Я предпочитаю второй способ, поскольку это дает возможность получить данные о количестве активных установок, описание плагина и другую полезную информацию, которая, по моему мнению, важна для отбора кандидатов на исследование.
Плагины с небольшим количеством установок обычно получают меньше внимания со стороны авторов и ресерчеров, а значит, в таких плагинах обычно меньше конкуренции. Еще один плюс этих плагинов — простота кода, ведь обычно на старте авторы не заморачиваются и не используют сложные фреймворки, что делает исследование кода более простой задачей.
Скрипт для массового скачивания
Как я уже говорил выше, я предпочитаю скачивать плагины прямо с официального сайта. К сожалению, недавно на сайте ввели ограничение по количеству запросов с одного IP-адреса, так что этот метод требует существенно больше времени, чем другие, но плюсы все еще перевешивают затраты времени.
Для скачивания я написал скрипт, который по ключевым словам скачивает все плагины и создает CSV-файл, который можно использовать для отбора кандидатов на исследование. Задача скрипта простая:
- По ключевому слову пройти все страницы поиска в каталоге.
- Для каждого найденного плагина собрать название, короткое имя (slug), краткое описание, количество активных установок, ссылку на ZIP-файл и полное описание.
- Скачать все ZIP-файлы.
- Сохранить всё в CSV.
Внутри все реализовано на requests + BS и учитывает недавно введенные лимиты. Сам скрипт получился весьма объемный, поэтому в статье его разбора не будет, но код довольно простой, и при необходимости ты легко в нем разберешься.
Пример запуска скрипта:
python3 downloader.py
-q backup
-o backup_plugins.csv
--download --download-dir ./download/
--workers 5
Тут ключ -q задает ключевую фразу для поиска плагинов, backup_plugins. — имя файла с результатами, ключ --download включает скачивание файлов, --download-dir указывает папку, в которую будут скачаны плагины, а --workers задает число потоков. Из‑за лимитов скачивание займет некоторое время. После завершения процесса появится CSV-файл, в котором будет вся информация о найденных плагинах.
www
Для удобства работы с CSV-файлами в Visual Studio Code есть хорошее расширение Excel Viewer.

Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
