# Kako PHP obdela nalaganje datotek

> Kako PHP interno obdela nalaganje datotek: multipart/form-data, začasne datoteke v upload_tmp_dir, polje $_FILES, omejitve in kode napak so razloženi.

Vir: https://phpsos.de/sl/php/datei-uploads-intern/  
Stanje: 25.10.2026  
Ponudnik: PHP SOS, Nujna pomoč za PHP in varnost strežnikov. Takojšnja pomoč po WhatsAppu: https://wa.me/491736494914?text=Pozdravljeni%2C%20PHP%20SOS%2C%20potrebujemo%20pomo%C4%8D%20z%20na%C5%A1im%20stre%C5%BEnikom%20PHP.%20Podjetje%3A%20%E2%80%A6%20Spletna%20stran%3A%20%E2%80%A6

**Na kratko:** Ko brskalnik naloži datoteko, pošlje zahtevo POST v obliki `multipart/form-data`, v kateri so polja obrazca in vsebine datotek kot posamezni deli zapisani drug za drugim. PHP to zahtevo razčleni sam, vsak del z datoteko zapiše v začasno datoteko v `upload_tmp_dir`, podatke vpiše v `$_FILES` in šele nato zažene vašo skripto. Skripta datoteko premakne z `move_uploaded_file()`; vse, kar ni premaknjeno, PHP ob koncu zahteve izbriše.

## Pot datoteke od brskalnika do skripte

Za odločevalce je predvsem pomembno eno: nalaganje ni opravilo same aplikacije. Velik del dela se opravi v interpreterju PHP, še preden je koda sistema CMS ali spletne trgovine sploh vpletena. Zato napake v tem delu PHP zadevajo vsako spletno stran, ne glede na to, ali ima obrazec za nalaganje ali ne.

Potek na kratko:

1. Odda se obrazec z `method="POST"` in `enctype="multipart/form-data"`.
2. Spletni strežnik zahtevo sprejme in jo preda PHP, odvisno od načina delovanja prek PHP-FPM, mod_php ali drugega vmesnika.
3. PHP preveri omejitve, kot je `post_max_size`, in vsebino razdeli na podlagi ločilnega niza, tako imenovane meje (boundary).
4. Navadna polja pristanejo v `$_POST`, deli z datotekami se zapišejo v začasne datoteke in opišejo v `$_FILES`.
5. Šele zdaj se zažene skripta, preveri datoteko in jo premakne na ciljno mesto.
6. Ob koncu zahteve PHP izbriše vse začasne datoteke, ki niso bile premaknjene.

## multipart/form-data: zgradba zahteve za nalaganje

Zahteva za nalaganje je čisto besedilo z vdelanimi podatki datoteke. Glava `Content-Type` navaja mejo (boundary), v vsebini pa ta niz loči posamezne dele. Vsak del ima lastne glave z imenom polja in po potrebi imenom datoteke. Močno skrajšan, neškodljiv primer:

```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--
```

Vsi podatki v tem besedilu izvirajo od pošiljatelja, tudi ime datoteke in `Content-Type` dela. PHP jih prevzame v `$_FILES`, vendar ne preverja njihove verodostojnosti.

## Polje $_FILES

Za polje z imenom `lebenslauf` `$_FILES['lebenslauf']` po priročniku PHP vsebuje te ključe:

| Ključ | Vsebina | Ali mu je mogoče zaupati? |
|---|---|---|
| `name` | prvotno ime datoteke pri pošiljatelju | ne |
| `full_path` | pot, ki jo sporoči brskalnik, od PHP 8.1.0 | ne |
| `type` | tip MIME po navedbi brskalnika, PHP ga ne preverja | ne |
| `size` | velikost v bajtih | ugotovi PHP |
| `tmp_name` | pot začasne datoteke na strežniku | dodeli PHP |
| `error` | koda napake pri obdelavi nalaganja | nastavi PHP |

Pri poljih z oglatimi oklepaji v imenu, na primer `bilder[]`, vsak ključ vsebuje polje z enim vnosom na datoteko. Aplikacija nato na primer prehodi `$_FILES['bilder']['error']` in prek istega indeksa dostopa do `tmp_name` in `name`.

Obrazec lahko pred poljem za datoteko dodatno pošlje skrito polje `MAX_FILE_SIZE`. Po priročniku je to samo namig brskalniku, ki ga je mogoče zlahka obiti. Zanesete se lahko izključno na meje, ki jih uveljavlja strežnik: v PHP, v spletnem strežniku in v sami aplikaciji.

## Omejitve: direktive php.ini za nalaganje

Te direktive nadzorujejo obdelavo nalaganja. Privzete vrednosti izhajajo iz priročnika PHP in veljajo, kadar jih php.ini ne prepiše.

| Direktiva | Privzeto | Mogoče spremeniti v |
|---|---|---|
| `file_uploads` | `1` | samo sistemska konfiguracija |
| `upload_tmp_dir` | prazno (sistemski temp) | samo sistemska konfiguracija |
| `upload_max_filesize` | `2M` | php.ini, pool, konfiguracija imenika |
| `post_max_size` | `8M` | php.ini, pool, konfiguracija imenika |
| `max_file_uploads` | `20` | php.ini, pool, konfiguracija imenika |
| `enable_post_data_reading` | `1` | php.ini, pool, konfiguracija imenika |

