8 phút

Bảo mật tải lên tệp: quyền truy cập, giới hạn, URL ký, quét mã độc

Việc tải lên tệp an toàn trong ứng dụng web cần quyền truy cập chặt chẽ, giới hạn kích thước, URL ký tạm thời và mẫu quét mã độc đơn giản để tránh sự cố.

Bảo mật tải lên tệp: quyền truy cập, giới hạn, URL ký, quét mã độc

Tại sao tải lên tệp rủi ro (nói dễ hiểu)

Tải lên tệp trông vô hại: ảnh đại diện, PDF, bảng tính. Nhưng chúng thường là nguồn gây ra sự cố bảo mật đầu tiên vì cho phép người lạ gửi vào hệ thống của bạn một “hộp bí ẩn”. Nếu bạn chấp nhận, lưu và hiển thị lại cho người khác, bạn đã tạo ra một đường tấn công mới cho ứng dụng.

Rủi ro không chỉ là “ai đó tải lên virus.” Một tệp xấu có thể làm lộ tài liệu riêng tư, làm tăng hóa đơn lưu trữ, hoặc lừa người dùng trao quyền truy cập. Một tệp tên là “invoice.pdf” có thể không phải PDF. Ngay cả PDF và ảnh thật cũng gây vấn đề nếu ứng dụng tin metadata, tự động tạo preview, hoặc phục vụ chúng với quy tắc sai.

Các sự cố thực tế thường trông như sau:

  • Ai đó đoán URL tệp và tải xuống tài liệu của người khác.
  • Một tệp HTML tải lên được phục vụ như trang web và hiển thị prompt đánh cắp đăng nhập.
  • Kẻ tấn công tải lên các tệp khổng lồ lặp đi lặp lại cho đến khi app chậm hoặc sập.
  • Một loại tệp “an toàn” bị giả mạo, rồi nhân viên mở nó trên máy nội bộ.

Một chi tiết gây ra nhiều sự cố: lưu trữ tệp không giống phục vụ tệp. Lưu trữ là nơi bạn giữ byte. Phục vụ là cách các byte đó được giao tới trình duyệt và app. Mọi chuyện đi sai khi app phục vụ tệp người dùng với cùng mức độ tin cậy và quy tắc như trang chính, khiến trình duyệt coi tệp tải lên là “đáng tin”.

“Mức độ an toàn đủ” cho một app nhỏ hoặc đang phát triển thường có nghĩa là bạn có thể trả lời bốn câu hỏi mà không vòng vo: ai có thể tải lên, bạn chấp nhận gì, kích thước và tần suất bao nhiêu, ai có thể đọc nó sau này. Ngay cả khi bạn xây dựng nhanh (với mã sinh tự động hoặc nền tảng chat-driven), các rào chắn đó vẫn quan trọng.

Một mô hình mối đe dọa đơn giản cho việc tải lên

Hãy coi mỗi tệp tải lên như input không đáng tin. Cách thực tế để giữ an toàn là hình dung ai có thể lạm dụng chúng và “thành công” với kẻ tấn công trông như thế nào.

Hầu hết kẻ tấn công là bot quét các form upload yếu hoặc người dùng thật thử vượt giới hạn để lấy lưu trữ miễn phí, cào dữ liệu, hoặc phá hoại dịch vụ. Đôi khi là đối thủ tìm lỗ hổng rò rỉ hay gây outage.

Họ muốn gì? Thường là một trong các kết quả sau:

  • Chạy mã trên server của bạn bằng cách tải lên thứ có thể được thực thi.
  • Ăn cắp tài liệu riêng tư bằng cách đoán, tái sử dụng hoặc chia sẻ URL tải xuống.
  • Ảnh hưởng khả năng phục vụ bằng cách tràn tải upload hoặc ép xử lý tốn kém.
  • Thổi phồng hóa đơn của bạn qua tăng lưu trữ hoặc băng thông tải xuống nặng.

Rồi bản đồ các điểm yếu. Endpoint upload là cửa trước (tệp quá khổ, định dạng lạ, tần suất yêu cầu cao). Lưu trữ là phòng sau (bucket công khai, quyền sai, thư mục chia sẻ). URL tải xuống là lối ra (dễ đoán, sống lâu, hoặc không ràng buộc với người dùng).

