เข้าใจความเสี่ยงของ policy สอบถามเรื่อง policy ได้ด้วยภาษาทั่วไป ขอชมการสาธิต →
Published:
การจำกัดความเสียหายเมื่อ EC2 Credentials ถูกบุกรุก โดย (หวังว่า) ระบบจะไม่สะดุด
by FireMon
การจำกัดความเสียหายจาก instance credentials ที่ถูกขโมยทำได้หลายวิธี วิธีที่ง่ายที่สุดมักเป็นวิธีที่ทำให้ระบบพังมากที่สุด แต่ก็มีทางเลือกที่สร้างสรรค์ซึ่งสกัดผู้โจมตีออกไปได้โดยไม่กระทบแอปพลิเคชัน
ในช่วงไม่กี่ปีที่ผ่านมา Amazon ได้พัฒนาความสามารถไปมาก เพื่อลดความเสี่ยงที่ผู้โจมตีจะขโมยและนำ credentials ที่กำหนดให้กับ AWS instance ไปใช้ในทางมิชอบ แน่นอนว่าหลายอย่างเกิดขึ้นหลังจากเหตุการณ์ข้อมูลรั่วไหลครั้งใหญ่ที่ยังถูกพูดถึงจนทุกวันนี้ แต่ตอนนี้เรามีเครื่องมือที่ดีขึ้นมากในการป้องกันการโจมตีลักษณะนี้ อย่างไรก็ตาม การโจมตีแบบนี้ยังพบได้บ่อยและติดอันดับต้น ๆ ของรายการภัยคุกคามบนคลาวด์
เมื่อเดือนที่แล้ว AWS ได้เปิดตัวตัวเลือก policy ใหม่สำหรับล็อกดาวน์ instance credentials ซึ่งเป็นจุดเริ่มต้นของบทความนี้ อีกเรื่องหนึ่งคือข้อค้นพบที่น่าสนใจจากชุมชน cloud security ซึ่งจะกล่าวถึงในอีกสักครู่ แม้ความสามารถใหม่นี้จะดีมาก และ AWS ยังปรับปรุงการตรวจจับของ GuardDuty สำหรับการขโมย credentials ออกไปได้ดีขึ้นอย่างมาก แต่ถึงจุดหนึ่งคุณก็อาจได้รับการแจ้งเตือนจากเครื่องมืออย่างของเรา และต้องเริ่มกระบวนการ incident response ทันที

ตลอดหลายปีที่ผ่านมา ผมได้รวบรวมและทดสอบแนวทางการจำกัดความเสียหายไว้จำนวนมาก ซึ่งส่วนใหญ่มีแล็บอยู่ในหลักสูตรอบรม cloud incident response ที่ผมจัดทำร่วมกับWill Bengtson การสกัดผู้โจมตีให้ได้โดยไม่ทำให้ระบบพังนั้นมีรายละเอียดปลีกย่อยมากกว่าที่คิด เมื่อทำงานกับ instance credentials คุณกำลังเกี่ยวข้องกับสามองค์ประกอบ ได้แก่ บริการ IAM ที่จัดการสิทธิ์, Instance Metadata Service (IMDS) ที่ส่ง credentials เหล่านั้นไปยัง instance และโค้ด/SDK ที่รันแอปพลิเคชันของคุณอยู่ภายใน instance และเป็นผู้ใช้ credentials เหล่านั้น โปรดทราบว่าผมตั้งใจทำให้บางเรื่องง่ายขึ้น และไม่ได้กล่าวถึง Service Control Policies และ Resource (bucket) policies (ภาพหน้าจอทั้งหมดในบทความนี้ผมหยิบมาจากหลักสูตรอบรมของผมเองอย่างไม่เกรงใจ ดังนั้นอย่าแจ้งความผมนะครับ)
ขอเล่าพื้นฐานสั้น ๆ สำหรับผู้ที่ยังไม่ทราบ เมื่อคุณกำหนด IAM role ให้กับ instance ตัว instance นั้นจะได้รับ credentials ที่หมุนเวียนเปลี่ยนอัตโนมัติ ซึ่งให้สิทธิ์ตาม role ดังกล่าว แต่หากผู้โจมตีเข้าถึง instance ได้ด้วยวิธีใดวิธีหนึ่ง เช่น การโจมตีแบบ SSRF หรือการ brute force SSH เขาก็สามารถคัดลอก credentials เหล่านั้นไปใช้นอก AWS หรือจากบัญชี AWS ที่อยู่ในการควบคุมของตนเองได้
ในการเตรียมสภาพแวดล้อม Will ได้เขียนแอปพลิเคชันขนาดเล็กที่พยายามเชื่อมต่อภายในไปยัง S3 bucket และรายงานกลับมาว่า credentials ยังใช้งานได้หรือไม่ (ไม่ต้องห่วง IP address ที่แสดงไว้ไม่ได้ใช้งานแล้ว):

