6 min

Bezpieczne przesyłanie plików: uprawnienia, limity, podpisane URL-e, skanowanie

Bezpieczne przesyłanie plików w aplikacjach webowych wymaga rygorystycznych uprawnień, limitów rozmiaru, podpisanych URL-i i prostych wzorców skanowania, aby uniknąć incydentów.

Bezpieczne przesyłanie plików: uprawnienia, limity, podpisane URL-e, skanowanie

Dlaczego przesyłanie plików jest ryzykowne (prosto)

Przesyłanie plików wydaje się nieszkodliwe: zdjęcie profilowe, PDF, arkusz. Ale często to pierwszy incydent bezpieczeństwa, bo pozwala obcym włożyć w twój system tajemnicze pudełko. Jeśli je przyjmiesz, zapiszesz i pokażesz innym, stwarzasz nowy wektor ataku.

Ryzyko to nie tylko „ktoś wysłał wirusa”. Zły upload może ujawnić prywatne pliki, wywindować koszty przechowywania lub oszukać użytkowników, by oddali dostęp. Plik nazwany „invoice.pdf” może nie być wcale PDF-em. Nawet prawdziwe PDF-y i obrazy mogą sprawiać problemy, jeśli aplikacja ufa metadanym, generuje podglądy automatycznie lub serwuje je z nieodpowiednimi regułami.

Prawdziwe awarie często wyglądają tak:

  • Ktoś odgadnie URL pliku i pobierze dokument innego użytkownika.
  • Wgrany plik HTML jest serwowany jak strona i pokazuje formularz kradnący loginy.
  • Atakujący wgrywa wielkie pliki wielokrotnie, aż aplikacja zwalnia lub pada.
  • „Bezpieczny” typ pliku jest sfałszowany, potem otwierany przez pracownika na maszynie wewnętrznej.

Jedna rzecz powoduje wiele incydentów: przechowywanie plików to nie to samo co ich serwowanie. Przechowywanie to miejsce, gdzie trzymasz bajty. Serwowanie to sposób, w jaki te bajty są dostarczane do przeglądarek i aplikacji. Problemy pojawiają się, gdy aplikacja serwuje uploady z takim samym poziomem zaufania i zasadami jak główna strona, dlatego przeglądarka traktuje upload jako „zaufany”.

„Wystarczająco bezpieczne” dla małej lub rosnącej aplikacji zwykle oznacza, że możesz bez lania wody odpowiedzieć na cztery pytania: kto może wgrywać, co akceptujesz, jak duże i jak często, oraz kto może to potem czytać. Nawet jeśli budujesz szybko (z generowanym kodem lub platformą sterowaną czatem), te zabezpieczenia wciąż się liczą.

Prosty model zagrożeń dla uploadów

Traktuj każdy upload jak niezaufane wejście. Praktyczny sposób na ochronę to wyobrazić sobie, kto może nadużyć uploadów i co „sukces” dla niego oznacza.

Większość atakujących to boty skanujące słabe formularze uploadu albo prawdziwi użytkownicy testujący granice, by zdobyć darmowe miejsce, zeskrobać dane lub trollować serwis. Czasem to konkurent szukający wycieków lub przyczyn awarii.

Czego chcą? Zazwyczaj jedno z tych rezultatów:

  • Uruchomić kod na twoich serwerach przez upload czegoś, co zostanie wykonane.
  • Ukradzić prywatne pliki przez odgadnięcie, ponowne użycie lub udostępnienie URL-i do pobrania.
  • Zniszczyć dostępność przez zalew uploadów lub wymuszenie kosztownego przetwarzania.
  • Nadmuchać twój rachunek przez wzrost przechowywania lub ciężkie pobrania.

Następnie zmapuj słabe punkty. Endpoint uploadu to drzwi wejściowe (za duże pliki, dziwne formaty, wysoki ruch). Przechowywanie to tylne pomieszczenie (publiczne buckety, złe uprawnienia, współdzielone foldery). URL-e do pobrania to wyjście (przewidywalne, długotrwałe lub niepowiązane z użytkownikiem).

Przykład: funkcja „upload CV”. Bot wgrywa tysiące dużych PDF-ów, żeby nabić koszty, a nadużywający wgrywa HTML i udostępnia go jako „dokument”, by oszukać innych.