Ví dụ: tính năng “tải lên hồ sơ xin việc”. Một bot tải hàng nghìn PDF lớn để làm tăng chi phí, trong khi một user lạm dụng tải tệp HTML và chia sẻ nó như “tài liệu” để lừa người khác.

Trước khi thêm kiểm soát, quyết định điều gì quan trọng nhất cho app của bạn: riêng tư (ai có thể đọc), khả năng phục vụ (có tiếp tục phục vụ được không), chi phí (lưu trữ và băng thông), và tuân thủ (dữ liệu lưu ở đâu và giữ bao lâu). Danh sách ưu tiên đó giúp các quyết định nhất quán.

Quyền truy cập và kiểm soát truy cập thực sự hữu hiệu

Hầu hết sự cố upload không phải là hack cầu kỳ. Chúng là lỗi đơn giản “tôi nhìn thấy tệp của người khác”. Hãy coi quyền truy cập là một phần của quá trình upload, không phải tính năng thêm sau.

Bắt đầu với một quy tắc: mặc định từ chối. Giả định mọi đối tượng tải lên là riêng tư cho đến khi bạn cho phép rõ ràng. “Riêng tư theo mặc định” là nền tảng mạnh cho hóa đơn, hồ sơ y tế, tài liệu tài khoản và bất cứ thứ gì liên quan đến user. Chỉ cho phép công khai khi user rõ ràng mong đợi điều đó (ví dụ avatar công khai), và ngay cả khi đó cũng cân nhắc truy cập có thời hạn.

Vai trò phù hợp với công việc thực

Giữ vai trò đơn giản và tách biệt. Một tách thường dùng là:

  • Uploader: có thể tạo upload cho chính tài khoản của họ
  • Viewer: có thể tải xuống các tệp họ được phép xem
  • Support: có thể truy cập tệp chỉ với grant tạm thời và có thể kiểm tra
  • Admin: quản lý chính sách, nhưng không nên mặc định đọc mọi thứ

Đừng dựa vào quy tắc theo thư mục như “bất cứ thứ gì trong /user-uploads/ là ổn.” Kiểm tra quyền sở hữu hoặc quyền tenant khi đọc, cho mọi tệp. Điều đó bảo vệ bạn khi ai đó chuyển team, rời tổ chức, hoặc tệp bị gán lại.

Mô hình hỗ trợ tốt là hẹp và tạm thời: cấp quyền cho một tệp cụ thể, ghi nhật ký, và tự hết hạn.

Xác thực loại tệp mà không tin cậy

Hầu hết cuộc tấn công bắt đầu bằng mẹo đơn giản: tệp trông an toàn vì tên hoặc header trình duyệt, nhưng thực ra là cái khác. Hãy đối xử mọi thứ client gửi là không đáng tin.

Bắt đầu bằng allowlist: quyết định chính xác định dạng bạn chấp nhận (ví dụ .jpg, .png, .pdf) và từ chối mọi thứ khác. Tránh “bất kỳ ảnh nào” hay “bất kỳ tài liệu nào” trừ khi bạn thực sự cần.

Đừng tin phần mở rộng tên tệp hay header Content-Type từ client. Cả hai đều dễ giả mạo. Một tệp tên invoice.pdf có thể là executable, và Content-Type: image/png có thể là lời nói dối.

Cách mạnh hơn là kiểm tra các byte đầu của tệp, thường gọi là “magic bytes” hoặc chữ ký tệp. Nhiều định dạng phổ biến có header nhất quán (như PNG và JPEG). Nếu header không khớp những gì bạn cho phép, từ chối.

Một thiết lập xác thực thực dụng:

  • Allowlist phần mở rộng bạn chấp nhận (ở phía server).
  • Phát hiện MIME type ở server (không tin từ client).
  • Kiểm tra magic bytes cho các định dạng hỗ trợ.
  • Sinh tên lưu trữ ngẫu nhiên và lưu tên gốc như metadata.
  • Chặn các định dạng rủi ro trừ khi bạn thực sự cần, đặc biệt HTML, SVG, và nội dung giống script.

Đổi tên quan trọng hơn bạn nghĩ. Nếu bạn lưu tên do người dùng cung cấp trực tiếp, bạn mời gọi trick về đường dẫn, ký tự lạ, và ghi đè vô ý. Dùng ID sinh để lưu và giữ tên gốc chỉ để hiển thị.

