Текуща ситуация: PHP2Shell. Две критични уязвимости в PHP (CVSS 9,2) позволяват злонамерен код без вход. Корекции от 21.10.2026 г.Какво да направите сега →

Как PHP обработва качването на файлове

Между щракването върху „Качване“ и Вашия код стои цяла верига от обработка в PHP. Тази страница обяснява multipart/form-data, временните файлове, $_FILES, ограниченията и връзката с PHP2Shell.

Актуалност 5 мин. четенеЗнания за PHP

Накратко: Когато браузърът качва файл, той изпраща POST заявка във формат multipart/form-data, в която полетата на формата и съдържанието на файловете стоят като отделни части една след друга. PHP сам разчленява тази заявка, записва всяка файлова част във временен файл в upload_tmp_dir, вписва данните в $_FILES и едва тогава стартира Вашия скрипт. Скриптът премества файла с move_uploaded_file(); всичко, което не е преместено, PHP изтрива в края на заявката.

Пътят на един файл от браузъра до скрипта

За вземащите решения най-важното е следното: качването не е процес само на приложението. Голяма част от работата се извършва в интерпретатора на PHP, преди кодът на CMS или на магазина изобщо да участва. Затова грешки в тази част на PHP засягат всеки уебсайт, независимо дали има форма за качване.

Последователността накратко:

  1. Формата с method="POST" и enctype="multipart/form-data" се изпраща.
  2. Уеб сървърът приема заявката и я предава на PHP, според режима на работа през PHP-FPM, mod_php или друг интерфейс.
  3. PHP проверява ограничения като post_max_size и разчленява съдържанието по разделителна последователност, т.нар. boundary.
  4. Обикновените полета попадат в $_POST, файловите части се записват във временни файлове и се описват в $_FILES.
  5. Едва сега скриптът стартира, проверява файла и го премества на целевото място.
  6. В края на заявката PHP изтрива всички временни файлове, които не са преместени.

multipart/form-data: структурата на една заявка за качване

Заявката за качване е чист текст с вградени файлови данни. Хедърът Content-Type назовава boundary, а в съдържанието тази последователност от знаци разделя отделните части. Всяка част има собствени заглавни редове с име на полето и евентуално име на файла. Силно съкратен, безобиден пример:

POST /bewerbung HTTP/1.1
Content-Type: multipart/form-data; boundary=----trenner42

------trenner42
Content-Disposition: form-data; name="vorname"

Erika
------trenner42
Content-Disposition: form-data; name="lebenslauf"; filename="lebenslauf.pdf"
Content-Type: application/pdf

%PDF-1.7 ... Dateiinhalt ...
------trenner42--

Всички данни в този текст идват от подателя, включително името на файла и Content-Type на частта. PHP ги вписва в $_FILES, но не проверява достоверността им.

Масивът $_FILES

За поле с име lebenslauf $_FILES['lebenslauf'] съдържа според ръководството на PHP следните ключове:

Ключ Съдържание Заслужава ли доверие?
name първоначалното име на файла при подателя не
full_path път, съобщен от браузъра, от PHP 8.1.0 не
type MIME тип според браузъра, непроверен от PHP не
size размер в байтове определя се от PHP
tmp_name път на временния файл на сървъра задава се от PHP
error код за грешка при обработката на качването задава се от PHP

При полета с квадратни скоби в името, например bilder[], всеки ключ съдържа масив с по един запис за файл. Приложението тогава обхожда например $_FILES['bilder']['error'] и през същия индекс достъпва tmp_name и name.

Една форма може допълнително да изпрати скрито поле MAX_FILE_SIZE пред полето за файл. Според ръководството това е само подсказка за браузъра, която лесно се заобикаля. Можете да разчитате единствено на ограниченията, които сървърът налага: в PHP, в уеб сървъра и в самото приложение.

Ограничения: директивите на php.ini за качване

Тези директиви управляват обработката на качването. Стойностите по подразбиране са от ръководството на PHP и важат, когато никой php.ini не ги презаписва.

Директива По подразбиране Променяема в
file_uploads 1 само системна конфигурация
upload_tmp_dir празно (временна директория на системата) само системна конфигурация
upload_max_filesize 2M php.ini, пул, конфигурация по директории
post_max_size 8M php.ini, пул, конфигурация по директории
max_file_uploads 20 php.ini, пул, конфигурация по директории
enable_post_data_reading 1 php.ini, пул, конфигурация по директории

post_max_size трябва да е по-голям от upload_max_filesize, а memory_limit според ръководството по правило трябва да е по-голям от post_max_size. Ако upload_tmp_dir не е достъпна за запис, PHP преминава към временната директория на системата. При open_basedir временната директория трябва да е разрешена, иначе качването се проваля. Практически стойности и техните последствия описва статията Разумно ограничаване на post_max_size и upload_max_filesize.

