กลับไปหน้าบทความ
#ความปลอดภัยเว็บ#อัปโหลดไฟล์#Object Storage#Signed URL#สแกนไวรัส

คู่มืออัปโหลดไฟล์บนเว็บให้ปลอดภัย สเกลได้ และไม่พังง่าย

การอัปโหลดไฟล์อย่างปลอดภัยไม่ใช่แค่รับไฟล์แล้วบันทึกลงเซิร์ฟเวอร์ แต่ต้องออกแบบตั้งแต่การตรวจชนิดไฟล์ การจำกัดขนาด การสแกนไวรัส ไปจนถึงการดาวน์โหลดแบบมีสิทธิ์ชั่วคราว เพื่อให้ระบบปลอดภัย ดูแลง่าย และรองรับการเติบโตได้จริ

22 มกราคม 2569อ่านประมาณ 2 นาที

แชร์บทความ

คู่มืออัปโหลดไฟล์บนเว็บให้ปลอดภัย สเกลได้ และไม่พังง่าย

คู่มืออัปโหลดไฟล์บนเว็บให้ปลอดภัย สเกลได้ และไม่พังง่าย

การเปิดให้ผู้ใช้อัปโหลดไฟล์เป็นความสามารถที่พบได้บ่อยในระบบสมัยใหม่ ไม่ว่าจะเป็นรูปภาพ เอกสาร ใบสมัคร หรือไฟล์แนบประเภทต่าง ๆ แต่ในมุมของการพัฒนาระบบ ฟีเจอร์นี้ถือเป็นจุดเสี่ยงสำคัญทั้งด้านความปลอดภัย ประสิทธิภาพ และค่าใช้จ่าย หากออกแบบไม่ดี ระบบอาจพังง่าย ถูกโจมตี หรือสร้างภาระในการดูแลระยะยาว

บทความนี้สรุปแนวทางแบบทีละขั้น เพื่อช่วยให้ระบบอัปโหลดไฟล์มีความปลอดภัย ใช้งานได้จริง และขยายระบบได้ในอนาคต

1) วางแนวคิดให้ถูกก่อนเริ่ม

หลักคิดสำคัญที่สุดคือ ห้ามเชื่อใจไฟล์ที่ผู้ใช้ส่งมา และ ไม่ควรเสิร์ฟไฟล์ตรงจากโฟลเดอร์เว็บ เพราะไฟล์เหล่านี้อาจถูกปลอมชนิด ซ่อนโค้ดอันตราย หรือทำให้ระบบทำงานผิดพลาดได้

แนวทางที่แนะนำคือแยกเส้นทางทำงานออกเป็น 2 ช่วงชัดเจน

  • ช่วงรับไฟล์เข้า (ingest) แล้วเก็บแบบดิบ
  • ช่วงตรวจสอบ แปลง สแกน และค่อยปล่อยให้ดาวน์โหลดหรือใช้งาน

การแยกขั้นแบบนี้ช่วยลดความเสี่ยงและทำให้ควบคุมสถานะของไฟล์ได้ง่ายขึ้น

2) จำกัดชนิดไฟล์ให้ถูกวิธี

การดูนามสกุลไฟล์จากชื่อ เช่น .jpg หรือ .pdf อย่างเดียวไม่เพียงพอ เพราะผู้ใช้สามารถเปลี่ยนชื่อไฟล์ได้ง่ายมาก สิ่งที่ควรทำคือ ตรวจ MIME type จากเนื้อไฟล์จริง หรือที่มักอ้างถึงในบริบทของการตรวจสอบไฟล์ว่าเป็นการดูจาก magic bytes

ตัวอย่างเช่น ไฟล์ PNG จะมีลายเซ็นของไฟล์เฉพาะตั้งแต่ส่วนต้นของข้อมูล ซึ่งใช้ตรวจสอบได้ว่าเป็น PNG จริงหรือไม่

แนวทางปฏิบัติที่เหมาะสมคือ

  • ฝั่งหน้าเว็บใช้ accept เพื่อช่วยกรองเบื้องต้น
  • ฝั่งเซิร์ฟเวอร์ต้องตรวจซ้ำทุกครั้ง
  • ใช้ allowlist ของชนิดไฟล์ที่อนุญาตเท่านั้น