ทางเลือกที่ 1: เพิ่ม Deny All Policy ให้กับ Role
วิธีนี้ง่ายและเร็วที่สุดอย่างไม่ต้องสงสัย เมื่อเพิ่ม Deny All policy ให้กับ IAM role การเรียก API ทั้งหมดจะล้มเหลว และผู้โจมตีจะไม่สามารถสร้างความเสียหายเพิ่มได้อีก

เร็ว ง่าย และเป็นวิธีที่ทำให้แอปพลิเคชันของคุณพังได้เร็วมากเช่นกัน เพราะมันจะหยุดการเรียก API ที่ถูกต้องตามปกติไปด้วย:

อย่าลืมว่าเป้าหมายของเราคือการสกัดผู้โจมตีออกไปโดยไม่ทำให้แอปพลิเคชันที่ทำงานอยู่ตามปกติเสียหาย
พลาดแล้ว
ทางเลือกที่ 2: เพิกถอน Session
AWS มีฟีเจอร์ที่น่าสนใจสำหรับการเพิกถอน session ที่ใช้งานอยู่ เพียงคลิกปุ่มเดียวในคอนโซล คุณก็สามารถเพิ่ม Deny All policy แบบกำหนดเองที่ปฏิเสธการเข้าถึงเฉพาะ session ที่เริ่มต้นก่อนเวลาที่คุณกดปุ่มนั้น (ไม่มีการเรียก API ง่าย ๆ สำหรับเรื่องนี้ แต่การเขียน policy ของคุณเองให้ทำงานแบบเดียวกันก็ไม่ยาก)

สมมติว่าคุณกันไม่ให้ผู้โจมตีเข้าถึง session ใหม่ได้แล้ว ในทางทฤษฎีวิธีนี้ควรจะสกัดเขาออกไปได้ด้วยการทำให้ credentials ที่ถูกขโมยหมดอายุ ขณะที่แอปพลิเคชันของคุณใช้ credentials ชุดใหม่ทำงานต่อไป แต่...

ไม่ได้ผล พลาดครั้งที่ 2 เพราะอะไร
IMDS จะอัปเดต credentials ก็ต่อเมื่อ credentials หมดอายุเท่านั้น IMDS เป็นคนละบริการกับ IAM และไม่รับรู้ว่าคุณได้เพิกถอน credentials ไปแล้ว อีกทั้งไม่มีเหตุผลหรือแรงจูงใจใดที่จะไปดึง credentials ชุดใหม่มาส่งให้ instance มันจะยังคงส่ง credentials ที่ถูกเพิกถอนไปแล้วต่อไปจนกว่า session จะสิ้นสุด
ทางเลือกที่ 3: เปลี่ยน IAM Role และปฏิเสธ Role เดิม
คราวนี้เริ่มซับซ้อนขึ้น จะเป็นอย่างไรถ้าเราสร้าง role ใหม่ เปลี่ยนให้ instance ใช้ role ใหม่ และบล็อก role เดิม เช่นเดียวกับก่อนหน้านี้ วิธีนี้จะได้ผลก็ต่อเมื่อคุณมั่นใจว่าผู้โจมตีไม่สามารถขโมย credentials ของ role ใหม่ไปได้อีก

ไม่ได้ผล พลาดครั้งที่ 3 เกิดอะไรขึ้น

