Subdomain Takeover คืออะไร ทำไมมันถึงอันตราย
เวลาเราสร้างเว็บไซต์ เรามักจะใช้ชื่อย่อยหรือ Subdomain (ชื่อที่อยู่หน้าชื่อหลัก เช่น blog.example.com) เพื่อแยกบริการต่างๆ ออกจากกัน บางครั้งเราอาจจะไปเช่าบริการจากเจ้าอื่นมาช่วยจัดการ เช่น ใช้ CloudFront (บริการฝากไฟล์ให้โหลดเร็วขึ้นทั่วโลก) แล้วเราก็ตั้งค่าให้ชื่อนั้นชี้ไปที่เซิร์ฟเวอร์ของเขา
ปัญหาจะเกิดขึ้นเมื่อเราเลิกใช้บริการนั้น แต่เราลืมเข้าไปลบค่าที่ชี้ไปหาเขาออก ค่านี้เรียกว่า CNAME (การตั้งค่าที่บอกว่าชื่อนี้คือชื่อเดียวกับอีกที่หนึ่ง) เมื่อค่านี้ยังค้างอยู่ มันเปรียบเหมือนป้ายบอกทางที่ชี้ไปในที่ว่างเปล่า ถ้ามีคนหัวใสไปจดทะเบียนใช้บริการนั้นต่อจากเรา เขาก็จะกลายเป็นเจ้าของชื่อนั้นทันที
นี่คือสิ่งที่เรียกว่า Subdomain Takeover (การยึดชื่อย่อยไปเป็นของตัวเอง) ฟังดูเหมือนเป็นแค่การเปลี่ยนป้ายชื่อ แต่มันอันตรายกว่านั้นมาก เพราะถ้าชื่อย่อยนั้นมีความสำคัญ ระบบต่างๆ มักจะให้ความเชื่อถือมันโดยอัตโนมัติ ซึ่งอาจนำไปสู่การขโมยข้อมูลหรือยึดบัญชีผู้ใช้ได้ทั้งระบบเลยทีเดียว
ความรุนแรงของช่องโหว่ไม่ได้อยู่ที่วิธีทำ แต่อยู่ที่ความสำคัญของชื่อ
หลายคนมักเข้าใจผิดว่าถ้าเจอช่องโหว่แบบเดียวกัน ความรุนแรงต้องเท่ากัน แต่ในโลกความปลอดภัย Context (บริบทหรือความสำคัญของส่วนนั้น) คือตัวกำหนดว่าเรื่องนี้จะกลายเป็นเรื่องใหญ่แค่ไหน การยึดชื่อย่อยที่เอาไว้แค่แปะรูปภาพ กับการยึดชื่อย่อยที่เอาไว้ล็อกอิน ผลลัพธ์มันต่างกันมหาศาล
ลองนึกภาพว่าถ้าคุณทำกุญแจบ้านหาย ถ้าเป็นกุญแจห้องเก็บของหลังบ้าน คุณก็แค่เปลี่ยนล็อก แต่ถ้าเป็นกุญแจประตูหลักที่เปิดเข้าได้ทุกห้องในบ้าน นั่นคือหายนะ การยึดชื่อย่อยก็เหมือนกันครับ ถ้าชื่อนั้นถูกระบบหลักเชื่อใจ ข้อมูลสำคัญก็อาจจะหลุดออกไปได้ง่ายๆ โดยที่ตัววิธีการยึดอาจจะใช้วิธีเดิมเป๊ะๆ
ดังนั้นเวลาเราตรวจหาช่องโหว่ อย่ามองแค่ว่ามันยึดได้ไหม แต่ต้องมองว่าถ้ามีคนยึดไปแล้ว เขาจะทำอะไรกับระบบหลักของเราได้บ้าง นี่คือเหตุผลที่ทีมรักษาความปลอดภัยต้องประเมินความเสี่ยงตามความสำคัญของข้อมูล ไม่ใช่ประเมินตามเทคนิคที่ใช้ยึดเพียงอย่างเดียว
อันตรายจากการเก็บข้อมูลผู้ใช้ไว้ใน Cookie
Cookie (ไฟล์ข้อมูลเล็กๆ ที่เว็บเก็บไว้ในเครื่องเรา) เป็นตัวสำคัญที่ทำให้เราไม่ต้องล็อกอินซ้ำบ่อยๆ แต่ถ้าเราตั้งค่าขอบเขตของมันกว้างเกินไป เช่น ตั้งให้ใช้ได้กับทุกชื่อย่อยในโดเมนหลัก มันจะกลายเป็นช่องโหว่ร้ายแรงทันที เพราะถ้าชื่อย่อยไหนโดนยึดไป เขาก็จะอ่านค่านี้ได้
ลองดูตัวอย่างการตั้งค่าที่ผิดพลาดนี้ครับ
// โค้ดนี้สมมติว่าเราเก็บข้อมูลผู้ใช้ไว้ใน LocalStorage
// ถ้าตั้งค่าขอบเขตคุกกี้ไว้ที่ *.example.com จะอันตรายมาก
if (localStorage.getItem('session_id')) {
console.log("เข้าถึงข้อมูลผู้ใช้ได้แล้ว");
}
ส่วนที่บรรทัด 3 ทำคือการดึงค่า session_id (รหัสประจำตัวการใช้งาน) ออกมา ถ้าชื่อย่อยที่โดนยึดไปมีสิทธิ์เข้าถึงคุกกี้ของโดเมนหลัก คนที่ยึดไปจะเห็นรหัสนี้ทันที ผลลัพธ์ที่ได้คือเขาสามารถปลอมตัวเป็นคุณเพื่อเข้าไปใช้งานระบบหลักได้โดยไม่ต้องรู้รหัสผ่านเลย
วิธีป้องกันที่ดีที่สุดคือการกำหนดขอบเขตให้แคบที่สุด หรือใช้สิ่งที่เรียกว่า __Host- prefix (การใส่ชื่อนำหน้าคุกกี้เพื่อล็อกให้ใช้ได้แค่ชื่อย่อยนั้นๆ) เพื่อป้องกันไม่ให้ข้อมูลหลุดไปยังชื่อย่อยอื่นๆ แม้ว่าชื่อย่อยนั้นจะถูกยึดไปแล้วก็ตาม
การเข้าใจผิดเรื่อง SameSite=Lax
หลายคนคิดว่าการตั้งค่า SameSite=Lax (การตั้งค่าไม่ให้ส่งคุกกี้ข้ามเว็บไซต์) จะช่วยป้องกันเรื่องนี้ได้ แต่ความจริงมันไม่ได้ป้องกันคนในบ้านเดียวกันครับ ถ้าผู้โจมตียึดชื่อย่อยได้ เขาจะถือว่าเป็นส่วนหนึ่งของเว็บไซต์คุณทันทีในสายตาของเบราว์เซอร์
ในมุมมองของความปลอดภัย Same-site (การที่เว็บสองเว็บอยู่ในโดเมนหลักเดียวกัน) คือการมองที่โดเมนหลัก เช่น blog.example.com กับ api.example.com ถือว่าเป็นพวกเดียวกัน ดังนั้นถ้าคุณยึดชื่อย่อยหนึ่งได้ คุณก็สามารถส่งคำสั่งไปหาอีกชื่อย่อยหนึ่งได้โดยไม่โดนเบราว์เซอร์บล็อก
นี่คือจุดที่มือใหม่พลาดบ่อยที่สุด เพราะคิดว่าการตั้งค่านี้ครอบคลุมทุกอย่างแล้ว แต่จริงๆ แล้วมันช่วยแค่ป้องกันจากเว็บภายนอกเท่านั้น ถ้าผู้โจมตีเข้ามาอยู่ในชื่อย่อยของคุณได้แล้ว การตั้งค่านี้ก็แทบจะไร้ผลทันทีครับ
เมื่อการยึดชื่อย่อยกลายเป็นช่องทางขโมยสิทธิ์ OAuth
OAuth (ระบบที่ให้เราล็อกอินผ่าน Google หรือ Facebook) เป็นอีกจุดที่เปราะบางมาก ถ้าเว็บไซต์ของคุณตั้งค่า redirect_uri (ที่อยู่ที่เว็บจะส่งผู้ใช้กลับไปหลังจากล็อกอินสำเร็จ) เป็นแบบกว้างๆ เช่น *.example.com คุณกำลังเปิดประตูให้ผู้โจมตีขโมยสิทธิ์ล็อกอินไปได้
ขั้นตอนการโจมตีมักเป็นแบบนี้ครับ:
- ผู้โจมตียึดชื่อย่อยที่อยู่ในเงื่อนไข
redirect_uriของคุณ - เขาหลอกให้เหยื่อกดลิงก์ล็อกอินที่ส่งไปยังชื่อย่อยนั้น
- ระบบล็อกอินส่งรหัสผ่านชั่วคราวไปให้ผู้โจมตีแทนที่จะเป็นเว็บหลัก
ผลลัพธ์คือผู้โจมตีจะได้รหัสผ่านชั่วคราวไปครอบครองโดยที่เหยื่อไม่รู้ตัวเลย วิธีแก้เรื่องนี้ง่ายมากคือต้องตั้งค่า redirect_uri ให้เป็นค่าที่แน่นอน (Exact-match) เท่านั้น ห้ามใช้เครื่องหมายดอกจันหรือรูปแบบกว้างๆ เด็ดขาด เพราะมันคือความเสี่ยงที่เลี่ยงได้
สรุปแนวทางการป้องกันและนำไปใช้จริง
การป้องกัน Subdomain Takeover ไม่ใช่เรื่องของการเปลี่ยนโค้ดให้ซับซ้อน แต่เป็นการจัดการ Configuration (การตั้งค่าระบบ) ให้รัดกุม สิ่งที่ต้องทำทันทีคือการไล่ตรวจสอบค่า CNAME ทั้งหมดที่ชี้ไปยังบริการภายนอกว่ายังใช้งานอยู่จริงหรือไม่
ถ้าคุณเป็นโปรแกรมเมอร์มือใหม่ นี่คือเช็คลิสต์ที่คุณควรทำทุกครั้งที่ดูแลระบบ:
- ตรวจสอบ CNAME ที่ไม่ได้ใช้งานแล้วลบออกทันที
- กำหนดขอบเขตคุกกี้ให้แคบที่สุดเท่าที่เป็นไปได้
- ตั้งค่า OAuth ให้ระบุที่อยู่ปลายทางแบบเจาะจงเสมอ
- หมั่นตรวจสอบการตั้งค่าความปลอดภัยของโดเมนหลักและโดเมนย่อยอยู่เสมอ
จำไว้ว่าความปลอดภัยไม่ได้มาจากการแก้บั๊กเพียงอย่างเดียว แต่มาจากการเข้าใจว่าแต่ละส่วนในระบบเชื่อมโยงกันอย่างไร ความรอบคอบในการตั้งค่าคือด่านแรกที่สำคัญที่สุด ของการเป็นโปรแกรมเมอร์มืออาชีพครับ
ที่มา: Subdomain Takeover Severity Comes From Security Context, Not the Exploit Mechanism — DEV Community