Chương 09: File Upload Security
Kiểm định tệp tải lên nhiều lớp: Whitelist định dạng, phát hiện MIME thực bằng Apache Tika, xác thực Magic Bytes, đổi tên UUID và lưu trữ ngoài Web Root.
#01 File Extension Validation (Kiểm tra phần mở rộng tệp)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Bạn chỉ cho phép người mang quốc tịch 'Việt Nam' vào tham quan. Kẻ gian mang hộ chiếu ghi tên: 'NguyenVanA.php.jpg' hoặc 'shell.pHp' hoặc 'shell.phtml'. Nếu người gác cổng chỉ nhìn lướt qua thấy chữ '.jpg' ở cuối hoặc không để ý chữ hoa chữ thường, kẻ gian sẽ lọt vào và phóng hỏa tòa nhà.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): File Extension Validation (Kiểm tra phần mở rộng tệp)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
shell.php.jpg (Double Extension)malware.PHP5 (Bypass hoa thường)avatar.png (Hợp lệ)
⚠️ Lỗi phân tích đuôi file: Web server Apache cấu hình cũ xử lý file có nhiều đuôi sẽ ưu tiên thực thi mã PHP trong file 'shell.php.jpg'!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// NGUY HIỂM: Dùng danh sách đen (Blacklist) để cấm phần mở rộng
String ext = filename.substring(filename.lastIndexOf(".") + 1);
if (ext.equalsIgnoreCase("exe") || ext.equalsIgnoreCase("sh")) {
throw new IllegalArgumentException("File bị cấm"); // Bỏ sót .jsp, .jspx, .php, .phtml!
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Dùng danh sách trắng (Whitelist) nghiêm ngặt
private static final Set ALLOWED_EXTS = Set.of("jpg", "jpeg", "png", "pdf");
public void validateExtension(String filename) {
String ext = FilenameUtils.getExtension(filename).toLowerCase();
if (!ALLOWED_EXTS.contains(ext)) {
throw new SecurityException("Định dạng file không được hỗ trợ");
}
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Kỹ thuật 'Double Extension' (ví dụ: avatar.php.png) khai thác điểm yếu gì của máy chủ web?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#02 MIME Type Validation (Xác thực kiểu MIME)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
MIME Type do client gửi lên giống như lời tự khai của một người lạ ở cổng kiểm soát: 'Tôi là đại sứ thiện chí hòa bình' (Content-Type: image/png). Bạn không bao giờ được tin lời tự khai đó, vì chính kẻ tấn công có thể chỉnh sửa header này tùy ý bằng Burp Suite trước khi gửi.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): MIME Type Validation (Xác thực kiểu MIME)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Giả mạo Content-Type: image/pngContent-Type: application/x-phpContent-Type đúng chuẩn
⚠️ Tin tưởng Content-Type từ Client: Hacker gửi file backdoor.php nhưng sửa header thành Content-Type: image/jpeg và vượt qua vòng kiểm tra!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// NGUY HIỂM: Tin tưởng multipartFile.getContentType() do trình duyệt gửi lên
if ("image/png".equals(file.getContentType())) {
// Kẻ gian sửa HTTP Header Content-Type thành image/png nhưng gửi file mã độc .jsp!
saveFile(file);
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Dùng Apache Tika đọc cấu trúc byte thực tế của tệp
Tika tika = new Tika();
String detectedMime = tika.detect(file.getInputStream());
if (!List.of("image/jpeg", "image/png").contains(detectedMime)) {
throw new SecurityException("Kiểu MIME không hợp lệ: " + detectedMime);
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Vì sao việc chỉ kiểm tra header Content-Type do trình duyệt gửi lên lại KHÔNG ĐƯỢC coi là một biện pháp bảo mật hợp lệ?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#03 File Signature & Magic Bytes (Chữ ký tệp & Byte ma thuật)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Dù kẻ trộm có cải trang thành công chúa, ADN của hắn vẫn là ADN của một tên trộm. Magic Bytes chính là 'ADN' của tập tin: Đó là những byte dữ liệu bất biến nằm ở đầu tệp tin do tiêu chuẩn quốc tế quy định (ví dụ: File JPEG luôn bắt đầu bằng FF D8 FF, file PNG luôn bắt đầu bằng 89 50 4E 47 0D 0A 1A 0A). Kẻ xấu đổi tên file thành .jpg nhưng Magic Bytes vẫn lộ nguyên hình là mã độc PHP hoặc ELF Binary.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): File Signature & Magic Bytes (Chữ ký tệp & Byte ma thuật)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Chữ ký PNG: 89 50 4E 47Chữ ký PHP: 3C 3F 70 68 70Chữ ký PDF: 25 50 44 46
⚠️ File có tên avatar.jpg nhưng 4 byte đầu tiên là 3C 3F 70 68 70 ('
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// NGUY HIỂM: Chỉ kiểm tra tên file mà không đọc các byte đầu tiên
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: So khớp Magic Bytes đầu tệp
byte[] magic = file.getInputStream().readNBytes(4);
// PNG Magic Bytes: 89 50 4E 47
if (!(magic[0] == (byte)0x89 && magic[1] == 0x50 && magic[2] == 0x4E && magic[3] == 0x47)) {
throw new SecurityException("Chữ ký tệp không khớp chuẩn PNG");
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Chuỗi byte ma thuật (Magic Bytes) nào sau đây đặc trưng cho định dạng tệp ảnh PNG?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#04 File Size Validation (Giới hạn kích thước tệp)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Bạn cho phép khách gửi quà tặng qua bưu điện. Một kẻ xấu gửi đến nhà bạn một kiện hàng chứa 100 tấn đất đá (file zip dung lượng 50GB hoặc Decompression Bomb). Nếu bạn không kiểm tra trọng lượng ở cửa nhận hàng, cả ngôi nhà bạn sẽ sập vì quá tải.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): File Size Validation (Giới hạn kích thước tệp)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
File ảnh: 2.5 MB (Hợp lệ)File video khổng lồ: 2 GBDecompression Bomb: 42 KB giải nén ra 4.5 TB
⚠️ Tệp vượt kích thước: 500 MB được tải thẳng vào RAM máy chủ làm cạn kiệt bộ nhớ và gây Crash tiến trình!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// NGUY HIỂM: Không giới hạn kích thước upload -> Bị DoS tràn ổ đĩa
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Cấu hình giới hạn trong application.properties
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=10MB
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Thuật ngữ 'Zip Bomb' (hoặc Decompression Bomb) ám chỉ loại tấn công nào liên quan đến kích thước tệp?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#05 File Naming & Path Traversal (Đổi tên tệp & Chống ghi đè)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Khách gửi đồ và tự đặt tên gói hàng là: '../../../../etc/passwd' hoặc 'index.php'. Nếu bạn lưu gói đồ đúng theo tên khách đặt, gói hàng đó sẽ đè bẹp và xóa sạch các file hệ thống sống còn của bạn. Quy tắc vàng: 'Khách mang đồ đến, bạn cất đồ của họ vào một chiếc hộp mới toanh được dán mã số ngẫu nhiên UUID do chính bạn tạo ra'.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): File Naming & Path Traversal (Đổi tên tệp & Chống ghi đè)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
../../../../etc/shadowavatar