---
title: 'Уязвимости загрузки файлов: Обход фильтров для выполнения удаленного кода (RCE) | DevSense'
description: 'Изучите механизмы безопасной загрузки файлов. Узнайте, как атакующие обходят клиентские проверки, эксплуатируют черные списки расширений, переопределяют конфигурации веб-сервера и как писать безопасный код в Laravel.'
faq:
    - { question: 'Почему белый список расширений безопаснее черного списка?', answer: 'Черный список пытается заблокировать известные опасные расширения (например, .php, .exe), но часто упускает альтернативные исполняемые расширения (например, .phtml, .phar, .php5) или специфические для ОС трюки с именами. Белый список строго определяет разрешенные безопасные форматы (например, .jpg, .pdf) и отклоняет все остальное по умолчанию.' }
    - { question: 'Как работает обход с использованием альтернативных потоков данных NTFS (ADS) в Windows?', answer: "На системах Windows добавление суффикса '::$DATA' к имени файла (например, 'shell.php::$DATA') указывает файловой системе NTFS записывать данные в основной поток файла 'shell.php'. При сохранении Windows отсекает суффикс '::$DATA', оставляя полностью исполняемый файл 'shell.php' на диске, обходя наивные фильтры черных списков." }
    - { question: 'Как переопределение .user.ini приводит к исполнению кода?', answer: "В окружениях PHP CGI/FastCGI загрузка пользовательского файла '.user.ini' в директорию загрузок позволяет злоумышленнику переопределить конфигурационные директивы PHP для этой папки. Установив 'auto_prepend_file=image.png', интерпретатор PHP будет автоматически выполнять PHP-код, встроенный в 'image.png', при любом обращении к любому PHP-скрипту в этой директории." }
published: '2026-07-09'
---
# Уязвимости загрузки файлов: Обход фильтров для выполнения удаленного кода (RCE)

Формы загрузки файлов являются одними из самых высокорисковых функций в веб-приложениях. Когда приложение позволяет пользователям загружать файлы, оно открывает прямой доступ к файловой системе сервера. Если бэкенд не проверяет загружаемые файлы должным образом, атакующий может загрузить веб-шелл, обойти ограничения на исполнение кода и добиться **удаленного выполнения кода (Remote Code Execution, RCE)**.

В этом руководстве мы проанализируем техническую механику загрузки файлов, методы обхода фильтров и способы построения надежного конвейера валидации файлов на PHP и Laravel.

---

## Содержание