Zanim dodasz kontrolki, zdecyduj co jest najważniejsze dla twojej aplikacji: prywatność (kto może czytać), dostępność (czy możesz dalej serwować), koszty (przechowywanie i transfer) i zgodność (gdzie przechowywane są dane i jak długo). Ta lista priorytetów ułatwia spójne decyzje.

Uprawnienia i kontrola dostępu, które naprawdę działają

Większość incydentów z uploadami to nie wyszukane ataki — to proste błędy typu „mogę zobaczyć czyjś plik”. Traktuj uprawnienia jako część uploadu, nie dodatek na później.

Zacznij od jednej zasady: domyślnie odmów. Zakładaj, że każdy przesłany obiekt jest prywatny, dopóki nie pozwolisz inaczej. „Domyślnie prywatne” to mocna baza dla faktur, dokumentów medycznych, dokumentów konta i wszystkiego związanego z użytkownikiem. Udostępniaj pliki publicznie tylko wtedy, gdy użytkownik wyraźnie tego oczekuje (np. publiczny avatar), a i tak rozważ dostęp czasowy.

Role dopasowane do realnej pracy

Utrzymuj role proste i rozdzielne. Typowy podział:

  • Uploader: może tworzyć uploady dla swojego konta
  • Viewer: może pobierać pliki, do których ma dostęp
  • Support: może uzyskać dostęp do plików tylko z audytowanym, tymczasowym przyznaniem
  • Admin: zarządza polityką, ale nie powinien automatycznie czytać wszystkiego

Nie polegaj na regułach folderowych typu „wszystko w /user-uploads/ jest OK”. Sprawdzaj własność lub dostęp tenancy przy odczycie, dla każdego pliku. To chroni, gdy ktoś zmienia zespół, odchodzi z organizacji albo plik jest przypisywany na nowo.

Dobry wzorzec dla supportu to wąskie i tymczasowe uprawnienie: przyznaj dostęp do jednego pliku, zaloguj to i ustaw automatyczne wygaśnięcie.

Walidacja typu pliku bez zaufania

Większość ataków zaczyna się prostym trikiem: plik wygląda bezpiecznie po nazwie lub nagłówku przeglądarki, ale w rzeczywistości jest czymś innym. Traktuj wszystko, co klient wysyła, jako niezaufane.

Zacznij od allowlisty: zdecyduj dokładne formaty, które akceptujesz (np. .jpg, .png, .pdf) i odrzucaj wszystko inne. Unikaj „dowolny obraz” albo „dowolny dokument”, chyba że naprawdę tego potrzebujesz.

Nie ufaj rozszerzeniu nazwy ani nagłówkowi Content-Type od klienta. Oba są łatwe do podrobienia. Plik invoice.pdf może być wykonywalny, a Content-Type: image/png może być kłamstwem.

Silniejsze podejście to sprawdzenie pierwszych bajtów pliku, zwanych „magic bytes” lub sygnaturą pliku. Wiele formatów ma stałe nagłówki (jak PNG czy JPEG). Jeśli nagłówek nie pasuje do dozwolonego formatu, odrzuć plik.

Praktyczna konfiguracja walidacji:

  • Miej allowlistę rozszerzeń po stronie serwera
  • Wykrywaj MIME type po stronie serwera (nie polegaj na nagłówku klienta)
  • Sprawdzaj magic bytes dla obsługiwanych formatów
  • Generuj nową losową nazwę przechowywania i przechowuj oryginalną nazwę jako metadane
  • Blokuj ryzykowne formaty, chyba że naprawdę ich potrzebujesz — zwłaszcza HTML, SVG i treści skryptowe

Zmiana nazwy ma większe znaczenie niż się wydaje. Jeśli zapisujesz nazwy dostarczone przez użytkownika bezpośrednio, narażasz się na sztuczki ścieżkowe, dziwne znaki i przypadkowe nadpisania. Używaj wygenerowanego ID do przechowywania i zachowaj oryginalną nazwę tylko do wyświetlenia.

Dla zdjęć profilowych akceptuj tylko JPEG i PNG, weryfikuj nagłówki i usuń metadane jeśli możesz. Dla dokumentów rozważ ograniczenie do PDF i odrzucanie wszystkiego z aktywną zawartością. Jeśli później zdecydujesz, że potrzebujesz SVG lub HTML, traktuj je jak potencjalnie wykonywalne i izoluj.

Limity rozmiaru, rate limiting i podstawy DoS