Với ảnh đại diện, chỉ chấp nhận JPEG và PNG, xác minh header và loại bỏ metadata nếu có thể. Với tài liệu, cân nhắc chỉ PDF và từ chối mọi thứ có nội dung tích cực. Nếu sau này bạn cần SVG hoặc HTML, coi chúng như có thể thực thi và cô lập chúng.

Giới hạn kích thước, giới hạn tốc độ và cơ bản về DoS

Hầu hết các sự cố upload không phải là “kỹ thuật hack”. Chúng là do tệp quá lớn, quá nhiều request, hoặc kết nối chậm chiếm máy chủ cho đến khi app cảm thấy sập. Hãy coi mỗi byte là một chi phí.

Đặt giới hạn kích thước có hiệu quả

Chọn kích thước tối đa theo tính năng, không phải một con số toàn cục. Avatar không cần cùng giới hạn với tài liệu thuế hoặc video ngắn. Đặt giới hạn nhỏ nhất vẫn hợp lý, rồi thêm luồng “upload lớn” riêng khi bạn thật sự cần.

Thực thi giới hạn ở nhiều nơi, vì client có thể giả mạo: trong logic app, ở web server hoặc reverse proxy, với timeout upload, và từ chối sớm khi kích thước khai báo quá lớn (trước khi đọc toàn bộ body).

Ví dụ cụ thể: avatar giới hạn 2 MB, PDF giới hạn 20 MB, và mọi thứ lớn hơn đòi hỏi flow khác (ví dụ direct-to-object-storage với signed URL).

Giới hạn tốc độ và kiểm soát lạm dụng

Ngay cả tệp nhỏ cũng có thể gây DoS nếu ai đó upload chúng liên tục. Thêm giới hạn tốc độ cho endpoint upload theo user và theo IP. Cân nhắc giới hạn chặt hơn cho traffic ẩn danh so với user đã đăng nhập.

Upload có thể nối tiếp (resumable) giúp người dùng thật trên mạng yếu, nhưng token session phải chặt: thời hạn ngắn, ràng buộc với user, và gắn với kích thước/tên tệp cụ thể. Nếu không, endpoint “resume” trở thành ống dẫn miễn phí vào lưu trữ của bạn.

Khi bạn chặn upload, trả lỗi rõ cho người dùng (tệp quá lớn, quá nhiều yêu cầu) nhưng đừng lộ nội bộ (stack trace, tên bucket, thông tin vendor).

Lựa chọn lưu trữ và phân phối an toàn

Build for web and mobile
Create React, Go, and Flutter apps that share one upload policy.

Tải lên an toàn không chỉ là bạn chấp nhận gì. Nó còn là tệp đi đâu và bạn trả lại nó như thế nào sau này.

Giữ byte tải lên ngoài database chính của bạn. Hầu hết app chỉ cần metadata trong DB (owner user ID, tên gốc, loại phát hiện, kích thước, checksum, storage key, thời gian tạo). Lưu byte ở object storage hoặc dịch vụ file được thiết kế cho blob lớn.

Tách biệt tệp công khai và riêng tư ở cấp lưu trữ. Dùng các bucket hoặc container khác nhau với quy tắc khác nhau. Tệp công khai (như avatar công khai) có thể đọc mà không cần login. Tệp riêng tư (hợp đồng, hóa đơn, hồ sơ y tế) không bao giờ được công khai đọc, ngay cả khi ai đó đoán URL.

Tránh phục vụ tệp người dùng từ cùng domain với app chính khi có thể. Nếu một tệp rủi ro lọt qua (HTML, SVG có script, hoặc trình duyệt sniff MIME lạ), host nó trên domain chính có thể dẫn đến chiếm đoạt tài khoản. Một domain tải xuống riêng (hoặc domain lưu trữ) giới hạn phạm vi thiệt hại.

Khi tải xuống, giữ header an toàn. Đặt Content-Type dựa trên những gì bạn cho phép, không phải do user khai báo. Với bất cứ thứ gì trình duyệt có thể diễn giải, ưu tiên gửi như tệp tải xuống.

Một vài mặc định ngăn ngừa bất ngờ:

  • Dùng Content-Disposition: attachment cho tài liệu.
  • Dùng Content-Type an toàn (hoặc application/octet-stream).
  • Lưu và phục vụ với object key mờ (không phải tên tệp người dùng).
  • Ghi nhật ký tải xuống các tệp riêng tư.

