Firebase คืออะไรและทำไมมือใหม่มักเข้าใจผิด
เวลาเราทำแอปมือถือหรือเว็บแอปพลิเคชัน เรามักใช้ Firebase (บริการสำเร็จรูปจาก Google ที่ช่วยจัดการฐานข้อมูลและระบบสมาชิก) เพราะมันใช้ง่ายมาก แค่ใส่ค่าตั้งค่า (Configuration) ลงไปในแอปก็ใช้งานได้ทันที หลายคนเข้าใจผิดว่าค่าเหล่านี้คือ Security Boundary (กำแพงป้องกันความปลอดภัย) ที่ปกป้องข้อมูลของเราไว้
ลองนึกภาพว่าคุณมีตู้เซฟเก็บของมีค่า แต่คุณดันแปะรหัสผ่านตู้เซฟไว้ที่หน้าประตูบ้านด้วยความหวังว่าไม่มีใครเห็น ค่าตั้งค่าของ Firebase ก็เหมือนรหัสผ่านนั้นแหละครับ มันถูกฝังอยู่ในตัวแอปที่ทุกคนโหลดไปแกะดูได้ การที่แอปทำงานได้ไม่ได้แปลว่าระบบนั้นปลอดภัยจากผู้ไม่หวังดี
ความเสี่ยงที่แท้จริงจะเริ่มขึ้นเมื่อเราวางใจแอปมากเกินไป จนลืมว่าแอปคือสิ่งที่ผู้ใช้ควบคุมได้ 100% ถ้าเราเขียนโค้ดโดยคิดว่า "ถ้าปุ่มในแอปกดไม่ได้ คนก็เข้าถึงข้อมูลไม่ได้" นั่นคือความคิดที่อันตรายที่สุดสำหรับนักพัฒนาครับ
เมื่อแอปไม่ใช่ปราการด่านสุดท้าย
นักโจมตี (Attacker) ไม่ได้ใช้งานผ่านหน้าจอแอปเหมือนผู้ใช้ทั่วไป พวกเขาใช้เครื่องมือพิเศษส่ง Request (คำสั่งขอข้อมูลจากเซิร์ฟเวอร์) เข้าไปที่ Firebase โดยตรงโดยไม่ผ่านแอปของเราเลย ถ้าเราตั้งค่าให้ทุกคนอ่านข้อมูลได้ ระบบก็จะส่งข้อมูลให้ทันทีโดยไม่สนว่าใครเป็นคนขอ
ลองเปรียบเทียบกับการสั่งอาหาร ถ้าพนักงานรับออเดอร์แค่ดูว่าเราใส่เสื้อยูนิฟอร์มร้านไหมแล้วยอมให้สั่งอาหารฟรี ถ้าคนนอกแอบไปซื้อเสื้อยูนิฟอร์มมาใส่ เขาก็สั่งอาหารฟรีได้เหมือนกัน การมีแอปหน้าตาดีไม่ได้แปลว่าหลังบ้านของเรามีระบบตรวจสอบตัวตนที่ดีพอ
การซ่อนปุ่มหรือจำกัดการกดในแอปจึงเป็นแค่การตกแต่งหน้าตา ไม่ใช่การป้องกันข้อมูล นักพัฒนาหน้าใหม่มักพลาดจุดนี้บ่อยมาก เราต้องเปลี่ยนความคิดจากการป้องกันที่ "หน้าจอ" มาเป็นการป้องกันที่ "ฐานข้อมูล" แทนครับ
การพิสูจน์ตัวตนไม่ใช่การอนุญาตให้เข้าถึง
หลายคนมักเขียนโค้ดเช็คแค่ว่า request.auth != null (ตรวจสอบว่าคนนี้ล็อกอินอยู่ใช่ไหม) ซึ่งนั่นพิสูจน์ได้แค่ว่าเขามีบัญชีผู้ใช้ แต่ไม่ได้บอกว่าเขามีสิทธิ์ทำอะไรกับข้อมูลนั้นบ้าง เช่น เขาอาจจะเป็นผู้ใช้ทั่วไปแต่พยายามไปแก้ไขข้อมูลของคนอื่น
การ Authentication (การยืนยันตัวตนว่าเป็นใคร) กับ Authorization (การให้สิทธิ์ว่าทำอะไรได้บ้าง) คือคนละเรื่องกัน การที่แอปตรวจสอบว่าล็อกอินแล้วไม่เท่ากับว่าเราอนุญาตให้เขาเข้าไปลบหรือแก้ไขทุกอย่างในระบบได้เสมอไป
คุณต้องเขียนกฎให้รัดกุมโดยผูกตัวตนของผู้ใช้เข้ากับข้อมูลที่เขามีสิทธิ์จริงๆ นี่คือตัวอย่างการตั้งค่ากฎความปลอดภัยของ Firebase ที่มักพบเห็น
// กฎแบบที่ผิด: ใครที่ล็อกอินแล้ว แก้ไขข้อมูลได้หมด
allow write: if request.auth != null;
// กฎแบบที่ถูก: เฉพาะเจ้าของข้อมูลเท่านั้นที่แก้ไขได้
allow write: if request.auth.uid == resource.data.ownerId;
อธิบายโค้ด:
- บรรทัดแรก: เช็คแค่ว่าล็อกอินหรือยัง ถ้าใช่ก็ให้สิทธิ์แก้ไขทันที ซึ่งอันตรายมาก
- บรรทัดที่สอง: ตรวจสอบเพิ่มว่า
uid(รหัสประจำตัวผู้ใช้) ต้องตรงกับownerId(รหัสเจ้าของข้อมูล) ในฐานข้อมูลเท่านั้น
ผลลัพธ์: หากผู้ใช้ A พยายามแก้ไขข้อมูลของผู้ใช้ B ระบบจะปฏิเสธคำสั่งทันทีด้วยข้อความแจ้งเตือน Permission Denied (ไม่มีสิทธิ์เข้าถึง) ทำให้ข้อมูลปลอดภัยขึ้นมาก
App Check ไม่ใช่ยาวิเศษ
หลายคนพยายามใช้ App Check (บริการยืนยันว่าคำสั่งมาจากแอปของเราจริงๆ) เพื่อป้องกันการโจมตี แต่ต้องจำไว้ว่ามันเป็นแค่ชั้นหนึ่งเท่านั้น มันช่วยบอกว่าคำสั่งมาจากแอปของจริง ไม่ใช่มาจากสคริปต์ที่เขียนขึ้นเอง แต่มันไม่ได้ตรวจสอบว่าผู้ใช้คนนั้นมีสิทธิ์เข้าถึงข้อมูลนั้นไหม
เปรียบเหมือนการมีบัตรพนักงานที่ปลอมไม่ได้ แต่ถ้าพนักงานคนนั้นถือบัตรไปเปิดห้องเก็บเงินบริษัท เขาก็ไม่ควรเปิดได้อยู่ดีหากเขาไม่ใช่ผู้จัดการ การมีเครื่องมือช่วยตรวจสอบเป็นเรื่องดี แต่ต้องมีกฎเกณฑ์ที่เข้มงวดมารองรับด้วย
แนวทางการป้องกันที่ดีที่สุดคือการใช้หลายชั้นร่วมกันครับ เริ่มจากการยืนยันตัวตน ตรวจสอบสิทธิ์ผ่านกฎของ Firebase และใช้ Trusted Backend (เซิร์ฟเวอร์ส่วนตัวที่เชื่อถือได้) ในการจัดการข้อมูลที่มีความสำคัญสูง
ปกป้องการทำงานที่สำคัญด้วยเซิร์ฟเวอร์หลังบ้าน
สำหรับงานที่มีความสำคัญสูง เช่น การคืนเงิน การอนุมัติเอกสาร หรือการเปลี่ยนสิทธิ์ผู้ดูแลระบบ ห้ามสั่งการผ่านแอปโดยตรงเด็ดขาด ให้ส่งคำสั่งไปที่ Backend (ระบบหลังบ้าน) ของเราเองเพื่อตรวจสอบความถูกต้องอีกครั้งก่อนบันทึกลงฐานข้อมูล
การทำงานหลังบ้านช่วยให้เราตรวจสอบ Business Invariants (เงื่อนไขทางธุรกิจที่ห้ามละเมิด) ได้ละเอียดขึ้น เช่น เช็คว่ายอดเงินในบัญชีพอไหม หรือเช็คว่าเหตุการณ์นี้เคยทำไปแล้วหรือยัง เพื่อป้องกันการกดปุ่มซ้ำซ้อนจนเกิดบั๊ก
นี่คือตัวอย่างการจัดการงานสำคัญที่ควรทำผ่านระบบหลังบ้านแทนที่จะปล่อยให้แอปทำเอง
// ตัวอย่างการตรวจสอบที่ควรทำในระบบหลังบ้าน (Node.js)
if (user.role !== 'admin') {
throw new Error('คุณไม่มีสิทธิ์อนุมัติรายการนี้');
}
if (request.amount > account.balance) {
throw new Error('ยอดเงินไม่เพียงพอ');
}
// ทำรายการคืนเงินหลังจากตรวจสอบผ่านแล้วเท่านั้น
อธิบายโค้ด:
- บรรทัดแรก: ตรวจสอบตำแหน่งหน้าที่ของผู้ใช้ว่าเป็นแอดมินจริงหรือไม่
- บรรทัดที่สาม: ตรวจสอบเงื่อนไขทางธุรกิจ เช่น ยอดเงินคงเหลือ ก่อนดำเนินการจริง
ผลลัพธ์: ระบบจะปฏิเสธการทำรายการที่ไม่ถูกต้อง และบันทึก Log (ประวัติการทำงาน) ไว้ตรวจสอบย้อนหลัง ทำให้เราควบคุมความปลอดภัยได้เบ็ดเสร็จ
บทสรุป: ความปลอดภัยเริ่มที่การออกแบบ
สรุปสั้นๆ คืออย่าไว้ใจสิ่งที่อยู่ในแอปพลิเคชันของคุณ เพราะมันคือสิ่งที่ใครก็เห็นและแก้ไขได้ การรักษาความปลอดภัยคือการสร้างกำแพงที่ฐานข้อมูลและระบบหลังบ้าน ไม่ใช่การซ่อนปุ่มหรือการเขียนโค้ดเช็คแค่ว่าล็อกอินอยู่ไหม
จงมองว่าแอปของคุณเป็นแค่หน้าต่างที่เปิดให้คนมองเห็นข้อมูล แต่ประตูหลังบ้านคือจุดที่คุณต้องล็อกให้แน่นหนาที่สุด การฝึกเขียนโค้ดที่ดีต้องเริ่มจากการคิดถึงกรณีที่เลวร้ายที่สุดเสมอ เช่น ถ้ามีคนพยายามส่งคำสั่งแปลกๆ เข้ามา ระบบของเราจะยังปลอดภัยอยู่ไหม
ลองกลับไปตรวจสอบโปรเจกต์ที่คุณกำลังทำอยู่ครับว่า มีการใช้ Security Rules (กฎความปลอดภัยของ Firebase) ที่รัดกุมแล้วหรือยัง ถ้ายัง ให้เริ่มจากเปลี่ยนกฎที่อนุญาตให้ทุกคนเข้าถึงข้อมูล มาเป็นการระบุสิทธิ์ให้ชัดเจนที่สุดเท่าที่จะทำได้ตั้งแต่วันนี้เลยครับ
ที่มา: How Attackers Abuse Firebase Misconfigurations in Production Apps — DEV Community