ตัวอย่าง allowlist เช่น

  • image/jpeg
  • image/png
  • application/pdf

หากพบว่าไฟล์ไม่อยู่ในรายการที่อนุญาต ควรปฏิเสธทันทีพร้อมข้อความสั้น กระชับ และเข้าใจง่าย

3) จำกัดขนาดไฟล์แบบหลายชั้น

การจำกัดขนาดไฟล์ไม่ใช่แค่เพื่อประสบการณ์ใช้งาน แต่เป็นมาตรการสำคัญในการป้องกันระบบล่มจากการใช้ทรัพยากรเกินจำเป็น ควรกำหนดเพดานให้ชัดเจน เช่น 5MB, 20MB หรือมากกว่านั้นตามลักษณะการใช้งาน

ที่สำคัญคือควรจำกัดพร้อมกัน 3 ชั้น

  1. Client-side เพื่อแจ้งผู้ใช้เร็ว
  2. Server หรือ gateway เพื่อกัน RAM และ CPU ถูกใช้เกิน
  3. Object storage policy เช่น presigned policy เพื่อป้องกันการอัปโหลดเกินขนาด

จุดที่หลายคนพลาดคือระบบอัปโหลดแบบ chunk แม้แต่ละก้อนจะเล็ก แต่เมื่อรวมทั้งหมดแล้วอาจเกินเพดานที่ต้องการได้ ดังนั้นต้องตรวจขนาดรวมด้วยเสมอ

4) เปลี่ยนชื่อไฟล์ใหม่ทุกครั้ง

ไม่ควรใช้ชื่อไฟล์เดิมจากผู้ใช้มาเป็นชื่อไฟล์จริงในระบบ เพราะชื่อเดิมอาจมีอักขระแปลก มีปัญหาเรื่อง path traversal หรือทำให้ URL และระบบจัดเก็บเกิดความผิดพลาดได้

แนวทางที่ปลอดภัยกว่า คือ

  • ตั้งชื่อใหม่ทุกครั้งด้วย UUID หรือ ULID
  • เก็บนามสกุลตามชนิดไฟล์ที่ตรวจแล้วเท่านั้น
  • แยก display name ไว้ในฐานข้อมูลเพื่อใช้แสดงผล
  • ไม่ใช้ชื่อที่ผู้ใช้ส่งมาเป็น storage key จริง

โครงสร้างที่แนะนำ เช่น

  • tenant/user/date/uuid.ext

วิธีนี้ช่วยทั้งเรื่องความปลอดภัย ความเป็นระเบียบ และการจัดการไฟล์ในระยะยาว

5) ใช้ Object Storage แทนดิสก์ของเครื่องเซิร์ฟเวอร์

การเก็บไฟล์ลงดิสก์ของเครื่องแอปพลิเคชันโดยตรงอาจเริ่มต้นง่าย แต่เมื่อระบบโตขึ้นจะมีปัญหาตามมามาก เช่น ดิสก์เต็ม ย้ายเครื่องลำบาก หรือรองรับหลายเครื่องได้ไม่ดี

การใช้ Object Storage มีข้อดีชัดเจน เช่น

  • สเกลได้ง่าย
  • ไม่ผูกกับเครื่องใดเครื่องหนึ่ง
  • จัดการ lifecycle ได้ เช่น ลบไฟล์ชั่วคราวอัตโนมัติ
  • เปิด versioning เพื่อป้องกันการทับไฟล์โดยไม่ตั้งใจ

ข้อแนะนำสำคัญคือ ตั้ง bucket ให้เป็น private เป็นค่าเริ่มต้นทั้งหมด แล้วจึงค่อยเปิดสิทธิ์การเข้าถึงเฉพาะกรณีที่จำเป็น

6) ลดโหลดเซิร์ฟเวอร์ด้วย Direct Upload และ Pre-signed URL

