Добавлено логгирование загрузок файлов

This commit is contained in:
2026-01-25 18:08:13 +05:00
parent 8412c19355
commit bfda704956
2 changed files with 148 additions and 30 deletions

View File

@@ -236,7 +236,32 @@ function _commentSettingsGet($param = '')
// Возвращаем количество комментариев для запрашиваемого документа
return $comments[$document_id];
}
/**
* Логирование подозрительной активности при загрузке файлов
*/
private function _logUploadSecurity($status, $file_info, $context = [])
{
$log_dir = BASE_DIR . '/modules/comment/logs/';
if (!is_dir($log_dir)) @mkdir($log_dir, 0775, true);
$log_file = $log_dir . 'security_upload.log';
$date = date('Y-m-d H:i:s');
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$user_id = $_SESSION['user_id'] ?? 0;
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'none';
// Формируем строку контекста: ID сообщения, имя автора и т.д.
$ctx_info = "";
if (!empty($context)) {
foreach ($context as $key => $val) {
$ctx_info .= " | $key: $val";
}
}
$message = "[$date] IP: $ip | UID: $user_id{$ctx_info} | STATUS: $status | FILE: {$file_info['name']} | MIME: {$file_info['mime']} | SIZE: {$file_info['size']} bytes | UA: $user_agent\n";
@file_put_contents($log_file, $message, FILE_APPEND);
}
/**
* Следующие методы описывают работу модуля в Публичной части сайта.
@@ -659,17 +684,34 @@ function commentPostFormShow($tpl_dir)
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
// Проверки расширения и безопасности
$is_allowed_ext = in_array($ext, $allowed_extensions);
$is_dangerous = in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'exe', 'pl', 'cgi']);
// Определяем списки расширений для разной логики проверок
$img_exts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'];
$is_image_ext = in_array($ext, $img_exts);
// Условие: расширение разрешено, файл не опасен и проходит по размеру
if ($is_allowed_ext && !$is_dangerous && $file['size'] > 0 && $file['size'] <= $max_file_size_bytes)
// 1. Проверки расширения и базовой безопасности
$is_allowed_ext = in_array($ext, $allowed_extensions);
$is_dangerous = in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'exe', 'pl', 'cgi', 'html', 'js']);
// 2. Проверка на "лже-картинку" (только если расширение из списка изображений)
$mime_is_valid = true;
if ($is_image_ext && strpos($mime, 'image/') !== 0) {
$mime_is_valid = false; // Попытка просунуть скрипт под видом картинки
}
// 3. Защита от опасных MIME-типов (даже если админ разрешил такое расширение)
$is_dangerous_mime = in_array($mime, [
'text/php', 'text/x-php', 'application/x-php',
'application/x-httpd-php', 'application/x-executable',
'text/html', 'text/javascript'
]);
// Итоговое условие: разрешено в админке + не опасно + валидный MIME + размер
if ($is_allowed_ext && !$is_dangerous && !$is_dangerous_mime && $mime_is_valid && $file['size'] > 0 && $file['size'] <= $max_file_size_bytes)
{
// 1. Берем имя
$original_basename = pathinfo($file['name'], PATHINFO_FILENAME);
// 2. Очищаем (БЕЗ $this->, просто вызываем функцию)
// 2. Очищаем
$clean_name = prepare_fname($original_basename);
// 3. Если пусто - дефолт
@@ -685,6 +727,39 @@ if ($is_allowed_ext && !$is_dangerous && $file['size'] > 0 && $file['size'] <= $
$processed_hashes[] = $file_hash;
}
}
else
{
// 1. Формируем причину отказа
$reason = "ОТКАЗ ПРИ СОЗДАНИИ КОММЕНТАРИЯ:";
if (!$is_allowed_ext) $reason .= " Запрещенное расширение($ext);";
if ($is_dangerous) $reason .= " Опасный формат файла;";
if ($is_dangerous_mime) $reason .= " Вредоносный тип контента($mime);";
if (!$mime_is_valid) $reason .= " Обнаружена подмена (Fake Image);";
if ($file['size'] > $max_file_size_bytes) $reason .= " Файл слишком большой;";
if ($file['size'] <= 0) $reason .= " Пустой файл;";
// 2. Собираем контекст (кто, куда и под каким именем писал)
// Берём данные из $_POST, так как запись в БД ещё не создана
$context = [
'AUTHOR' => $_POST['comment_author_name'] ?? 'Неизвестно',
'EMAIL' => $_POST['comment_author_email'] ?? '-',
'DOC_ID' => (int)($_POST['doc_id'] ?? 0)
];
// 3. Записываем в лог с контекстом
$this->_logUploadSecurity($reason, [
'name' => $file['name'],
'mime' => $mime,
'size' => $file['size']
], $context);
// 4. Защита от дублей в логе (если один и тот же файл пришел дважды)
$processed_hashes[] = $file_hash;
}
}
}
@@ -877,6 +952,8 @@ if (!$can_upload && isset($_FILES['comment_image'])) {
// Получаем текущие файлы из БД в массив
$current_files = !empty($row['comment_file']) ? explode(',', $row['comment_file']) : [];
$processed_hashes = []; // Для защиты лога от дубликатов
// --- Обработка выборочного удаления ---
if (!empty($_POST['delete_files'])) {
$files_to_remove = explode(',', $_POST['delete_files']);
@@ -919,46 +996,81 @@ if (!$can_upload && isset($_FILES['comment_image'])) {
// Перебираем загруженные файлы
foreach ($_FILES['comment_image']['name'] as $i => $fname) {
if ($_FILES['comment_image']['error'][$i] == UPLOAD_ERR_OK) {
$tmp_name = $_FILES['comment_image']['tmp_name'][$i];
$file_ext = strtolower(pathinfo($fname, PATHINFO_EXTENSION));
$file_size = $_FILES['comment_image']['size'][$i];
// Проверка MIME-типа
if ($_FILES['comment_image']['error'][$i] == UPLOAD_ERR_OK) {
$tmp_name = $_FILES['comment_image']['tmp_name'][$i];
$file_size = $_FILES['comment_image']['size'][$i];
$file_ext = strtolower(pathinfo($fname, PATHINFO_EXTENSION));
// 1. Проверка MIME-типа и хэша
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $tmp_name);
finfo_close($finfo);
// Проверки расширения и опасных файлов
$is_allowed_ext = in_array($file_ext, $allowed_extensions);
$is_dangerous = in_array($file_ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'exe', 'pl', 'cgi']);
$file_hash = md5_file($tmp_name);
if (in_array($file_hash, $processed_hashes)) continue;
if ($is_allowed_ext && !$is_dangerous && $file_size > 0 && $file_size <= ($max_kb * 1024))
// 2. Логика безопасности
$img_exts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'];
$is_image_ext = in_array($file_ext, $img_exts);
$is_allowed_ext = in_array($file_ext, $allowed_extensions);
$is_dangerous = in_array($file_ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'exe', 'pl', 'cgi', 'html', 'js']);
$mime_is_valid = true;
if ($is_image_ext && strpos($mime, 'image/') !== 0) {
$mime_is_valid = false;
}
$is_dangerous_mime = in_array($mime, [
'text/php', 'text/x-php', 'application/x-php',
'application/x-httpd-php', 'application/x-executable',
'text/html', 'text/javascript'
]);
// 3. Условие загрузки
if ($is_allowed_ext && !$is_dangerous && !$is_dangerous_mime && $mime_is_valid && $file_size > 0 && $file_size <= ($max_kb * 1024))
{
if (!is_dir($upload_dir)) @mkdir($upload_dir, 0775, true);
$original_basename = pathinfo($fname, PATHINFO_FILENAME);
$clean_name = prepare_fname($original_basename);
if (empty($clean_name)) $clean_name = 'file';
// Получаем оригинальное имя файла без расширения
$original_basename = pathinfo($fname, PATHINFO_FILENAME);
// Очищаем имя
$clean_name = prepare_fname($original_basename);
// Защита от пустого имени
if (empty($clean_name)) {
$clean_name = 'file';
}
// Формируем новое имя: чистое_имя + время + расширение
$new_file_name = $clean_name . '_' . time() . '.' . $file_ext;
$new_file_name = $clean_name . '_' . time() . '.' . $file_ext;
if (move_uploaded_file($tmp_name, $upload_dir . $new_file_name)) {
$current_files[] = $new_file_name;
$processed_hashes[] = $file_hash;
}
}
else
{
// 4. ЛОГИРОВАНИЕ (с контекстом комментария)
$reason = "ОТКАЗ ПРИ РЕДАКТИРОВАНИИ КОММЕНТАРИЯ:";
if (!$is_allowed_ext) $reason .= " Запрещенное расширение($file_ext);";
if ($is_dangerous) $reason .= " Опасный формат файла;";
if ($is_dangerous_mime) $reason .= " Вредоносный тип контента($mime);";
if (!$mime_is_valid) $reason .= " Обнаружена подмена (Fake Image);";
if ($file_size > ($max_kb * 1024)) $reason .= " Файл слишком большой;";
$context = [
'COMMENT_ID' => $comment_id,
'AUTHOR_DB' => $row['comment_author_name'],
'CURRENT_UID' => $user_id
];
$this->_logUploadSecurity($reason, [
'name' => $fname,
'mime' => $mime,
'size' => $file_size
], $context);
$processed_hashes[] = $file_hash;
}
}
}
}
// Собираем итоговую строку файлов для БД через запятую

6
logs/.htaccess Normal file
View File

@@ -0,0 +1,6 @@
# Запрещаем доступ к файлам логов извне (через браузер)
Order Deny,Allow
Deny from all
# Отключаем листинг директории
Options -Indexes