Aktualna sytuacja: PHP2Shell. Dwie krytyczne luki w PHP (CVSS 9,2) umożliwiają wykonanie złośliwego kodu bez logowania. Poprawki od 21.10.2026.Co należy teraz zrobić →

Bezpieczne przesyłanie plików w PHP: bezpieczeństwo uploadu

Formularze uploadu to częsta droga dla webshelli. Jak prawidłowo sprawdzać typy plików, bezpiecznie je zapisywać i zapobiegać wykonywaniu przesłanych skryptów przez serwer.

Stan 5 min czytaniaPoradniki

Krótko: W bezpieczeństwie uploadu w PHP nie decyduje pojedyncza kontrola, lecz połączenie: sprawdzić typ pliku po stronie serwera, zmienić nazwy plików, zapisać je poza webrootem i nie dopuszczać wykonywania skryptów w folderze uploadu. Niesprawdzony formularz uploadu to jedna z klasycznych dróg, którymi atakujący wprowadzają tylną furtkę na serwer WWW.

Dlaczego formularze uploadu są ulubionym punktem wejścia

Każdy formularz przyjmujący pliki zapisuje obce dane na Państwa serwerze. Należą do nich formularze aplikacyjne, portale klientów, galerie obrazów, obszary wsparcia i wiele rozszerzeń CMS oraz sklepów. Jeśli atakującemu uda się tam umieścić plik, który serwer WWW następnie wykona jako PHP, zyskuje on trwałą tylną furtkę.

Dla zarządu oznacza to: pozornie niewinny upload życiorysów może w najgorszym razie doprowadzić do przejęcia całego serwera WWW. Ponadto w folderach uploadu często leżą dane osobowe, które przy błędnej konfiguracji są publicznie dostępne.

Dla administratorów i programistów oznacza to: kod aplikacji i konfiguracja serwera muszą do siebie pasować. Słabość na jednym poziomie często jest przechwytywana przez drugi, jeśli oba są czysto wdrożone. Jak znaleźć już wprowadzone pliki, opisuje poradnik Jak wykryć i usunąć webshell.

Zasady podstawowe bezpieczeństwa uploadu w PHP

Poniższe zasady odpowiadają zaleceniom OWASP Cheat Sheets i sprawdziły się w eksploatacji:

  • Lista dozwolonych zamiast listy zakazanych: przyjmować tylko wyraźnie dozwolone typy plików, na przykład PDF, JPG i PNG. Listy zakazanych pojedynczych rozszerzeń można obejść przez warianty i podwójne rozszerzenia.
  • Sprawdzać zawartość, nie tylko rozszerzenie: rzeczywisty typ ustalać po stronie serwera przez finfo. Typ MIME w $_FILES jest informacją nadawcy i nie zasługuje na zaufanie.
  • Nowa nazwa pliku: nigdy nie używać nazwy oryginalnej jako nazwy pliku. Wygenerować losową nazwę i dodać rozszerzenie z listy dozwolonych.
  • Zapis poza webrootem: zapisywać pliki tak, aby nie były bezpośrednio osiągalne przez adres URL. Udostępnianie przez skrypt ze sprawdzeniem uprawnień.
  • Ograniczyć rozmiar: w aplikacji, w PHP i na serwerze WWW.
  • Brak wykonywania: jeśli uploady muszą jednak leżeć w webrootcie, wyłączyć wykonywanie skryptów w tym katalogu.
  • Kontrola tylko na serwerze: kontrole w JavaScript w przeglądarce poprawiają obsługę, ale nie chronią.

Bezpieczny przykład z finfo i losową nazwą

Poniższy przykład pokazuje rdzeń bezpiecznego przetwarzania. Jest celowo zwięzły i trzeba go osadzić w Państwa aplikacji, czyli z logowaniem, kontrolą uprawnień, ochroną CSRF i protokołowaniem.

<?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'; // poza webrootem

$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);
// Oryginalną nazwę zapisywać tylko jako tekst w bazie danych
// i przy każdym wyświetlaniu escapować przez htmlspecialchars().

W przypadku obrazów warto wykonać jeszcze jeden krok: zakodować obraz od nowa za pomocą GD lub Imagick, odrzucając przy tym metadane. Nie zastępuje to pozostałych kontroli, ale obniża ryzyko szczątkowe wynikające z osadzonych obcych danych.

Dla dokumentów z nieznanych źródeł, na przykład aplikacji lub zdjęć reklamacyjnych, sensowny jest dodatkowo skan antywirusowy przed przekazaniem dalej. Chroni on mniej serwer WWW, a bardziej pracowników, którzy później otwierają te pliki na swoich komputerach. Proszę ponadto protokołować każdy upload z czasem, kontem, rozmiarem pliku i rozpoznanym typem, aby nietypowe wzorce dało się później prześledzić.

W CMS i sklepach wbudowane biblioteki mediów mają zwykle własne kontrole. Ryzykowniejsze są rozszerzenia formularzy, funkcje importu i samodzielnie programowane strony dodatkowe, które zapisują pliki z pominięciem biblioteki. Właśnie te miejsca powinni Państwo przy kontroli sprawdzić w pierwszej kolejności.

Wskazówka

Pliki z chronionego folderu proszę udostępniać z Content-Disposition: attachment i X-Content-Type-Options: nosniff. Wtedy przeglądarka nie wyświetla problematycznych treści bezpośrednio, lecz oferuje je do pobrania.

Wyłączenie wykonywania PHP w folderze uploadu

