เข้าใจความเสี่ยงของ policy สอบถามเรื่อง policy ได้ด้วยภาษาทั่วไป ขอชมการสาธิต →

Published:

เทคนิคขั้นสูงในการป้องกัน AWS ExternalID และการเข้าถึงแบบ Cross-Account AssumeRole

by FireMon

เมื่อเดือนที่ผ่านมา Kesten Broughton จาก Praetorian Security ได้เผยแพร่งานวิจัยที่น่าสนใจเกี่ยวกับผลิตภัณฑ์ cloud security ของผู้ให้บริการภายนอกที่ใช้เทคนิคการเชื่อมต่อข้ามบัญชีซึ่ง Amazon แนะนำ ได้แก่ AWS IAM Assume Role Vulnerabilities Found in Many Top Vendors ย่อหน้าเปิดของงานวิจัยสรุปภาพรวมไว้ได้อย่างชัดเจน ดังนี้

ในบล็อกแรกของซีรีส์ว่าด้วย cross-account-trust นี้ เราจะนำเสนอผลการตรวจสอบผู้ให้บริการ 90 ราย ซึ่งพบว่า 37% ไม่ได้นำ ExternalId ไปใช้อย่างถูกต้องเพื่อป้องกันการโจมตีแบบ confused-deputy และอีก 15% ของผู้ให้บริการใช้งานการเชื่อมต่อบัญชี AWS บน UI ได้อย่างถูกต้อง แต่ไม่ได้ตรวจสอบพารามิเตอร์ ExternalId อย่างเหมาะสมในฝั่ง backend ทำให้ไซต์เหล่านั้นมีความเสี่ยงเช่นกัน ในตอนท้ายเราจะอภิปรายถึงพื้นผิวการโจมตีรูปแบบใหม่ที่เกิดขึ้นจาก AWS cross-account-assume-role trust และเราสรุปว่าผู้ให้บริการและลูกค้าควรพิจารณาอย่างถี่ถ้วนว่า role trust เป็นกลไกความไว้วางใจที่เหมาะสมที่สุดสำหรับโซลูชัน SaaS แบบ multi-tenant ของตนหรือไม่

ปฏิกิริยาแรกของผมเมื่ออ่านงานวิจัยนี้คือ "ทำไมถึงมีคนตัดสินใจแย่ขนาดนี้" แต่ความจริงก็คือแนวคิดเรื่อง "ผู้เชี่ยวชาญด้าน cloud security" ยังค่อนข้างใหม่ และแม้ AWS จะอธิบายปัญหา confused deputy ไว้พอสมควร แต่ก็ไม่ได้ครอบคลุมประเด็นเชิงปฏิบัติบางอย่างที่คุณมักจะไม่ได้นึกถึงจนกว่าผลิตภัณฑ์ของคุณจะเชื่อมต่อกับลูกค้าจริง การเชื่อมต่อข้ามบัญชีด้วย AssumeRole มีแนวทางทางวิศวกรรมที่ตรงไปตรงมา แต่หากขาดการทำ threat modeling ที่เหมาะสม ก็เกิดความผิดพลาดแบบที่งานวิจัยของ Kesten บันทึกไว้ได้ง่ายมาก

ที่ DisruptOps เราตัดสินใจอย่างรอบคอบตั้งแต่ช่วงแรกโดยอาศัย threat modeling เบื้องต้น ซึ่งช่วยให้เราปลอดภัย อย่างไรก็ตาม เราก็ได้ข้อมูลที่เป็นประโยชน์จากงานวิจัยนี้และกำลังเพิ่มมาตรการ hardening ให้มากขึ้นไปอีก นอกจากนี้เรายังมีโครงการ skunkworks ที่จะขจัดปัญหาส่วนใหญ่นี้ออกไป พร้อมทั้งยังรองรับการทำงานอัตโนมัติในระดับขนาดใหญ่ได้โดยไม่ต้องขอสิทธิ์เขียนโดยตรงในสภาพแวดล้อมของลูกค้า

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

