Röviden: A PHP-feltöltés biztonságánál nem egyetlen ellenőrzés dönt, hanem a kombináció: a fájltípus szerveroldali ellenőrzése, a fájlok átnevezése, tárolásuk a webgyökéren kívül, és a szkriptek végrehajtásának tiltása a feltöltési mappában. Egy ellenőrizetlen feltöltési űrlap az egyik klasszikus út, amelyen a támadók hátsó ajtót juttatnak egy webszerverre.
Miért kedvelt belépési pont a feltöltési űrlap
Minden fájlokat fogadó űrlap idegen adatokat ír a szerverére. Ide tartoznak a jelentkezési űrlapok, az ügyfélportálok, a képgalériák, a támogatási területek és a CMS-ek és webshopok sok bővítménye. Ha egy támadónak sikerül ott olyan fájlt elhelyeznie, amelyet a webszerver ezután PHP-ként hajt végre, tartós hátsó ajtója van.
A vezetőség számára ez azt jelenti: egy ártalmatlannak tűnő önéletrajz-feltöltés a legrosszabb esetben a teljes webszerver átvételéhez vezethet. Emellett a feltöltési mappákban gyakran személyes adatok vannak, amelyek hibás konfiguráció esetén nyilvánosan lekérhetők.
Az adminok és fejlesztők számára ez azt jelenti: az alkalmazáskódnak és a szerverkonfigurációnak egymáshoz kell illeniük. Az egyik szint gyengeségét gyakran a másik fogja fel, ha mindkettő szabályosan van megvalósítva. Hogyan találhatja meg a már bejuttatott fájlokat, azt a Webshellek felismerése és eltávolítása útmutató írja le.
A PHP-feltöltés biztonságának alapszabályai
A következő szabályok megfelelnek az OWASP Cheat Sheetek ajánlásainak, és az üzemben bevált:
- Engedélylista tiltólista helyett: Csak kifejezetten engedélyezett fájltípusokat fogadjon el, például PDF-et, JPG-t és PNG-t. Az egyes kiterjesztések tiltólistái változatokkal és dupla kiterjesztésekkel megkerülhetők.
- A tartalmat ellenőrizze, ne csak a kiterjesztést: A tényleges típust szerveroldalon állapítsa meg a
finfo-val. A$_FILES-ban lévő MIME-típus a feladó adata, és nem megbízható. - Új fájlnév: Az eredeti nevet soha ne használja fájlnévként. Hozzon létre véletlenszerű nevet, és fűzze hozzá az engedélylistából származó kiterjesztést.
- Tárolás a webgyökéren kívül: A fájlokat úgy mentse, hogy URL-en keresztül közvetlenül ne legyenek elérhetők. A kiszolgálás jogosultság-ellenőrzéssel ellátott szkripten keresztül történik.
- A méret korlátozása: Az alkalmazásban, a PHP-ban és a webszerveren.
- Nincs végrehajtás: Ha a feltöltéseknek mégis a webgyökérben kell lenniük, kapcsolja ki a szkriptek végrehajtását ebben a könyvtárban.
- Ellenőrzés csak a szerveren: A böngészőben végzett JavaScript-ellenőrzések javítják a kezelhetőséget, de nem védenek.
Egy biztonságos példa finfo-val és véletlenszerű névvel
A következő példa a biztonságos feldolgozás magját mutatja. Szándékosan rövid, és be kell ágyazni az alkalmazásába, vagyis bejelentkezéssel, jogosultság-ellenőrzéssel, CSRF-védelemmel és naplózással együtt.
<?php
declare(strict_types=1);
const MAX_BYTES = 5 * 1024 * 1024; // 5 MB
const ERLAUBT = [
'application/pdf' => 'pdf',
'image/jpeg' => 'jpg',
'image/png' => 'png',
];
const ZIELORDNER = '/var/www/daten/uploads'; // a webgyökéren kívül
$datei = $_FILES['dokument'] ?? null;
if (!is_array($datei) || $datei['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
exit('Upload fehlgeschlagen.');
}
if ($datei['size'] > MAX_BYTES || !is_uploaded_file($datei['tmp_name'])) {
http_response_code(400);
exit('Datei ungültig.');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$typ = $finfo->file($datei['tmp_name']);
if (!isset(ERLAUBT[$typ])) {
http_response_code(415);
exit('Dateityp nicht erlaubt.');
}
$name = bin2hex(random_bytes(16)) . '.' . ERLAUBT[$typ];
$ziel = ZIELORDNER . '/' . $name;
if (!move_uploaded_file($datei['tmp_name'], $ziel)) {
http_response_code(500);
exit('Speichern fehlgeschlagen.');
}
chmod($ziel, 0640);
// Az eredeti nevet csak szövegként tárolja az adatbázisban,
// és minden kiadásnál escape-elje a htmlspecialchars()-szal.
Képeknél érdemes egy további lépés: a képet GD-vel vagy Imagickkel újra kell kódolni, és közben a metaadatokat el kell dobni. Ez nem pótolja a többi ellenőrzést, de csökkenti a beágyazott idegen adatokból eredő maradék kockázatot.
Ismeretlen forrásból származó dokumentumoknál, például jelentkezéseknél vagy reklamációs fotóknál ezen felül célszerű egy vírusellenőrzés a továbbadás előtt. Ez kevésbé a webszervert védi, inkább a munkatársakat, akik a fájlokat később a munkaállomásukon megnyitják. Naplózzon emellett minden feltöltést idővel, fiókkal, fájlmérettel és felismert típussal, hogy a szokatlan minták később visszakövethetők legyenek.
A CMS-eknél és webshopoknál a beépített médiakönyvtárak többnyire saját ellenőrzéseket hoznak magukkal. Kockázatosabbak az űrlap-bővítmények, az importfunkciók és az önállóan programozott kiegészítő oldalak, amelyek a könyvtárat megkerülve mentik a fájlokat. Egy ellenőrzésnél éppen ezeket a helyeket nézze meg elsőként.
A fájlokat a védett mappából Content-Disposition: attachment és X-Content-Type-Options: nosniff fejléccel szolgálja ki. Ekkor a böngésző a problémás tartalmakat nem jeleníti meg közvetlenül, hanem letöltésként kínálja fel.
A PHP-végrehajtás kikapcsolása a feltöltési mappában
Még ha egy hibás tartalmú fájl át is csúszik, a webszervernek nem szabad szkriptként végrehajtania. Ez a második védelmi réteg minden olyan szerveren kell, amely feltöltéseket a webgyökérben tárol.
Nginx PHP-FPM-mel:
location ^~ /uploads/ {
location ~* \.(php|phtml|phar)$ {
deny all;
}
}
Apache esetén, a legjobb közvetlenül a VirtualHostban, nem egy .htaccess-ben, hogy a szabályt az alkalmazás ne tudja felülírni:
<Directory "/var/www/html/uploads">
AllowOverride None
<FilesMatch "\.(php|phtml|phar)$">
Require all denied
</FilesMatch>
</Directory>
Ha a PHP Apache-modulként (mod_php) fut, ugyanabban a blokkban emellett beállíthatja a php_admin_flag engine off értéket. A módosítás után egy ártalmatlan teszt fájllal, amely csak egy szöveget ad ki, ellenőrizze, hogy a szerver 403-mal válaszol a rá irányuló kérésre.
Méretkorlátok és a kapcsolat a PHP2Shellel
A feltöltések szorosan összefüggnek a post_max_size és az upload_max_filesize PHP-beállításokkal. A PHP2Shell kapcsán a php.net átmeneti intézkedésként azt ajánlja, hogy a post_max_size értékét állítsa 8M-ra, ha nincs szükség feltöltésekre, vagy 32M-ra feltöltésekkel, és az upload_max_filesize ennél kisebb maradjon. Ez nem teljes védelem, és nem pótolja a javított PHP-verzióra való frissítést.
; php.ini vagy FPM-pool (php_admin_value), utána indítsa újra az összes workert
file_uploads = On
post_max_size = 32M
upload_max_filesize = 20M
max_file_uploads = 5
Ha egy weboldalnak egyáltalán nincs szüksége feltöltésekre, a file_uploads = Off a legtisztább megoldás. Hogy a javításig mely beállítások célszerűek, azt az Azonnali intézkedések javítás nélkül oldalon találja. A feltöltési könyvtárak megfelelő jogait a Fájljogosultságok a webszerveren írja le.
Tipikus hibák a feltöltési funkciókban
A meglévő alkalmazások átnézésekor újra és újra ugyanazok a minták bukkannak fel. Ellenőrizze az űrlapjait célzottan ezekre a pontokra:
- A kiterjesztést csak részben ellenőrzik, így a több kiterjesztéssel rendelkező fájlnevek átcsúsznak.
- Az űrlapból származó eredeti név a tárolási útvonalba kerül. Ez utat nyit az útvonal-manipulációnak és a meglévő fájlok felülírásának.
- A feltöltések nyilvánosan, kiszámítható nevekkel vannak. Jelentkezéseknél vagy szerződéseknél ez adatvédelmi probléma is.
- A feltöltési mappa a webszerver számára írható és egyben futtatható.
- A régi feltöltési bővítményeket vagy űrlap-plugineket nem frissítik, noha a gyártó biztonsági frissítéseket adott ki.
- A hibákat nem naplózzák. Az elutasított feltöltések azonban korai jelzések a célzott kísérletekről.
Hogyan segít konkrétan a PHP SOS?
- Ellenőrizzük a feltöltési funkcióit a kódban és a szerverkonfigurációban, és prioritással megnevezzük a konkrét gyengeségeket.
- A szerver-keményítés keretében beállítjuk a végrehajtási tiltást a feltöltési mappában, a megfelelő jogokat és a méretkorlátokat.
- Gyanú esetén átvizsgáljuk a feltöltési könyvtárakat bejuttatott szkriptek után, és megtisztítjuk őket a kártevő-eltávolítással.
- Kívánságra a megvalósítást egy penetrációs teszttel függetlenül ellenőriztetjük.
Gyakori kérdések
Elég a fájl kiterjesztését ellenőrizni feltöltéskor?
Nem. A kiterjesztés és a böngésző által jelentett MIME-típus a feladótól származik, és szabadon megválasztható. Ellenőrizze a tartalmat szerveroldalon, adjon új fájlnevet, és tiltsa le a szkriptek végrehajtását a feltöltési könyvtárban.
Hol érdemes tárolni a feltöltött fájlokat?
A legjobb a nyilvános webkönyvtáron kívül. A kiszolgálást ekkor egy szkript végzi, amely előtte ellenőrzi, hogy a felhasználó láthatja-e a fájlt. Így a fájlok sem közvetlenül nem hívhatók meg, sem nem futtathatók.
Ártalmatlanok az SVG-fájlok képként?
Nem automatikusan. Az SVG XML, és aktív tartalmat tartalmazhat. Csak akkor engedélyezze az SVG-t, ha megtisztítja a fájlokat, vagy kizárólag letöltésként, megfelelő Content-Disposition fejléccel szolgálja ki őket.
Van köze a feltöltési méretnek a PHP2Shellhez?
Igen, közvetve. A php.net átmeneti intézkedésként alacsonyabb értékeket ajánl a post_max_size és az upload_max_filesize számára. Ez csökkenti a kockázatot, de nem pótolja a javított PHP-verzióra való frissítést és az összes worker újraindítását.