---
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`) у папках завантаження. |
| **Заборона виконання** | Явно вимкніть виконання скриптів у каталогах з користувацькими файлами. |