Przejdź do treści

Bezpieczny upload plików na serwer

  • Pliki i obrazy
  • poziom: średni
  • aktualizacja: 12 lipca 2026

Upload plików to najczęstsze miejsce włamań na strony PHP — wystarczy, że ktoś wgra plik .php udający obrazek, a przejmie cały serwer. Ten skrypt zamyka typowe furtki: sprawdza rozszerzenie i rzeczywisty typ pliku, ogranicza rozmiar, nadaje losową nazwę i pokazuje, jak wyłączyć wykonywanie PHP w katalogu z plikami.

PHP 114 linii Pobierz .php
<?php
/**
 * Bezpieczny upload plików — skryptyPHP.com
 * Zapisz jako: upload.php
 *
 * WAŻNE: w katalogu docelowym (tu: pliki/) umieść plik .htaccess z linią:
 *     php_flag engine off
 * lub (Apache 2.4+, gdy powyższe nie działa):
 *     RemoveHandler .php .phtml .php3 .php4 .php5 .php7 .phps
 * To wyłącza wykonywanie PHP nawet gdyby coś prześlizgnęło się przez kontrole.
 */

// ── USTAWIENIA ──────────────────────────────────────────────
$katalog_docelowy = __DIR__ . '/pliki';
$max_rozmiar      = 5 * 1024 * 1024; // 5 MB

// Biała lista: rozszerzenie => dozwolone typy MIME
$dozwolone = [
    'jpg'  => ['image/jpeg'],
    'jpeg' => ['image/jpeg'],
    'png'  => ['image/png'],
    'gif'  => ['image/gif'],
    'webp' => ['image/webp'],
    'pdf'  => ['application/pdf'],
    'zip'  => ['application/zip', 'application/x-zip-compressed'],
];

$komunikat = '';
$blad      = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $plik = $_FILES['plik'] ?? null;

    // 1. Czy w ogóle coś przyszło i bez błędu transferu?
    if (!$plik || $plik['error'] !== UPLOAD_ERR_OK) {
        $kody = [
            UPLOAD_ERR_INI_SIZE   => 'Plik przekracza limit serwera (upload_max_filesize).',
            UPLOAD_ERR_FORM_SIZE  => 'Plik przekracza limit formularza.',
            UPLOAD_ERR_PARTIAL    => 'Plik wgrał się tylko częściowo — spróbuj ponownie.',
            UPLOAD_ERR_NO_FILE    => 'Nie wybrano żadnego pliku.',
        ];
        $blad = $kody[$plik['error'] ?? -1] ?? 'Wystąpił błąd podczas wgrywania.';

    // 2. Rozmiar
    } elseif ($plik['size'] > $max_rozmiar) {
        $blad = 'Plik jest za duży (maksymalnie 5 MB).';

    } else {
        // 3. Rozszerzenie z białej listy
        $rozszerzenie = strtolower(pathinfo($plik['name'], PATHINFO_EXTENSION));

        if (!isset($dozwolone[$rozszerzenie])) {
            $blad = 'Niedozwolony typ pliku. Dozwolone: '
                  . implode(', ', array_keys($dozwolone)) . '.';
        } else {
            // 4. PRAWDZIWY typ pliku — czytamy go z zawartości, nie z nazwy
            $finfo = new finfo(FILEINFO_MIME_TYPE);
            $mime  = $finfo->file($plik['tmp_name']);

            if (!in_array($mime, $dozwolone[$rozszerzenie], true)) {
                $blad = 'Zawartość pliku nie zgadza się z jego rozszerzeniem.';

            // 5. Czy to na pewno plik z formularza?
            } elseif (!is_uploaded_file($plik['tmp_name'])) {
                $blad = 'Nieprawidłowe źródło pliku.';

            } else {
                // 6. Losowa nazwa — nadpisań i kolizji nie będzie,
                //    a oryginalna nazwa nie wstrzyknie nam nic złego
                if (!is_dir($katalog_docelowy)) {
                    mkdir($katalog_docelowy, 0755, true);
                }
                $nowa_nazwa = bin2hex(random_bytes(16)) . '.' . $rozszerzenie;
                $cel        = $katalog_docelowy . '/' . $nowa_nazwa;

                if (move_uploaded_file($plik['tmp_name'], $cel)) {
                    $komunikat = 'Plik wgrany jako: ' . $nowa_nazwa;
                } else {
                    $blad = 'Nie udało się zapisać pliku na serwerze.';
                }
            }
        }
    }
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Wgrywanie plików</title>
<style>
    body { font-family: sans-serif; max-width: 480px; margin: 20px auto; }
    .sukces { color: #1b7a2f; } .blad { color: #b00020; }
</style>
</head>
<body>

<h1>Wgraj plik</h1>

<?php if ($komunikat): ?><p class="sukces"><?= htmlspecialchars($komunikat) ?></p><?php endif; ?>
<?php if ($blad): ?><p class="blad"><?= htmlspecialchars($blad) ?></p><?php endif; ?>

<form method="post" action="" enctype="multipart/form-data">
    <p><input type="file" name="plik" required
              accept=".jpg,.jpeg,.png,.gif,.webp,.pdf,.zip"></p>
    <p><button type="submit">Wgraj na serwer</button></p>
    <p><small>Maksymalny rozmiar: 5 MB.
        Dozwolone typy: JPG, PNG, GIF, WEBP, PDF, ZIP.</small></p>
</form>

</body>
</html>

Jak użyć tego skryptu

  1. Zapisz kod jako upload.php; katalog pliki/ utworzy się automatycznie przy pierwszym wgraniu.
  2. Koniecznie umieść w katalogu pliki/ plik .htaccess z treścią z komentarza na górze skryptu — wyłącza on wykonywanie PHP w tym katalogu.
  3. Dostosuj białą listę $dozwolone i limit $max_rozmiar do swoich potrzeb.
  4. Jeśli duże pliki się nie wgrywają, zwiększ w konfiguracji PHP (lub w panelu hostingu) wartości upload_max_filesize i post_max_size.

Warto wiedzieć

  • Sprawdzamy i rozszerzenie, i rzeczywisty typ MIME odczytany z zawartości pliku (finfo) — samo rozszerzenie można sfałszować zmianą nazwy.
  • Losowa nazwa z random_bytes() eliminuje nadpisywanie cudzych plików i ataki przez spreparowaną nazwę (np. zdjecie.php.jpg czy znaki ../).
  • Atrybut accept w formularzu to tylko podpowiedź dla przeglądarki — prawdziwa kontrola zawsze odbywa się w PHP.
  • Jeżeli chcesz przechowywać oryginalną nazwę (np. do wyświetlania), zapisz ją w bazie danych, a przy wyświetlaniu użyj htmlspecialchars().
#upload #pliki #finfo #mime #bezpieczeństwo

Podobne skrypty