Ajankohtainen tilanne: PHP2Shell. Kaksi kriittistä PHP-haavoittuvuutta (CVSS 9.2) sallii haittakoodin suorittamisen ilman kirjautumista. Korjaukset 21.10.2026 alkaen.Mitä nyt on tehtävä →

Miten PHP käsittelee tiedostolataukset

Latauspainikkeen napsautuksen ja oman koodinne välillä on PHP:ssä kokonainen käsittelyketju. Tällä sivulla selitetään multipart/form-data, tilapäistiedostot, $_FILES, rajat ja yhteys PHP2Shelliin.

Tilanne Lukuaika 4 minPHP-tietoa

Lyhyesti: Kun selain lataa tiedoston, se lähettää POST-pyynnön muodossa multipart/form-data, jossa lomakekentät ja tiedostojen sisältö ovat peräkkäisinä erillisinä osina. PHP pilkkoo pyynnön itse, kirjoittaa jokaisen tiedosto-osan tilapäistiedostoon hakemistoon upload_tmp_dir, kirjaa tiedot taulukkoon $_FILES ja käynnistää komentosarjanne vasta sen jälkeen. Komentosarja siirtää tiedoston funktiolla move_uploaded_file(); kaiken, mitä ei siirretä, PHP poistaa pyynnön päättyessä.

Tiedoston matka selaimesta komentosarjaan

Päättäjälle on tärkeintä ymmärtää yksi asia: lataus ei ole pelkästään sovelluksen toimi. Suuri osa työstä tehdään PHP-tulkissa jo ennen kuin CMS- tai verkkokauppakoodi on lainkaan mukana. Siksi tämän PHP:n osan virheet koskevat jokaista verkkosivustoa, olipa siinä latauslomake tai ei.

Kulku pähkinänkuoressa:

  1. Lomake, jossa on method="POST" ja enctype="multipart/form-data", lähetetään.
  2. Verkkopalvelin ottaa pyynnön vastaan ja välittää sen PHP:lle ajotavasta riippuen PHP-FPM:n, mod_php:n tai muun rajapinnan kautta.
  3. PHP tarkistaa rajat, kuten post_max_size, ja pilkkoo sisällön erotinmerkkijonon, niin sanotun boundaryn, perusteella.
  4. Tavalliset kentät päätyvät taulukkoon $_POST, tiedosto-osat kirjoitetaan tilapäistiedostoihin ja kuvataan taulukossa $_FILES.
  5. Vasta nyt komentosarja käynnistyy, tarkistaa tiedoston ja siirtää sen kohteeseensa.
  6. Pyynnön päättyessä PHP poistaa kaikki tilapäistiedostot, joita ei ole siirretty.

multipart/form-data: latauspyynnön rakenne

Latauspyyntö on pelkkää tekstiä, johon on upotettu tiedostodataa. Otsake Content-Type kertoo boundaryn, ja sisällössä tämä merkkijono erottaa osat toisistaan. Jokaisella osalla on omat otsakerivinsä, joissa on kentän nimi ja tarvittaessa tiedostonimi. Voimakkaasti lyhennetty, vaaraton esimerkki:

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--

Kaikki tämän tekstin tiedot tulevat lähettäjältä, myös tiedostonimi ja osan Content-Type. PHP ottaa ne $_FILES-taulukkoon mutta ei tarkista niiden uskottavuutta.

$_FILES-taulukko

Kentälle nimeltä lebenslauf taulukko $_FILES['lebenslauf'] sisältää PHP-käsikirjan mukaan nämä avaimet:

Avain Sisältö Luotettava?
name tiedoston alkuperäinen nimi lähettäjällä ei
full_path selaimen ilmoittama polku, versiosta PHP 8.1.0 lähtien ei
type selaimen ilmoittama MIME-tyyppi, jota PHP ei tarkista ei
size koko tavuina PHP:n määrittämä
tmp_name tilapäistiedoston polku palvelimella PHP:n antama
error latauskäsittelyn virhekoodi PHP:n asettama

Jos kentän nimessä on hakasulkeet, esimerkiksi bilder[], jokainen avain sisältää taulukon, jossa on yksi alkio tiedostoa kohti. Sovellus käy silloin läpi esimerkiksi $_FILES['bilder']['error'] ja hakee tmp_name- ja name-arvot samalla indeksillä.

Lomake voi lisäksi lähettää tiedostokentän edellä piilokentän MAX_FILE_SIZE. Käsikirjan mukaan se on vain selaimelle tarkoitettu vihje, jonka voi helposti kiertää. Voitte luottaa yksinomaan niihin rajoihin, jotka palvelin pakottaa: PHP:ssä, verkkopalvelimessa ja itse sovelluksessa.

Rajat: latausten php.ini-direktiivit

Nämä direktiivit ohjaavat latausten käsittelyä. Oletusarvot ovat PHP-käsikirjasta, ja ne pätevät, jos mikään php.ini ei ylikirjoita niitä.

Direktiivi Oletus Muutettavissa
file_uploads 1 vain järjestelmäkonfiguraatiossa
upload_tmp_dir tyhjä (järjestelmän tilapäishakemisto) vain järjestelmäkonfiguraatiossa
upload_max_filesize 2M php.ini, pooli, hakemistokonfiguraatio
post_max_size 8M php.ini, pooli, hakemistokonfiguraatio
max_file_uploads 20 php.ini, pooli, hakemistokonfiguraatio
enable_post_data_reading 1 php.ini, pooli, hakemistokonfiguraatio