Większość awarii uploadu to nie „wyszukane haki”. To ogromne pliki, za dużo żądań albo powolne połączenia blokujące serwery, aż aplikacja przestaje odpowiadać. Traktuj każdy bajt jako koszt.

Ustal limity tam, gdzie mają sens

Wybierz maksymalny rozmiar per funkcję, nie jeden globalny. Avatar nie potrzebuje tego samego limitu co dokument podatkowy czy krótki film. Ustal najmniejszy limit, który wciąż jest sensowny, a dla rzeczy większych stwórz osobną ścieżkę (np. direct-to-object-storage ze signed URL).

Wymuszaj limity w więcej niż jednym miejscu, bo klienci kłamią: w logice aplikacji, na serwerze WWW lub reverse proxy, przez timeouty uploadu i przez wczesne odrzucenie, gdy zadeklarowany rozmiar jest za duży (zanim przeczytasz całe ciało).

Konkretny przykład: awatary do 2 MB, PDF-y do 20 MB, a wszystko większe wymaga innego flow (np. podpisany URL do storage).

Rate limiting i kontrola nadużyć

Nawet małe pliki mogą być DoS-em, jeśli ktoś wgrywa je w pętli. Dodaj rate limits na endpointy uploadu na użytkownika i na IP. Rozważ ostrzejsze limity dla ruchu anonimowego niż dla zalogowanych.

Resumable uploads pomagają prawdziwym użytkownikom na złych sieciach, ale token sesji musi być ścisły: krótka ważność, powiązany z użytkownikiem i przypięty do konkretnego rozmiaru i miejsca docelowego. W przeciwnym razie endpoint „resume” stanie się darmową rurą do twojego storage.

Gdy blokujesz upload, zwracaj jasne błędy dla użytkownika (plik za duży, za dużo żądań), ale nie wyciekaj szczegółów wewnętrznych (stack trace, nazwy bucketów, dane vendorów).

Bezpieczne opcje przechowywania i dostarczania

Plan before you code
Use planning mode to map roles, access rules, and retention in one place.

Bezpieczne uploady to nie tylko to, co akceptujesz. To też gdzie plik trafia i jak go potem oddajesz.

Trzymaj bajty uploadu poza główną bazą danych. Większość aplikacji potrzebuje w DB tylko metadanych (ID właściciela, oryginalna nazwa, wykryty typ, rozmiar, checksum, klucz przechowywania, czas utworzenia). Bajty przechowuj w object storage lub serwisie plikowym zaprojektowanym pod duże bloby.

Oddziel publiczne i prywatne pliki na poziomie storage. Używaj różnych bucketów/kontenerów z odmiennymi regułami. Pliki publiczne (np. publiczne avatary) mogą być czytelne bez logowania. Pliki prywatne (umowy, faktury, dokumenty medyczne) nigdy nie powinny być publicznie czytelne, nawet jeśli ktoś odgadnie URL.

Unikaj serwowania plików użytkowników z tej samej domeny co twoja aplikacja, gdy możesz. Jeśli ryzykowny plik się przedostanie (HTML, SVG z skryptami lub problemy z MIME sniffingiem), hostowanie go na głównej domenie może doprowadzić do przejęcia konta. Dedykowana domena do pobierania (lub domena storage) ogranicza zasięg szkód.

Przy pobieraniu narzucaj bezpieczne nagłówki. Ustaw przewidywalny Content-Type oparty na tym, co akceptujesz, a nie na tym, co twierdzi użytkownik. Dla wszystkiego, co może być interpretowane przez przeglądarkę, preferuj wysłanie jako pobranie.

Kilka domyślnych ustawień zapobiegających niespodziankom:

  • Używaj Content-Disposition: attachment dla dokumentów.
  • Ustawiaj bezpieczny Content-Type (lub application/octet-stream).
  • Przechowuj i serwuj z nieprzejrzystymi kluczami obiektów (nie nazwy plików użytkowników).
  • Loguj pobrania plików prywatnych.

Retencja to też bezpieczeństwo. Usuwaj porzucone uploady, usuwaj stare wersje po zastąpieniu i ustawiaj limity czasowe dla tymczasowych plików. Mniej przechowywanych danych to mniej do wycieku.

Signed URLs: kiedy ich używać i jak je ograniczyć

Signed URLs (pre-signed URLs) to powszechny sposób, by pozwolić na upload lub pobranie bez udostępniania bucketa i bez przesyłania każdego bajtu przez API. URL niesie tymczasowe uprawnienie, potem wygasa.

