---
title: 'Уязвимости при качване на файлове: Заобикаляне на филтри за отдалечено изпълнение на код (RCE) | DevSense'
description: 'Овладейте механиката на сигурното качване на файлове. Научете как атакуващите заобикалят клиентските проверки, експлоатират черни списъци с разширения, презаписват конфигурации на уеб сървъра и как да пишете сигурен Laravel код.'
faq:
    - { question: 'Защо белият списък с разширения е по-сигурен от черния списък?', answer: 'Черният списък се опитва да блокира известни опасни разширения (напр. .php, .exe), но често пропуска алтернативни изпълними разширения (напр. .phtml, .phar, .php5) или специфични за ОС трикове с имена. Белият списък строго дефинира разрешените безопасни формати (напр. .jpg, .pdf) и отхвърля всичко останало по подразбиране.' }
    - { question: 'Как работи заобикалянето чрез NTFS Alternate Data Streams (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 данните по различен начин.
   - Някои парсери обработват липсващи заглавки или некоректни двойни тирета твърде свободно.
   - Например, беше открита уязвимост в популярната Node.js библиотека за multipart данни **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
# Пренасочване на PNG файлове да се изпълняват като PHP в .htaccess
AddType application/x-httpd-php .png
```
Или:
```apache
<Files "logo.png">
    ForceType application/x-httpd-php
</Files>
```

След качването на този `.htaccess` файл, атакуващият качва файл с име `logo.png`, съдържащ PHP код. Apache ще третира `logo.png` като 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), създаването на файлове се влияе от правилата за нормализация на NTFS и Win32 API. Това позволява уникални методи за заобикаляне.

### 1. Точки и интервали в края на името
Windows автоматично премахва интервалите и точките в края на имената на файловете при създаването им на диска.
- Ако приложението проверява разширенията с черен списък (напр. блокирайки `.php`), атакуващият може да качи файл с име `shell.php.` или `shell.php `.
- Регулярният израз проверява `shell.php.` (което не съвпада точно с `.php`) и позволява качването.
- Файловата система на Windows създава файла на диска и нормализира името му на `shell.php`, което го прави изпълним.

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

---

<a id="secure-implementation"></a>
## Сигурна реализация на Laravel и PHP

За защита на качването на файлове трябва да се спазва принципът за **многослойна защита (Defense in Depth)**. Никога не разчитайте на една-единствена стъпка за валидация.

### Сигурен контролер за качване на файлове в Laravel

Ето как да имплементирате сигурен контролер за качване на файлове в 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. Валидация на размера (напр. максимум 5MB)
        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 или криптографски хеш)
        // Това неутрализира атаки за преминаване през директории, трикове с нормализация под 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">
    # Премахване на PHP обработчиците
    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`). |
| **Блокиране на код** | Изрично забранете изпълнението на скриптове в пътя за качване. |