Nawet jeśli plik o błędnej zawartości się prześlizgnie, serwer WWW nie może wykonać go jako skryptu. Ta druga warstwa ochrony należy na każdy serwer, który zapisuje uploady w webrootcie.

Dla Nginx z PHP-FPM:

location ^~ /uploads/ {
    location ~* \.(php|phtml|phar)$ {
        deny all;
    }
}

Dla Apache, najlepiej bezpośrednio w VirtualHost zamiast w .htaccess, aby reguła nie mogła zostać nadpisana przez aplikację:

<Directory "/var/www/html/uploads">
    AllowOverride None
    <FilesMatch "\.(php|phtml|phar)$">
        Require all denied
    </FilesMatch>
</Directory>

Jeśli PHP działa jako moduł Apache (mod_php), mogą Państwo w tym samym bloku dodatkowo ustawić php_admin_flag engine off. Po zmianie proszę sprawdzić za pomocą niewinnego pliku testowego, który tylko wypisuje tekst, że serwer odpowiada na żądanie do niego kodem 403.

Limity rozmiaru i związek z PHP2Shell

Uploady są ściśle związane z ustawieniami PHP post_max_size i upload_max_filesize. W związku z PHP2Shell php.net zaleca jako środek przejściowy ustawić post_max_size na 8M, jeśli uploady nie są potrzebne, lub na 32M z uploadami, a upload_max_filesize utrzymywać poniżej. Nie jest to pełna ochrona i nie zastępuje aktualizacji do naprawionej wersji PHP.

; php.ini lub pula FPM (php_admin_value), potem zrestartować wszystkie procesy robocze
file_uploads        = On
post_max_size       = 32M
upload_max_filesize = 20M
max_file_uploads    = 5

Jeśli strona w ogóle nie potrzebuje uploadów, najczystszym rozwiązaniem jest file_uploads = Off. Które ustawienia mają sens do czasu załatania, opisuje strona Środki natychmiastowe bez poprawki. Odpowiednie uprawnienia dla katalogów uploadu opisuje Uprawnienia do plików na serwerze WWW.

Typowe błędy w funkcjach uploadu

Podczas przeglądu istniejących aplikacji wciąż pojawiają się te same wzorce. Proszę celowo sprawdzić swoje formularze pod kątem tych punktów:

  • Rozszerzenie jest sprawdzane tylko częściowo, przez co przechodzą nazwy plików z kilkoma rozszerzeniami.
  • Oryginalna nazwa z formularza trafia do ścieżki zapisu. Otwiera to drogę do manipulacji ścieżką i nadpisywania istniejących plików.
  • Uploady leżą publicznie z przewidywalnymi nazwami. W przypadku aplikacji lub umów jest to także problem ochrony danych.
  • Folder uploadu jest zapisywalny dla serwera WWW i jednocześnie wykonywalny.
  • Stare rozszerzenia uploadu lub wtyczki formularzy nie są aktualizowane, mimo że producent opublikował aktualizacje bezpieczeństwa.
  • Błędy nie są protokołowane. Odrzucone uploady są jednak wczesnym sygnałem celowych prób.

Jak konkretnie pomaga PHP SOS?

  • Sprawdzamy Państwa funkcje uploadu w kodzie i w konfiguracji serwera oraz wskazujemy konkretne słabości z priorytetem.
  • Ustawiamy blokadę wykonywania w folderze uploadu, odpowiednie uprawnienia i limity rozmiaru w ramach utwardzania serwera.
  • W razie podejrzenia przeszukujemy katalogi uploadu pod kątem wprowadzonych skryptów i oczyszczamy je w ramach naszego usuwania malware.
  • Na życzenie zlecamy niezależną weryfikację wdrożenia przez test penetracyjny.

Częste pytania

Czy wystarczy sprawdzać rozszerzenie pliku przy uploadzie?

Nie. Rozszerzenie i typ MIME zgłoszony przez przeglądarkę pochodzą od nadawcy i można je dowolnie wybierać. Proszę sprawdzać zawartość po stronie serwera, nadawać nową nazwę pliku i zablokować wykonywanie skryptów w katalogu uploadu.

Gdzie należy zapisywać przesłane pliki?

Najlepiej poza publicznym katalogiem WWW. Udostępnianie przejmuje wtedy skrypt, który wcześniej sprawdza, czy użytkownik może zobaczyć plik. Pliki nie są wówczas ani bezpośrednio dostępne, ani wykonywalne.

Czy pliki SVG są jako obrazy bezpieczne?

Nie automatycznie. SVG to XML i może zawierać aktywną zawartość. Proszę dopuszczać SVG tylko wtedy, gdy pliki są oczyszczane lub udostępniane wyłącznie jako pobranie z odpowiednim nagłówkiem Content-Disposition.

Czy rozmiar uploadu ma coś wspólnego z PHP2Shell?

Tak, pośrednio. php.net zaleca jako środek przejściowy niższe wartości post_max_size i upload_max_filesize. To obniża ryzyko, ale nie zastępuje aktualizacji do naprawionej wersji PHP i restartu wszystkich procesów roboczych.

Źródła

Kontakt natychmiast

Liczy się każda godzina. Proszę napisać do nas teraz.

Proszę krótko opisać system, wersję PHP i obserwacje. Skontaktujemy się z Państwem, ustalimy sytuację i będziemy pracować zdalnie, aż incydent zostanie zamknięty i udokumentowany.

Natychmiastowa pomoc