Sandworm Lazarus แฟ้มคดี / CASE-010

CASE-010ระดับ 2 · เปิดฝาดูกลไกควรอ่าน CASE-004 และ CASE-009 มาก่อน

พิสูจน์ตัวตน & สิทธิ์การเข้าถึง

Authentication & Authorization

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

เขียนและวาดภาพโดย Sandworm เวลาอ่าน ประมาณ 25 นาที ภาพอธิบาย 8 ภาพ อ่านจบแล้วจะ บอกได้ว่าระบบพลาดที่ด่าน “ตัวตน” หรือด่าน “สิทธิ์”

สีในภาพประกอบของแฟ้มนี้: แดง ผู้โจมตีเหลือง จุดเสี่ยงเขียว การป้องกันฟ้า ข้อมูลของเรา

01คุณคือใคร กับ คุณทำอะไรได้ AuthN vs AuthZ

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

ระบบนี้ไม่ได้ถูก “แฮก” แบบในหนัง ไม่มีรหัสผ่านรั่ว ไม่มีใครเดารหัสได้ สมชายเป็นสมชายจริง ๆ ระบบรู้ถูกต้องว่าเขาเป็นใคร สิ่งที่ระบบลืมไปคืออีกคำถามหนึ่ง คือ “สมชายมีสิทธิ์ดูเกรดของมานีไหม”

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

ศัพท์น่ารู้: Authentication (AuthN) และ Authorization (AuthZ)
การพิสูจน์ตัวตน (Authentication) ตอบว่า “คุณคือใคร” เช่น ล็อกอินด้วยรหัสผ่าน สแกนนิ้ว หรือพาสคีย์ การกำหนดสิทธิ์ (Authorization) ตอบว่า “คุณทำสิ่งนี้ได้ไหม” เช่น ดูข้อมูลชิ้นนี้ได้ไหม แก้ได้ไหม ลบได้ไหม คนในวงการมักย่อว่า AuthN กับ AuthZ เพื่อไม่ให้สับสน

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

FIG 01ประตูหน้าตรวจว่าเป็นใคร ลิฟต์ตรวจว่าไปชั้นไหนได้

บันทึกเป็นภาพ
สองด่านนี้ต้องมีครบเสมอ ผ่านประตูหน้าได้ ไม่ได้แปลว่าขึ้นไปได้ทุกชั้น ระบบในโลกจริงก็ต้องตรวจทั้งสองอย่าง และตรวจด่านสิทธิ์ซ้ำทุกครั้งที่มีคำขอ
อ่านภาพนี้เป็นข้อความ
  1. ผู้มาเยือนมาถึงประตูหน้า อ้างว่าเป็นสมชาย ห้อง 805
  2. รปภ. ตรวจบัตรเทียบกับหน้าตาและรายชื่อลูกบ้าน (การพิสูจน์ตัวตน) ผ่านแล้วจึงให้คีย์การ์ดที่ผูกกับตัวสมชาย
  3. คีย์การ์ดพาเข้าลิฟต์ได้ แต่ลิฟต์ยอมไปแค่บางชั้น (การกำหนดสิทธิ์): ล็อบบี้ ฟิตเนส และชั้น 8 ห้องของตัวเองเปิดได้ ส่วนชั้น 12 ห้องคนอื่น ห้องเครื่องชั้น 2 และดาดฟ้าเปิดไม่ได้
  4. เทียบกับแอป: ล็อกอินคือการพิสูจน์ตัวตน ส่วนการตรวจว่าดูหรือแก้ข้อมูลชิ้นไหนได้คือการกำหนดสิทธิ์ ซึ่งต้องตรวจซ้ำทุกคำขอ

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

ก่อนจะไปต่อ มีอีกสองคำที่มักมาคู่กัน คือ การระบุตัว (Identification) ซึ่งก็แค่การบอกว่า “ฉันคือสมชาย” เช่น พิมพ์ชื่อผู้ใช้ ยังไม่ได้พิสูจน์อะไร และ การตรวจย้อนได้ (Accountability) คือการจดไว้ว่าใครทำอะไร เมื่อไร เพื่อให้ตามรอยได้ภายหลัง รวมทั้งหมดจะเป็นลำดับ ระบุตัว → พิสูจน์ตัวตน → ตรวจสิทธิ์ → บันทึก ที่เราจะไล่ไปทีละด่านในบทนี้

02พิสูจน์ตัวตนด้วยอะไร Authentication factors

