การโจมตี CSRF บน GraphQL API: ช่องโหว่ที่ทำให้ข้อมูลถูกแก้ไขโดยไม่รู้ตัว

การโจมตี CSRF บน GraphQL API: ช่องโหว่ที่ทำให้ข้อมูลถูกแก้ไขโดยไม่รู้ตัว

โลกของการพัฒนาเว็บในปัจจุบันก้าวหน้าไปมาก ทำให้เรามีเครื่องมือและเทคโนโลยีใหม่ๆ ที่ช่วยให้การสร้างแอปพลิเคชันมีประสิทธิภาพมากขึ้น หนึ่งในนั้นคือ GraphQL API ที่ได้รับความนิยมอย่างแพร่หลาย ด้วยความยืดหยุ่นในการดึงข้อมูล แต่ก็เหมือนกับเทคโนโลยีอื่นๆ GraphQL API ก็ยังคงเผชิญหน้ากับภัยคุกคามทางไซเบอร์แบบดั้งเดิม หนึ่งในนั้นคือ CSRF หรือ Cross-Site Request Forgery ที่เราจะมาทำความเข้าใจและเรียนรู้วิธีป้องกันกัน

รู้จัก CSRF: การหลอกให้ทำสิ่งที่ไม่อยากทำ

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

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

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

GraphQL กับความเสี่ยงจาก CSRF

GraphQL API มักถูกออกแบบมาให้มีความยืดหยุ่นสูง โดยส่วนใหญ่แล้วการส่งคำขอไปยัง GraphQL API จะใช้เมธอด POST request และมีรูปแบบข้อมูลเป็น JSON payload ซึ่งนี่เป็นจุดที่อาจเกิดช่องโหว่ได้

โดยทั่วไปแล้ว การป้องกัน CSRF ใน API จะอาศัย CSRF token ที่เป็นค่าสุ่มเฉพาะที่เซิร์ฟเวอร์สร้างและส่งให้ไคลเอนต์นำไปแนบกับทุกคำขอ หรือการตั้งค่า SameSite cookies ที่ช่วยจำกัดการส่งคุกกี้ในคำขอข้ามโดเมน

แต่บางครั้ง การตั้งค่าหรือการตรวจสอบเหล่านี้อาจไม่ครอบคลุมเพียงพอ ทำให้ GraphQL API กลายเป็นเป้าหมายที่น่าสนใจสำหรับผู้โจมตี

เมื่อ Content-Type ไม่เป็นไปตามที่คิด: ช่องโหว่เกิดได้อย่างไร

หัวใจสำคัญของการโจมตี CSRF บน GraphQL API มักจะอยู่ที่การจัดการกับเฮดเดอร์ Content-Type

โดยปกติแล้ว GraphQL API จะคาดหวังว่าคำขอที่เข้ามาจะมี Content-Type เป็น application/json ซึ่งเมื่อเบราว์เซอร์ต้องการส่งคำขอข้ามโดเมนด้วย Content-Type นี้ มักจะต้องผ่านกระบวนการตรวจสอบ CORS preflight request ที่จะช่วยป้องกันการโจมตีข้ามโดเมน

แต่ถ้าหาก GraphQL API ของเราไม่ได้ตรวจสอบ Content-Type อย่างเข้มงวด และยอมรับ application/x-www-form-urlencoded หรือ text/plain แทน application/json นั่นคือจุดเริ่มต้นของปัญหา

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

เนื่องจาก application/x-www-form-urlencoded เป็น Content-Type ที่เบราว์เซอร์สามารถส่งได้โดยไม่ต้องผ่าน CORS preflight ในหลายกรณี ทำให้คำขอที่เป็นอันตรายนี้สามารถทะลุผ่านไปได้โดยง่าย

เจาะลึกการสร้าง Admin ปลอมด้วย CSRF

เพื่อให้เห็นภาพชัดเจน ลองนึกถึงสถานการณ์ที่ผู้โจมตีต้องการสร้างบัญชีผู้ดูแลระบบ (admin) ขึ้นมาในระบบของเหยื่อผ่าน GraphQL API ที่มีช่องโหว่ CSRF

ผู้โจมตีจะสร้างฟอร์ม HTML ที่มีลักษณะดังนี้:

<form action="https://your-graphql-api.com/graphql" method="POST" enctype="application/x-www-form-urlencoded">
    <input type="hidden" name="operationName" value="CreateUser" />
    <input type="hidden" name="query" value="mutation CreateUser($username: String!, $password: String!, $role: UserRole!) { createUser(username: $username, password: $password, role: $role) { id username role } }" />
    <input type="hidden" name="variables" value='{"username": "attacker_admin", "password": "securepassword123", "role": "ADMIN"}' />
    <input type="submit" value="Click me to win a prize!" />
</form>

เมื่อผู้ใช้งานที่ล็อกอินอยู่ในระบบหลงเชื่อและคลิกปุ่ม “Click me to win a prize!” เบราว์เซอร์จะส่ง POST request พร้อม คุกกี้เซสชัน ไปยัง GraphQL API ของ your-graphql-api.com โดยใช้ Content-Type เป็น application/x-www-form-urlencoded

หาก GraphQL API ไม่ได้ตรวจสอบ CSRF token หรือ Content-Type อย่างเข้มงวด คำขอ createUser นี้ก็จะถูกประมวลผล ราวกับว่าผู้ใช้งานจริงเป็นคนสั่งให้สร้างบัญชีผู้ดูแลระบบ attacker_admin ด้วยตนเอง

ผลลัพธ์คือผู้โจมตีสามารถเข้าถึงระบบในฐานะผู้ดูแลระบบได้สำเร็จ และดำเนินการ CRUD operations (Create, Read, Update, Delete) ได้อย่างอิสระโดยไม่ได้รับอนุญาต

การป้องกันไม่ให้ตกเป็นเหยื่อ

การป้องกัน CSRF เป็นสิ่งสำคัญอย่างยิ่งสำหรับทุกแอปพลิเคชัน รวมถึง GraphQL API ด้วย นี่คือแนวทางปฏิบัติที่ควรนำไปใช้:

  • ใช้ CSRF Token สำหรับทุกคำขอ POST: สำหรับทุกคำขอที่เปลี่ยนแปลงสถานะของระบบ เช่น mutation ใน GraphQL ควรกำหนดให้มี CSRF token ที่เซิร์ฟเวอร์สร้างขึ้น และไคลเอนต์ต้องส่งกลับมาในทุกคำขอ เซิร์ฟเวอร์จะต้องตรวจสอบความถูกต้องของ token นี้
  • ตรวจสอบ Content-Type อย่างเข้มงวด: เซิร์ฟเวอร์ควรตรวจสอบและยอมรับเฉพาะ Content-Type ที่คาดหวังเท่านั้น โดยเฉพาะอย่างยิ่ง application/json สำหรับ GraphQL API การปฏิเสธคำขอที่มี Content-Type อื่นๆ เช่น application/x-www-form-urlencoded หรือ text/plain สามารถช่วยป้องกันการโจมตีได้
  • ตั้งค่า SameSite Cookies: ตั้งค่าแอตทริบิวต์ SameSite=Lax หรือ SameSite=Strict สำหรับ คุกกี้เซสชัน เพื่อจำกัดการส่งคุกกี้ในคำขอข้ามโดเมน
  • ใช้ CORS อย่างรัดกุม: กำหนดค่า CORS (Cross-Origin Resource Sharing) ให้จำกัดเฉพาะ Origin ที่เชื่อถือได้เท่านั้น เพื่อป้องกันคำขอจากโดเมนที่ไม่ได้รับอนุญาต

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