Asetuksen post_max_size on oltava suurempi kuin upload_max_filesize, ja memory_limit kannattaa käsikirjan mukaan yleensä asettaa suuremmaksi kuin post_max_size. Jos upload_tmp_dir ei ole kirjoitettavissa, PHP siirtyy käyttämään järjestelmän tilapäishakemistoa. Kun käytössä on open_basedir, tilapäishakemiston on oltava sallittu, muuten lataukset epäonnistuvat. Käytännön arvoja ja niiden seurauksia käsittelee artikkeli post_max_size ja upload_max_filesize järkevästi rajattuina.

Virhekoodien oikea tulkinta

Kenttä error kertoo, mitä käsittelyssä tapahtui. Vakiot ulottuvat arvosta UPLOAD_ERR_OK (0) arvojen UPLOAD_ERR_INI_SIZE (1), UPLOAD_ERR_FORM_SIZE (2), UPLOAD_ERR_PARTIAL (3) ja UPLOAD_ERR_NO_FILE (4) kautta arvoihin UPLOAD_ERR_NO_TMP_DIR (6), UPLOAD_ERR_CANT_WRITE (7) ja 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);
}

Esimerkki näyttää vain mekaniikan. Tiedostotyypin tarkistusta, tallennusta verkkojuuren ulkopuolelle ja komentosarjojen suorituksen estämistä käsittelee opas Tiedostolataukset PHP:ssä turvallisesti.

Lataukset ja PHP2Shell: mitä väliaikaiset toimenpiteet tarkoittavat

PHP2Shell-haavoittuvuudet CVE-2026-109618 ja CVE-2026-109619 koskevat pyyntöä ympäröivää käsittelyä PHP-tulkissa. Siksi php.net mainitsee väliaikaisina toimenpiteinä muun muassa arvon post_max_size = 8M (ilman latauksia) tai 32M (latausten kanssa), sitä pienemmän upload_max_filesize-arvon sekä asetuksen session.upload_progress.enabled=0. Nämä arvot kuuluvat php.ini-tiedostoon, FPM-poolin tai palvelimen konfiguraatioon, ja sen jälkeen kaikkien worker-prosessien on käynnistyttävä uudelleen.

Asiantuntijat ehdottavat lisäksi, että multipart/form-data-pyynnöt estetään välityspalvelimella tai WAF:ssa muualla kuin aidoilla latauspoluilla. Se on epävirallinen toimenpide, ja riskinä on lomakkeiden rikkoutuminen; punnintaa käsittelee artikkeli Latauspyyntöjen suodatus reunalla. Mikään näistä toimenpiteistä ei korvaa päivitystä versioon PHP 8.5.12, 8.4.27, 8.3.36 tai 8.2.35.

Miten PHP SOS auttaa konkreettisesti?

  • Selvitämme, mitkä verkkosivustoistanne todella tarvitsevat latauksia ja millä poluilla.
  • Asetamme rajat ja väliaikaiset toimenpiteet poolikohtaisesti, käynnistämme kaikki worker-prosessit uudelleen ja testaamme lomakkeet.
  • Tarkistamme latauskansioista suoritettavat tiedostot ja estämme komentosarjojen suorituksen palvelimen koventamisen puitteissa.
  • Asennamme PHP-päivityksen ja dokumentoimme korjaustilanteen sivustoittain.

Usein kysytyt kysymykset

Minne PHP tallentaa ladatut tiedostot ensin?

Tilapäishakemistoon. Oletuksena se on järjestelmän tilapäishakemisto, ja sitä voi muuttaa asetuksella upload_tmp_dir. Jos komentosarja ei siirrä tiedostoa, PHP poistaa sen pyynnön päättyessä.

Voinko luottaa $_FILES-taulukon type-kenttään?

Et voi. MIME-tyypin ilmoittaa selain, eikä PHP käsikirjan mukaan tarkista sitä. Myös name ja full_path tulevat lähettäjältä. Todellinen tiedostotyyppi selvitetään palvelinpuolella tiedoston sisällön perusteella.

Miksi $_FILES on tyhjä, vaikka tiedosto lähetettiin?

Useimmiten koko pyyntö oli suurempi kuin post_max_size. Silloin PHP tyhjentää $_POST- ja $_FILES-taulukon kokonaan, eikä taulukossa ole virhekoodia. Muita syitä ovat lomakkeesta puuttuva enctype tai pois päältä kytketty file_uploads.

Auttaako lataustenkielto sovelluksessa PHP2Shelliä vastaan?

Ei auta. Pyyntötietojen käsittely tapahtuu PHP:ssä ennen kuin sovelluskoodi käynnistyy. Tehokkaita ovat PHP-päivitys ja kaikkien worker-prosessien uudelleenkäynnistys; tiukemmat rajat ovat vain väliaikainen toimenpide.

Mikä on ero upload_max_filesize- ja post_max_size-asetusten välillä?

upload_max_filesize rajoittaa jokaista yksittäistä tiedostoa, post_max_size koko pyynnön sisältöä kaikkine tiedostoineen ja kenttineen. Siksi post_max_size:n on oltava suurempi kuin upload_max_filesize.

Lähteet

Pikayhteys

Jokainen tunti on tärkeä. Kirjoittakaa meille nyt.

Kuvatkaa lyhyesti järjestelmä, PHP-versio ja havainnot. Otamme yhteyttä, selvitämme tilanteen ja työskentelemme etänä, kunnes tapaus on käsitelty loppuun ja dokumentoitu.

Pikaapu