ระบบไม่มีทางเห็นหน้าเราจริง ๆ สิ่งที่มันทำได้คือขอหลักฐานบางอย่างที่ “ควรมีแต่ตัวจริงเท่านั้น” หลักฐานพวกนี้แบ่งได้เป็นสามกลุ่มใหญ่ ซึ่งในวงการเรียกว่าปัจจัยการพิสูจน์ตัวตน (authentication factor)

  • สิ่งที่เรารู้: รหัสผ่าน PIN คำตอบคำถามลับ
  • สิ่งที่เรามี: มือถือที่รับรหัสใช้ครั้งเดียว แอปยืนยันตัวตน กุญแจความปลอดภัยแบบเสียบ USB บัตรประชาชนที่มีชิป
  • สิ่งที่เราเป็น: ลายนิ้วมือ ใบหน้า เสียง

ใช้หลักฐานจากสองกลุ่มขึ้นไปพร้อมกัน เรียกว่าการยืนยันตัวตนหลายชั้น (MFA) ข้อดีของมันคือคนร้ายต้องขโมยของสองอย่างที่อยู่คนละที่ ซึ่งยากกว่าขโมยอย่างเดียวมาก ส่วนพาสคีย์ (passkey) ไปไกลกว่านั้นอีก คือเลิกใช้ความลับที่ต้องพิมพ์ไปเลย

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

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

ศัพท์น่ารู้: ตัวตนดิจิทัล (Digital identity) และบัญชี (Account)
คนหนึ่งคนอาจมีหลายบัญชีในหลายระบบ แต่ละบัญชีคือ “ตัวตน” หนึ่งตัวในสายตาของระบบนั้น ไม่ใช่แค่คนเท่านั้นที่มีบัญชี โปรแกรมที่คุยกันเองหลังบ้านก็มี เรียกว่าบัญชีบริการ (service account) และมันต้องถูกจำกัดสิทธิ์เหมือนคนเช่นกัน

03ระบบจำเราได้อย่างไร Sessions & cookies

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

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

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

FIG 02ล็อกอินครั้งเดียว แล้วใช้คุกกี้แทนบัตรในทุกคำขอ

บันทึกเป็นภาพ
สังเกตว่าคุกกี้มีแค่เลขสุ่ม ไม่มีชื่อหรือรหัสผ่านอยู่ในนั้นเลย ข้อมูลจริงอยู่ที่เซิร์ฟเวอร์ และการออกจากระบบที่ถูกต้องคือการลบเซสชันฝั่งเซิร์ฟเวอร์ ไม่ใช่แค่ลบคุกกี้ในเครื่อง
อ่านภาพนี้เป็นข้อความ
  1. เบราว์เซอร์ส่งอีเมลและรหัสผ่านไปที่เซิร์ฟเวอร์ผ่าน HTTPS (POST /login)
  2. เซิร์ฟเวอร์ตรวจรหัสผ่านกับค่าแฮชที่เก็บไว้ ผ่านแล้วสร้างเซสชันใหม่ จดว่าเลขสุ่ม 9f2c…e81a คือ somchai หมดอายุใน 30 นาที
  3. เซิร์ฟเวอร์ส่งคุกกี้กลับมา: Set-Cookie: sid=9f2c…e81a; Secure; HttpOnly; SameSite=Lax
  4. เบราว์เซอร์เก็บคุกกี้ไว้ เหมือนสายรัดข้อมือเข้าคอนเสิร์ต
  5. คำขอถัดไป (GET /profile) เบราว์เซอร์แนบคุกกี้ไปเอง
  6. เซิร์ฟเวอร์ค้นเซสชัน รู้ว่าเป็นสมชาย แล้วตรวจสิทธิ์ ทุกคำขอต้องผ่านสองด่านนี้
  7. เซิร์ฟเวอร์ส่งหน้าโปรไฟล์ของสมชายกลับไป
  8. ตอนออกจากระบบ (POST /logout) เซิร์ฟเวอร์ลบเซสชันทิ้ง คุกกี้เดิมจึงใช้ต่อไม่ได้อีก แม้ใครจะขโมยไป

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

  • เลขเซสชันต้องสุ่มจริงและยาวพอ จนเดาไม่ได้ ถ้าเลขเรียงกัน เช่น 1001, 1002 ใครก็เดาของคนข้าง ๆ ได้
  • ออกเลขใหม่ทุกครั้งที่ล็อกอินสำเร็จ ห้ามใช้เลขเดิมที่มีอยู่ก่อนล็อกอิน ไม่อย่างนั้นคนร้ายที่ฝังเลขไว้ล่วงหน้าจะได้สิทธิ์ตามเราไปด้วย ปัญหานี้มีชื่อเรียกว่า session fixation
  • มีวันหมดอายุสองแบบ คือหมดเมื่อไม่ได้ใช้งานนานเกินไป (idle timeout) และหมดแน่ ๆ เมื่อครบเวลาสูงสุด (absolute timeout) แม้จะใช้อยู่ตลอด
  • ออกจากระบบต้องลบฝั่งเซิร์ฟเวอร์ และเมื่อเปลี่ยนรหัสผ่าน ควรมีปุ่ม “ออกจากระบบทุกอุปกรณ์” ให้กดได้

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

