File Upload Security: Extension, MIME và Magic Bytes chứng minh được gì?

Security Lab đánh giá extension, MIME, magic bytes, storage và delivery controls: mỗi lớp chứng minh được gì và chưa chứng minh được gì về file upload security.

File upload security evidence for extension MIME magic bytes and non executable storage

Tóm tắt: Kiểm tra extension, MIME type hay magic bytes đều hữu ích, nhưng không phép kiểm nào tự chứng minh một file upload là “an toàn”. Security của upload flow phụ thuộc vào nhiều lớp: loại file được phép, cách nhận diện content, tên file, quyền upload, nơi lưu file, khả năng web server thực thi file, cách file được phục vụ lại cho người dùng và các bước content scanning khi cần.

Phạm vi nghiên cứu: Security Lab này là technical evaluation dựa trên OWASP File Upload Cheat Sheet, PortSwigger Web Security Academy, PHP Fileinfo documentation và WordPress Developer Reference. SecureWebOps không tuyên bố đã tự benchmark tỷ lệ bypass của từng control; phần “Đánh giá của SecureWebOps” là synthesis từ documented behavior và security guidance của các nguồn trên.

Technical question: extension, MIME và magic bytes thực sự chứng minh được gì?

Một upload flow thường bắt đầu bằng câu hỏi “file này có phải JPG/PDF/DOCX hợp lệ không?”. Nhưng security question khó hơn là:

“Một control xác nhận loại file đang chứng minh điều gì — và chưa chứng minh điều gì về khả năng file gây hại?”

SecureWebOps dùng mô hình đánh giá theo năm lớp:

Declared type → Detected type → Content plausibility → Storage/execution context → Delivery/runtime behavior

Đây là framework của SecureWebOps, không phải taxonomy nguyên văn của OWASP hay PortSwigger.

Nguồn và phạm vi đánh giá

Nguồn Dùng để đánh giá Giới hạn
OWASP File Upload Cheat Sheet Defense in depth, extension allowlist, Content-Type, signature, filename, storage, AV/CDR Security guidance tổng quát, không phải benchmark định lượng
PortSwigger Web Security Academy Các failure mode thực tế: blacklist bypass, MIME/content mismatch, hybrid files, executable upload, server config Training/research-oriented examples, không mô tả riêng WordPress
PHP Fileinfo Cách MIME được suy đoán từ content/magic sequences PHP nói rõ đây là heuristic, không phải phương pháp “bullet proof”
WordPress Developer Reference Allowed MIME types, wp_check_filetype_and_ext(), upload handling Chỉ mô tả behavior của WordPress core; plugin/theme có thể thêm upload path riêng

Extension validation chứng minh được gì?

Allowlist tốt hơn blacklist, nhưng extension vẫn chỉ là một signal

OWASP khuyến nghị allowlist những extension thực sự cần cho business thay vì cố liệt kê mọi extension nguy hiểm. PortSwigger cũng chỉ ra blacklist dễ bị bypass bởi alternative extensions, mixed case, double extension hoặc cách parser xử lý filename khác nhau.

Vì vậy extension validation có giá trị ở chỗ:

  • giới hạn attack surface theo business requirement;
  • loại bỏ nhiều loại file không cần thiết ngay từ đầu;
  • giảm khả năng application chấp nhận một executable format hiển nhiên.

Nhưng extension PASS chưa chứng minh content thực sự thuộc loại đó. Một filename có thể nói .jpg trong khi bytes bên trong không phải ảnh hợp lệ, hoặc có thể được xây theo cách nhiều component diễn giải khác nhau.

Vì sao filename parsing quan trọng?

OWASP cảnh báo các failure mode như double extension, null-byte style bypass trong những stack cũ và regex validation kém. PortSwigger cũng mô tả các trường hợp mixed case, encoded separators, trailing characters và alternative executable extensions.

Điểm SecureWebOps quan tâm không phải ghi nhớ một danh sách bypass, mà là nguyên tắc:

Validation chỉ đáng tin khi cùng một representation của filename được normalize và diễn giải nhất quán giữa validation layer, filesystem và runtime layer.

