# Jak PHP przetwarza przesyłane pliki

> Jak PHP przetwarza przesyłane pliki: multipart/form-data, pliki tymczasowe w upload_tmp_dir, tablica $_FILES, limity i kody błędów wyjaśnione krok po kroku.

Źródło: https://phpsos.de/pl/php/datei-uploads-intern/  
Stan: 25.10.2026  
Dostawca: PHP SOS, Pomoc awaryjna PHP i bezpieczeństwo serwerów. Natychmiastowa pomoc przez WhatsApp: https://wa.me/491736494914?text=Dzie%C5%84%20dobry%2C%20PHP%20SOS%2C%20potrzebujemy%20pomocy%20z%20naszym%20serwerem%20PHP.%20Firma%3A%20%E2%80%A6%20Strona%20internetowa%3A%20%E2%80%A6

**Krótko:** Gdy przeglądarka przesyła plik, wysyła żądanie POST w formacie `multipart/form-data`, w którym pola formularza i zawartość plików występują jako kolejne osobne części. PHP samo rozbija to żądanie, zapisuje każdą część z plikiem w pliku tymczasowym w `upload_tmp_dir`, wpisuje dane do `$_FILES` i dopiero wtedy uruchamia Państwa skrypt. Skrypt przenosi plik za pomocą `move_uploaded_file()`; wszystko, czego nie przeniesie, PHP usuwa na końcu żądania.

## Droga pliku od przeglądarki do skryptu

Dla osób podejmujących decyzje ważne jest przede wszystkim jedno: przesyłanie plików nie jest operacją samej aplikacji. Duża część pracy odbywa się w interpreterze PHP, zanim kod CMS lub sklepu w ogóle weźmie w niej udział. Dlatego błędy w tej części PHP dotyczą każdej strony, niezależnie od tego, czy ma formularz przesyłania plików.

Przebieg w skrócie:

1. Formularz z `method="POST"` i `enctype="multipart/form-data"` zostaje wysłany.
2. Serwer WWW przyjmuje żądanie i przekazuje je do PHP, zależnie od trybu pracy przez PHP-FPM, mod_php lub inny interfejs.
3. PHP sprawdza limity, takie jak `post_max_size`, i rozbija zawartość na podstawie ciągu znaków rozdzielającego, czyli tzw. boundary.
4. Zwykłe pola trafiają do `$_POST`, części z plikami są zapisywane w plikach tymczasowych i opisywane w `$_FILES`.
5. Dopiero teraz zaczyna działać skrypt, sprawdza plik i przenosi go do miejsca docelowego.
6. Na końcu żądania PHP usuwa wszystkie pliki tymczasowe, które nie zostały przeniesione.

## multipart/form-data: budowa żądania z plikiem

Żądanie z plikiem to zwykły tekst z osadzonymi danymi pliku. Nagłówek `Content-Type` podaje boundary, a w treści ten ciąg znaków oddziela poszczególne części. Każda część ma własne nagłówki z nazwą pola i ewentualnie nazwą pliku. Mocno skrócony, nieszkodliwy przykład:

```http
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--
```

Wszystkie dane w tym tekście pochodzą od nadawcy, także nazwa pliku i `Content-Type` danej części. PHP przejmuje je do `$_FILES`, ale nie sprawdza ich wiarygodności.

## Tablica $_FILES

Dla pola o nazwie `lebenslauf` tablica `$_FILES['lebenslauf']` zawiera według podręcznika PHP następujące klucze:

| Klucz | Zawartość | Wiarygodne? |
|---|---|---|
| `name` | pierwotna nazwa pliku u nadawcy | nie |
| `full_path` | ścieżka zgłoszona przez przeglądarkę, od PHP 8.1.0 | nie |
| `type` | typ MIME według przeglądarki, niesprawdzany przez PHP | nie |
| `size` | rozmiar w bajtach | ustala PHP |
| `tmp_name` | ścieżka pliku tymczasowego na serwerze | nadaje PHP |
| `error` | kod błędu przetwarzania przesyłania | ustawia PHP |

W polach z nawiasami kwadratowymi w nazwie, na przykład `bilder[]`, każdy klucz zawiera tablicę z jednym wpisem na plik. Aplikacja przechodzi wtedy na przykład przez `$_FILES['bilder']['error']` i przez ten sam indeks sięga po `tmp_name` i `name`.

Formularz może dodatkowo wysłać przed polem pliku ukryte pole `MAX_FILE_SIZE`. Według podręcznika jest to tylko wskazówka dla przeglądarki, którą łatwo obejść. Polegać można wyłącznie na granicach, które wymusza serwer: w PHP, w serwerze WWW i w samej aplikacji.

## Limity: dyrektywy php.ini dla przesyłania plików

Te dyrektywy sterują przetwarzaniem przesyłanych plików. Wartości domyślne pochodzą z podręcznika PHP i obowiązują, gdy żadne php.ini ich nie nadpisuje.

| Dyrektywa | Domyślnie | Zmienialna w |
|---|---|---|
| `file_uploads` | `1` | tylko konfiguracja systemowa |
| `upload_tmp_dir` | puste (katalog tymczasowy systemu) | tylko konfiguracja systemowa |
| `upload_max_filesize` | `2M` | php.ini, pula, konfiguracja katalogu |
| `post_max_size` | `8M` | php.ini, pula, konfiguracja katalogu |
| `max_file_uploads` | `20` | php.ini, pula, konfiguracja katalogu |
| `enable_post_data_reading` | `1` | php.ini, pula, konfiguracja katalogu |

