# Bezpieczne przesyłanie plików w PHP: bezpieczeństwo uploadu

> Bezpieczeństwo uploadu w PHP w praktyce: sprawdzanie typu pliku po stronie serwera, zmiana nazw, zapis poza webrootem i blokada wykonywania PHP.

Źródło: https://phpsos.de/pl/ratgeber/datei-upload-sicher/  
Stan: 25.10.2026  
Dostawca: PHP SOS, Pomoc awaryjna PHP i bezpieczeństwo serwerów. Natychmiastowa pomoc przez WhatsApp: https://wa.me/491736494914?text=Dzie%C5%84%20dobry%2C%20PHP%20SOS%2C%20potrzebujemy%20pomocy%20z%20naszym%20serwerem%20PHP.%20Firma%3A%20%E2%80%A6%20Strona%20internetowa%3A%20%E2%80%A6

**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](https://phpsos.de/pl/ratgeber/webshell-erkennen/).

## 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
<?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:

```nginx
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ę:

```apache
<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.

```ini
; 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](https://phpsos.de/pl/php2shell/sofortmassnahmen-ohne-patch/). Odpowiednie uprawnienia dla katalogów uploadu opisuje [Uprawnienia do plików na serwerze WWW](https://phpsos.de/pl/ratgeber/dateirechte-webserver/).

## 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](https://phpsos.de/pl/leistungen/server-haertung/).
- W razie podejrzenia przeszukujemy katalogi uploadu pod kątem wprowadzonych skryptów i oczyszczamy je w ramach naszego [usuwania malware](https://phpsos.de/pl/leistungen/malware-entfernung/).
- Na życzenie zlecamy niezależną weryfikację wdrożenia przez [test penetracyjny](https://phpsos.de/pl/leistungen/sicherheitsaudit-penetrationstest/).


## 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

- [OWASP Cheat Sheet Series (File Upload)](https://cheatsheetseries.owasp.org/)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [php.net: komunikaty o bezpieczeństwie dotyczące PHP2Shell](https://www.php.net/)