04เมื่อสายรัดข้อมือถูกขโมย Session hijacking

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

คุกกี้รั่วได้หลายทาง แต่ละทางมีเกราะป้องกันคนละแบบ และส่วนหนึ่งของเกราะพวกนี้ก็คือ “ธง” ที่เซิร์ฟเวอร์แปะมากับคุกกี้ตอนส่ง อย่างที่เห็นบรรทัด Set-Cookie ในภาพก่อนหน้า

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

แล้วเราทำอะไรได้บ้าง แบ่งเป็นสองฝั่ง

  1. อย่าเปิดไฟล์โปรแกรมที่ไม่ได้รอ

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

  2. ตรวจรายการอุปกรณ์ที่ล็อกอินอยู่

    บัญชีใหญ่ ๆ อย่าง Google, Facebook หรือ LINE มีหน้ารวมอุปกรณ์และเซสชันที่ใช้งานอยู่ ถ้าเห็นเครื่องที่ไม่รู้จัก ให้สั่งออกจากระบบเครื่องนั้น แล้วเปลี่ยนรหัสผ่านทันที

  3. อย่าส่งคุกกี้หรือไฟล์บันทึกเบราว์เซอร์ให้ใคร

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

  4. ผู้สร้างเว็บ: ตั้งธงครบ และทำให้เซสชันที่ถูกขโมยมีค่าน้อยที่สุด

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

05โทเค็น: บัตรผ่านที่พกข้อมูลมาเอง Tokens & JWT

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

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

รูปแบบโทเค็นที่เห็นบ่อยที่สุดในเว็บและแอปปัจจุบันชื่อว่า JWT (JSON Web Token) อ่านว่า “จอต” ก็มี แต่คนไทยส่วนใหญ่เรียก เจ-ดับเบิลยู-ที หน้าตาของมันคือข้อความยาวสามท่อนคั่นด้วยจุด

FIG 04ผ่า JWT ออกเป็นสามท่อน

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

ตัวอย่างโทเค็นจริงที่เซ็นด้วยกุญแจสาธิต ประกอบด้วยสามท่อนคั่นด้วยจุด:

  1. ส่วนหัว (header) บอกชนิดโทเค็นและอัลกอริทึมที่ใช้เซ็น: {"alg": "HS256", "typ": "JWT"}
  2. ข้อมูลข้างใน (payload หรือ claims) ใครก็ถอดอ่านได้ เพราะเป็นแค่การแปลงรูปแบบ (Base64URL) ไม่ใช่การเข้ารหัสลับ: {"sub": "65001", "name": "Somchai", "role": "student", "exp": 1793174400}
  3. ลายเซ็น (signature) คำนวณจากสองท่อนแรกกับกุญแจลับของเซิร์ฟเวอร์ ด้วย HMAC-SHA256 ถ้าใครแก้ role เป็น admin ลายเซ็นจะไม่ตรงทันที

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

จุดที่ต้องจำให้ขึ้นใจมีสามข้อ

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

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

แบบที่พังถอดอ่านเฉย ๆ ไม่ได้ตรวจลายเซ็น

// ใช้ข้อมูลในโทเค็นทั้งที่ยังไม่รู้ว่าใครเป็นคนเขียน
const claims = jwt.decode(token);
if (claims.role === "admin") { showAdminPanel(); }

แบบที่ถูกตรวจลายเซ็น อัลกอริทึม ผู้ออก ผู้รับ และวันหมดอายุก่อน

// ถ้าข้อใดไม่ผ่าน verify จะโยนข้อผิดพลาด และเราปฏิเสธคำขอทันที
const claims = jwt.verify(token, publicKey, {
  algorithms: ["RS256"],          // กำหนดเอง ไม่เชื่อค่าในส่วนหัว
  issuer: "https://login.school.example",
  audience: "grades-app"
});                                  // exp ถูกตรวจให้อัตโนมัติ

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