ปัญหา

ทุกวันนี้แอปพลิเคชันหลากหลายประเภทจำเป็นต้องเชื่อมต่อกับ cloud API โดยตรง อาจเรียบง่ายอย่างการเข้าถึง S3 bucket หรือซับซ้อนอย่างแพลตฟอร์ม Cloud Detection and Response แบบอัตโนมัติเต็มรูปแบบ (ซึ่งก็ยกมาเป็นตัวอย่างแบบสุ่ม ๆ นะครับ) คำขอเหล่านี้ต้องใช้ credentials บางรูปแบบ ซึ่งอาจเป็นแบบ static (เช่น ชื่อผู้ใช้และรหัสผ่าน หรือ IAM access key และ secret key) หรือแบบ dynamic โดย dynamic credentials จะมี token หรือแอตทริบิวต์ชั่วคราวอื่น ๆ ที่มีอายุการใช้งานจำกัด

static credentials ของแอปพลิเคชันที่เปิดให้เข้าถึง cloud management plane ของคุณได้โดยตรงนั้น... ไม่ดีเลย เราแก้ปัญหานี้ให้ผู้ใช้งานได้ด้วย MFA แต่วิธีนี้ไม่ช่วยอะไรกับแอปพลิเคชันอัตโนมัติ เช่น แพลตฟอร์ม Cloud Detection and Response ที่ไม่ได้เป็นเพียงทฤษฎีของเรา เมื่อระยะหนึ่งก่อนหน้านี้ Amazon Web Services แก้ปัญหาดังกล่าวด้วยแนวคิด IAM Role โดย role ใน AWS คือคอนเทนเนอร์ของสิทธิ์การใช้งานซึ่งมีสองนโยบาย ได้แก่ คอนเทนเนอร์นั้นทำอะไรได้บ้าง และใคร (หรือสิ่งใด) สามารถ assume role นั้นได้ จากนั้น role จะทำงานแบบอิงเซสชัน ดังนั้นเมื่อเอนทิตีที่ได้รับอนุญาต assume role ก็จะได้รับชุด credentials (access key, secret key และ session token) ที่ใช้ได้ตลอดระยะเวลาของเซสชัน (ตั้งแต่ 1 ถึง 24 ชั่วโมง)

role ใน AWS สามารถ assume ได้ผ่านการเชื่อมต่อ SAML จากภายนอก (สำหรับผู้ใช้งาน) การเชื่อมต่อแบบ "trusted" ภายใน (จากบัญชี AWS อื่น) หรือผ่านบริการของ AWS (เช่น EC2 instance หรือ Lambda function) คุณจึงทำสิ่งที่น่าสนใจได้ เช่น รันโค้ดใน instance โดยที่ instance นั้นไม่เคยจัดเก็บ static credentials เลย ซึ่งช่วยลดความยุ่งยากไปได้มาก

การอนุญาตการเชื่อมต่อจากบัญชีที่คุณไม่ได้ควบคุมเองนั้นต่างออกไปเล็กน้อย โดยเฉพาะเมื่อบัญชีนั้นคือแพลตฟอร์มที่ให้บริการลูกค้าหลายราย แพลตฟอร์มลักษณะนี้ (ก็คือเรานี่แหละ) ต้องเข้าถึงบัญชี AWS อื่นนับร้อยหรือนับพันบัญชี ลองนึกภาพว่าผู้โจมตีสามารถหลอกให้แพลตฟอร์มไปดำเนินการบางอย่างในบัญชีที่ไม่ถูกต้อง เช่น ทำ configuration assessment กับบัญชีที่ผู้ใช้ปัจจุบันไม่ได้เป็นเจ้าของ นี่คือคำอธิบายอย่างย่อของปัญหา confused deputy กล่าวคือ deputy ได้รับความไว้วางใจจากหลายบัญชี และผู้ใช้หลอก deputy ให้มอบสิทธิ์เข้าถึงบัญชีที่ผู้ใช้รายนั้นไม่ควรแตะต้อง

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

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