HTTP Content-Type có giá trị đến đâu?

Trong multipart upload, client có thể gửi một Content-Type cho file. OWASP nói rõ không nên tin header này như nguồn xác thực chính vì client có thể spoof nó.

Do đó:

Content-Type PASS có thể hỗ trợ Content-Type PASS chưa chứng minh
Request khai báo một media type phù hợp với policy Bytes thực sự thuộc loại đó
Giúp routing/UX hoặc preliminary validation File không chứa payload độc hại
Có thể dùng như một signal để so chéo với extension/content detection File sẽ không bị runtime xử lý nguy hiểm

Vì vậy SecureWebOps xem client-declared MIME là declared metadata, không phải security proof.

Server-side MIME detection có mạnh hơn không?

PHP Fileinfo cố gắng suy đoán content type và encoding bằng cách tìm các magic-byte sequence ở những vị trí nhất định trong file. Tài liệu PHP nói rõ đây là heuristic hoạt động tốt nhưng không phải phương pháp bullet proof.

Điểm này rất quan trọng vì đôi khi teams chuyển từ việc tin Content-Type sang finfo_file() rồi xem vấn đề đã “được giải quyết”. Thực tế, server-side content detection là signal tốt hơn client declaration, nhưng vẫn chỉ trả lời câu hỏi gần với:

“Nội dung file này trông giống media type nào theo signature/heuristic hiện có?”

Nó không trả lời:

  • file có chứa active content hoặc parser exploit hay không;
  • file có phải polyglot/hybrid file hay không;
  • file có an toàn khi được browser hiển thị inline hay không;
  • file có bị web server execute do location/handler configuration hay không.

Magic bytes / file signature có phải bằng chứng mạnh nhất?

OWASP khuyến nghị signature validation như một lớp bổ sung cho content-type validation, nhưng cảnh báo không dùng signature check một mình vì việc bypass tương đối phổ biến.

Điều này có lý về mặt kỹ thuật: nhiều file format chỉ cần một header/signature đặc trưng để parser nhận diện; việc file bắt đầu bằng đúng magic bytes không có nghĩa toàn bộ nội dung là benign. PortSwigger còn nhắc tới hybrid files có thể mang hơn một semantics, ví dụ file vừa thỏa một format này vừa chứa content được parser khác diễn giải.

Do đó SecureWebOps đánh giá:

Magic-byte match là “content plausibility evidence”, không phải “content safety evidence”.

WordPress thực sự kiểm loại file như thế nào?

WordPress core có danh sách MIME/extension được phép và cho phép điều chỉnh thông qua filter upload_mimes. Trong upload handling, WordPress có thể test size và file type theo danh sách MIME được phép.

Hàm wp_check_filetype_and_ext() cố xác định “real file type” của file. Developer Reference ghi rõ: nếu không xác định được, filename extension có thể được dùng để suy ra type; với images, WordPress còn dùng logic như wp_get_image_mime() để kiểm MIME thực tế và điều chỉnh filename khi extension không khớp.

Điều này cho thấy WordPress core không chỉ dựa vào một header do browser gửi. Nhưng nó cũng không biến mọi upload path trong ecosystem thành an toàn tuyệt đối:

  • plugin/theme có thể triển khai upload handler riêng;
  • plugin có thể thay đổi allowed MIME types;
  • một file được chấp nhận đúng loại vẫn có thể chứa content nguy hiểm cho parser hoặc browser;
  • impact còn phụ thuộc file được lưu và phục vụ như thế nào.

Vì vậy khi audit WordPress, cần kiểm tra actual upload code path, không chỉ Media Library behavior. Một ví dụ mới là CVE-2026-87796 trong Multi Uploader for Gravity Forms, nơi upload handler của plugin có file-type validation không đầy đủ và tạo arbitrary file upload risk.

Đặt upload path vào bức tranh kiểm soát rộng hơn giúp tránh tối ưu một lớp rồi bỏ sót lớp khác. Xem thêm hardening WordPress theo từng lớp, cách giới hạn upload file để giảm nguy cơ webshell và mã độc và checklist bảo mật WordPress trước khi go-live để đối chiếu upload control với quyền, file, cấu hình, logging và validation production.