Dwa typowe flow:

  • Direct-to-storage upload: aplikacja wydaje krótkotrwały signed URL i przeglądarka wysyła bezpośrednio do object storage.
  • Upload-through-server: plik trafia najpierw do twojego API, potem serwer zapisuje go dalej.

Direct-to-storage zmniejsza obciążenie API, ale wymusza ścisłe reguły storage i ograniczenia URL-i.

Jak trzymać signed URLs krótkie i dokładne

Traktuj signed URL jak jednorazowy klucz. Spraw, by był specyficzny i krótkotrwały.

  • Wygasaj write URL szybko (często 1–5 minut). Trzymaj read URL w minutach, nie dniach.
  • Powiąż URL z dokładnym kluczem obiektu, którego oczekujesz (jeden obiekt, nie folder).
  • Dodaj ograniczenia tam, gdzie są wspierane: oczekiwany Content-Type, maksymalny rozmiar, checksum.
  • Wydawaj URL-e dopiero po sprawdzeniu uprawnień.
  • Loguj, kto poprosił o URL i dlaczego (ID użytkownika, klucz obiektu, cel, IP/user agent).

Praktyczny wzorzec: najpierw utwórz rekord uploadu (status: pending), potem wydaj signed URL. Po uploadzie potwierdź, że obiekt istnieje i pasuje rozmiarem i typem, zanim oznaczysz go jako gotowy.

Krok po kroku: bezpieczny flow uploadu, który możesz wdrożyć

Export the source anytime
Keep control by exporting the generated code for review and audits.

Bezpieczny flow to głównie jasne reguły i jasny stan. Traktuj każdy upload jako niezaufany, dopóki kontrole nie zostaną wykonane.

Zapisz, co każda funkcja pozwala. Zdjęcie profilowe i dokument podatkowy nie powinny dzielić tych samych typów plików, limitów rozmiaru ani widoczności.

Praktyczny proces (ze stanami)

  1. Zdefiniuj dozwolone typy i limit rozmiaru per funkcję (np. zdjęcia do 5 MB; PDF-y do 20 MB). Wymuszaj te same zasady w backendzie.

  2. Utwórz „rekord uploadu” zanim pojawią się bajty. Zapisz: właściciela (user lub org), cel (avatar, invoice, attachment), oryginalną nazwę pliku, oczekiwany maksymalny rozmiar i status jak pending.

  3. Uploaduj do prywatnej lokalizacji. Nie pozwól klientowi wybierać finalnej ścieżki.

  4. Waliduj ponownie po stronie serwera: rozmiar, magic bytes/typ, allowlista. Jeśli przejdzie, ustaw status na uploaded.

  5. Skanuj pod kątem malware i zaktualizuj status na clean lub quarantined. Jeśli skan jest asynchroniczny, trzymaj dostęp zablokowany do czasu wyniku.

  6. Pozwalaj na pobranie, podgląd lub przetwarzanie tylko, gdy status = clean.

Mały przykład: dla zdjęcia profilowego utwórz rekord powiązany z użytkownikiem i celem avatar, przechowuj prywatnie, potwierdź, że to naprawdę JPEG/PNG (nie tylko nazwa), przeskanuj, a potem wygeneruj URL podglądu.

Podstawowe wzorce skanowania malware (bez obiecywania cudów)

Skanowanie to siatka bezpieczeństwa, nie obietnica. Wyłapie znane złe pliki i oczywiste sztuczki, ale nie wszystko. Cel jest prosty: zmniejszyć ryzyko i domyślnie uczynić nieznane pliki nieszkodliwymi.

Niezawodny wzorzec to najpierw kwarantanna. Zapisz każdy nowy upload w prywatnym, niepublicznym miejscu i oznacz go jako pending. Dopiero po przejściu kontroli przenieś go do „clean” (lub oznacz jako dostępny).

Skanowanie synchroniczne działa tylko dla małych plików i niskiego ruchu, bo użytkownik czeka. Większość aplikacji skanuje asynchronicznie: przyjmij upload, zwróć stan „przetwarzanie”, skanuj w tle.

Co zwykle zawiera „podstawowe skanowanie”

Podstawowe skanowanie to zazwyczaj silnik antywirusowy (lub usługa) plus kilka zabezpieczeń: skan AV, sprawdzenie typu pliku (magic bytes), limity dla archiwów (zip bomb, zagnieżdżone zipy, ogromny rozmiar po rozpakowaniu) i blokowanie formatów, których nie potrzebujesz.