Retention cũng là an ninh. Xóa các upload bị bỏ rơi, loại bỏ phiên bản cũ sau khi thay thế, và đặt giới hạn thời gian cho tệp tạm. Dữ liệu ít hơn nghĩa là ít thứ có thể bị rò rỉ.

URL ký: khi nào dùng và làm sao giữ chặt

Signed URLs (thường gọi pre-signed URLs) là cách phổ biến để cho phép người dùng upload hoặc download mà không làm bucket công khai, và không cần truyền mọi byte qua API của bạn. URL mang quyền tạm thời, rồi hết hạn.

Hai flow phổ biến:

  • Direct-to-storage upload: app của bạn phát hành signed URL thời hạn ngắn và trình duyệt upload thẳng tới object storage.
  • Upload qua server: tệp đến API của bạn trước, rồi server lưu nó.

Direct-to-storage giảm tải API, nhưng làm cho quy tắc lưu trữ và ràng buộc URL quan trọng hơn.

Làm sao giữ signed URL chặt

Hãy coi signed URL như một chìa khóa dùng một lần. Làm nó cụ thể và thời hạn ngắn.

  • Hết hạn URL ghi nhanh (thường 1–5 phút). Giữ URL đọc ở mức phút, không phải ngày.
  • Ràng buộc URL với object key chính xác bạn mong đợi (một object, không phải thư mục).
  • Thêm ràng buộc nếu hỗ trợ: content type mong đợi, kích thước tối đa, checksum.
  • Phát hành URL chỉ sau khi kiểm tra quyền.
  • Ghi log ai yêu cầu URL và lý do (user ID, object key, mục đích, IP/user agent).

Một mẫu thực tế là tạo bản ghi upload trước (status: pending), rồi phát hành signed URL. Sau khi upload, xác nhận object tồn tại và khớp kích thước/loại mong đợi trước khi đánh dấu là sẵn sàng.

Bước từng bước: một flow upload an toàn bạn có thể triển khai

Keep data where it belongs
Choose where your app runs to match privacy and cross-border requirements.

Một flow upload an toàn chủ yếu là quy tắc rõ ràng và trạng thái rõ ràng. Coi mọi upload là không đáng tin cho tới khi kiểm tra xong.

Viết ra mỗi tính năng cho phép gì. Ảnh đại diện và tài liệu thuế không nên dùng chung loại tệp, giới hạn kích thước, hoặc quyền hiển thị.

Một flow thực dụng (với trạng thái thực tế)

  1. Xác định loại cho phép và giới hạn kích thước theo tính năng (ví dụ: ảnh tới 5 MB; PDF tới 20 MB). Thực thi cùng quy tắc ở backend.

  2. Tạo “bản ghi upload” trước khi byte đến. Lưu: owner (user hoặc org), mục đích (avatar, invoice, attachment), tên gốc, kích thước tối đa mong đợi, và trạng thái như pending.

  3. Upload vào vị trí riêng tư. Đừng để client chọn đường dẫn cuối cùng.

  4. Xác thực lại ở server: kích thước, magic bytes/loại, allowlist. Nếu qua, chuyển trạng thái sang uploaded.

  5. Quét mã độc và cập nhật trạng thái sang clean hoặc quarantined. Nếu quét bất đồng bộ, giữ quyền truy cập khóa trong khi chờ.

  6. Cho phép tải xuống, xem trước, hoặc xử lý chỉ khi trạng thái là clean.

Ví dụ nhỏ: với ảnh đại diện, tạo bản ghi liên kết với user và mục đích avatar, lưu riêng tư, xác nhận thật sự là JPEG/PNG (không chỉ tên giống), quét, rồi tạo URL preview.

Mẫu quét mã độc cơ bản (không hứa quá nhiều)

Quét là một mạng lưới an toàn, không phải lời hứa. Nó bắt được tệp xấu đã biết và chiêu trò rõ ràng, nhưng không phát hiện hết mọi thứ. Mục tiêu là giảm rủi ro và làm cho tệp chưa rõ rủi ro trở nên vô hại theo mặc định.

Mẫu đáng tin là cách ly trước. Lưu mọi upload mới vào vị trí riêng tư, đánh dấu là pending. Chỉ sau khi vượt kiểm tra mới chuyển sang vị trí “clean” (hoặc đánh dấu là sẵn sàng).

