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

การอัปโหลดไฟล์ดูไร้พิษภัย: รูปโปรไฟล์, PDF, สเปรดชีต แต่บ่อยครั้งมันเป็นจุดเริ่มต้นของเหตุการณ์ด้านความปลอดภัยเพราะมันให้คนแปลกหน้าส่ง "กล่องปริศนา" เข้ามาในระบบของคุณ ถ้าคุณรับ มัน เก็บ แล้วแสดงกลับให้คนอื่น คุณก็สร้างช่องทางโจมตีใหม่ให้แอปของคุณ
ความเสี่ยงไม่ได้มีเพียงแค่ว่า "มีคนอัปโหลดไวรัส" ไฟล์ที่ไม่ถูกต้องอาจทำให้ข้อมูลส่วนตัวรั่วไหล ค่าที่เก็บข้อมูลพุ่งสูง หรือล่อลวงผู้ใช้ให้เปิดสิทธิ์เข้า ระบบ อาจมีชื่อไฟล์ว่า “invoice.pdf” แต่จริงๆ แล้วไม่ใช่ PDF เลย แม้แต่ PDF และภาพที่แท้จริงก็อาจก่อปัญหาได้ถ้าแอปของคุณไว้วางใจเมตาดาต้า พรีวิวเนื้อหาโดยอัตโนมัติ หรือให้บริการด้วยกฎที่ไม่ปลอดภัย
ความล้มเหลวจริงๆ มักเป็นแบบนี้:
รายละเอียดข้อหนึ่งที่ทำให้เกิดเหตุการณ์เยอะ: การเก็บไฟล์ไม่เท่ากับการเสิร์ฟไฟล์ การเก็บคือที่เก็บไบต์ ส่วนการเสิร์ฟคือวิธีที่ไบต์เหล่านั้นถูกส่งถึงเบราว์เซอร์และแอป เรื่องจะไปผิดพลาดเมื่อแอปเสิร์ฟไฟล์ผู้ใช้ด้วยระดับความเชื่อถือเดียวกับเว็บไซต์หลัก ทำให้เบราว์เซอร์มองว่าไฟล์นั้น "เชื่อถือได้"
"ปลอดภัยพอ" สำหรับแอปขนาดเล็กหรือกำลังเติบโตมักหมายถึงคุณตอบคำถามสี่ข้อได้โดยไม่ต้องเว้นวรรค: ใครอัปโหลดได้, รับอะไรบ้าง, ขนาดและความถี่เท่าไหร่, และใครอ่านได้ภายหลัง แม้คุณจะสร้างเร็วก็ตาม (ด้วยโค้ดสร้างอัตโนมัติหรือแพลตฟอร์มขับเคลื่อนด้วยแชท) กฎพวกนี้ยังสำคัญเสมอ
ถือว่าการอัปโหลดทุกชิ้นเป็นอินพุตที่ไม่น่าเชื่อถือ วิธีปฏิบัติที่ใช้งานได้จริงคือมองว่าใครอาจเอาเปรียบมันและ "เป้าหมาย" ของเขาคืออะไร
ผู้โจมตีส่วนใหญ่เป็นบอทที่สแกนหาแบบฟอร์มอัปโหลดอ่อนแอ หรือผู้ใช้จริงที่ผลักขอบเขตเพื่อขอที่เก็บฟรี ดึงข้อมูล หรือก่อกวนบางครั้งอาจเป็นคู่แข่งที่ลองหาช่องโหว่เพื่อขโมยข้อมูลหรือทำให้เกิดการล่ม
เป้าหมายของพวกเขามักเป็นหนึ่งในผลลัพธ์เหล่านี้:
จากนั้นพิจารณาจุดอ่อน: endpoint อัปโหลดเป็นประตูหน้า (ไฟล์เกินขนาด ฟอร์แมตแปลก ค่าเรียกร้องสูง) ที่เก็บคือห้องเก็บของหลัง (บัคเก็ตสาธารณะ สิทธิ์ผิดพลาด โฟลเดอร์แชร์) URL ดาวน์โหลดคือทางออก (คาดเดาได้ ยาวนาน หรือไม่ผูกกับผู้ใช้)
ตัวอย่าง: ฟีเจอร์ "อัปโหลดเรซูเม่" บอทอัปโหลด PDF ขนาดใหญ่เป็นพันไฟล์เพื่อให้ค่าใช้จ่ายพุ่ง ในขณะที่ผู้ใช้ร้ายกาจอัปโหลดไฟล์ HTML แล้วแชร์เป็น "เอกสาร" หลอกคนอื่น
ก่อนเพิ่มการควบคุม ให้ตัดสินใจก่อนว่าอะไรสำคัญที่สุดสำหรับแอปของคุณ: ความเป็นส่วนตัว (ใครอ่านได้), ความพร้อมให้บริการ (ยังให้บริการได้ไหม), ค่าใช้จ่าย (พื้นที่และแบนด์วิดท์), และการปฏิบัติตามกฎ (ข้อมูลเก็บที่ไหน เก็บนานเท่าไหร่) ลำดับความสำคัญนี้ช่วยให้การตัดสินใจสอดคล้องกัน
เหตุการณ์การอัปโหลดส่วนใหญ่ไม่ใช่การแฮ็กหรูหรา แต่เป็นบั๊กง่ายๆ แบบ "ฉันเห็นไฟล์ของคนอื่นได้" ถือว่าสิทธิ์เป็นส่วนหนึ่งของการอัปโหลด ไม่ใช่ฟีเจอร์ที่ต่อเติมทีหลัง
เริ่มจากกฎเดียว: ปฏิเสธโดยค่าเริ่มต้น สมมติว่าอ็อบเจ็กต์ที่อัปโหลดทุกชิ้นเป็นส่วนตัวจนกว่าจะอนุญาตอย่างชัดเจน "ตั้งเป็นส่วนตัวโดยค่าเริ่มต้น" เป็นฐานที่แข็งแกร่งสำหรับใบแจ้งหนี้ เอกสารทางการแพทย์ เอกสารบัญชี และสิ่งใดที่เกี่ยวข้องกับผู้ใช้ ทำให้ไฟล์เป็นสาธารณะก็ต่อเมื่อผู้ใช้คาดหวังอย่างชัดเจน (เช่น อวาตาร์สาธารณะ) และแม้แต่กรณีนั้นให้พิจารณาการเข้าถึงแบบจำกัดเวลา
ให้บทบาทเรียบง่ายและแยกจากกัน การแยกที่พบบ่อยคือ:
อย่าเชื่อถือกฎแบบโฟลเดอร์ เช่น "ทุกอย่างใน /user-uploads/ ปลอดภัย" ให้ตรวจสอบความเป็นเจ้าของหรือการเข้าถึงแบบ tenant ตอนอ่านทุกครั้ง นั่นปกป้องเมื่อคนเปลี่ยนทีม ออกจากองค์กร หรือไฟล์ถูกย้าย
รูปแบบการซัพพอร์ตที่ดีคือเข้มงวดและชั่วคราว: ให้สิทธิ์กับไฟล์หนึ่งชิ้น บันทึกเหตุผล และหมดอายุอัตโนมัติ
การโจมตีการอัปโหลดส่วนใหญ่เริ่มจากเทคนิคง่ายๆ: ไฟล์ที่ดูปลอดภัยจากชื่อหรือ header ของเบราว์เซอร์ แต่จริงๆ แล้วไม่ใช่สิ่งนั้น ถือทุกสิ่งที่ลูกค้าส่งว่าไม่น่าเชื่อถือ
เริ่มด้วย allowlist: ตัดสินใจว่าฟอร์แมตใดบ้างที่ยอมรับ (เช่น .jpg, .png, .pdf) และปฏิเสธที่เหลือ หลีกเลี่ยง "ทุกภาพ" หรือ "เอกสารใดก็ได้" เว้นแต่คุณต้องการจริงๆ
อย่าเชื่อชื่อไฟล์หรือ header Content-Type จากฝั่งไคลเอนต์ ทั้งคู่ปลอมได้ง่าย ไฟล์ชื่อ invoice.pdf อาจเป็น executable และ Content-Type: image/png ก็หลอกได้
วิธีที่เข้มแข็งกว่าคือการตรวจไบต์แรกของไฟล์ ซึ่งมักเรียกว่า "magic bytes" หรือ signature ของไฟล์ ฟอร์แมตที่พบบ่อยหลายชนิดมี header ที่สอดคล้องกัน (เช่น PNG และ JPEG) ถ้า header ไม่ตรงกับที่อนุญาต ให้ปฏิเสธ
การตั้งค่าการตรวจที่ใช้งานได้จริง:
การเปลี่ยนชื่อมีความสำคัญมากกว่าที่คิด ถ้าคุณเก็บชื่อที่ผู้ใช้ให้มาทันที คุณจะชวนให้เกิดการใช้ path tricks ตัวอักษรแปลก และการเขียนทับโดยไม่ตั้งใจ ใช้ ID ที่สร้างโดยระบบสำหรับการเก็บและเก็บชื่อเดิมเพื่อแสดงเท่านั้น
สำหรับรูปโปรไฟล์ ยอมรับเฉพาะ JPEG และ PNG ตรวจ header และลบเมตาดาต้าถ้ามี สำหรับเอกสาร ให้พิจารณาจำกัดเฉพาะ PDF และปฏิเสธสิ่งที่มีเนื้อหาเชิงโต้ตอบ ถ้าคุณตัดสินใจรองรับ SVG หรือ HTML ต่อมา ให้ถือว่ามันอาจรันโค้ดและแยกเก็บไว้ให้ปลอดภัย
การล่มจากการอัปโหลดส่วนใหญ่ไม่ใช่การแฮ็กหรูหรา แต่เป็นไฟล์ใหญ่ คำขอมาก หรือการเชื่อมต่อช้าที่ผูกทรัพยากรเซิร์ฟเวอร์จนแอปรู้สึกเหมือนล่ม ถือทุกไบต์ว่าเป็นต้นทุน
เลือกขนาดสูงสุดตามฟีเจอร์ ไม่ใช่ตัวเลขเดียวทั่วแอป อวาตาร์ไม่ต้องการขีดจำกัดเท่ากับเอกสารทางภาษีหรือวิดีโอสั้น ตั้งขนาดเล็กที่สุดที่ยังเป็นปกติ แล้วแยกเส้นทางอัปโหลดสำหรับไฟล์ใหญ่เมื่อจำเป็น
บังคับใช้ขีดจำกัดในหลายจุด เพราะไคลเอนต์โกหกได้: ในตรรกะแอป ที่เว็บเซิร์ฟเวอร์หรือ reverse proxy ด้วยไทม์เอาต์การอัปโหลด และปฏิเสธแต่เนิ่นๆ เมื่อขนาดที่ระบุเกิน (ก่อนอ่านตัวเนื้อหาเต็ม)
ตัวอย่างชัดเจน: อวาตาร์จำกัดที่ 2 MB, PDF ที่ 20 MB, และไฟล์ที่ใหญ่กว่านั้นต้องเส้นทางพิเศษ (เช่น direct-to-object-storage ด้วย signed URL)
ไฟล์เล็กก็ทำให้เกิด DoS ได้ถ้าใครบางคนอัปโหลดวนไปมา เพิ่ม rate limits ที่ endpoint อัปโหลดต่อผู้ใช้และต่อ IP พิจารณาเข้มงวดขึ้นสำหรับทราฟฟิกที่ไม่ได้ล็อกอิน
การอัปโหลดแบบ resumable ช่วยผู้ใช้จริงในเครือข่ายไม่ดี แต่โทเค็นเซสชันต้องเข้มงวด: หมดอายุสั้น ผูกกับผู้ใช้ และผูกกับขนาดไฟล์และปลายทางเฉพาะ มิฉะนั้น endpoint “resume” จะกลายเป็นทางเข้าฟรีไปยังที่เก็บของคุณ
เมื่อบล็อกการอัปโหลด ให้ส่งข้อผิดพลาดที่ชัดเจนแก่ผู้ใช้ (ไฟล์ใหญ่เกิน คำขอมากเกิน) แต่ไม่เปิดเผยข้อมูลภายใน (stack traces, ชื่อบัคเก็ต, รายละเอียดผู้ให้บริการ)
การอัปโหลดที่ปลอดภัยไม่ใช่แค่สิ่งที่คุณรับ แต่รวมถึงที่ไฟล์ไปและวิธีที่คุณส่งคืนด้วย
เก็บไบต์การอัปโหลดให้พ้นจากฐานข้อมูลหลักของคุณ แอปส่วนใหญ่ต้องการ metadata ใน DB เท่านั้น (owner user ID, ชื่อไฟล์เดิม, ชนิดที่ตรวจเจอ, ขนาด, checksum, storage key, เวลาสร้าง) เก็บไบต์ใน object storage หรือบริการไฟล์ที่ออกแบบมาสำหรับบล็อบขนาดใหญ่
แยกไฟล์สาธารณะและส่วนตัวที่ระดับที่เก็บ ใช้บัคเก็ตหรือตู้คอนเทนเนอร์ต่างกันพร้อมกฎต่างกัน ไฟล์สาธารณะ (เช่น อวาตาร์สาธารณะ) สามารถอ่านได้โดยไม่ต้องล็อกอิน ไฟล์ส่วนตัว (สัญญา ใบแจ้งหนี้ เอกสารทางการแพทย์) ไม่ควรอ่านได้สาธารณะ แม้จะมีคนเดา URL ก็ตาม
หลีกเลี่ยงการเสิร์ฟไฟล์ผู้ใช้จากโดเมนเดียวกับแอปของคุณเมื่อเป็นไปได้ หากไฟล์เสี่ยงหลุดผ่าน (HTML, SVG ที่มีสคริปต์, หรือปัญหา MIME sniffing ของเบราว์เซอร์) การโฮสต์บนโดเมนหลักอาจกลายเป็นการยึดบัญชี การแยกโดเมนดาวน์โหลดหรือโดเมนที่เก็บจำกัดขอบเขตผลกระทบ
เมื่อดาวน์โหลด ให้บังคับ header ที่ปลอดภัย กำหนด Content-Type ที่คาดการณ์ได้ตามสิ่งที่คุณอนุญาต ไม่ใช่ที่ผู้ใช้ระบุ สำหรับไฟล์ที่เบราว์เซอร์อาจตีความ ให้ส่งเป็นการดาวน์โหลด
ค่าปกติที่ช่วยป้องกันความประหลาดใจ:\n\n- ใช้ Content-Disposition: attachment สำหรับเอกสาร\n- ใช้ Content-Type ที่ปลอดภัย (หรือ application/octet-stream)\n- เก็บและเสิร์ฟด้วยคีย์วัตถุที่ไม่โปร่งใส (ไม่ใช่ชื่อไฟล์ผู้ใช้)\n- บันทึกการดาวน์โหลดไฟล์ส่วนตัว\n\nการเก็บข้อมูลระยะยาวก็เป็นเรื่องความปลอดภัย ลบการอัปโหลดที่ถูกทิ้ง รื้อเวอร์ชันเก่าหลังจากเปลี่ยน และตั้งเวลาหมดอายุสำหรับไฟล์ชั่วคราว ข้อมูลที่เก็บน้อยลงหมายถึงสิ่งที่อาจรั่วไหลก็น้อยลง
Signed URLs (หรือ pre-signed URLs) เป็นวิธีที่พบบ่อยในการให้ผู้ใช้สามารถอัปโหลดหรือดาวน์โหลดไฟล์โดยไม่ต้องทำให้บัคเก็ตสาธารณะ และไม่ต้องส่งทุกไบต์ผ่าน API ของคุณ URL จะถือสิทธิชั่วคราวแล้วหมดอายุ
สองฟลอว์ที่พบบ่อย:\n\n- Direct-to-storage upload: แอปของคุณออก signed URL ระยะสั้น แล้วเบราว์เซอร์อัปโหลดตรงไปยัง object storage\n- Upload-through-server: ไฟล์ผ่าน API ของคุณก่อน แล้วเซิร์ฟเวอร์ของคุณเก็บมัน
การอัปโหลดตรงลดภาระ API แต่ทำให้กฎที่เก็บและข้อจำกัดของ URL สำคัญขึ้น
ถือ signed URL ราวกับกุญแจใช้ครั้งเดียว ทำให้มันเฉพาะและหมดอายุเร็ว\n\n- ให้ write URLs หมดอายุเร็ว (มัก 1–5 นาที) อ่าน URL ก็ตั้งเป็นนาที ไม่ใช่วัน\n- ผูก URL กับ object key ที่คาดไว้ (หนึ่งอ็อบเจ็กต์ ไม่ใช่โฟลเดอร์)\n- เพิ่มข้อจำกัดเมื่อระบบรองรับ: Content-Type ที่คาดหวัง ขนาดสูงสุด checksum\n- ออก URL เฉพาะหลังการตรวจสิทธิ์\n- บันทึกว่าผู้ใดร้องขอ URL และทำไม (user ID, object key, จุดประสงค์, IP/user agent)\n\nรูปแบบปฏิบัติได้จริงคือสร้างระเบียนอัปโหลดก่อน (สถานะ: pending) แล้วออก signed URL หลังอัปโหลด ยืนยันว่าอ็อบเจ็กต์มีอยู่และตรงกับขนาด/ชนิดที่คาดไว้ก่อนเปลี่ยนเป็น ready
ฟลอว์ที่ปลอดภัยส่วนใหญ่คือกฎชัดเจนและสถานะชัดเจน ถือทุกการอัปโหลดว่าไม่น่าเชื่อถือจนกว่าจะตรวจสอบเสร็จ
เขียนลงว่าแต่ละฟีเจอร์อนุญาตอะไร อวาตาร์และเอกสารภาษีไม่ควรมีชนิดไฟล์ ขีดจำกัดขนาด หรือระดับการมองเห็นเหมือนกัน
กำหนดชนิดที่อนุญาตและขีดจำกัดขนาดต่อฟีเจอร์ (เช่น: รูปถ่ายสูงสุด 5 MB; PDF สูงสุด 20 MB) บังคับใช้กฎเดียวกันที่ฝั่งแบ็คเอนด์
สร้าง "ระเบียนอัปโหลด" ก่อนที่ไบต์จะมาถึง เก็บ: เจ้าของ (user หรือ org), จุดประสงค์ (avatar, invoice, attachment), ชื่อไฟล์เดิม, ขนาดสูงสุดที่คาดไว้, และสถานะเช่น pending
อัปโหลดไปยังที่เก็บแบบส่วนตัว อย่าให้ไคลเอนต์เลือกพาธสุดท้าย
ตรวจอีกครั้งฝั่งเซิร์ฟเวอร์: ขนาด, magic bytes/ชนิด, allowlist ถ้าผ่าน เปลี่ยนสถานะเป็น uploaded
สแกนหามัลแวร์และอัปเดตสถานะเป็น clean หรือ quarantined ถ้าการสแกนแบบอะซิงโครนัส ให้ล็อกการเข้าถึงระหว่างรอผล
อนุญาตดาวน์โหลด พรีวิว หรือการประมวลผลเมื่อสถานะเป็น clean
ตัวอย่างเล็กๆ: สำหรับรูปโปรไฟล์ สร้างระเบียนผูกกับผู้ใช้และจุดประสงค์ avatar เก็บแบบส่วนตัว ยืนยันว่าเป็น JPEG/PNG จริงๆ (ไม่ใช่แค่ชื่อแบบนั้น) สแกน แล้วสร้าง preview URL
การสแกนเป็นตาข่ายความปลอดภัย ไม่ใช่คำมั่น มันจับไฟล์ที่รู้จักและเทคนิคชัดเจนได้ แต่ไม่สามารถตรวจจับทุกอย่างได้ เป้าหมายคือ ลดความเสี่ยงและทำให้ไฟล์ที่ไม่รู้จักปลอดภัยตามค่าเริ่มต้น
รูปแบบที่เชื่อถือได้คือกักกันก่อน บันทึกการอัปโหลดใหม่ทุกชิ้นในที่เก็บส่วนตัวและทำเครื่องหมายว่า pending จนกว่าจะผ่านการตรวจ
การสแกนแบบ synchronous ทำได้เฉพาะไฟล์เล็กและปริมาณน้อยเพราะผู้ใช้ต้องรอ ส่วนใหญ่สแกนแบบอะซิงโครนัส: ยอมรับการอัปโหลด ส่งสถานะ "กำลังประมวลผล" แล้วสแกนเบื้องหลัง
การสแกนพื้นฐานมักเป็นเครื่องมือแอนตี้ไวรัส (หรือบริการ) บวกกับการป้องกันเสริม: การสแกน AV, การตรวจชนิดไฟล์ (magic bytes), ขีดจำกัดบนอาร์ไคฟ์ (zip bombs, zip ซ้อนกันหลายชั้น, ขนาดเมื่อขยายตัวมหาศาล), และการบล็อกฟอร์แมตที่คุณไม่ต้องการ
ถ้าตัวสแกนล้มเหลว หมดเวลา หรือตอบว่า "ไม่รู้" ให้ถือว่าไฟล์น่าสงสัย กักมันและอย่าให้ลิงก์ดาวน์โหลด นี่คือจุดที่ทีมหลายทีมพลาด: "สแกนล้มเหลว" ไม่ควรแปลเป็น "ส่งต่อได้ตามปกติ"
เมื่อบล็อกไฟล์ ให้ข้อความกลางๆ ต่อผู้ใช้: "เราไม่สามารถรับไฟล์นี้ได้ ลองใช้ไฟล์อื่นหรือติดต่อซัพพอร์ต" อย่าอ้างว่าคุณตรวจพบมัลแวร์ถ้าคุณยังไม่มั่นใจ
สมมติสองฟีเจอร์: รูปโปรไฟล์ (แสดงแบบสาธารณะ) และใบเสร็จ PDF (ส่วนตัว ใช้สำหรับการเรียกเก็บเงินหรือซัพพอร์ต) ทั้งสองเป็นปัญหาการอัปโหลด แต่ไม่ควรมีทั้งกฎเดียวกัน
สำหรับรูปโปรไฟล์ ให้เข้มงวด: อนุญาตเฉพาะ JPEG/PNG จำกัดขนาด (เช่น 2–5 MB) และเข้ารหัสใหม่ฝั่งเซิร์ฟเวอร์เพื่อไม่ให้เสิร์ฟไบต์ดั้งเดิมของผู้ใช้ เก็บในที่เก็บสาธารณะก็ต่อเมื่อผ่านการตรวจ
สำหรับใบเสร็จ PDF ยอมรับขนาดใหญ่กว่า (เช่น สูงสุด 20 MB) เก็บเป็นส่วนตัวโดยค่าเริ่มต้น และหลีกเลี่ยงการเรนเดอร์แบบ inline จากโดเมนหลักของแอป
โมเดลสถานะง่ายๆ ช่วยให้ผู้ใช้ทราบโดยไม่เปิดเผยข้อมูลภายใน:\n\n- pending: ผู้ใช้เลือกไฟล์ ยังไม่เริ่มอัปโหลด\n- uploaded: ที่เก็บได้รับไบต์แล้ว\n- scanning: งานเบื้องหลังกำลังตรวจ\n- clean (หรือ rejected): ไฟล์พร้อมให้ใช้ (หรือถูกบล็อก)
Signed URLs เหมาะกับสถาปัตยกรรมนี้: ใช้ signed URL หมดอายุสั้นสำหรับอัปโหลด (เขียนอย่างเดียว คีย์อ็อบเจ็กต์หนึ่งชิ้น) ออก signed URL แยกสำหรับการอ่าน และเฉพาะเมื่อสถานะเป็น clean เท่านั้น
บันทึกสิ่งที่ต้องใช้สำหรับการสืบสวน ไม่ใช่เนื้อหาไฟล์: user ID, file ID, การคาดเดาชนิดไฟล์, ขนาด, storage key, timestamps, ผลการสแกน, request IDs หลีกเลี่ยงการบันทึกเนื้อหาดิบหรือข้อมูลอ่อนไหวภายในเอกสาร
บั๊กการอัปโหลดส่วนใหญ่เกิดจากทางลัดเล็กๆ ที่กลายเป็นถาวร สมมติว่าทุกไฟล์เป็นสิ่งที่ไม่น่าเชื่อถือ ทุก URL จะถูกแชร์ได้ และทุกการตั้งค่า "จะแก้ทีหลัง" จะถูกลืมกับกาลเวลา
กับดักที่พบบ่อย:\n\n- พึ่งการตรวจฝั่งไคลเอนต์เพียงอย่างเดียว เบราว์เซอร์ถูกข้ามได้ในไม่กี่วินาที\n- ให้ผู้ใช้กำหนดพาธ ชื่อไฟล์ หรือ object key\n- ทำให้การอัปโหลดเป็นสาธารณะ "เดี๋ยวเดียว"\n- ใช้ signed URLs ที่มีอายุยาวหรือใช้ได้กับหลายผู้ใช้\n- เสิร์ฟไฟล์ด้วย Content-Type ผิด ทำให้เบราว์เซอร์ตีความเนื้อหาเสี่ยงได้
การมอนิเตอร์คือสิ่งที่ทีมมักข้ามจนกว่าบิลที่เก็บข้อมูลจะพุ่ง ติดตามปริมาณการอัปโหลด ขนาดเฉลี่ย ผู้ส่งสูงสุด และอัตราข้อผิดพลาด บัญชีที่ถูกแฮ็กหนึ่งบัญชีอาจอัปโหลดไฟล์ใหญ่เป็นพันชิ้นในชั่วข้ามคืน
ตัวอย่าง: ทีมเก็บอวาตาร์ภายใต้ชื่อไฟล์ที่ผู้ใช้ให้มา เช่น “avatar.png” ในโฟลเดอร์แชร์ ผู้ใช้คนหนึ่งเขียนทับรูปคนอื่น แก้ไขที่น่าเบื่อแต่ได้ผลคือ: สร้าง object keys ฝั่งเซิร์ฟเวอร์ เก็บเป็นส่วนตัวโดยค่าเริ่มต้น และเปิดเผยรูปที่ย่อขนาดผ่านการตอบรับที่ควบคุม
ใช้สิ่งนี้เป็นการตรวจสุดท้ายก่อนปล่อย ให้แต่ละรายการเป็นเงื่อนไขการปล่อย เพราะเหตุการณ์ส่วนใหญ่เกิดจากการขาดการคุ้มกันหนึ่งอย่าง
Content-Type ที่คาดไว้ ชื่อไฟล์ที่ปลอดภัย และ attachment สำหรับเอกสารเขียนกฎของคุณเป็นภาษาง่ายๆ: ชนิดที่อนุญาต ขนาดสูงสุด ใครเข้าถึงอะไร อายุของ signed URLs และความหมายของ "สแกนผ่าน" นั่นจะกลายเป็นสัญญาร่วมระหว่างโปรดักต์ วิศวกรรม และซัพพอร์ต
เพิ่มเทสต์ที่จับความล้มเหลวยอดนิยม: ไฟล์เกินขนาด ไฟล์ที่เปลี่ยนนามสกุลเป็น executable การอ่านโดยไม่ได้รับอนุญาต signed URLs หมดอายุ และการดาวน์โหลดขณะที่ "สแกนยังไม่เสร็จ" เทสต์พวกนี้ถูกเมื่อเทียบกับค่าใช้จ่ายจากเหตุการณ์
ถ้าคุณสร้างและปรับบ่อย ใช้วิธีการที่วางแผนได้และย้อนกลับได้ ทีมที่ใช้ Koder.ai (koder.ai) มักใช้โหมดวางแผนและ snapshots/rollback ขณะคับกฎการอัปโหลด แต่ข้อกำหนดหลักยังคงเดิม: ฝั่งแบ็คเอนด์เป็นผู้บังคับใช้นโยบาย ไม่ใช่ UI
เริ่มต้นด้วย ตั้งเป็นส่วนตัวโดยค่าเริ่มต้น และถือว่าการอัปโหลดทุกชิ้นเป็นข้อมูลที่ไม่น่าเชื่อถือ บังคับใช้หลักการพื้นฐาน 4 ข้อทางฝั่งเซิร์ฟเวอร์:\n\n- ใครอัปโหลดได้\n- ชนิดไฟล์ที่รับได้ (allowlist)\n- ขนาดและความถี่ (ขีดจำกัดขนาด + rate limits)\n- ใครอ่านไฟล์ได้หลังจากนั้น (ตรวจสิทธิ์ต่อไฟล์)\n\nถ้าคุณตอบคำถามเหล่านี้ได้ชัดเจน คุณก็ปลอดภัยกว่ากรณีส่วนใหญ่แล้ว
เพราะผู้ใช้สามารถส่ง “กล่องปริศนา” ที่ระบบเก็บไว้และอาจส่งคืนให้ผู้ใช้คนอื่น ซึ่งนำไปสู่:\n\n- การเข้าถึงเอกสารส่วนตัวโดยไม่ได้รับอนุญาต\n- ฟิชชิงหรือยึดบัญชีถ้าไฟล์ถูกเสิร์ฟเป็นเนื้อหาเว็บที่เชื่อถือได้\n- การล่มของบริการและบิลสูงจากการอัปโหลดมากหรือไฟล์ใหญ่\n\nเรื่องส่วนใหญ่ไม่ใช่แค่ “มีคนอัปโหลดไวรัส” เท่านั้น
การเก็บคือการเก็บไบต์ไว้ที่ใดที่หนึ่ง การเสิร์ฟคือการส่งมอบไบต์นั้นไปยังเบราว์เซอร์และแอป\n\nอันตรายเกิดขึ้นเมื่อแอปของคุณเสิร์ฟไฟล์ที่ผู้ใช้ส่งด้วยระดับความเชื่อถือและกฎเดียวกับเว็บไซต์หลัก หากไฟล์เสี่ยงถูกปฏิบัติเป็นหน้าเว็บปกติ เบราว์เซอร์อาจรันหรือผู้ใช้ก็อาจไว้วางใจมันเกินไป\n\nค่าเริ่มต้นที่ปลอดภัยคือ: เก็บเป็นส่วนตัวก่อน แล้วเสิร์ฟผ่านการตอบดาวน์โหลดที่ควบคุมได้พร้อม header ที่ปลอดภัย
ใช้ default deny และตรวจสิทธิ์ ทุกครั้งที่มีการดาวน์โหลดหรือพรีวิวไฟล์\n\nกฎปฏิบัติที่เป็นประโยชน์:\n\n- แต่ละระเบียนไฟล์ควรมีเจ้าของ (user/org) และจุดประสงค์ (avatar, invoice ฯลฯ)\n- เมื่อต้องการอ่าน/ดาวน์โหลด ให้ยืนยันว่าผู้ขอมีสิทธิสำหรับไฟล์ชิ้นนั้น\n- หลีกเลี่ยงการรักษาความปลอดภัยแบบ “ตามโฟลเดอร์” เช่น “ทุกอย่างใน /uploads/ ปลอดภัย”\n- ให้สิทธิ์ฝ่ายซัพพอร์ตแบบชั่วคราวและมีบันทึก (grant เฉพาะไฟล์และหมดอายุอัตโนมัติ)\n\nบั๊กจริงๆ ส่วนใหญ่เป็นข้อผิดพลาดง่ายๆ ที่ทำให้เห็นไฟล์ของคนอื่นได้
อย่าไว้ใจนามสกุลไฟล์หรือ Content-Type จากเบราว์เซอร์ ตรวจสอบที่ฝั่งเซิร์ฟเวอร์:\n\n- ใช้ allowlist ของฟอร์แมตต่อฟีเจอร์ (เช่น JPEG/PNG สำหรับอวาตาร์, PDF สำหรับใบเสร็จ)\n- ตรวจชนิดไฟล์บนเซิร์ฟเวอร์และเช็ค magic bytes (ลายเซ็นไฟล์)\n- เปลี่ยนชื่อไฟล์สำหรับการเก็บโดยใช้ ID แบบสุ่ม เก็บชื่อเดิมเป็น metadata เท่านั้น\n- บล็อกฟอร์แมตที่เสี่ยงถ้าคุณไม่จำเป็นต้องรองรับ (โดยเฉพาะ HTML, SVG, เนื้อหาแบบสคริปต์)\n\nถ้าไบต์ไม่ตรงกับฟอร์แมตที่อนุญาต ให้ปฏิเสธการอัปโหลด
เพราะการล่มมักมาจากการใช้งานธรรมดา: อัปโหลดมากเกินไป ไฟล์ใหญ่เกิน หรือการเชื่อมต่อช้าใช้ทรัพยากรเซิร์ฟเวอร์\n\nค่าเริ่มต้นที่ได้ผลดี:\n\n- ตั้งขนาดสูงสุดแยกตามฟีเจอร์ (อวาตาร์เล็ก เอกสารใหญ่กว่า)\n- บังคับใช้ขีดจำกัดหลายชั้น (แอป + reverse proxy + timeouts)\n- เพิ่ม rate limits ต่อผู้ใช้และต่อ IP โดยจำกัดเข้มกว่าสำหรับการเข้าชมแบบไม่ล็อกอิน\n\nถือว่าไบต์ทุกตัวมีค่าและทุกคำขอมีโอกาสถูกนำไปใช้ในทางที่ผิด
ใช่ แต่ต้องระวัง Signed URLs ช่วยให้เบราว์เซอร์อัปโหลด/ดาวน์โหลดตรงสู่ที่เก็บโดยไม่ต้องเปิดบัคเก็ตสาธารณะ\n\nค่าพื้นฐานที่ปลอดภัย:\n\n- ให้ write URLs มีอายุสั้น (มัก 1–5 นาที)\n- จำกัดแต่ละ URL ให้ชี้ไปยัง object key เดียว ไม่ใช่ทั้งโฟลเดอร์\n- ออก URL เฉพาะหลังผ่านการตรวจสิทธิ์\n- บันทึกว่าผู้ใดร้องขอ URL และเพราะอะไร\n\nการอัปโหลดตรงช่วยลดภาระ API แต่การจำกัดขอบเขตและการหมดอายุเป็นสิ่งที่ห้ามประมาท
รูปแบบที่ปลอดภัยที่สุดคือ:\n\n1. สร้างระเบียนอัปโหลดด้วยสถานะ pending\n2. อัปโหลดไบต์ไปยังตำแหน่ง ส่วนตัว\n3. ตรวจขนาด + ชนิด (magic bytes) ที่ฝั่งเซิร์ฟเวอร์\n4. สแกน (มักทำแบบ async) และเปลี่ยนสถานะเป็น clean หรือ quarantined\n5. ให้ดาวน์โหลด/พรีวิวได้ก็ต่อเมื่อสถานะเป็น clean เท่านั้น\n\nการทำแบบนี้ช่วยป้องกันการแชร์ไฟล์ที่ “สแกนไม่เสร็จ” หรือ “ยังประมวลผล” โดยไม่ได้ตั้งใจ
การสแกนช่วยได้ แต่ไม่ใช่การันตี ใช้มันเป็นตาข่ายความปลอดภัย ไม่ใช่การควบคุมเพียงอย่างเดียว\n\nแนวปฏิบัติที่ใช้งานได้จริง:\n\n- กักกันก่อน: อย่าเผยลิงก์จนกว่าสแกนจะเสร็จ\n- สแกนแบบอะซิงโครนัสเพื่อรองรับปริมาณสูง; แสดงสถานะ “กำลังประมวลผล” ให้ผู้ใช้เห็น\n- หากการสแกนล้มเหลวหรือหมดเวลา ให้ถือว่าไฟล์มีความเสี่ยงและกักมันไว้\n- ใส่ข้อจำกัดสำหรับไฟล์อัด (zip bombs, ขนาดเมื่อขยายตัวใหญ่เกินไป) ถ้าคุณรองรับ\n\nกฎสำคัญคือ: “ยังไม่ได้สแกน” ไม่ควรหมายถึง “เปิดให้ดาวน์โหลดได้”
เสิร์ฟไฟล์ให้ลดความเสี่ยงที่เบราว์เซอร์จะตีความเป็นหน้าเว็บ:\n\nค่าเริ่มต้นที่ดี:\n\n- ตั้ง Content-Disposition: attachment สำหรับเอกสาร\n- ใช้ Content-Type ที่เซิร์ฟเวอร์กำหนด (หรือ application/octet-stream)\n- ใช้คีย์เก็บข้อมูลที่ไม่โปร่งใส (ไม่ใช่ชื่อไฟล์ผู้ใช้) ใน URL\n- ถ้าเป็นไปได้ ให้แยกโดเมนสำหรับดาวน์โหลดเนื้อหาผู้ใช้\n\nการทำแบบนี้ลดความเสี่ยงที่ไฟล์อัปโหลดจะกลายเป็นหน้าฟิชชิงหรือสคริปต์ที่รันได้