Jeśli skaner się nie powiedzie, przekroczy czas lub zwróci „nieznany”, traktuj plik jako podejrzany. Trzymaj go w kwarantannie i nie udostępniaj linku. Tu zespoły często się potykają: „skan nie powiódł się” nie powinno oznaczać „i tak publikujemy”.

Gdy blokujesz plik, komunikuj neutralnie: „Nie mogliśmy przyjąć tego pliku. Spróbuj innego pliku lub skontaktuj się z supportem.” Nie twierdz, że wykryto malware, chyba że masz pewność.

Przykład: zdjęcie profilowe i upload dokumentu w typowej aplikacji

Weź dwie funkcje: zdjęcie profilowe (pokazywane publicznie) i paragon PDF (prywatny, używany do bilingów lub supportu). Obie to problemy uploadu, ale nie powinny dzielić tych samych reguł.

Dla zdjęcia profilowego utrzymaj rygor: tylko JPEG/PNG, limit rozmiaru (np. 2–5 MB) i re-enkoduj po stronie serwera, żeby nie serwować oryginalnych bajtów użytkownika. Przechowuj publicznie dopiero po kontrolach.

Dla paragonu PDF akceptuj większy rozmiar (np. do 20 MB), trzymaj prywatnie domyślnie i unikaj renderowania inline z głównej domeny aplikacji.

Prosty model statusów informuje użytkownika, nie ujawniając wnętrza systemu:

  • pending: użytkownik wybrał plik, upload nie rozpoczęty
  • uploaded: storage otrzymał bajty
  • scanning: praca w tle sprawdza plik
  • clean (lub rejected): plik jest dostępny (albo zablokowany)

Signed URLs pasują tu dobrze: używaj krótkotrwałego signed URL do uploadu (write-only, jeden object key). Wystawiaj osobny, krótkotrwały signed URL do odczytu dopiero, gdy status = clean.

Loguj to, co potrzebne do śledztwa, nie samych treści: ID użytkownika, ID pliku, typ-guess, rozmiar, klucz przechowywania, timestampy, wynik skanu, request IDs. Unikaj logowania surowej zawartości lub wrażliwych danych znalezionych w dokumentach.

Częste błędy i proste pułapki

Rollback risky changes
Use snapshots and rollback when tightening upload rules during fast iteration.

Większość bugów pojawia się, bo mały „tymczasowy” skrót zostaje na stałe. Zakładaj, że każdy plik jest niezaufany, każdy URL będzie udostępniony, i każde „naprawimy to później” ustawienie zostanie zapomniane.

Pułapki, które się powtarzają:

  • Poleganie wyłącznie na sprawdzeniach po stronie klienta. Przeglądarki łamie się w sekundach.
  • Pozwolenie użytkownikom wpływać na ścieżki, nazwy plików lub klucze obiektów.
  • Robienie uploadów publicznymi „na chwilę”.
  • Używanie signed URL, które żyją zbyt długo lub działają dla wielu użytkowników.
  • Serwowanie plików z nieodpowiednim Content-Type, pozwalając przeglądarce interpretować ryzykowną zawartość.

Monitoring to element, który zespoły omijają, dopóki rachunek za storage nie wystrzeli. Śledź wolumen uploadów, średni rozmiar, największych uploaderów i stawki błędów. Jedno skompromitowane konto może potajemnie wgrać tysiące dużych plików w nocy.

Przykład: zespół trzyma avatary pod nazwami podanymi przez użytkownika jak „avatar.png” w współdzielonym folderze. Jeden użytkownik nadpisuje obrazy innych. Poprawka jest nudna, ale skuteczna: generuj klucze obiektów po stronie serwera, trzymaj uploady domyślnie prywatne i udostępniaj zmieniony rozmiar obrazu przez kontrolowaną odpowiedź.

Szybka lista kontrolna i kolejne kroki

Użyj tego jako ostatniego przeglądu przed wdrożeniem. Traktuj każdy punkt jako blocker wydania, bo większość incydentów wynika z jednego brakującego zabezpieczenia.