Quét đồng bộ chỉ hợp lý cho tệp nhỏ và traffic thấp vì user phải đợi. Hầu hết app quét bất đồng bộ: chấp nhận upload, trả trạng thái “đang xử lý”, quét ở background.

Những gì “quét cơ bản” thường bao gồm

Quét cơ bản thường là engine antivirus (hoặc dịch vụ) cộng vài rào cản: AV scan, kiểm tra loại tệp (magic bytes), giới hạn archive (zip bombs, zip lồng nhau, kích thước giải nén khổng lồ), và chặn các định dạng bạn không cần.

Nếu scanner lỗi, timeout, hoặc trả “unknown”, coi tệp là đáng ngờ. Giữ nó cách ly và không cung cấp link tải xuống. Đây là nơi nhiều đội bị đau: “scan failed” không nên biến thành “vẫn cho dùng”.

Khi bạn chặn tệp, giữ thông báo trung tính: “Chúng tôi không thể chấp nhận tệp này. Thử tệp khác hoặc liên hệ hỗ trợ.” Đừng khẳng định bạn đã phát hiện mã độc trừ khi bạn chắc chắn.

Ví dụ: ảnh đại diện và tải lên tài liệu trong app điển hình

Xem hai tính năng: ảnh đại diện (hiển thị công khai) và biên lai PDF (riêng tư, dùng cho thanh toán hoặc hỗ trợ). Cả hai đều là vấn đề upload, nhưng không nên có cùng quy tắc.

Với ảnh đại diện, giữ chặt: chỉ cho phép JPEG/PNG, giới hạn kích thước (ví dụ 2–5 MB), và mã hóa lại phía server để bạn không phục vụ byte gốc của người dùng. Lưu công khai chỉ sau khi kiểm tra.

Với biên lai PDF, chấp nhận kích thước lớn hơn (ví dụ tới 20 MB), giữ mặc định là riêng tư, và tránh render inline từ domain chính của bạn.

Mô hình trạng thái đơn giản giữ người dùng biết mà không lộ nội bộ:

  • pending: user chọn tệp, chưa bắt đầu upload
  • uploaded: storage đã nhận byte
  • scanning: tác vụ nền đang kiểm tra
  • clean (hoặc rejected): tệp sẵn sàng (hoặc bị chặn)

Signed URLs phù hợp ở đây: dùng signed URL ngắn hạn cho upload (chỉ ghi, một object key). Phát hành signed URL đọc ngắn hạn riêng và chỉ khi trạng thái là clean.

Ghi lại những gì cần cho điều tra, không phải nội dung tệp: user ID, file ID, đoán loại, kích thước, storage key, timestamps, kết quả quét, request IDs. Tránh log nội dung thô hoặc dữ liệu nhạy bên trong tài liệu.

Những sai lầm phổ biến và bẫy dễ vấp

Ship safer upload flows
Create upload endpoints with private defaults, type checks, and clean status states.

Hầu hết lỗi upload vì một shortcut “tạm thời” trở thành vĩnh viễn. Giả sử mọi tệp là không đáng tin, mọi URL sẽ được chia sẻ, và mọi cài đặt “sẽ sửa sau” sẽ bị quên.

Các bẫy lặp lại:

  • Tin vào kiểm tra phía client. Trình duyệt bị bỏ qua trong vài giây.
  • Để user quyết định đường dẫn, tên tệp, hoặc object key.
  • Làm tệp công khai “chỉ trong chốc lát”.
  • Dùng signed URL sống quá lâu hoặc cho nhiều user.
  • Phục vụ tệp với Content-Type sai, để trình duyệt diễn giải nội dung rủi ro.

Giám sát là điều các đội bỏ qua cho tới khi hóa đơn lưu trữ tăng vọt. Theo dõi khối lượng upload, kích thước trung bình, người upload nhiều nhất, và tỷ lệ lỗi. Một tài khoản bị xâm có thể lặng lẽ upload hàng nghìn tệp lớn trong một đêm.

Ví dụ: một đội lưu avatar theo tên do user cung cấp như “avatar.png” trong thư mục chia sẻ. Một user ghi đè ảnh người khác. Cách sửa tẻ nhạt nhưng hiệu quả: sinh object key phía server, giữ upload mặc định riêng tư, và cung cấp ảnh resize thông qua phản hồi được kiểm soát.