* [Механика Multipart Form-Data](#multipart-mechanics)
* [Обход проверки на стороне клиента](#client-side-bypass)
* [Обходы фильтрации по расширениям](#extension-bypasses)
* [Переопределение конфигурации веб-сервера](#config-overrides)
* [Нормализация имен файлов в Windows](#windows-normalization)
* [Безопасная реализация на Laravel и PHP](#secure-implementation)
* [Методы укрепления безопасности сервера](#server-hardening)
* [Контрольный список безопасности](#checklist)

---

<a id="multipart-mechanics"></a>
## Механика Multipart Form-Data

При загрузке файла через HTTP браузеры используют схему кодирования `multipart/form-data`, определенную в стандарте **RFC 7578**. Понимание этой структуры важно для анализа расхождений парсеров.

### Структура Multipart-запроса
Типичный запрос на загрузку файла выглядит следующим образом:

```http
POST /upload.php HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 345

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="backdoor.php"
Content-Type: application/x-php

<?php system($_GET['cmd']); ?>
------WebKitFormBoundary7MA4YWxkTrZu0gW--
```

### Критически важные элементы Multipart:
1. **Параметр Boundary:** Атрибут `boundary` в заголовке `Content-Type` задает уникальную строку, используемую для разделения полей формы.
2. **Двойные дефисы:** В теле запроса перед каждой границей добавляются два дефиса (`--`). Финальная граница, обозначающая конец тела запроса, также должна заканчиваться двумя дефисами (`--boundary--`).
3. **Расхождения парсеров:** Различные языки программирования и веб-серверы разбирают multipart-запросы по-разному.
   - Некоторые парсеры лояльно относятся к отсутствующим заголовкам или некорректным дефисам.
   - Например, в популярной библиотеке парсинга multipart для Node.js **Multer** была обнаружена уязвимость: при отсутствии завершающих двойных дефисов (`--`) парсер зависал на неопределенный срок в ожидании новых данных, что вызывало отказ в обслуживании (DoS).

---

<a id="client-side-bypass"></a>
## Обход проверки на стороне клиента

Многие разработчики реализуют проверку файлов только на стороне клиента с помощью JavaScript, чтобы обеспечить быстрый отклик интерфейса. Типичная проверка ограничивает атрибуты тега input:

```html
<!-- Ограничение на стороне клиента -->
<input type="file" id="avatar" accept=".jpg, .png" onchange="validateFile()">
```

### Механизм обхода
Проверки на стороне клиента полностью небезопасны, поскольку клиент находится под контролем атакующего. Обойти их можно несколькими способами:
1. **Отключение JavaScript:** Использование инструментов разработчика в браузере для отключения выполнения JS, что позволяет форме отправлять любые файлы.
2. **Прокси-перехватчик (например, Burp Suite):**
   - Выберите легитимный файл (например, `profile.png`).
   - Отправьте форму.
   - Перехватите исходящий запрос в Burp Suite.
   - Измените параметр filename на `shell.php`, установите `Content-Type` в `application/x-php` и замените бинарное содержимое изображения на PHP-код: `<?php phpinfo(); ?>`.

> [!WARNING]
> Никогда не полагайтесь на валидацию на стороне клиента. Любые клиентские проверки служат только для удобства пользователей. Безопасность должна принудительно обеспечиваться на стороне сервера.

---

<a id="extension-bypasses"></a>
## Обходы фильтрации по расширениям

При создании защитных проверок на бэкенде разработчики часто полагаются на фильтрацию расширений. Это может быть реализовано с помощью **черного списка** (блокировка вредоносных расширений) или **белого списка** (разрешение только конкретных форматов).

### Обход черных списков
Черные списки фундаментально неэффективны. Атакующие могут обойти черный список, блокирующий `.php`, следующими способами:

1. **Альтернативные исполняемые расширения:**
   В зависимости от конфигурации веб-сервера, для выполнения PHP-кода могут использоваться альтернативные расширения:
   - `.phtml`, `.php3`, `.php4`, `.php5`, `.php7`, `.phps`
   - `.phar` (PHP-архив, который также может содержать векторы десериализации)
   - `.pht`

2. **Манипуляции с регистром символов:**
   Если проверка ищет только строку `.php`, а операционная система сервера нечувствительна к регистру символов, разработчики могут забыть про расширения:
   - `.pHp`, `.Php`, `.PHp`, `.pHTML`

3. **Двойные и вложенные расширения:**
   - **Двойные расширения:** Если сервер некорректно настроен на выполнение файлов, содержащих `.php` в любой части имени (например, через директиву `AddHandler` в Apache), атакующий может загрузить `shell.php.jpg`.
   - **Обход очистки строк:** Если код пытается вырезать подстроку `.php` без рекурсивной обработки:
     `shell.p.phphp.hp` -> Вырезание `php` один раз вернет имя `shell.php`.

---

<a id="config-overrides"></a>
## Переопределение конфигурации веб-сервера

Если приложение использует строгий черный список, но разрешает загрузку файлов конфигурации, злоумышленник может изменить правила выполнения файлов в директории загрузок.

### 1. Переопределение конфигурации Apache (`.htaccess`)
Если для директории загрузок в Apache разрешена директива `AllowOverride All`, атакующий может загрузить кастомный файл `.htaccess`:

```apache
# Назначение PHP-интерпретатора для файлов .png в .htaccess
AddType application/x-httpd-php .png
```
Или:
```apache
<Files "logo.png">
    ForceType application/x-httpd-php
</Files>
```

После загрузки этого `.htaccess` файла злоумышленник загружает файл с именем `logo.png`, содержащий PHP-код. При обращении к `logo.png` Apache выполнит его как PHP-скрипт.

### 2. Переопределение настроек PHP CGI/FastCGI (`.user.ini`)
В средах Nginx или IIS, использующих PHP-FPM или PHP CGI, файлы `.htaccess` не обрабатываются. Однако PHP поддерживает конфигурационные файлы уровня директории, называемые `.user.ini`.

Атакующий может загрузить `.user.ini` с директивой:

```ini
# Выполнить PHP-код, внедренный внутрь PNG-файла
auto_prepend_file=avatar.png
```

Если злоумышленник загрузит `avatar.png` (содержащий вредоносный код) и затем обратится к любому легитимному файлу `.php` в этой директории (даже к стандартному пустому скрипту), интерпретатор PHP сначала выполнит код из `avatar.png`.

---

<a id="windows-normalization"></a>
## Нормализация имен файлов в Windows

Когда веб-приложения работают под управлением ОС Windows (IIS или Apache на Windows), создание файлов подчиняется правилам нормализации API Win32 и NTFS. Это открывает специфические векторы обхода.

### 1. Замыкающие точки и пробелы
Windows автоматически удаляет пробелы и точки в конце имен файлов при их сохранении на диск.
- Если бэкенд блокирует расширение `.php`, злоумышленник может загрузить файл с именем `shell.php.` или `shell.php `.
- Валидатор на регулярных выражениях проверяет строку `shell.php.` (которая не совпадает с `.php`) и разрешает загрузку.
- Файловая система Windows сохраняет этот файл как `shell.php`, делая его доступным для исполнения.

### 2. Альтернативные потоки данных NTFS (ADS)
NTFS поддерживает альтернативные потоки данных для записи метаданных. Поток по умолчанию обозначается как `::$DATA`.
- Атакующий загружает файл с именем `shell.php::$DATA`.
- Черный список видит расширение `.php::$DATA` (или считает его неизвестным и пропускает, если проверяется только окончание имени после последней точки).
- При сохранении Windows отсекает часть `::$DATA` и записывает содержимое в основной поток файла `shell.php`. В результате в веб-директории создается рабочий исполняемый файл `shell.php`.

---

<a id="secure-implementation"></a>
## Безопасная реализация на Laravel и PHP

Для защиты от уязвимостей загрузки файлов необходимо использовать принцип **эшелонированной обороны (Defense in Depth)**. Никогда не полагайтесь на один рубеж проверки.

### Безопасный контроллер загрузки файлов на Laravel

Пример реализации безопасного контроллера с использованием белого списка расширений, валидации MIME-типов и хранения файлов за пределами веб-корня:

```php
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class SecureUploadController extends Controller
{
    // 1. Строгий белый список разрешенных расширений и MIME-типов
    private const ALLOWED_TYPES = [
        'jpg'  => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'png'  => 'image/png',
        'pdf'  => 'application/pdf',
    ];

    public function store(Request $request): JsonResponse
    {
        // Проверка наличия файла
        if (!$request->hasFile('document')) {
            return response()->json(['error' => 'No file uploaded.'], 400);
        }

        $file = $request->file('document');

        if (!$file instanceof UploadedFile || !$file->isValid()) {
            return response()->json(['error' => 'Invalid or corrupted file.'], 400);
        }

        // 2. Валидация размера (например, не более 5 МБ)
        if ($file->getSize() > 5 * 1024 * 1024) {
            return response()->json(['error' => 'File size exceeds 5MB limit.'], 400);
        }

        // 3. Определение реального MIME-типа по содержимому (не доверяя заголовкам клиента)
        $realMimeType = $file->getMimeType();
        $originalName = $file->getClientOriginalName();
        $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));

        // 4. Проверка соответствия расширения и MIME-типа белому списку
        if (!array_key_exists($extension, self::ALLOWED_TYPES)) {
            return response()->json(['error' => 'Unsupported file extension.'], 400);
        }

        if (self::ALLOWED_TYPES[$extension] !== $realMimeType) {
            return response()->json(['error' => 'MIME type and file extension mismatch.'], 400);
        }

        // 5. Генерация случайного имени файла (UUID или криптографический хэш)
        // Это предотвращает атаки Path Traversal, обходы нормализации Windows и коллизии имен.
        $safeName = Str::uuid()->toString() . '.' . $extension;

        // 6. Хранение ЗА пределами веб-корня (например, в приватном хранилище S3 или локальной директории)
        // Не используйте public_path(). Храните файлы в закрытых локальных директориях.
        $path = $file->storeAs('uploads/secure_docs', $safeName, 'local');

        if ($path === false) {
            return response()->json(['error' => 'Failed to store file.'], 500);
        }

        return response()->json([
            'message'   => 'File uploaded securely.',
            'safe_name' => $safeName
        ], 201);
    }
}
```

---

<a id="server-hardening"></a>
## Методы укрепления безопасности сервера

Даже если в коде приложения будет найдена уязвимость, правильная настройка инфраструктуры сервера может предотвратить исполнение кода.

### 1. Отключение выполнения PHP в директории загрузок
В конфигурации Nginx заблокируйте обработку PHP-файлов в папке с загрузками:

```nginx
# Запрет на запуск PHP-скриптов в папке uploads
location ~* ^/uploads/.*\.php$ {
    deny all;
    return 403;
}
```

Для веб-сервера Apache добавьте следующие правила в конфигурационный файл хоста:

```apache
<Directory "/var/www/html/uploads">
    # Отключение обработчиков скриптов
    RemoveHandler .php .phtml .php3
    RemoveType .php .phtml .php3
    
    # Принудительная отдача файлов как обычного текста
    ForceType text/plain
    
    # Отключение htaccess в данной директории
    AllowOverride None
</Directory>
```

### 2. Запуск веб-сервера с минимальными привилегиями
Убедитесь, что веб-сервер (например, `www-data`, `nginx`) имеет права на чтение и запись только в строго отведенных директориях и не может изменять исходный код приложения или системные файлы.

---

<a id="checklist"></a>
## Контрольный список безопасности

| Уровень защиты | Проверка реализации |
| :--- | :--- |
| **Белые списки** | Проверяйте расширения только по строгому белому списку (не черному). |
| **Проверка MIME** | Валидируйте реальный тип содержимого файла с помощью системных библиотек (`fileinfo`). |
| **Генерация имен** | Генерируйте случайные имена файлов (например, UUID) при сохранении. |
| **Место хранения** | Сохраняйте загруженные файлы вне публично доступного веб-корня. |
| **Конфиг сервера** | Запретите переопределение настроек сервера (`.htaccess`, `.user.ini`) в папках загрузки. |
| **Запрет выполнения** | Явно отключите исполнение скриптов в каталогах с пользовательскими файлами. |