Szybka lista kontrolna

  • Waliduj po stronie serwera z allowlistą, rzeczywistymi sprawdzeniami zawartości (nie tylko nazwa pliku) i twardym max rozmiarem per plik.
  • Przechowuj uploady domyślnie prywatnie i sprawdzaj uprawnienia przy każdym odczycie/pobraniu/podglądzie.
  • Jeśli używasz signed URL do uploadu, trzymaj je krótkotrwałe, scope'owane do jednego object key i loguj ich wydawanie, by móc prześledzić nadużycia.
  • Najpierw kwarantanna, potem skan: nie generuj podglądów ani nie pozwalaj na pobranie, dopóki plik nie będzie clean.
  • Wymuszaj bezpieczne zachowanie przy pobieraniu: przewidywalny Content-Type, bezpieczne nazwy plików i attachment dla dokumentów.

Następne kroki, które się opłacają

Spisz swoje reguły prostym językiem: dozwolone typy, maksymalne rozmiary, kto ma dostęp do czego, jak długo żyją signed URL i co znaczy „skan przeszedł”. To staje się wspólnym kontraktem między produktem, inżynierią i supportem.

Dodaj kilka testów, które łapią typowe błędy: za duże pliki, pliki zmienione z rozszerzeniem wykonywalnym, nieautoryzowane odczyty, wygasłe signed URL i pobrania, gdy status jest „scan pending”. Te testy są tanie w porównaniu do incydentu.

Jeśli budujesz i iterujesz szybko, pomaga workflow z możliwością planowania zmian i bezpiecznego przywracania. Zespoły korzystające z Koder.ai (koder.ai) często polegają na trybie planowania i snapshotach/rollback, dopracowując reguły uploadu w czasie, ale podstawowy wymóg się nie zmienia: politykę egzekwuje backend, nie UI.

Często zadawane pytania

Co jest minimum, które powinienem zrobić, aby przesyłanie plików było „wystarczająco bezpieczne”?

Zacznij od zasady domyślnie prywatne i traktuj każde przesłanie jako niezaufane wejście. Wymuś cztery podstawy po stronie serwera:

  • Kto może przesyłać
  • Jakie typy plików akceptujesz (allowlist)
  • Jak duże / jak często (limity rozmiaru + limitowanie zapytań)
  • Kto może to później przeczytać (sprawdzenia uprawnień dla każdego pliku)

Jeśli możesz na to wszystko jasno odpowiedzieć, jesteś już przed większością incydentów.

Dlaczego przesyłanie plików jest częstym pierwszym incydentem bezpieczeństwa?

Bo użytkownicy mogą przesłać „tajemnicze pudełko”, które aplikacja zapisze, a potem może je komuś pokazać. To może prowadzić do:

  • Nieautoryzowanego dostępu do prywatnych dokumentów
  • Phishingu lub przejęcia konta, jeśli plik jest traktowany jak zaufana zawartość webowa
  • Przestojów i wysokich rachunków z powodu lawiny przesyłanych lub ogromnych plików

Rzadko chodzi tylko o „ktoś przesłał wirusa”.

Jaka jest różnica między przechowywaniem plików a ich serwowaniem i dlaczego to ma znaczenie?

Przechowywanie to miejsce, gdzie trzymasz bajty. Serwowanie to sposób, w jaki te bajty są dostarczane do przeglądarek i aplikacji.

Problem pojawia się, gdy aplikacja serwuje pliki użytkowników z tym samym poziomem zaufania i zasadami co główna strona. Jeśli ryzykowny plik zostanie potraktowany jak zwykła strona, przeglądarka może go wykonać (albo użytkownicy zaufają mu zbyt mocno).

Bezpieczniejszy domyślny model: przechowuj prywatnie, a następnie serwuj przez kontrolowane odpowiedzi z bezpiecznymi nagłówkami.

Jak powstrzymać użytkowników przed pobieraniem pliku innej osoby?

Użyj zasady domyślnego odmówienia i sprawdzaj uprawnienia za każdym razem, gdy plik jest pobierany lub podglądany.

Praktyczne reguły:

  • Każdy rekord pliku powinien mieć właściciela (user/org) i cel (avatar, faktura itp.)
  • Przy odczycie/pobieraniu weryfikuj, czy żądający ma dostęp do konkretnego pliku
  • Unikaj zabezpieczeń opartych na folderach typu „wszystko w /uploads/ jest ok”
  • Dostęp supportu powinien być tymczasowy i rejestrowany (przydziel dostępu do jednego pliku, automatycznie wygasaj)

Większość błędów to proste „widzę plik innego użytkownika”.

