เข้าใจเรื่อง Session Cookie และวิธีป้องกันช่องโหว่ความปลอดภัยสำหรับมือใหม่

11 นาที 10 views บันทึกเป็น PDF
เข้าใจเรื่อง Session Cookie และวิธีป้องกันช่องโหว่ความปลอดภัยสำหรับมือใหม่

Session Cookie เปรียบเสมือนกุญแจชั่วคราวที่ต้องดูแลให้ดี เรียนรู้วิธีป้องกันช่องโหว่ CSRF และ Session Fixation เพื่อสร้างเว็บที่ปลอดภัยสำหรับโปรแกรมเมอร์มือใหม่

ทำไม Session Cookie ถึงเปรียบเสมือนกุญแจชั่วคราว

ในการเขียนโปรแกรมเว็บ เรื่องของ Session Cookie (คุกกี้ที่เก็บสถานะการใช้งานชั่วคราว) เป็นสิ่งที่มือใหม่ต้องเจอแน่นอน ลองจินตนาการว่าคุณไปโรงแรมแล้วได้รับคีย์การ์ดมาใบหนึ่ง คีย์การ์ดนี้ไม่ได้บอกว่าคุณคือใคร แต่บอกว่าคุณมีสิทธิ์เข้าห้องพักได้ในช่วงเวลาที่กำหนด Session (ช่วงเวลาที่ผู้ใช้ล็อกอินอยู่บนเว็บ) ก็ทำงานแบบเดียวกันเป๊ะ คือเป็นตัวยืนยันว่าคุณล็อกอินผ่านแล้วนะ ไม่ต้องกรอกรหัสผ่านทุกครั้งที่เปลี่ยนหน้าเว็บ

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

เราต้องมองว่า Session Cookie คือของมีค่าที่ต้องดูแลตลอดวงจรชีวิต ตั้งแต่ตอนที่มันถูกสร้างขึ้น ถูกนำไปใช้งาน ไปจนถึงตอนที่มันหมดอายุและต้องถูกทำลายทิ้ง ถ้าเราปล่อยให้มันอยู่นานเกินไปหรือจัดการไม่ดีพอ ข้อมูลของผู้ใช้ในระบบที่คุณสร้างขึ้นก็ตกอยู่ในความเสี่ยงทันที

CSRF: เมื่อเบราว์เซอร์กลายเป็นเครื่องมือของผู้ไม่หวังดี

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

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

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

// ตัวอย่างการตรวจสอบ CSRF Token ในเซิร์ฟเวอร์
app.post("/update-email", (req, res) => {
  const userToken = req.body.csrf_token; // รับรหัสจากฟอร์ม
  const sessionToken = req.session.csrfToken; // รหัสที่เก็บใน Session

  if (userToken !== sessionToken) {
    return res.status(403).send("คำขอไม่ปลอดภัย");
  }
  // ดำเนินการเปลี่ยนอีเมลต่อ...
});

คำอธิบายโค้ด: บรรทัดที่ 3 คือการรับค่ารหัสที่ส่งมาจากหน้าเว็บ ส่วนบรรทัดที่ 4 คือการหยิบรหัสที่เซิร์ฟเวอร์เคยสร้างไว้ขึ้นมาเทียบ ถ้าค่าไม่ตรงกัน (บรรทัดที่ 6) ระบบจะตีกลับทันที ผลลัพธ์ที่ได้คือหากมีเว็บอันตรายพยายามส่งคำสั่งแทนเรา มันจะไม่มีรหัสลับตัวนี้ ทำให้คำสั่งถูกปฏิเสธด้วยสถานะ 403 Forbidden (ห้ามเข้าถึง)

Session Fixation: การดักจับเซสชันก่อนการล็อกอิน

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

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

โปรแกรมเมอร์มืออาชีพจึงต้องมีกฎเหล็กข้อหนึ่งเสมอคือ เมื่อมีการเปลี่ยนสถานะการล็อกอิน ต้องสร้างไอดีเซสชันใหม่ทิ้งของเก่าทันที ขั้นตอนนี้เรียกว่า Session Regeneration (การสร้างไอดีเซสชันใหม่) ซึ่งเป็นเกราะป้องกันชั้นดีที่ช่วยให้ไอดีเดิมที่แฮกเกอร์ถืออยู่กลายเป็นเศษกระดาษที่ใช้งานไม่ได้อีกต่อไป

// การสร้าง Session ใหม่หลังล็อกอิน
app.post("/login", async (req, res) => {
  const user = await authenticate(req.body.username, req.body.password);
  if (!user) return res.status(401).send("ล็อกอินไม่สำเร็จ");

  req.session.regenerate((err) => { // สร้างไอดีใหม่ที่นี่
    req.session.userId = user.id;
    res.redirect("/dashboard");
  });
});

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

การกำหนดเวลาหมดอายุเพื่อลดความเสี่ยง

เราไม่ควรปล่อยให้เซสชันมีอายุยืนยาวตลอดกาล เพราะถ้าวันหนึ่งมีใครเผลอทำคุกกี้หลุดไป แฮกเกอร์ก็จะมีเวลาแอบอ้างเป็นเราได้นานขึ้น การตั้งเวลาหมดอายุจึงเป็นเรื่องที่ต้องทำ ทั้งแบบ Idle Timeout (หมดอายุเมื่อไม่มีการใช้งาน) และ Absolute Timeout (หมดอายุตามเวลาที่กำหนดตายตัว)

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

