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
/**
* 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
- Zapisz kod jako
upload.php; katalogpliki/utworzy się automatycznie przy pierwszym wgraniu. - Koniecznie umieść w katalogu
pliki/plik.htaccessz treścią z komentarza na górze skryptu — wyłącza on wykonywanie PHP w tym katalogu. - Dostosuj białą listę
$dozwolonei limit$max_rozmiardo swoich potrzeb. - Jeśli duże pliki się nie wgrywają, zwiększ w konfiguracji PHP (lub w panelu hostingu) wartości
upload_max_filesizeipost_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.jpgczy znaki../). - Atrybut
acceptw 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().
Podobne skrypty
Tworzenie miniatur obrazków (GD)
Funkcja generująca miniatury JPEG, PNG i WebP z zachowaniem proporcji i przezroczystości, plus przykład przetworzenia całego katalogu zdjęć naraz.
średni Zobacz kod → Pliki i obrazyEksport danych do pliku CSV (zgodny z Excelem)
Generowanie pliku CSV do pobrania wprost z tablicy PHP lub zapytania SQL — ze znacznikiem BOM i średnikami, dzięki czemu polski Excel otwiera plik bez krzaczków.
początkujący Zobacz kod → Pliki i obrazyListowanie plików w katalogu (strona pobierania)
Czytelna lista plików z wybranego katalogu: rozmiar w KB/MB, data modyfikacji, sortowanie i linki do pobrania. Idealne na prostą stronę „do pobrania".
początkujący Zobacz kod →