Jak walidować typ pliku bez ufania nazwie pliku lub `Content-Type`?

Nie ufaj rozszerzeniu pliku ani nagłówkowi Content-Type z przeglądarki. Waliduj po stronie serwera:

  • Użyj allowlisty formatów per funkcja (np. JPEG/PNG dla avatarów, PDF dla paragonów)
  • Wykrywaj typ po stronie serwera i sprawdzaj magic bytes (sygnaturę pliku)
  • Zmieniaj nazwy plików do przechowywania na losowe ID; oryginalną nazwę trzymaj tylko jako metadane do wyświetlenia
  • Blokuj ryzykowne formaty, których nie potrzebujesz (szczególnie HTML, SVG i treści skryptowe)

Jeśli bajty nie pasują do dozwolonego formatu, odrzuć upload.

Jakie limity powinienem ustawić, aby zapobiec DoS przez uploady?

Awarie często wynikają z nudnego nadużycia: zbyt wiele uploadów, wielkie pliki albo powolne połączenia blokujące zasoby.

Domyślne dobre praktyki:

  • Ustal maksymalne rozmiary per funkcja (avatar mały, dokument większy)
  • Wymuszaj limity na kilku warstwach (aplikacja + reverse proxy + timeouty)
  • Dodaj limitowanie żądań na użytkownika i na IP; ostrzejsze limity dla anonimowych

Traktuj każdy bajt jak koszt, a każde żądanie jak potencjalne nadużycie.

Czy powinienem używać signed URLs do uploadów i jaki jest najbezpieczniejszy domyślny sposób?

Tak, ale ostrożnie. Signed URLs pozwalają przeglądarce wysyłać bezpośrednio do storage bez publicznego bucketa.

Dobre domyślny:

  • Trzymaj write-URL krótkotrwały (często 1–5 minut)
  • Scope'uj każdy URL do jednego klucza obiektu, nie folderu
  • Wydawaj URL tylko po sprawdzeniu uprawnień
  • Loguj kto poprosił o URL i w jakim celu

Direct-to-storage zmniejsza obciążenie API, ale wymaga ścisłego zakresu i wygaśnięcia.

Jaki jest bezpieczny krok po kroku flow uploadu, który mogę zaimplementować?

Najbezpieczniejszy wzorzec to:

  1. Zdefiniuj dozwolone typy i limit rozmiaru per funkcję
  2. Utwórz rekord uploadu przed przyjściem bajtów (status pending)
  3. Uploaduj do prywatnego miejsca; klient nie wybiera docelowej ścieżki
  4. Zweryfikuj po stronie serwera: rozmiar, magic bytes / typ
  5. Skanuj i zmień status na clean lub quarantined; jeśli skan async — trzymaj blokadę
  6. Pozwól na pobranie/podgląd dopiero, gdy status = clean

To zapobiega przypadkom „skan nieudany = udostępnij”.

Czy naprawdę potrzebuję skanowania antywirusowego i jak wygląda „podstawowe skanowanie”?

Skanowanie pomaga, ale nie daje gwarancji. Traktuj je jako siatkę bezpieczeństwa.

Praktyczne podejście:

  • Najpierw kwarantanna: nie udostępniaj linków zanim skan się nie skończy
  • Skanuj asynchronicznie dla skali; pokaż użytkownikowi stan „przetwarzanie”
  • Jeśli skan się nie powiedzie lub przekroczy czas, traktuj plik jako podejrzany i trzymaj go zablokowanego
  • Jeżeli pozwalasz na archiwa, dodaj zabezpieczenia przed zip-bombami i ogromnymi rozmiarami po rozpakowaniu

Klucz: „niezeskanowane” nie powinno oznaczać „dostępne”.

Jak bezpiecznie dostarczać przesłane pliki (nagłówki, domeny, pobieranie)?

Serwuj pliki tak, by przeglądarki nie interpretowały ich jako strony webowej.

Dobre domyślne:

  • Ustaw Content-Disposition: attachment dla dokumentów
  • Używaj bezpiecznego, serwerowego Content-Type (lub application/octet-stream)
  • Używaj nieprzejrzystych kluczy obiektów (nie nazwy użytkowników) w URL-ach
  • Preferuj oddzielną domenę do pobierania treści użytkowników, jeśli to możliwe

To zmniejsza ryzyko, że upload stanie się stroną phishingową lub skryptem.

Related posts