ศัพท์น่ารู้: Access token, Refresh token และ Bearer token
Access token คือบัตรผ่านอายุสั้น เช่น ไม่กี่นาทีถึงหนึ่งชั่วโมง ใช้แนบไปกับทุกคำขอ Refresh token คือบัตรอายุยาวกว่าที่เก็บไว้อย่างดี ใช้ขอ access token ใบใหม่เมื่อใบเก่าหมดอายุ และเซิร์ฟเวอร์สั่งยกเลิกได้ ส่วนคำว่า bearer แปลว่า “ผู้ถือ” หมายถึงใครถือโทเค็นอยู่ก็ใช้ได้ทันที เหมือนธนบัตร ไม่มีชื่อเจ้าของติดอยู่ จึงต้องรักษามันเท่ากับรักษากุญแจ

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

06ปุ่ม “เข้าสู่ระบบด้วย Google” ทำงานอย่างไร SSO, OAuth 2.0 & OpenID Connect

เกือบทุกแอปที่เราโหลดมาวันนี้มีปุ่มหน้าตาคุ้นเคยอยู่ใต้ช่องกรอกรหัสผ่าน “เข้าสู่ระบบด้วย Google” “ดำเนินการต่อด้วย LINE” “Sign in with Apple” กดปุ่มเดียวก็เข้าได้ ไม่ต้องตั้งรหัสผ่านใหม่ให้จำเพิ่มอีกชุด

แนวคิดเบื้องหลังเรียกว่าการลงชื่อเข้าใช้ครั้งเดียว (Single Sign-On หรือ SSO) คือให้ผู้เชี่ยวชาญด้านตัวตนเพียงเจ้าเดียวทำหน้าที่ตรวจบัตรแทนทุกแอป เปรียบเหมือนบัตรพนักงานใบเดียวที่ใช้เข้าได้ทุกตึกในบริษัท ฝ่ายบุคคลออกบัตรและยกเลิกบัตรที่จุดเดียว ตึกแต่ละตึกไม่ต้องทำทะเบียนพนักงานเอง

ศัพท์น่ารู้: ผู้ให้บริการตัวตน (Identity Provider หรือ IdP)
ระบบที่ทำหน้าที่ตรวจตัวตนแล้ว “รับรอง” ให้ระบบอื่น เช่น บัญชี Google, Microsoft, Apple หรือ LINE ฝั่งผู้ใช้ และระบบอย่าง Microsoft Entra ID หรือ Okta ฝั่งองค์กร ส่วนแอปที่เชื่อคำรับรองนั้น เรียกว่าผู้ให้บริการ (Service Provider) หรือในภาษาของ OAuth เรียกว่า client

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

ตัวที่สองคือ OpenID Connect (OIDC) ซึ่งสร้างต่อบน OAuth 2.0 เพื่อเติมสิ่งที่ขาดไป คือใบรับรองตัวตนที่เรียกว่า ID token บอกแอปอย่างเป็นทางการว่า “คนที่เพิ่งล็อกอินคือใคร” ส่วนในองค์กรใหญ่ ยังเจอมาตรฐานรุ่นพี่ชื่อ SAML ซึ่งทำงานคล้ายกันแต่ใช้เอกสาร XML แทน

FIG 05เกิดอะไรขึ้นบ้างหลังกดปุ่ม “เข้าสู่ระบบด้วย LINE”

บันทึกเป็นภาพ
ขั้นตอนนี้เรียกว่า authorization code flow ร่วมกับ PKCE ซึ่งเป็นแบบที่แนะนำสำหรับทั้งเว็บและแอปมือถือ เส้นประคือข้อความที่เดินทางผ่านเบราว์เซอร์ของเรา ส่วนเส้นทึบที่มีแม่กุญแจคือการคุยกันตรง ๆ หลังบ้าน
อ่านภาพนี้เป็นข้อความ
  1. เรากดปุ่ม “เข้าสู่ระบบด้วย LINE” ในแอปจองตั๋วหนัง
  2. แอปพาเบราว์เซอร์ไปที่หน้าล็อกอินของ LINE พร้อมรายการสิทธิ์ที่ขอ (scope เช่น openid profile) และรหัสท้าทาย PKCE
  3. เราล็อกอินกับ LINE โดยตรง รหัสผ่านและการยืนยันสองชั้นกรอกที่หน้าของ LINE เท่านั้น แอปไม่เคยเห็นรหัสผ่าน
  4. LINE แสดงหน้าขอความยินยอม ว่าแอปนี้ขอดูชื่อและรูปโปรไฟล์ ควรอ่านก่อนกดเสมอ
  5. LINE ส่งรหัสใช้ครั้งเดียว (authorization code) อายุสั้นมาก กลับไปที่แอปผ่านเบราว์เซอร์
  6. แอปนำรหัสนั้นพร้อมคำตอบของ PKCE ไปแลกกับ LINE โดยตรงทางหลังบ้าน
  7. LINE ส่ง ID token (บอกว่าเราคือใคร) และ access token (ใช้ได้เฉพาะสิทธิ์ที่ยอมไว้) กลับมา
  8. แอปตรวจลายเซ็นของ ID token ว่าออกโดย LINE จริง ออกให้แอปนี้ และยังไม่หมดอายุ แล้วจึงสร้างเซสชันของตัวเอง
  9. แอปต้อนรับเราเข้าสู่ระบบ

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