แนวทางที่มีประสิทธิภาพมากคือให้เซิร์ฟเวอร์ออก URL ชั่วคราวสำหรับอัปโหลด แล้วให้ผู้ใช้อัปโหลดไฟล์ตรงเข้า Object Storage โดยไม่ต้องวิ่งผ่านตัวแอปเซิร์ฟเวอร์เต็ม ๆ

หน้าที่ของเซิร์ฟเวอร์จึงเหลือเพียง

  • ตรวจสิทธิ์ของผู้ใช้
  • กำหนด policy เช่น ชนิดไฟล์ ขนาดไฟล์ และเวลาหมดอายุ
  • บันทึก metadata ที่จำเป็น

ข้อดีของวิธีนี้คือ

  • ลดภาระเซิร์ฟเวอร์ในการรับไฟล์ก้อนใหญ่
  • ลดโอกาส timeout
  • ลดค่า bandwidth ผ่านแอปพลิเคชัน

โดยทั่วไปควรกำหนดเวลาหมดอายุของ URL สั้น ๆ เช่น 60–300 วินาที

7) สแกนไวรัสแบบแยกกระบวนการ

ไม่ควรสแกนไวรัสใน request เดียวกับการอัปโหลด เพราะจะทำให้ช้าและเกิด timeout ได้ง่าย วิธีที่เหมาะสมกว่าคือใช้กระบวนการแบบ asynchronous

ตัวอย่าง flow ที่ดีคือ

  1. อัปโหลดไฟล์เข้าโซน quarantine
  2. ส่ง event หรือ queue ให้ worker มาดึงไฟล์ไปสแกน
  3. ถ้าผ่าน จึงย้ายหรือคัดลอกไปโซน clean และอัปเดตสถานะในฐานข้อมูล

เครื่องมือที่นิยมใช้ ได้แก่

  • ClamAV
  • บริการสแกนจากผู้ให้บริการคลาวด์

หากไฟล์ไม่ผ่านการตรวจ ควรลบทันทีและบันทึกเหตุการณ์ไว้เพื่อใช้ตรวจสอบย้อนหลังหรือทำ audit

8) ดาวน์โหลดผ่าน Signed URL แทนการเปิด public

การเปิดไฟล์ให้เข้าถึงแบบสาธารณะโดยตรงเป็นความเสี่ยงที่สูงมาก ทางเลือกที่ปลอดภัยกว่าคือเก็บไฟล์ไว้แบบ private แล้วออก Signed URL สำหรับดาวน์โหลดเป็นครั้งคราว

แนวทางนี้ช่วยให้กำหนดสิทธิ์ได้ละเอียดขึ้น เช่น

  • จำกัดเวลาหมดอายุ 1–10 นาที
  • บังคับให้ดาวน์โหลดด้วย content-disposition=attachment
  • ตั้งค่า cache-control ตามประเภทไฟล์

ผลลัพธ์คือไฟล์ยังอยู่ในพื้นที่ปลอดภัย แต่ผู้ใช้ที่มีสิทธิ์ก็ยังเข้าถึงได้สะดวก

9) ป้องกันไฟล์ที่อาจรันบนเบราว์เซอร์

ไฟล์บางชนิด เช่น SVG หรือ HTML อาจกลายเป็นช่องทางโจมตีผ่านเบราว์เซอร์ได้ หากเสิร์ฟออกไปโดยไม่มีมาตรการป้องกันที่เหมาะสม

สิ่งที่ควรตั้งค่าเมื่อเสิร์ฟไฟล์ผ่าน CDN หรือ proxy ได้แก่

  • X-Content-Type-Options: nosniff
  • Content-Security-Policy สำหรับหน้า preview
  • แยกโดเมนสำหรับเสิร์ฟไฟล์ออกจากโดเมนแอป เช่น files.example.com

วิธีนี้ช่วยลดความเสี่ยงจาก XSS และป้องกันไม่ให้ไฟล์บางประเภทถูกรันในบริบทเดียวกับแอปหลัก

10) เก็บ Metadata ให้ครบเพื่อการดูแลรักษา