Storage location thay đổi security outcome như thế nào?

OWASP xếp storage location là một lớp phòng thủ quan trọng: ưu tiên host tách biệt; nếu không thể thì lưu ngoài webroot; nếu buộc lưu trong webroot thì cần quyền và delivery control chặt chẽ. PortSwigger giải thích vì sao điều này quan trọng: một uploaded file chỉ trở thành server-side code execution khi runtime/server configuration xử lý nó như executable content.

Đây là distinction lớn:

File accepted và file executable không phải cùng một trạng thái.

Một PHP payload nằm trong directory mà web server/PHP handler không execute có threat profile khác hẳn cùng payload nằm ở location được FastCGI/PHP handler xử lý.

Non-executable storage tạo ra bằng chứng gì?

Non-executable storage không chứng minh file “sạch”. Nó chứng minh một điều khác: một class attack dựa trên server-side execution bị giảm mạnh hoặc bị chặn bởi runtime boundary.

Nhưng file vẫn có thể nguy hiểm theo các hướng khác:

  • stored XSS/client-side active content;
  • malware download;
  • parser vulnerability khi backend xử lý file;
  • DoS hoặc decompression bomb;
  • path traversal/overwrite nếu filename handling sai.

Vì vậy storage isolation là một control cực mạnh nhưng không phải silver bullet.

Serving uploaded files cũng là một phần của security model

PortSwigger khuyến nghị xem xét cách uploaded content được trả lại cho user: Content-Type, Content-Disposition, và khả năng browser execute HTML/JavaScript trong file. OWASP cũng khuyến nghị dùng accurate Content-Type, X-Content-Type-Options: nosniff và attachment disposition khi phù hợp.

Điều này cho thấy một file upload flow có ít nhất hai attack surfaces:

  1. Ingestion: file đi vào hệ thống.
  2. Consumption: server, parser hoặc browser xử lý file về sau.

Chỉ validate ở ingestion mà không kiểm delivery/runtime behavior có thể bỏ sót client-side hoặc parser-side risk.

Antivirus, sandbox và CDR chứng minh được gì?

OWASP khuyến nghị anti-malware/sandbox khi phù hợp và Content Disarm & Reconstruct (CDR) cho một số format như PDF/DOCX. Các control này thêm bằng chứng về content risk, nhưng SecureWebOps không coi một scan result “clean” là chứng minh tuyệt đối rằng file vô hại.

Lý do là mỗi engine có detection scope, signature/model và file-format support riêng. “No detection” là kết quả của một phép kiểm tại một thời điểm, không phải proof of absence.

File Upload Evidence Ladder

Layer Evidence Chứng minh được gì Chưa chứng minh được gì
1 Extension allowlist PASS Filename thuộc nhóm extension được phép Content thực sự đúng loại
2 Declared MIME matches Client khai báo type phù hợp Client nói thật
3 Server-side MIME / Fileinfo match Bytes phù hợp heuristic/signature của type File vô hại
4 Format/signature/content validation File hợp lý hơn theo parser/policy Không có polyglot, parser exploit hay malicious content
5 Non-executable storage Giảm/chặn server-side execution path tương ứng Không còn client-side/parser/malware risk
6 Controlled delivery Giảm browser execution/content-sniffing risk theo cấu hình File hoàn toàn benign
7 AV/sandbox/CDR Thêm lớp content-risk analysis Zero-risk guarantee

Lưu ý: đây là synthesis của SecureWebOps, không phải maturity model chính thức của OWASP.

Đánh giá của SecureWebOps

1. “File type validation” không đồng nghĩa “file safety validation”

Extension, MIME và signature đều chủ yếu trả lời câu hỏi về identity/plausibility của format. Chúng không tự chứng minh file không chứa malicious content hoặc không kích hoạt behavior nguy hiểm ở parser/runtime.

2. Client-declared MIME là evidence yếu

Vì client có thể tự đặt Content-Type, SecureWebOps chỉ xem nó là metadata để cross-check, không dùng làm gate duy nhất cho security decision.

3. Server-side detection tốt hơn nhưng vẫn là heuristic