Danh sách kiểm tra nhanh và bước tiếp theo

Dùng đây như bước rà soát cuối trước khi phát hành. Xem mỗi mục là blocker phát hành, vì hầu hết sự cố đến từ một rào chắn bị thiếu.

Danh sách kiểm tra nhanh

  • Xác thực ở server với allowlist, kiểm tra nội dung thực (không chỉ tên tệp), và giới hạn kích thước cứng theo tệp.
  • Lưu upload mặc định là riêng tư, và kiểm tra quyền mỗi lần tệp được đọc, tải xuống, hay xem trước.
  • Nếu dùng signed URL, giữ chúng thời hạn ngắn, giới hạn cho một object key, và ghi log khi phát hành.
  • Cách ly trước, quét sau: đừng tạo preview hay cho phép tải xuống cho tới khi tệp sạch.
  • Ép hành vi tải xuống an toàn: Content-Type dự đoán được, tên tệp an toàn, và attachment cho tài liệu.

Bước tiếp theo hữu ích

Viết ra quy tắc bằng ngôn ngữ đơn giản: loại cho phép, kích thước tối đa, ai truy cập gì, signed URL sống bao lâu, và “scan passed” nghĩa là gì. Đó là hợp đồng chung giữa product, engineering và support.

Thêm vài test bắt lỗi thường gặp: tệp quá lớn, executable đổi tên, đọc trái phép, signed URL hết hạn, và tải xuống khi “scan pending”. Những test này rẻ so với một sự cố.

Nếu bạn đang xây dựng và lặp nhanh, hữu ích khi dùng workflow có thể lên kế hoạch và rollback an toàn. Các nhóm dùng Koder.ai (koder.ai) thường dựa vào planning mode và snapshots/rollback khi siết chặt quy tắc upload theo thời gian, nhưng yêu cầu cốt lõi vẫn vậy: backend phải thực thi chính sách, không phải UI.

Câu hỏi thường gặp

What’s the minimum I should do to make file uploads “safe enough”?“

Bắt đầu với mặc định là riêng tư và coi mọi tệp tải lên như dữ liệu không đáng tin. Thực thi bốn điều cơ bản ở phía server:

  • Ai có thể tải lên
  • Những loại tệp được chấp nhận (allowlist)
  • Kích thước/tần suất (giới hạn kích thước + giới hạn tốc độ)
  • Ai có thể đọc nó sau này (kiểm tra quyền theo từng tệp)

Nếu bạn trả lời được rõ ràng những điều đó, bạn đang an toàn hơn hầu hết các sự cố.

Why are file uploads a common first security incident?

Bởi vì người dùng có thể tải lên một “hộp bí ẩn” mà ứng dụng của bạn lưu lại và có thể phục vụ lại cho người khác. Điều này có thể dẫn đến:

  • Truy cập trái phép vào tài liệu riêng tư
  • Lừa đảo hoặc chiếm đoạt tài khoản nếu tệp được phục vụ như nội dung web đáng tin
  • Sự cố ngưng dịch vụ và hóa đơn tăng cao do upload ồ ạt hoặc tệp lớn

Hiếm khi chỉ là “ai đó tải lên một con virus”.

What’s the difference between storing files and serving files, and why does it matter?

Lưu là đặt các byte ở đâu đó. Phục vụ là cách các byte đó được gửi đến trình duyệt và ứng dụng.

Nguy hiểm xảy ra khi ứng dụng phục vụ tệp người dùng với cùng mức độ tin cậy và quy tắc như trang chính của bạn. Nếu một tệp rủi ro bị xử lý như một trang bình thường, trình duyệt có thể thực thi nó (hoặc người dùng tin tưởng nó quá mức).

Một mặc định an toàn hơn là: lưu ở chế độ riêng tư, rồi phục vụ qua các phản hồi tải xuống được kiểm soát với header an toàn.

How do I stop users from downloading someone else’s uploaded file?

Sử dụng mặc định là từ chối và kiểm tra quyền mỗi lần một tệp được tải xuống hoặc xem trước.

Quy tắc thực tế:

  • Mỗi bản ghi tệp nên có chủ sở hữu (user/org) và mục đích (avatar, hóa đơn, v.v.)
  • Khi đọc/tải xuống, xác minh người yêu cầu có quyền với chính tệp đó
  • Tránh bảo mật dựa trên thư mục như “bất cứ thứ gì trong /uploads/ đều ổn”
  • Giữ quyền hỗ trợ tạm thời và có log (cấp quyền cho 1 tệp, tự hết hạn)