การเก็บข้อมูลประกอบของไฟล์อย่างครบถ้วนจะช่วยให้แก้ปัญหาได้ง่าย ตรวจสอบย้อนหลังได้ และทำระบบจัดการไฟล์ได้ดีขึ้น

ข้อมูลอย่างน้อยที่ควรเก็บ เช่น

  • original_name
  • detected_mime
  • size
  • checksum (SHA-256)
  • uploader_id
  • storage_key
  • status เช่น uploaded, scanning, clean, rejected
  • created_at
  • expires_at หากมี

โดยเฉพาะ checksum มีประโยชน์มากทั้งในการตรวจความถูกต้องของไฟล์ และช่วยตรวจจับไฟล์ซ้ำ

11) ใส่ Rate Limit และป้องกันบอท

ถึงแม้ระบบจะตรวจไฟล์เข้มแค่ไหน แต่หากไม่มีการจำกัดอัตราการใช้งาน ก็ยังเสี่ยงถูก spam หรือถูกใช้จนค่า storage และค่าโครงสร้างพื้นฐานพุ่งสูงได้

มาตรการที่ควรมี ได้แก่

  • จำกัดจำนวนอัปโหลดต่อผู้ใช้หรือ IP ภายในช่วงเวลา
  • ใช้ CAPTCHA เฉพาะจุดที่มีความเสี่ยง เช่น guest upload
  • เฝ้าระวังรูปแบบการใช้งานผิดปกติ

ขั้นตอนนี้ช่วยปิดช่องทางโจมตีเชิงปริมาณและควบคุมต้นทุนได้ดีขึ้น

12) ตัวอย่าง Flow ที่ใช้งานได้จริง

รูปแบบการทำงานที่พบได้บ่อยและเหมาะกับระบบจริงมีลำดับประมาณนี้

  1. แอปขอ upload ticket จาก API
  2. API ตรวจสิทธิ์และออก presigned upload URL พร้อมบันทึกสถานะ uploaded
  3. Client อัปโหลดตรงเข้า Object Storage
  4. Storage ส่ง event ไปยัง queue
  5. Worker สแกนไวรัสและตรวจชนิดไฟล์ซ้ำ
  6. หากผ่าน จึง mark เป็น clean และออก signed download URL เมื่อมีการร้องขอ

Flow นี้ทำให้ระบบแบ่งหน้าที่ชัดเจน ลดความเสี่ยง และขยายการทำงานได้ง่าย

13) ข้อผิดพลาดที่พบบ่อยและควรหลีกเลี่ยง

มีหลายจุดที่มักถูกมองข้ามในการทำระบบอัปโหลดไฟล์ เช่น

  • เชื่อ MIME type จาก header ที่เบราว์เซอร์ส่งมา
  • เปิด bucket เป็น public แล้วหวังว่าการซ่อน URL จะปลอดภัย
  • ให้ผู้ใช้กำหนด path เอง แม้จะพยายาม sanitize แล้วก็ตาม

ข้อควรจำคือข้อมูลจากฝั่งผู้ใช้เชื่อถือไม่ได้เสมอ และการออกแบบโครงสร้างการจัดเก็บที่ดีตั้งแต่ต้นจะช่วยลดปัญหาได้มากกว่าการค่อย ๆ อุดช่องโหว่ภายหลัง

สรุป

การอัปโหลดไฟล์อย่างปลอดภัยต้องคิดเป็นระบบ ตั้งแต่การไม่เชื่อใจไฟล์ต้นทาง การตรวจชนิดไฟล์จากเนื้อจริง การจำกัดขนาดหลายชั้น การเปลี่ยนชื่อไฟล์ใหม่ การใช้ Object Storage แบบ private การอัปโหลดตรงผ่าน pre-signed URL การสแกนไวรัสแบบ async ไปจนถึงการดาวน์โหลดด้วย signed URL และการเก็บ metadata อย่างครบถ้วน

หากทำครบตามแนวทางเหล่านี้ ระบบอัปโหลดไฟล์จะไม่เพียงปลอดภัยขึ้น แต่ยังสเกลได้ดี ดูแลง่าย และดีบักสะดวกขึ้นอย่างมากในระยะยาว