ส่วน PKCE (อ่านว่า “พิกซี่”) คือกลไกเล็ก ๆ ที่กันไม่ให้ใครดักเอารหัสใช้ครั้งเดียวกลางทางไปแลกโทเค็นแทนแอปตัวจริง แอปสร้างความลับขึ้นมาก่อนเริ่ม ส่งไปแค่ “ร่องรอย” ของมัน แล้วค่อยโชว์ความลับตัวจริงตอนแลก คนที่ดักได้แค่รหัสจึงแลกไม่ได้ LINE เองก็รองรับ PKCE ในบริการ LINE Login

ความเชื่อ

“ล็อกอินด้วย Google ก็แปลว่าแอปนั้นรู้รหัสผ่าน Google ของเรา และเข้าอีเมลเราได้ทุกฉบับ”

ความจริง

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

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

  1. ป้องกันบัญชีหลักให้แน่นที่สุด

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

  2. อย่ากดยอมโดยไม่อ่าน

    แอปดูดวงหรือแอปแต่งรูปไม่มีเหตุผลต้องขอสิทธิ์อ่านอีเมลหรือรายชื่อผู้ติดต่อ ขอเกินงาน ให้ยกเลิก

  3. ไล่ถอนแอปที่เลิกใช้แล้ว

    ในหน้าตั้งค่าความปลอดภัยของบัญชี Google, Facebook หรือ LINE มีรายการแอปที่เชื่อมต่อไว้ ลองเปิดดูปีละสองสามครั้ง อันไหนจำไม่ได้หรือไม่ใช้แล้ว กดถอนสิทธิ์ได้เลย

  4. ผู้สร้างแอป: ทำตามมาตรฐานให้ครบ

    ใช้ authorization code flow กับ PKCE ตรวจค่า state และ nonce ลงทะเบียนที่อยู่ปลายทาง (redirect URI) แบบตรงตัว ตรวจลายเซ็น ผู้ออก ผู้รับ และวันหมดอายุของ ID token และขอ scope เท่าที่จำเป็นจริง ๆ

07ให้สิทธิ์เท่าที่จำเป็น และจัดเป็นบทบาท Least privilege & RBAC

พอผ่านด่านตัวตนมาแล้ว ก็ถึงคำถามที่สอง ว่าคนคนนี้ควรทำอะไรได้บ้าง หลักที่นักความปลอดภัยทุกคนยึดไว้คือ หลักสิทธิ์น้อยที่สุด (Principle of Least Privilege) ให้แต่ละคน แต่ละโปรแกรม มีสิทธิ์เท่าที่งานต้องใช้จริงเท่านั้น และให้เฉพาะช่วงเวลาที่ต้องใช้

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

ในองค์กรที่มีคนเป็นร้อยเป็นพัน การไล่แจกสิทธิ์ทีละคนทีละระบบเป็นไปไม่ได้ วิธีที่นิยมที่สุดจึงเป็นการจัดสิทธิ์ตามบทบาท (Role-Based Access Control หรือ RBAC) คือผูกสิทธิ์ไว้กับ “บทบาท” เช่น นักเรียน ครู ฝ่ายทะเบียน แล้วค่อยใส่คนเข้าไปในบทบาท ใครย้ายตำแหน่งก็ย้ายบทบาท สิทธิ์ตามไปเอง

FIG 06ตารางสิทธิ์ตามบทบาทของระบบทะเบียนโรงเรียน