เว้นแต่ว่า...

คุณคงพอเดาได้ว่าเรื่องนี้จะไปทางไหน Praetorian พบผู้ให้บริการจำนวนมากที่ใช้ค่า AWS ExternalId แบบ default หรือเปิดให้ลูกค้ากำหนดค่า ExternalId ที่ไม่ซ้ำกันเพียงค่าเดียวสำหรับทั้งบัญชี อีกทั้งยังพบผลิตภัณฑ์ที่ไม่ตรวจสอบด้วยซ้ำว่าลูกค้าได้บังคับใช้ External ID ใน role trust policy ของตนจริงหรือไม่ Praetorian พบแพลตฟอร์มที่ถูกโจมตีได้จริง เปิดช่องให้เกิดการโจมตีแบบ confused deputy อันเนื่องมาจากการรักษาความปลอดภัยของ cross account role ที่ไม่รัดกุม

การทำ hardening ให้กับการเชื่อมต่อข้ามบัญชี

ทั้งหมดนี้อยู่ในการทำ threat modeling ของเราที่ DisruptOps อยู่แล้ว เราจึงอยู่ในสถานะที่ดี แม้ว่าเราจะได้แนวคิดเพิ่มเติมอีกหนึ่งหรือสองอย่างมาปรับปรุงให้ดียิ่งขึ้น มาดูทีละประเด็นที่ Praetorian ระบุไว้ พร้อมแนวทางที่ดีที่สุดในการทำ security hardening กัน

  • AWS ExternalID ใช้ค่า default: เราใช้ ExternalID แบบสุ่ม
  • AWS ExternalID ถูกใช้ร่วมกันในหลายบัญชีของลูกค้า:เราใช้ ExternalID แบบสุ่มแยกตามแต่ละบัญชี ไม่ใช่แยกตามแต่ละลูกค้า
  • AWS ExternalID สามารถไล่เดาหรือคาดเดาได้: เราใช้ AWS ExternalID ที่สุ่มสมบูรณ์และมีความยาวมาก ผมไม่กังวลกับการเลือก PRNG ของเรา เพราะมีการทำ API rate limiting อยู่ (สำหรับสาย crypto โดยเฉพาะ)
  • ลูกค้ากำหนด ExternalID ของตนเองได้ และอาจใช้ซ้ำหรือใช้ ExternalID ที่อ่อนแอ: เราไม่รองรับให้ลูกค้ากำหนด ExternalID ของตนเอง แม้ว่าจะมีคำขอในเรื่องนี้เข้ามาจริง ๆ ก็ตาม ผมคิดว่านี่คือจุดที่ผู้ให้บริการบางรายพลาดไป ลูกค้าต้องการความสามารถนี้เพื่อจะได้ทำ provisioning ผลิตภัณฑ์แบบอัตโนมัติด้วยตนเอง แต่หากจะทำอย่างปลอดภัย เราต้องตรวจสอบว่า ExternalID เป็นไปตามข้อกำหนดด้านความยาวและความสุ่ม หากมีการตรวจสอบที่เหมาะสม ก็น่าจะทำได้อย่างปลอดภัย
  • แพลตฟอร์มอนุญาตให้ทำ provisioning บัญชี AWS เดียวกันได้หลายครั้ง: เราไม่อนุญาตให้ทำเช่นนี้
  • IAM Role ไม่ได้ถูกจำกัดไว้เฉพาะเอนทิตีเดียวในบัญชีของผู้ให้บริการ แต่เปิดให้ role ใดก็ได้ในบัญชีนั้น: เรายังไม่ได้พูดถึงประเด็นนี้ในบทความนี้ แต่คุณสามารถเลือกให้สิทธิ์ role ใดก็ได้ในบัญชีเข้าถึง role "worker" ในบัญชีของลูกค้า หรือจะให้สิทธิ์เฉพาะทรัพยากรหรือ role เดียวก็ได้ เราจำกัดการเข้าถึงไว้เฉพาะ role ที่เราใช้สำหรับการเข้าถึงข้ามบัญชีเท่านั้น
  • ชื่อ IAM Role เป็นค่าคงที่เหมือนกันในทุกบัญชีของลูกค้า: ความเสี่ยงในกรณีนี้โดยพื้นฐานคือชื่อผู้ใช้ (ชื่อ role) สามารถคาดเดาได้ ผมไม่ถือว่านี่เป็นความเสี่ยงเลย เว้นแต่จะมีความผิดพลาดพื้นฐานอื่น ๆ ร่วมด้วย ตัวอย่างหนึ่งคือ หากผู้โจมตีทราบชื่อ role และสามารถเข้าถึงบัญชีของลูกค้าได้ ก็อาจเพิ่มตัวเองเข้าไปใน role trust policy แล้วใช้สิทธิ์เหล่านั้นได้ (ผมสอนเทคนิคทำนองนี้ในคลาสอบรม incident response ของผม) นี่เป็นความเสี่ยงที่เป็นไปได้ แต่เราให้ระดับความเสี่ยงค่อนข้างต่ำ เพราะหากผู้โจมตีเข้าถึงได้ถึงระดับนั้นแล้ว ก็น่าจะยึด role ใดก็ได้ในบัญชีนั้นอยู่ดี
  • ผู้ให้บริการไม่ตรวจสอบว่ามีการตั้ง ExternalID เป็นเงื่อนไขของการเข้าถึงหรือไม่: เราจัดเตรียม CloudFormation Template ให้ลูกค้าใช้ในการทำ provisioning เพื่อให้แน่ใจว่ามีการตั้งค่านี้อย่างถูกต้อง และเราจะเพิ่มการตรวจสอบว่าค่านี้ยังคงอยู่ โดยเฉพาะเมื่อเราเพิ่มตัวเลือกการทำ provisioning แบบอื่น (ที่ไม่ใช่ CloudFormation) เพื่อตอบโจทย์ความต้องการของลูกค้า