Правилно оценяване на кодовете за грешки

Полето error показва какво е станало при обработката. Константите са от UPLOAD_ERR_OK (0) през UPLOAD_ERR_INI_SIZE (1), UPLOAD_ERR_FORM_SIZE (2), UPLOAD_ERR_PARTIAL (3) и UPLOAD_ERR_NO_FILE (4) до UPLOAD_ERR_NO_TMP_DIR (6), UPLOAD_ERR_CANT_WRITE (7) и UPLOAD_ERR_EXTENSION (8).

<?php
declare(strict_types=1);

$datei = $_FILES['lebenslauf'] ?? null;
if ($datei === null) {
    exit('Keine Upload-Daten, eventuell wurde post_max_size überschritten.');
}

$meldung = match ($datei['error']) {
    UPLOAD_ERR_OK => null,
    UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'Datei zu groß.',
    UPLOAD_ERR_NO_FILE => 'Keine Datei gewählt.',
    default => 'Upload fehlgeschlagen.',
};

if ($meldung === null && is_uploaded_file($datei['tmp_name'])) {
    $ziel = '/srv/app/uploads/' . bin2hex(random_bytes(16)) . '.pdf';
    move_uploaded_file($datei['tmp_name'], $ziel);
}

Примерът показва само механиката. Проверката на типа на файла, съхранението извън webroot и блокирането на изпълнението на скриптове описва наръчникът Сигурно реализиране на качването на файлове в PHP.

Качване и PHP2Shell: какво означават временните мерки

Уязвимостите PHP2Shell CVE-2026-109618 и CVE-2026-109619 засягат обработката около заявката в интерпретатора на PHP. Затова php.net посочва като временни мерки, наред с другото, post_max_size = 8M (без качване) или 32M (с качване), upload_max_filesize под тази стойност, както и session.upload_progress.enabled=0. Тези стойности принадлежат в php.ini, FPM пула или конфигурацията на сървъра, след което всички worker процеси трябва да се рестартират.

Специализирани автори предлагат допълнително да се блокират заявките multipart/form-data извън истинските пътища за качване на прокси или в WAF. Това е неофициална мярка с риск формите да спрат да работят; преценката описва статията Филтриране на заявките за качване на периферията. Нито една от тези мерки не замества актуализацията до PHP 8.5.12, 8.4.27, 8.3.36 или 8.2.35.

Как помага PHP SOS конкретно?

  • Установяваме кои от Вашите уебсайтове действително се нуждаят от качване и по кои пътища.
  • Задаваме ограниченията и временните мерки за всеки пул, рестартираме всички worker процеси и тестваме формите.
  • Проверяваме директориите за качване за изпълними файлове и блокираме изпълнението на скриптове в рамките на заздравяването на сървъра.
  • Внедряваме актуализацията на PHP и документираме състоянието на пачовете за всеки уебсайт.

Често задавани въпроси

Къде PHP съхранява първоначално качените файлове?

Във временна директория. По подразбиране това е временната директория на системата; с upload_tmp_dir тя може да се промени. Ако файлът не бъде преместен в скрипта, PHP го изтрива в края на заявката.

Мога ли да вярвам на полето type в $_FILES?

Не. MIME типът се съобщава от браузъра, а PHP според ръководството не го проверява. И name, и full_path идват от подателя. Действителния тип на файла определяте от страна на сървъра по съдържанието.

Защо $_FILES е празен, макар да е изпратен файл?

Най-често цялата заявка е била по-голяма от post_max_size. Тогава PHP изчиства напълно $_POST и $_FILES, без код за грешка в масива. Други причини са липсващ enctype във формата или изключена настройка file_uploads.

Помага ли срещу PHP2Shell, ако забраня качването в приложението?

Не. Обработката на данните от заявката се извършва в PHP, преди да се изпълни кодът на приложението. Ефективни са актуализацията на PHP и рестартирането на всички worker процеси; по-тесните ограничения са само временна мярка.

Каква е разликата между upload_max_filesize и post_max_size?

upload_max_filesize ограничава всеки отделен файл, а post_max_size – цялото съдържание на заявката, включително всички файлове и полета. Затова post_max_size трябва да е по-голям от upload_max_filesize.

Източници

Незабавен контакт

Всеки час има значение. Пишете ни сега.

Опишете накратко системата, версията на PHP и какво сте забелязали. Ние се свързваме с Вас, уточняваме ситуацията и работим отдалечено, докато инцидентът бъде приключен и документиран.

Незабавна помощ