บันทึกเป็นภาพ
บางช่องมีเงื่อนไขกำกับ เช่น ครูแก้เกรดได้ “เฉพาะวิชาที่สอน” ตรงนี้บทบาทอย่างเดียวไม่พอ ระบบต้องดูความสัมพันธ์ระหว่างคนกับข้อมูลชิ้นนั้นด้วย
อ่านภาพนี้เป็นข้อความ
  • นักเรียน (สมชาย, มานี): ดูเกรดของตัวเองได้เท่านั้น
  • ครูประจำวิชา (ครูสุดา): ดูเกรดของนักเรียนคนอื่นและแก้เกรดได้ เฉพาะวิชาที่สอน
  • ฝ่ายทะเบียน (คุณวิภา): ดูเกรดของนักเรียนได้ทุกคน และออกใบแสดงผลการเรียน แต่แก้เกรดไม่ได้
  • ผู้ดูแลระบบ (คุณต้น ฝ่ายไอที): จัดการบัญชีและบทบาทได้ แต่ไม่มีสิทธิ์ดูหรือแก้เกรด

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

ตารางนี้ซ่อนแนวคิดสำคัญไว้สามอย่าง

ปฏิเสธเป็นค่าเริ่มต้น (deny by default) ช่องที่เป็นขีดคือ “ไม่ได้” ระบบที่ดีเริ่มจากไม่ให้อะไรเลย แล้วค่อยเปิดทีละอย่างที่จำเป็น ไม่ใช่เปิดทุกอย่างแล้วค่อยไล่ปิด เพราะสิ่งที่ลืมปิดคือช่องโหว่เสมอ

แบ่งแยกหน้าที่ (separation of duties) สังเกตว่าผู้ดูแลระบบซึ่งดูเหมือนมีอำนาจที่สุด กลับแก้เกรดไม่ได้ ส่วนฝ่ายทะเบียนที่ออกใบแสดงผลการเรียนก็แก้เกรดไม่ได้เช่นกัน งานสำคัญถูกแบ่งให้ต้องมีมากกว่าหนึ่งคน คนเดียวโกงคนเดียวไม่ได้ ถูกยึดบัญชีเดียวก็ยังไม่พังทั้งระบบ

บทบาทอย่างเดียวบางทีไม่พอ “ครูแก้เกรดได้” ฟังดูง่าย แต่จริง ๆ คือ “ครูแก้เกรดได้เฉพาะวิชาที่ตัวเองสอน” ระบบจึงต้องดูคุณสมบัติประกอบด้วย ว่าคนนี้สอนวิชาอะไร ข้อมูลชิ้นนี้เป็นของวิชาไหน การตัดสินสิทธิ์จากคุณสมบัติแบบนี้มีชื่อเรียกว่า ABAC (Attribute-Based Access Control) ระบบจริงส่วนใหญ่ใช้ผสมกันทั้งสองแบบ

ศัพท์น่ารู้: สิทธิ์แบบทันเวลา (Just-in-time access) และบัญชีฉุกเฉิน (Break-glass)
แทนที่จะให้แอดมินถือสิทธิ์สูงไว้ตลอด 24 ชั่วโมง ระบบสมัยใหม่ให้ขอสิทธิ์สูงเฉพาะตอนจะทำงาน มีคนอนุมัติ และหมดอายุเองเมื่อครบเวลา ส่วนบัญชีฉุกเฉินคือบัญชีสิทธิ์สูงที่เก็บไว้ใช้ตอนระบบหลักล่มเท่านั้น เหมือนตู้ดับเพลิงที่ต้องทุบกระจก ใครเปิดใช้ ทุกคนต้องรู้

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

08เมื่อการตรวจสิทธิ์พัง Broken access control

กลับมาที่สมชายกับเกรดของมานี สิ่งที่เกิดขึ้นมีชื่อเรียกอย่างเป็นทางการว่าการควบคุมการเข้าถึงที่บกพร่อง (Broken Access Control) โครงการ OWASP ซึ่งเป็นชุมชนด้านความปลอดภัยเว็บที่คนทั่วโลกใช้อ้างอิง จัดให้มันอยู่อันดับหนึ่งใน OWASP Top 10 ทั้งฉบับปี 2021 และฉบับปี 2025 ไม่ใช่เพราะมันซับซ้อน แต่เพราะมันพลาดง่ายและพบได้แทบทุกที่

ความผิดพลาดแบบนี้ทำให้คนที่ล็อกอินอย่างถูกต้อง ได้สิทธิ์มากกว่าที่ควรได้ ซึ่งเรียกว่าการยกระดับสิทธิ์ (privilege escalation) มีสองทิศ

FIG 07ยกระดับสิทธิ์แนวราบและแนวดิ่ง และจุดที่ต้องตัด

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

