# Ako PHP spracúva nahrávanie súborov

> Ako PHP interne spracúva nahrávanie súborov: multipart/form-data, dočasné súbory v upload_tmp_dir, pole $_FILES, limity a chybové kódy vysvetlené.

Zdroj: https://phpsos.de/sk/php/datei-uploads-intern/  
Stav: 25.10.2026  
Poskytovateľ: PHP SOS, Núdzová pomoc pri PHP a bezpečnosť serverov. Okamžitá pomoc cez WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20de%C5%88%2C%20PHP%20SOS%2C%20potrebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20serverom%20s%20PHP.%20Spolo%C4%8Dnos%C5%A5%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**Stručne:** Keď prehliadač nahráva súbor, odošle požiadavku POST vo formáte `multipart/form-data`, v ktorej polia formulára a obsah súborov nasledujú za sebou ako jednotlivé časti. PHP túto požiadavku rozloží samo, každú časť so súborom zapíše do dočasného súboru v `upload_tmp_dir`, údaje uloží do `$_FILES` a až potom spustí váš skript. Skript súbor presunie pomocou `move_uploaded_file()`; všetko, čo sa nepresunie, PHP na konci požiadavky zmaže.

## Cesta súboru od prehliadača až do skriptu

Pre rozhodovateľov je dôležité predovšetkým jedno: nahrávanie nie je úkon samotnej aplikácie. Veľká časť práce prebieha v interpreteri PHP skôr, než sa kód CMS alebo obchodu vôbec zapojí. Preto chyby v tejto časti PHP postihujú každý web, či už má formulár na nahrávanie, alebo nie.

Priebeh v prehľade:

1. Odošle sa formulár s `method="POST"` a `enctype="multipart/form-data"`.
2. Webový server prijme požiadavku a odovzdá ju PHP, podľa spôsobu prevádzky cez PHP-FPM, mod_php alebo iné rozhranie.
3. PHP skontroluje limity, napríklad `post_max_size`, a rozloží obsah podľa oddeľovacieho reťazca, takzvanej boundary.
4. Bežné polia skončia v `$_POST`, časti so súbormi sa zapíšu do dočasných súborov a opíšu sa v `$_FILES`.
5. Až teraz sa spustí skript, súbor skontroluje a presunie na cieľové miesto.
6. Na konci požiadavky PHP zmaže všetky dočasné súbory, ktoré sa nepresunuli.

## multipart/form-data: štruktúra požiadavky na nahrávanie

Požiadavka na nahrávanie je čistý text s vloženými údajmi súboru. Hlavička `Content-Type` uvádza boundary a v obsahu tento reťazec oddeľuje jednotlivé časti. Každá časť má vlastné hlavičky s názvom poľa a prípadne názvom súboru. Veľmi skrátený, neškodný príklad:

```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--
```

Všetky údaje v tomto texte pochádzajú od odosielateľa, vrátane názvu súboru a `Content-Type` danej časti. PHP ich prevezme do `$_FILES`, ale nekontroluje ich vierohodnosť.

## Pole $_FILES

Pre pole s názvom `lebenslauf` obsahuje `$_FILES['lebenslauf']` podľa príručky PHP tieto kľúče:

| Kľúč | Obsah | Dôveryhodné? |
|---|---|---|
| `name` | pôvodný názov súboru u odosielateľa | nie |
| `full_path` | cesta nahlásená prehliadačom, od PHP 8.1.0 | nie |
| `type` | typ MIME podľa prehliadača, PHP ho nekontroluje | nie |
| `size` | veľkosť v bajtoch | zisťuje PHP |
| `tmp_name` | cesta k dočasnému súboru na serveri | prideľuje PHP |
| `error` | chybový kód spracovania nahrávania | nastavuje PHP |

Pri poliach s hranatými zátvorkami v názve, napríklad `bilder[]`, obsahuje každý kľúč pole s jedným záznamom na súbor. Aplikácia potom prechádza napríklad `$_FILES['bilder']['error']` a cez ten istý index pristupuje k `tmp_name` a `name`.

Formulár môže pred poľom pre súbor navyše odoslať skryté pole `MAX_FILE_SIZE`. Podľa príručky je to iba pokyn pre prehliadač, ktorý sa dá ľahko obísť. Spoľahnúť sa môžete výlučne na hranice, ktoré presadzuje server: v PHP, vo webovom serveri a v samotnej aplikácii.

## Limity: direktívy php.ini pre nahrávanie

Tieto direktívy riadia spracovanie nahrávania. Štandardné hodnoty pochádzajú z príručky PHP a platia, ak ich nepredefinuje žiadny súbor php.ini.

| Direktíva | Štandard | Zmeniteľná v |
|---|---|---|
| `file_uploads` | `1` | iba systémová konfigurácia |
| `upload_tmp_dir` | prázdna (systémový temp) | iba systémová konfigurácia |
| `upload_max_filesize` | `2M` | php.ini, pool, konfigurácia adresára |
| `post_max_size` | `8M` | php.ini, pool, konfigurácia adresára |
| `max_file_uploads` | `20` | php.ini, pool, konfigurácia adresára |
| `enable_post_data_reading` | `1` | php.ini, pool, konfigurácia adresára |