`post_max_size` mora biti večji od `upload_max_filesize`, `memory_limit` pa mora biti po priročniku praviloma večji od `post_max_size`. Če v `upload_tmp_dir` ni mogoče pisati, PHP preide na temp imenik sistema. Pri `open_basedir` mora biti temp imenik dovoljen, sicer nalaganje ne uspe. Praktične vrednosti in njihove posledice opisuje prispevek [Smiselna omejitev post_max_size in upload_max_filesize](https://phpsos.de/sl/blog/post-max-size-upload-limits/).

## Pravilno ovrednotenje kod napak

Polje `error` pove, kaj se je zgodilo pri obdelavi. Konstante segajo od `UPLOAD_ERR_OK` (0) prek `UPLOAD_ERR_INI_SIZE` (1), `UPLOAD_ERR_FORM_SIZE` (2), `UPLOAD_ERR_PARTIAL` (3) in `UPLOAD_ERR_NO_FILE` (4) do `UPLOAD_ERR_NO_TMP_DIR` (6), `UPLOAD_ERR_CANT_WRITE` (7) in `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);
}
```

Primer kaže samo mehaniko. Preverjanje tipa datoteke, shranjevanje zunaj korena spleta (webroot) in onemogočanje izvajanja skript opisuje vodnik [Varno nalaganje datotek v PHP](https://phpsos.de/sl/ratgeber/datei-upload-sicher/).

## Nalaganje in PHP2Shell: kaj pomenijo prehodni ukrepi

Ranljivosti PHP2Shell CVE-2026-109618 in CVE-2026-109619 zadevata obdelavo okoli zahteve v interpreterju PHP. Zato php.net kot prehodne ukrepe med drugim navaja `post_max_size = 8M` (brez nalaganja) ali `32M` (z nalaganjem), `upload_max_filesize` pod tem, pa tudi `session.upload_progress.enabled=0`. Te vrednosti sodijo v php.ini, pool FPM ali konfiguracijo strežnika, nato pa je treba znova zagnati vse workerje.

Strokovni avtorji dodatno predlagajo, da se zahteve `multipart/form-data` zunaj pravih poti za nalaganje blokirajo na proxyju ali v WAF. To je neuradni ukrep s tveganjem, da se obrazci pokvarijo; tehtanje opisuje prispevek [Filtriranje zahtev za nalaganje na robu](https://phpsos.de/sl/blog/upload-anfragen-am-rand-filtern/). Noben od teh ukrepov ne nadomesti posodobitve na PHP 8.5.12, 8.4.27, 8.3.36 ali 8.2.35.

## Kako PHP SOS pomaga konkretno?

- Ugotovimo, katere vaše spletne strani dejansko potrebujejo nalaganje in na katerih poteh.
- Omejitve in prehodne ukrepe nastavimo po poolih, znova zaženemo vse workerje in preizkusimo obrazce.
- Imenike za nalaganje preverimo glede izvršljivih datotek in onemogočimo izvajanje skript v okviru [utrjevanja strežnika](https://phpsos.de/sl/leistungen/server-haertung/).
- Namestimo posodobitev PHP in dokumentiramo stanje popravkov po posameznih spletnih straneh.


## Pogosta vprašanja

**Kam PHP najprej shrani naložene datoteke?**  
V začasen imenik. Privzeto je to začasni imenik sistema; z upload_tmp_dir ga je mogoče spremeniti. Če skripta datoteke ne premakne, jo PHP izbriše ob koncu zahteve.

**Ali lahko zaupam polju type v $_FILES?**  
Ne. Tip MIME sporoči brskalnik, PHP ga po priročniku ne preverja. Tudi name in full_path izvirata od pošiljatelja. Dejanski tip datoteke ugotovite na strežniku na podlagi vsebine.

**Zakaj je $_FILES prazen, čeprav je bila datoteka poslana?**  
Najpogosteje je bila celotna zahteva večja od post_max_size. Takrat PHP popolnoma izprazni $_POST in $_FILES, brez kode napake v polju. Drugi vzroki so manjkajoč enctype v obrazcu ali izklopljen file_uploads.

**Ali proti PHP2Shell pomaga, če v aplikaciji prepovem nalaganje?**  
Ne. Obdelava podatkov zahteve poteka v PHP, še preden se izvede koda aplikacije. Učinkovita sta posodobitev PHP in ponovni zagon vseh workerjev; ožje omejitve so samo prehodni ukrep.

**Kakšna je razlika med upload_max_filesize in post_max_size?**  
upload_max_filesize omejuje vsako posamezno datoteko, post_max_size pa celotno vsebino zahteve, vključno z vsemi datotekami in polji. post_max_size mora zato biti večji od upload_max_filesize.

## Viri

- [Priročnik PHP: nalaganje datotek z metodo POST](https://www.php.net/manual/de/features.file-upload.post-method.php)
- [Priročnik PHP: sporočila o napakah pri nalaganju](https://www.php.net/manual/de/features.file-upload.errors.php)
- [Priročnik PHP: direktive php.ini](https://www.php.net/manual/de/ini.core.php)
- [php.net: varnostna obvestila](https://www.php.net/)