ระดับสิทธิ์จากบนลงล่าง: ผู้ดูแลระบบ (จัดการบัญชีทั้งหมด) ครู (แก้เกรดวิชาที่สอน) และนักเรียน (ดูข้อมูลของตัวเอง)

  1. แนวราบ: สมชายซึ่งเป็นนักเรียน พยายามดูเกรดของมานี ซึ่งเป็นนักเรียนระดับเดียวกัน กันได้ด้วยการตรวจความเป็นเจ้าของ คือทุกครั้งที่ขอข้อมูลชิ้นไหน เซิร์ฟเวอร์ต้องเช็กว่าชิ้นนั้นเป็นของผู้ขอจริง ไม่ใช่แค่เช็กว่าล็อกอินแล้ว
  2. แนวดิ่ง: สมชายพยายามเข้าหน้าของผู้ดูแลระบบ กันได้ด้วยการตรวจบทบาทฝั่งเซิร์ฟเวอร์ทุกคำขอที่ต้องใช้สิทธิ์สูง ไม่เชื่อค่าที่เบราว์เซอร์ส่งมา และไม่พึ่งการซ่อนปุ่ม

แนวราบ (horizontal) คือการเข้าถึงข้อมูลของคนอื่นที่อยู่ระดับเดียวกัน อย่างที่สมชายทำ รูปแบบที่พบบ่อยที่สุดมีชื่อว่า IDOR (Insecure Direct Object Reference) คือระบบรับเลขอ้างอิงข้อมูล เช่น เลขคำสั่งซื้อ เลขใบเสร็จ รหัสนักเรียน มาจากผู้ใช้ตรง ๆ แล้วหยิบข้อมูลชิ้นนั้นให้ โดยไม่ถามว่าเป็นของคนขอหรือเปล่า

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

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

แบบที่พังตรวจแค่ว่าล็อกอินแล้ว

app.get("/grades/:studentId", requireLogin, (req, res) => {
  // ใครล็อกอินได้ ขอเลขอะไรมาก็ได้ของคนนั้น
  res.json(db.grades.find(req.params.studentId));
});

แบบที่ถูกตรวจทั้งตัวตนและสิทธิ์ต่อข้อมูลชิ้นนั้น ที่เซิร์ฟเวอร์

app.get("/grades/:studentId", requireLogin, (req, res) => {
  const me = req.session.user;              // มาจากเซสชันฝั่งเซิร์ฟเวอร์
  const target = req.params.studentId;
  if (!canReadGrades(me, target)) {      // ของตัวเอง หรือเป็นครูวิชานั้น
    audit.log(me.id, "grades.read", target, "DENIED");
    return res.status(403).end();         // ปฏิเสธเป็นค่าเริ่มต้น
  }
  res.json(db.grades.find(target));
});

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

  1. ตรวจสิทธิ์ที่เซิร์ฟเวอร์ ทุกคำขอ

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

  2. รวมการตรวจไว้ที่เดียว

    เขียนฟังก์ชันตรวจสิทธิ์กลางแล้วเรียกใช้ทุกที่ ดีกว่าให้แต่ละหน้าเขียนเงื่อนไขเอง ซึ่งวันหนึ่งต้องมีหน้าที่ลืม

  3. ปฏิเสธเป็นค่าเริ่มต้น

    หน้าใหม่ ฟีเจอร์ใหม่ ต้องปิดไว้ก่อนจนกว่าจะกำหนดสิทธิ์ ไม่ใช่เปิดไว้ก่อนแล้วค่อยมาปิด

  4. อย่าเชื่อค่าจากฝั่งผู้ใช้

    บทบาท รหัสผู้ใช้ หรือราคาที่ส่งมาจากเบราว์เซอร์ใช้ตัดสินสิทธิ์ไม่ได้ ดึงจากเซสชันหรือฐานข้อมูลฝั่งเซิร์ฟเวอร์เสมอ

  5. เขียนเทสต์ที่ลองใช้สิทธิ์เกิน

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

09บันทึกทุกการเข้าถึง และทบทวนสิทธิ์สม่ำเสมอ Logging & access review

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

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

บันทึกการเข้าถึงตัวอย่างที่ระบบตรวจจับควรส่งเสียงเตือน

เวลา (UTC)            ผู้ใช้     บทบาท    การกระทำ      ข้อมูล    ผล
2026-10-27T13:02:11Z  somchai   student  grades.read   65001    ALLOW
2026-10-27T13:02:40Z  somchai   student  grades.read   65002    DENIED
2026-10-27T13:02:41Z  somchai   student  grades.read   65003    DENIED
2026-10-27T13:02:43Z  somchai   student  grades.read   65004    DENIED
…
2026-10-27T19:47:05Z  ton       admin    role.grant    ton → teacher  ALLOW  ← แอดมินแจกสิทธิ์ให้ตัวเอง

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

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

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