`post_max_size` musí byť väčšie než `upload_max_filesize` a `memory_limit` by podľa príručky malo byť spravidla väčšie než `post_max_size`. Ak do `upload_tmp_dir` nemožno zapisovať, PHP prejde na temp adresár systému. S `open_basedir` musí byť temp adresár povolený, inak nahrávanie zlyhá. Praktické hodnoty a ich dôsledky opisuje článok [Rozumné obmedzenie post_max_size a upload_max_filesize](https://phpsos.de/sk/blog/post-max-size-upload-limits/).

## Správne vyhodnotenie chybových kódov

Pole `error` hovorí, čo sa pri spracovaní stalo. Konštanty siahajú od `UPLOAD_ERR_OK` (0) cez `UPLOAD_ERR_INI_SIZE` (1), `UPLOAD_ERR_FORM_SIZE` (2), `UPLOAD_ERR_PARTIAL` (3) a `UPLOAD_ERR_NO_FILE` (4) až po `UPLOAD_ERR_NO_TMP_DIR` (6), `UPLOAD_ERR_CANT_WRITE` (7) a `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);
}
```

Príklad ukazuje iba mechaniku. Kontrolu typu súboru, uloženie mimo webrootu a zablokovanie spúšťania skriptov opisuje sprievodca [Bezpečná implementácia nahrávania súborov v PHP](https://phpsos.de/sk/ratgeber/datei-upload-sicher/).

## Nahrávanie a PHP2Shell: čo znamenajú prechodné opatrenia

Zraniteľnosti PHP2Shell CVE-2026-109618 a CVE-2026-109619 sa týkajú spracovania v okolí požiadavky v interpreteri PHP. Preto php.net ako prechodné opatrenia uvádza okrem iného `post_max_size = 8M` (bez nahrávania) alebo `32M` (s nahrávaním), `upload_max_filesize` pod touto hodnotou a `session.upload_progress.enabled=0`. Tieto hodnoty patria do php.ini, poolu FPM alebo konfigurácie servera, potom sa musia reštartovať všetky workery.

Odborní autori navyše navrhujú blokovať požiadavky `multipart/form-data` mimo skutočných ciest na nahrávanie na proxy alebo vo WAF. Ide o neoficiálne opatrenie s rizikom, že formuláre prestanú fungovať; zvažovanie opisuje článok [Filtrovanie požiadaviek na nahrávanie na okraji](https://phpsos.de/sk/blog/upload-anfragen-am-rand-filtern/). Žiadne z týchto opatrení nenahrádza aktualizáciu na PHP 8.5.12, 8.4.27, 8.3.36 alebo 8.2.35.

## Ako PHP SOS konkrétne pomáha?

- Zisťujeme, ktoré z vašich webov nahrávanie skutočne potrebujú a na akých cestách.
- Nastavujeme limity a prechodné opatrenia pre každý pool, reštartujeme všetky workery a testujeme formuláre.
- Kontrolujeme adresáre na nahrávanie, či neobsahujú spustiteľné súbory, a blokujeme spúšťanie skriptov v rámci [zabezpečenia servera](https://phpsos.de/sk/leistungen/server-haertung/).
- Nasadzujeme aktualizáciu PHP a dokumentujeme stav záplat pre každý web.


## Časté otázky

**Kam PHP ukladá nahrané súbory ako prvé?**  
Do dočasného adresára. Štandardne je to dočasný adresár systému; pomocou upload_tmp_dir sa dá zmeniť. Ak sa súbor v skripte nepresunie, PHP ho na konci požiadavky zmaže.

**Môžem veriť poľu type v $_FILES?**  
Nie. Typ MIME hlási prehliadač a PHP ho podľa príručky nekontroluje. Aj name a full_path pochádzajú od odosielateľa. Skutočný typ súboru zistíte na strane servera podľa obsahu.

**Prečo je $_FILES prázdne, hoci bol súbor odoslaný?**  
Zvyčajne bola celá požiadavka väčšia než post_max_size. PHP potom úplne vyprázdni $_POST aj $_FILES, bez chybového kódu v poli. Ďalšie príčiny sú chýbajúci enctype vo formulári alebo vypnuté file_uploads.

**Pomôže proti PHP2Shell, ak v aplikácii zakážem nahrávanie?**  
Nie. Spracovanie údajov požiadavky prebieha v PHP skôr, než sa spustí kód aplikácie. Účinná je aktualizácia PHP a reštart všetkých workerov; prísnejšie limity sú iba prechodné opatrenie.

**Aký je rozdiel medzi upload_max_filesize a post_max_size?**  
upload_max_filesize obmedzuje každý jednotlivý súbor, post_max_size celý obsah požiadavky vrátane všetkých súborov a polí. post_max_size preto musí byť väčšie než upload_max_filesize.

## Zdroje

- [Príručka PHP: Nahrávanie cez POST](https://www.php.net/manual/de/features.file-upload.post-method.php)
- [Príručka PHP: Chybové hlásenia pri nahrávaní](https://www.php.net/manual/de/features.file-upload.errors.php)
- [Príručka PHP: Direktívy php.ini](https://www.php.net/manual/de/ini.core.php)
- [php.net: Bezpečnostné oznámenia](https://www.php.net/)