PHP Fileinfo tự mô tả cơ chế của mình là heuristic dựa trên magic sequences và không bullet proof. Vì vậy finfo_file() nên là một signal trong pipeline, không phải proof cuối.

4. Storage/execution boundary thường có giá trị security lớn hơn việc cố “nhận diện hoàn hảo”

Nếu threat chính là uploaded PHP/webshell dẫn đến code execution, việc đảm bảo user-controlled upload không nằm trong executable path tạo một independent security boundary. Đây là defense-in-depth: validation có thể sai nhưng runtime vẫn không được phép execute file.

5. Security review phải theo upload lifecycle, không chỉ upload form

SecureWebOps review theo chuỗi:

Who can upload → What is accepted → How it is normalized → Where it is stored → What can execute it → How it is served → What later parses it

Đây là framework editorial của SecureWebOps.

6 sai lầm thường gặp khi đánh giá upload security

  1. Chỉ blacklist .php. Alternative extension và parsing differences có thể làm denylist không đầy đủ.
  2. Tin Content-Type từ browser. Header này có thể bị spoof.
  3. Thấy magic bytes đúng và coi file an toàn. Signature match không chứng minh toàn bộ content benign.
  4. Cho phép upload vào executable webroot. Một lỗi validation nhỏ có thể chuyển thành code execution.
  5. Đổi tên file nhưng không kiểm delivery. Client-side active content hoặc content sniffing vẫn có thể tạo risk.
  6. Scan “clean” rồi coi như zero risk. AV/sandbox chỉ cung cấp thêm evidence, không guarantee tuyệt đối.

Case Study và Security Lab khác nhau ở đây như thế nào?

Case Study Webshell qua File Upload và PHP Execution trong WordPress phân tích một failure pattern cụ thể: attacker đưa file lên và runtime execution biến upload weakness thành server compromise.

Security Lab này không kể lại incident đó. Nó đặt câu hỏi khác: mỗi upload control thực sự chứng minh được gì và boundary nào còn thiếu? Đây là technical evaluation của control semantics.

Khi nào kết luận trong bài này không áp dụng?

  • Bài không benchmark tỷ lệ bypass của extension, MIME, signature hay antivirus engine.
  • PHP Fileinfo behavior phụ thuộc magic database và environment; bài không tuyên bố mọi server cho kết quả giống nhau.
  • WordPress core behavior không đại diện cho mọi upload handler do plugin/theme/custom code triển khai.
  • Một số file format có parser-specific risk riêng; cần review theo format và downstream parser cụ thể.
  • Non-executable storage không loại bỏ client-side, malware, parser hoặc DoS risk.
  • Không có control đơn lẻ nào trong bài được xem là “đủ để secure upload” cho mọi hệ thống.

Kết luận

File type validation chứng minh file trông giống loại được phép; nó không chứng minh file vô hại.

Extension allowlist giảm attack surface. Server-side MIME detection mạnh hơn client declaration. Signature/content validation thêm evidence về format. Nhưng security outcome còn phụ thuộc rất lớn vào storage, execution boundary và cách file được phục vụ hoặc xử lý về sau.

Vì vậy SecureWebOps không đánh giá upload security bằng một checkbox “MIME validated”. Chúng tôi ưu tiên defense-in-depth và review toàn bộ lifecycle từ upload identity tới runtime consumption.

Nếu mục tiêu là hardening upload path trên website production, xem thêm Website Hardening.

References

◇
Cần hỗ trợ kiểm tra và xử lý website?Đội ngũ SecureWebOps sẵn sàng hỗ trợ theo phạm vi phù hợp.
Liên hệ ngay →
Đoàn Trình Dục
Về tác giả Đoàn Trình Dục TD Digital · Website Operations & Security

Đoàn Trình Dục là tác giả nội dung trên SecureWebOps. Các bài viết tập trung vào bảo mật WordPress, giám sát an ninh website, malware/backdoor, hardening, backup/recovery và Incident Response theo hướng thực hành, có bước kiểm tra, bằng chứng, validation và rollback khi phù hợp.

Xem hồ sơ tác giả →