Hầu hết lỗi thực tế là những sai sót đơn giản kiểu “tôi thấy được tệp của người khác”.

How do I validate file type without trusting the filename or Content-Type?

Đừng tin phần mở rộng tên tệp hay Content-Type từ trình duyệt. Xác thực ở phía server:

  • Dùng allowlist các định dạng theo từng tính năng (ví dụ: JPEG/PNG cho avatar, PDF cho biên lai)
  • Phát hiện loại tệp ở phía server và kiểm tra magic bytes (chữ ký tệp)
  • Đổi tên tệp khi lưu bằng ID ngẫu nhiên; giữ tên gốc chỉ làm metadata
  • Chặn các định dạng rủi ro nếu không thực sự cần (nhất là HTML, SVG, nội dung dạng script)

Nếu byte không khớp định dạng cho phép, từ chối việc tải lên.

What limits should I set to prevent upload-based DoS attacks?

Bởi vì nhiều sự cố là do lạm dụng nhàm chán: quá nhiều upload, tệp quá lớn, hoặc kết nối chậm làm chiếm tài nguyên máy chủ.

Các mặc định hữu ích:

  • Đặt giới hạn kích thước theo tính năng (avatar nhỏ, tài liệu lớn hơn)
  • Thực thi giới hạn ở nhiều lớp (ứng dụng + reverse proxy + timeout)
  • Thêm giới hạn tốc độ theo user và theo IP, siết chặt hơn cho traffic ẩn danh

Xem mỗi byte như chi phí và mỗi request như khả năng lạm dụng.

Should I use signed URLs for uploads, and what’s the safest default?

Có, nhưng phải cẩn trọng. Signed URLs cho phép trình duyệt upload/download trực tiếp tới object storage mà không làm bucket công khai.

Mặc định an toàn:

  • Giữ URL ghi (write) thời hạn ngắn (thường 1–5 phút)
  • Scope mỗi URL cho một object key duy nhất, không phải thư mục
  • Chỉ phát hành URL sau khi kiểm tra quyền
  • Ghi log ai yêu cầu URL và cho tệp nào

Direct-to-storage giúp giảm tải API, nhưng việc đặt phạm vi và thời hạn là bắt buộc.

What’s a safe step-by-step upload flow I can implement?

Mẫu an toàn nhất là:

  1. Tạo một bản ghi upload với trạng thái pending
  2. Upload byte vào vị trí riêng tư
  3. Xác thực kích thước + loại (magic bytes) ở server
  4. Quét (thường bất đồng bộ) rồi chuyển trạng thái sang clean hoặc quarantined
  5. Chỉ cho phép tải xuống/preview khi trạng thái là clean

Điều này ngăn tình trạng “quét thất bại” hoặc “đang xử lý” bị chia sẻ vô tình.

Do I really need malware scanning, and what does “basic scanning” look like?

Quét giúp giảm rủi ro, nhưng không bảo đảm. Dùng nó như một lớp an toàn bổ sung, không phải là biện pháp duy nhất.

Cách thực tế:

  • Cách ly trước: không hiển thị links cho tới khi quét xong
  • Quét bất đồng bộ để có thể mở rộng; hiện trạng “đang xử lý” cho người dùng
  • Nếu quét thất bại hoặc timeout, coi tệp là đáng ngờ và khóa lại
  • Thêm biện pháp với archive (zip bombs, kích thước giải nén lớn) nếu cho phép

Chìa khóa là chính sách: “chưa quét” không bao giờ được hiểu là “sẵn sàng dùng”.

How should I deliver uploaded files safely (headers, domains, downloads)?

Phục vụ tệp theo cách ngăn trình duyệt diễn giải nó như trang web.

Mặc định tốt:

  • Đặt Content-Disposition: attachment cho tài liệu
  • Dùng Content-Type an toàn do server chọn (hoặc application/octet-stream)
  • Dùng khóa object mờ (opaque) chứ không dùng tên tệp của user trong URL
  • Nếu có thể, dùng domain tải xuống riêng cho nội dung người dùng

Điều này giảm nguy cơ một tệp tải lên biến thành trang lừa đảo hoặc thực thi script.

Related posts