FIG 08เข้าทำงาน ย้ายแผนก ลาออก: สิทธิ์ต้องตามคนให้ทัน

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

ทำถูก

  1. เข้าทำงาน (Joiner): ได้อีเมลและระบบขาย ให้เท่าที่งานต้องใช้
  2. ย้ายจากฝ่ายขายไปฝ่ายบัญชี (Mover): ได้ระบบบัญชี และถอดระบบขายออกทันทีที่ย้าย
  3. วันสุดท้ายก่อนลาออก (Leaver): ปิดอีเมล ระบบบัญชี และ VPN ทั้งหมด

ลืมจัดการ

  1. เข้าทำงาน: เริ่มต้นเหมือนกัน คืออีเมลและระบบขาย
  2. ย้ายแผนก: ได้ระบบบัญชีเพิ่ม แต่ระบบขายยังอยู่ สิทธิ์พอกพูน (privilege creep)
  3. ลาออก: อีเมล ระบบขาย ระบบบัญชี และ VPN ยังเปิดอยู่ทั้งหมด กลายเป็นบัญชีผีที่ออกไปแล้วยังล็อกอินได้
  1. ผูกบัญชีกับระบบบุคคล

    เมื่อฝ่ายบุคคลบันทึกว่ามีคนเข้า ย้าย หรือออก ให้ระบบเปิด ปรับ หรือปิดบัญชีตามไปเองโดยอัตโนมัติ คนเรามักลืม ระบบไม่ลืม

  2. ทบทวนเป็นรอบ

    ทุกไตรมาสหรือทุกครึ่งปี ส่งรายชื่อคนที่มีสิทธิ์ในระบบสำคัญให้หัวหน้าแต่ละฝ่ายยืนยัน คนไหนไม่ควรมีแล้ว ถอดทันที บัญชีแอดมินและบัญชีบริการต้องถูกทบทวนถี่ที่สุด

  3. ไล่หาบัญชีที่ไม่ได้ใช้

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

  4. เก็บบันทึกไว้ในที่ที่แก้ไม่ได้

    ส่งบันทึกการเข้าถึงไปเก็บแยกจากระบบที่ถูกบันทึก ให้แม้แต่แอดมินของระบบนั้นก็ลบไม่ได้ และกำหนดว่าใครมีหน้าที่อ่านมัน เรื่องนี้เล่าต่อใน บทศูนย์ปฏิบัติการความปลอดภัย (SOC)

10สรุปให้จำ Recap

ทั้งบทนี้ย่อได้เหลือสองคำถามที่ระบบต้องถามทุกครั้ง “คุณคือใคร” และ “คุณทำสิ่งนี้ได้ไหม” ถ้าจำได้แค่หกข้อ ให้จำหกข้อนี้

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

ลองทบทวนดู

สมชายล็อกอินถูกต้อง แต่เปลี่ยนรหัสนักเรียนในลิงก์แล้วเห็นเกรดของเพื่อน ระบบพลาดที่ด่านไหน

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

ทำไมคนร้ายที่ขโมยคุกกี้เซสชันไปได้ ถึงเข้าบัญชีได้ทั้งที่เจ้าของเปิดยืนยันสองชั้นไว้

เพราะคุกกี้เซสชันออกให้หลังจากผ่านการตรวจรหัสผ่านและ MFA ไปแล้ว มันเหมือนสายรัดข้อมือที่บอกว่า “คนนี้ตรวจแล้ว” ระบบจึงไม่ถามซ้ำ วิธีลดความเสียหายคือเซสชันอายุสั้น ขอยืนยันตัวตนซ้ำก่อนทำเรื่องสำคัญ และสั่งออกจากระบบทุกอุปกรณ์เมื่อสงสัย

ถ้าแก้คำว่า student ในท่อนกลางของ JWT เป็น admin จะเกิดอะไรขึ้นกับระบบที่ทำถูกต้อง

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

OAuth 2.0 กับ OpenID Connect ต่างกันอย่างไร

OAuth 2.0 เป็นเรื่องการมอบสิทธิ์ ให้แอปทำบางอย่างแทนเราได้ตามที่ยินยอม ผ่าน access token ส่วน OpenID Connect สร้างต่อบน OAuth 2.0 เพื่อบอกว่าเราคือใคร ผ่าน ID token ปุ่ม “เข้าสู่ระบบด้วย Google หรือ LINE” ใช้ทั้งสองอย่างร่วมกัน

ในตาราง RBAC ทำไมผู้ดูแลระบบถึงไม่มีสิทธิ์แก้เกรด ทั้งที่ดูเหมือนควรมีอำนาจมากที่สุด

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