Kesten ดูจะนิยมโมเดลแบบ static credential ร่วมกับการทำ vaulting ที่ดีในฝั่งผู้ให้บริการเพื่อรักษาความปลอดภัยของการเชื่อมต่อ ส่วนตัวผมไม่เห็นด้วย และผมคิดว่าโมเดลของ AWS ปลอดภัยกว่าตั้งแต่ต้น หากปฏิบัติตามข้อควรระวังพื้นฐาน

ปัจจุบันเราใช้มาตรการป้องกันเพิ่มเติมอีกสองข้อนอกเหนือจากข้อเสนอแนะของ Praetorian ได้แก่

  • เราปกปิด ExternalID ใน CloudFormation: เรากำหนด ExternalID เป็นพารามิเตอร์ใน CloudFormation template ของเราโดยเปิดใช้ตัวเลือก NoEcho ซึ่งจะปกปิดค่าดังกล่าวในคอนโซล เครื่องมือ command line และ API วิธีนี้ช่วยลดความเสี่ยงที่ค่าจะถูกเปิดเผยต่อผู้ที่มีสิทธิ์รัน CloudFormation แต่ไม่มีสิทธิ์ IAM
  • เราจำกัดการเข้าถึง CloudFormation template ไว้เฉพาะบัญชีเป้าหมายเท่านั้น: วิธีนี้ช่วยลดความเสี่ยงที่จะมีผู้ฉวยโอกาสเข้ามาดึง ExternalID ไประหว่างกระบวนการ provisioning

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

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

และโปรดติดตามโครงการ skunkworks ใหม่นี้ เราน่าจะพูดถึงรายละเอียดได้ในอีกไม่กี่เดือนข้างหน้า และมันจะเปลี่ยนเกมของปัญหาลักษณะนี้ไปอย่างแท้จริง

เทคนิคขั้นสูงในการป้องกัน AWS ExternalID | FireMon