โค้ดและ SDK ส่วนใหญ่จะสร้าง IAM session ขึ้นตอนเริ่มทำงาน และดึง credentials จาก metadata service โดย credentials เหล่านี้มีระยะเวลา session ซึ่งเทียบได้กับ Time to Live (TTL) credentials จะถูกเก็บไว้ในหน่วยความจำและใช้งานต่อเนื่องจนใกล้สิ้นสุดระยะเวลาดังกล่าว ตัวอย่างเช่น เมื่อใช้ไลบรารี Boto ใน Python (ซึ่งเราใช้ในเดโมนี้) โค้ดจะไม่มองหา credentials ชุดใหม่จนกว่าจะเหลือเวลาอีก 15 นาทีก่อน credentials หมดอายุ
ดังนั้นแอปพลิเคชันจะล้มเหลวต่อไปเรื่อย ๆ จนกว่าจะถึงเวลามองหา credentials ชุดใหม่ ซึ่งขึ้นอยู่กับการตั้งค่า โดยทั่วไปค่าเริ่มต้นใน EC2 instance มักอยู่ที่ทุก 6 ชั่วโมง เอาล่ะ บางทีคุณอาจมีนักพัฒนาเก่ง ๆ ที่จัดการ error ได้ดีเยี่ยมและเขียนให้ดึง credentials ใหม่เมื่อการเรียก API ล้มเหลว แต่โอกาสนั้นมีน้อย เพราะไม่ใช่กรณีใช้งานทั่วไป
โค้ดภายใน instance ยังคงพยายามใช้ role เดิมอยู่ เพราะมันไม่รู้ว่ามีการเปลี่ยนแปลง ในทางเลือกที่ 2 ระบบพังเพราะ IMDS ไม่รู้ว่าต้องไปตรวจสอบกับ IAM เพื่ออัปเดต credentials ส่วนครั้งนี้ โค้ดไม่รู้ว่าต้องไปคุยกับ IMDS เพื่อขอ credentials ชุดใหม่
ทางเลือกที่ 4: แทรก VPC Endpoint
วิธีนี้เป็นลูกเล่นของผมเอง ซับซ้อนที่สุดอย่างไม่ต้องสงสัย แต่ได้ผลดีมาก ทุก instance อยู่ใน VPC (Virtual Private Cloud) ซึ่งเป็นเครือข่ายเสมือนของ instance นั้นบน AWS โดยปกติการเรียก API ทั้งหมดจะออกไปทางอินเทอร์เน็ตเพื่อไปยัง public endpoint ของ AWS อันที่จริง หากคุณมี instance อยู่ใน private subnet ที่ไม่มีเส้นทางออกสู่อินเทอร์เน็ต การเรียก API เหล่านั้นจะล้มเหลว
ซึ่งนับเป็นปัญหาใหญ่พอสมควร หากคุณต้องการให้ private instance ของคุณคุยกับ private S3 bucket ของคุณเอง ในอดีตเราต้องเปิดการเข้าถึงอินเทอร์เน็ต ซึ่งมีค่าใช้จ่ายและเปิดช่องในแบบที่คนทำงานด้านความปลอดภัยอย่างเราอยากลดให้เหลือน้อยที่สุด
คำตอบของ Amazon คือสิ่งที่เรียกว่า Service Endpoint ซึ่งเป็นโครงสร้างการกำหนดเส้นทางแบบ software-defined ภายใน ที่คุณกำหนดค่าให้ทรัพยากรใน VPC คุยกับ API endpoint ได้ (รวมถึงสิ่งอื่น ๆ แต่ขอไม่ลงรายละเอียดในบทความนี้) โดยอยู่บนเครือข่ายภายในของ Amazon ทั้งหมด จุดที่น่าสนใจคือ เมื่อคุณใช้ VPC endpoint ทาง AWS จะแทรกบริบทเพิ่มเติมเข้าไปในทราฟฟิกเครือข่าย เพราะสามารถฝังข้อมูลไว้ใน private header ของตนได้ และเราก็สามารถนำสิ่งนี้มาใช้เป็นเงื่อนไขใน IAM policy ของเราได้
ขั้นแรก เราเพิ่ม VPC endpoint สำหรับ S3 ลงใน subnet ที่ instance ของเราอยู่:

จากนั้นเราเพิ่มเงื่อนไขใน IAM policy ที่ปฏิเสธทราฟฟิกทั้งหมดที่ไม่ได้มาจาก VPC ที่คาดไว้ นี่คือวิธีที่เราทำในแล็บฝึกอบรม:

หากวิธีนี้ได้ผล การเรียก API จาก instance ของเราจะยังทำงานได้ตามปกติ แต่ credentials จะใช้ไม่ได้หากถูกเรียกจากที่อื่นนอก VPC นี้ (ก็ได้แต่หวังว่าผู้โจมตีจะไม่มีสิทธิ์เข้าถึงทรัพยากรอื่นใน VPC เดียวกัน)

สำเร็จ credentials ของเราจะใช้ได้จากภายใน VPC เท่านั้น และผู้โจมตีถูกสกัดออกไป นี่คือหลักการทำงานของ Service Control Policy ที่ผมกล่าวถึงข้างต้นอย่างแท้จริง ปัญหาของ SCP คือ service endpoint เพิ่มทั้งค่าใช้จ่ายและความซับซ้อน และการเปิดใช้ policy ดังกล่าวโดยยังไม่มี endpoint ครบถ้วนจะทำให้ระบบพัง เพียงแต่คราวนี้พังในระดับองค์กร
พฤติกรรมที่ไม่คาดคิด
อย่างที่กล่าวไป เราสร้างแล็บนี้เมื่อประมาณ 2 ปีก่อน และมีผู้เรียนผ่านแล็บนี้ไปแล้วหลายร้อยคน สัปดาห์ที่แล้ว Andre Rall จาก Uptycs ได้โพสต์ข้อความต่อไปนี้ในชุมชน cloud security ที่เราทั้งคู่เป็นสมาชิกอยู่ (เผยแพร่โดยได้รับอนุญาต):
มีใครทราบไหมว่าสิ่งนี้ควรเกิดขึ้นหรือไม่ ผมมี role ที่ผูกกับ instance profile ซึ่งเชื่อมโยงกับ instance อยู่ เมื่อผมนำ role ออกจาก instance profile (ผ่าน CLI) แต่ยังคงให้ instance profile เชื่อมโยงกับ instance ไว้ ปรากฏว่า instance ยังคงใช้ credentials จาก role นั้นได้อยู่ ความเข้าใจของผมคือเมื่อ role ไม่ได้ผูกอยู่แล้ว instance ก็ไม่ควรใช้งานต่อได้ แต่ผมอาจมองข้ามอะไรบางอย่างไป
ปรากฏว่าเราเจอการทำงานร่วมกันระหว่างบริการแบบเดิมอีกครั้ง เบื้องหลังแล้ว instance profile คือสิ่งที่ AWS ใช้เชื่อม role เข้ากับ instance ใน metadata service โดย Andre นำ role ออกจาก instance profile แต่ role นั้นยังคงอยู่ และ instance profile ก็ยังคงอยู่เช่นกัน
คุณอาจคิดว่า IMDS จะส่ง credentials ไม่ได้อีกต่อไป แต่ความจริงคือ IMDS ยังมี credentials อยู่ในมือ และไม่รับรู้ว่าเกิดอะไรขึ้นฝั่งบริการ IAM มันจึงยังส่ง credentials ต่อไป และ role ก็ยังอนุญาต credentials เหล่านั้น ทำให้ใช้งานได้อยู่ ในกรณีนี้การเพิกถอน credentials จะได้ผล เพราะ IMDS จะไม่สามารถดึง credentials ชุดใหม่มาได้หลังจาก instance profile และ role ถูกตัดการเชื่อมโยงกันแล้ว
การจำกัดความเสียหายจาก IAM credentials มีรายละเอียดปลีกย่อยมาก และนี่เป็นเพียงตัวอย่างชุดเดียวจากบริการเดียว (EC2) แต่เมื่อคุณเห็นภาพการทำงานร่วมกันระหว่างบริการ IAM กับ IMDS และกับ SDK แล้ว คุณจะมีพื้นฐานที่มั่นคงในหลักการสำคัญบางประการที่นำไปใช้กับสถานการณ์อื่นได้
และอย่าลืมลองใช้ FireMon Cloud Defense Free-Tier ที่เราเพิ่งเปิดตัว.