
การโจมตี 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 ก็สามารถสร้างความแตกต่างระหว่างระบบที่ปลอดภัยและระบบที่มีความเสี่ยงได้อย่างมหาศาล