สำหรับ Absolute Timeout คือการตั้งค่าว่า "ไม่ว่าคุณจะใช้งานต่อเนื่องแค่ไหน แต่พอครบ 8 ชั่วโมง คุณต้องล็อกอินใหม่นะ" วิธีนี้ช่วยจำกัดเวลาที่แฮกเกอร์จะแอบใช้เซสชันของเราได้หากเขาขโมยไปได้สำเร็จ การผสมผสานทั้งสองแบบนี้จะทำให้เว็บของคุณมีความปลอดภัยสูงขึ้นมากสำหรับผู้ใช้งานจริง

เมื่อฟีเจอร์ "จดจำฉันไว้" กลายเป็นดาบสองคม

ฟีเจอร์ Remember Me (จดจำการล็อกอิน) มักจะทำให้คุกกี้มีอายุยืนยาวขึ้นมาก เช่น จาก 30 นาที เป็น 30 วัน ซึ่งนั่นหมายความว่าความเสี่ยงก็เพิ่มขึ้นเป็นทวีคูณ หากมีใครได้ไฟล์คุกกี้ในเครื่องเราไป เขาก็จะสวมรอยเป็นเราได้นานถึงหนึ่งเดือนเต็มๆ ซึ่งเป็นช่องโหว่ที่ใหญ่มากสำหรับระบบที่มีความสำคัญ

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

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

อย่าลืมทำลายเซสชันเมื่อผู้ใช้กดออกจากระบบ

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

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

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

// การทำลายเซสชันบนเซิร์ฟเวอร์ให้หมดจด
app.post("/logout", (req, res) => {
  req.session.destroy((err) => {
    if (err) return res.sendStatus(500);
    res.clearCookie("session_id"); // ลบคุกกี้ในเบราว์เซอร์ออกด้วย
    res.redirect("/");
  });
});

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

สรุป

การเข้าใจวงจรชีวิตของเซสชันตั้งแต่เริ่มสร้างจนถึงการทำลาย คือทักษะสำคัญที่แยกโปรแกรมเมอร์ทั่วไปออกจากโปรแกรมเมอร์ที่ใส่ใจความปลอดภัย สิ่งที่คุณควรทำเมื่อต้องสร้างระบบล็อกอินคือ 1) ใช้ CSRF Token ทุกครั้งที่มีการส่งข้อมูล 2) สั่ง regenerate ไอดีเซสชันใหม่เสมอหลังล็อกอิน 3) ตั้งเวลาหมดอายุที่เหมาะสม และ 4) ทำลายเซสชันบนเซิร์ฟเวอร์ให้จบเมื่อผู้ใช้กดออก

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


ที่มา: Your Session Cookie Is Basically a Temporary Password - Part 2 — DEV Community

แชร์บทความ

Facebook X LINE

บทความที่เกี่ยวข้อง

จัดการเซิร์ฟเวอร์ผ่าน VS Code ให้ง่ายขึ้นด้วย Easy SSH พร้อมฟีเจอร์โหลดไฟล์ผ่านคลิกเดียว

จัดการเซิร์ฟเวอร์ผ่าน VS Code ให้ง่ายขึ้นด้วย Easy SSH พร้อมฟีเจอร์โหลดไฟล์ผ่านคลิกเดียว

เบื่อไหมที่ต้องสลับหน้าจอไปมาเพื่อจัดการเซิร์ฟเวอร์? มาลองใช้ Easy SSH ปลั๊กอิน VS Code ที่ช่วยให้คุณรีโมทผ่าน Terminal ได้สะดวก แถมโหลดไฟล์ได้ง่ายแค่กด Ctrl+click

ที่มา: DEV Community

5 hours ago 11 นาที
3 views
วิธีดึงข้อมูลราคาจาก Google Hotels ด้วย API สำหรับนักพัฒนา

วิธีดึงข้อมูลราคาจาก Google Hotels ด้วย API สำหรับนักพัฒนา

อยากทำแอปท่องเที่ยวแต่ดึงข้อมูลราคาจาก Google Hotels ไม่ได้? มาดูวิธีใช้ Apify Actor ช่วยดึงข้อมูลแบบอัตโนมัติด้วย Python ง่ายๆ ไม่ต้องกลัวเว็บพัง

ที่มา: DEV Community

8 hours ago 8 นาที
5 views
วิธีเช็กความพร้อมโปรเจกต์ก่อนปล่อยงานจริงด้วย ReleaseReady

วิธีเช็กความพร้อมโปรเจกต์ก่อนปล่อยงานจริงด้วย ReleaseReady

เคยไหม? โค้ดรันได้ในเครื่องแต่พอปล่อยจริงกลับพัง! มาดูวิธีตรวจสอบความพร้อมของโปรเจกต์ก่อนอัปขึ้น GitHub ด้วยเครื่องมือ ReleaseReady กัน

ที่มา: DEV Community

12 hours ago 9 นาที
5 views