`post_max_size` musi być większe niż `upload_max_filesize`, a `memory_limit` powinno według podręcznika z reguły być większe niż `post_max_size`. Jeśli do `upload_tmp_dir` nie można zapisywać, PHP przechodzi na katalog tymczasowy systemu. Przy `open_basedir` katalog tymczasowy musi być dozwolony, w przeciwnym razie przesyłanie plików się nie powiedzie. Wartości praktyczne i ich skutki opisuje artykuł [Sensowne ograniczanie post_max_size i upload_max_filesize](https://phpsos.de/pl/blog/post-max-size-upload-limits/).

## Prawidłowa ocena kodów błędów

Pole `error` mówi, co się stało podczas przetwarzania. Stałe obejmują zakres od `UPLOAD_ERR_OK` (0) przez `UPLOAD_ERR_INI_SIZE` (1), `UPLOAD_ERR_FORM_SIZE` (2), `UPLOAD_ERR_PARTIAL` (3) i `UPLOAD_ERR_NO_FILE` (4) do `UPLOAD_ERR_NO_TMP_DIR` (6), `UPLOAD_ERR_CANT_WRITE` (7) i `UPLOAD_ERR_EXTENSION` (8).

```php
<?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);
}
```

Przykład pokazuje tylko mechanikę. Sprawdzanie typu pliku, zapis poza webrootem i blokowanie wykonywania skryptów opisuje poradnik [Bezpieczne przesyłanie plików w PHP](https://phpsos.de/pl/ratgeber/datei-upload-sicher/).

## Przesyłanie plików a PHP2Shell: co oznaczają środki przejściowe

Luki PHP2Shell CVE-2026-109618 i CVE-2026-109619 dotyczą przetwarzania w interpreterze PHP związanego z żądaniem. Dlatego php.net podaje jako środki przejściowe między innymi `post_max_size = 8M` (bez przesyłania plików) lub `32M` (z przesyłaniem plików), `upload_max_filesize` poniżej tej wartości oraz `session.upload_progress.enabled=0`. Te wartości należą do php.ini, puli FPM lub konfiguracji serwera, po czym wszystkie workery muszą się zrestartować.

Autorzy branżowi proponują dodatkowo blokowanie żądań `multipart/form-data` poza rzeczywistymi ścieżkami przesyłania plików na proxy lub w WAF. Jest to środek nieoficjalny, z ryzykiem, że formularze przestaną działać; rozważenie za i przeciw opisuje artykuł [Filtrowanie żądań przesyłania plików na brzegu](https://phpsos.de/pl/blog/upload-anfragen-am-rand-filtern/). Żaden z tych środków nie zastępuje aktualizacji do PHP 8.5.12, 8.4.27, 8.3.36 lub 8.2.35.

## Jak konkretnie pomaga PHP SOS?

- Ustalamy, które z Państwa stron rzeczywiście potrzebują przesyłania plików i na jakich ścieżkach.
- Ustawiamy limity i środki przejściowe dla każdej puli, restartujemy wszystkie workery i testujemy formularze.
- Sprawdzamy katalogi z przesłanymi plikami pod kątem plików wykonywalnych i blokujemy wykonywanie skryptów w ramach [utwardzania serwera](https://phpsos.de/pl/leistungen/server-haertung/).
- Instalujemy aktualizację PHP i dokumentujemy stan poprawek dla każdej strony.


## Częste pytania

**Gdzie PHP zapisuje przesłane pliki w pierwszej kolejności?**  
W katalogu tymczasowym. Domyślnie jest to katalog tymczasowy systemu; można go zmienić przez upload_tmp_dir. Jeśli skrypt nie przeniesie pliku, PHP usuwa go na końcu żądania.

**Czy mogę ufać polu type w $_FILES?**  
Nie. Typ MIME zgłasza przeglądarka, a PHP według podręcznika go nie sprawdza. Także name i full_path pochodzą od nadawcy. Rzeczywisty typ pliku należy ustalić po stronie serwera na podstawie zawartości.

**Dlaczego $_FILES jest puste, mimo że plik został wysłany?**  
Zwykle całe żądanie było większe niż post_max_size. Wtedy PHP całkowicie opróżnia $_POST i $_FILES, bez kodu błędu w tablicy. Inne przyczyny to brak enctype w formularzu lub wyłączone file_uploads.

**Czy zakaz przesyłania plików w aplikacji pomaga przeciw PHP2Shell?**  
Nie. Przetwarzanie danych żądania odbywa się w PHP, zanim zacznie działać kod aplikacji. Skuteczne są aktualizacja PHP i restart wszystkich workerów; węższe limity to tylko środek przejściowy.

**Jaka jest różnica między upload_max_filesize a post_max_size?**  
upload_max_filesize ogranicza każdy pojedynczy plik, a post_max_size całą zawartość żądania, łącznie ze wszystkimi plikami i polami. Dlatego post_max_size musi być większe niż upload_max_filesize.

## Źródła

- [Podręcznik PHP: przesyłanie plików metodą POST](https://www.php.net/manual/de/features.file-upload.post-method.php)
- [Podręcznik PHP: komunikaty o błędach przy przesyłaniu plików](https://www.php.net/manual/de/features.file-upload.errors.php)
- [Podręcznik PHP: dyrektywy php.ini](https://www.php.net/manual/de/ini.core.php)
- [php.net: komunikaty o bezpieczeństwie](https://www.php.net/)
