Przejdź do głównej zawartości

Upload plikow w PHP: ryzyka i dobre praktyki

Co przekazesz słuchaczom?

Słuchacze zrozumieja, jak działa upload plikow w PHP, jakie zagrożenia się z tym wiaza oraz jak implementować bezpieczny mechanizm przesyłania plikow. Poznaja techniki walidacji, generowania bezpiecznych nazw i prawidłowego przechowywania plikow.

  1. Wprowadzenie (1 min) - Gdzie spotykamy upload plikow w aplikacjach
  2. Teoria: Jak działa upload (2 min) - Formularz, multipart/form-data, $_FILES
  3. Teoria: Zagrożenia (3 min) - Wykonanie kodu, path traversal, DoS
  4. Demo praktyczne (4 min) - Implementacja bezpiecznego uploadu
  5. Podsumowanie (2 min) - Checklist bezpieczeństwa

Formularz musi mieć odpowiednie atrybuty:

<form action="upload.php" method="POST" enctype="multipart/form-data">
<label for="file">Wybierz plik:</label>
<input type="file" name="userfile" id="file" accept=".jpg,.png,.pdf">
<button type="submit">Wyslij</button>
</form>

Kluczowe elementy:

  • method="POST" - pliki wysyłane w body
  • enctype="multipart/form-data" - kodowanie dla plikow
  • accept - sugestia typow (ale nie walidacja!)

Wykonanie kodu

Atakujacy wysyła plik .php lub .php.jpg który może zostac wykonany przez serwer.

Skutki: Pełna kontrola nad serwerem, kradzież danych, backdoor.

Path Traversal

Nazwa pliku zawiera ../ pozwalajac zapisać plik w dowolnym miejscu.

Przykład: ../../config.php - nadpisanie konfiguracji.

Denial of Service

Wysyłanie bardzo dużych plikow lub wielu plikow naraz przeciaza serwer.

Skutki: Brak miejsca na dysku, przeciazenie serwera.

Malware/Wirusy

Użytkownik wysyła zainfekowany plik, który może być pobrany przez innych.

Skutki: Rozprzestrzenianie malware przez Twoja aplikacje.

Whitelist dozwolonych rozszerzeń:

<?php
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
$filename = $_FILES['userfile']['name'];
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($extension, $allowedExtensions)) {
die('Niedozwolone rozszerzenie pliku!');
}

Uwaga: Same rozszerzenie to za mało! Można wysłac plik evil.php jako evil.jpg.

Diagram pokazujacy:

  1. Użytkownik wybiera plik w formularzu
  2. Przegladarka koduje plik (multipart/form-data)
  3. Serwer otrzymuje plik w katalogu tymczasowym
  4. PHP przenosi plik do docelowej lokalizacji

Tabela pokazujaca wszystkie pola tablicy $_FILES z przykładowymi wartosciami i ostrzezeniami (np. ‘type’ - niezaufane!).

Trzy scenariusze ataku z ikona zagrożenia:

  • Plik .php uploadowany jako obrazek
  • Nazwa ../../../etc/passwd
  • Plik 10 GB zapełniający dysk

Lista kontrolna z checkboxami:

  • Walidacja rozszerzenia (whitelist)
  • Walidacja MIME (finfo)
  • Ograniczenie rozmiaru
  • Generowanie bezpiecznej nazwy
  • Przechowywanie poza webroot
  • Odpowiednie uprawnienia katalogu
  1. Pokaz prosty formularz bez walidacji
  2. Wyslij plik z podwojnym rozszerzeniem (np. evil.php.jpg)
  3. Pokaz, że plik został zapisany
  4. Omow, dlaczego to jest niebezpieczne
<?php
// PRZYKŁAD NIEBEZPIECZNEGO KODU - NIE UZYWAC!
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$target = 'uploads/' . $_FILES['file']['name'];
move_uploaded_file($_FILES['file']['tmp_name'], $target);
echo "Zapisano: " . htmlspecialchars($target);
}
?>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="file">
<button>Upload</button>
</form>
  1. Pokaz poprawiona wersje z walidacja
  2. Wyslij złośliwy plik - pokaz odrzucenie
  3. Wyslij prawidłowy plik - pokaz sukces
  4. Pokaz wygenerowana bezpieczna nazwe
<?php
class SecureUploader {
private array $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif'];
private array $allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
private int $maxSize = 2 * 1024 * 1024; // 2 MB
private string $uploadDir = '/var/www/uploads/';
public function upload(array $file): string {
// 1. Sprawdz błędy uploadu
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new Exception('Błąd uploadu: ' . $file['error']);
}
// 2. Walidacja rozmiaru
if ($file['size'] > $this->maxSize) {
throw new Exception('Plik za duży');
}
// 3. Walidacja rozszerzenia
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowedExtensions)) {
throw new Exception('Niedozwolone rozszerzenie');
}
// 4. Walidacja MIME (finfo)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);
if (!in_array($mimeType, $this->allowedMimes)) {
throw new Exception('Niedozwolony typ pliku');
}
// 5. Generowanie bezpiecznej nazwy
$safeName = bin2hex(random_bytes(16)) . '.' . $extension;
$targetPath = $this->uploadDir . $safeName;
// 6. Przeniesienie pliku
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
throw new Exception('Nie udało się zapisać pliku');
}
return $safeName;
}
}
// Użycie:
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$uploader = new SecureUploader();
try {
$filename = $uploader->upload($_FILES['userfile']);
echo "Zapisano jako: " . htmlspecialchars($filename);
} catch (Exception $e) {
echo "Błąd: " . htmlspecialchars($e->getMessage());
}
}

Wymagania minimalne:

  • Wyjasnij jak działa upload (formularz, $_FILES)
  • Pokaz 1 schemat przepływu uploadu
  • Pokaz minimum 2 walidacje (rozmiar + rozszerzenie)
Ocena: 3.0 (minimum)

Pytanie 1

Dlaczego nie można polegac na wartości $_FILES['type'] do walidacji typu pliku?

Pytanie 2

Jaka jest różnica miedzy przechowywaniem plikow wewnatrz i poza katalogiem webroot?

Pytanie 3

Jak zapobiec sytuacji, gdy dwoch użytkowników wysyła plik o tej samej nazwie?

Pytanie 4

Jakie dodatkowe zabezpieczenia można zastosowac przy uploadzie obrazkow?