Allowlist VS Denylist
สวัสดีทุกท่านที่เข้ามาอ่านบทความนี้นะครับ สำหรับท่านที่เข้ามาอ่านอาจจะสนใจในเรื่องของ Allowlist กับ Denylist ซึ่งหัวข้อในวันนี้อาจจะไม่ได้เกี่ยวข้องกับการโจมตีช่องโหว่ต่างๆ แต่เป็นการเข้าใจสิ่งที่ใช้ในการป้องกันการโจมตีประเภท Input เช่น SQL Injection หรือ Cross-site scripting(XSS) โดยปกติการจะป้องกันการโจมตีรูปแบบ Input ได้ก็จะมีฟังก์ชันต่างๆเข้ามาช่วยเช่น การป้องกัน SQL Injection ด้วย Prepared Statement เป็นต้น สำหรับบทความนี้เราจะมาทำความเข้าใจการป้องกันด้วย Input validation นั่นเองครับ
หัวข้อที่เราจะอธิบายกันในวันนี้ได้แก่
- Input validation คืออะไร?
- Client-side และ Server-side validation คืออะไร?
-ตัวอย่างการใช้งานของ Allowlist และ Denylist
-เราควรเลือกใช้ Input validation แบบไหนในการป้องกัน
Input validation คืออะไร?
มีหลายแหล่งข้อมูลที่อธิบายคำว่า Input validation ซึ่งมีความใกล้เคียงกันโดยจะกล่าวว่าเป็นกระบวนการหนึ่งในการตรวจสอบการรับข้อมูล (input) ว่าเป็นไปตามมาตรฐานที่กำหนดไว้ภายใน Application เพื่อให้แน่ใจว่ามีเฉพาะข้อมูลที่ตรงตามข้อกำหนดของ Application อย่างถูกต้องและเข้าสู่กระบวนการต่อไปภายในระบบ ซึ่งในแต่ละ Application ที่ใช้งานมีความเป็นไปได้ที่จะใช้มาตรฐานที่กำหนดไว้ต่างกัน เช่น บางครั้งอาจจะต้องการค่าที่เป็นตัวเลขเท่านั้น เพื่อให้ข้อมูลถูกต้อง อาจจะใช้เป็น Regular expressions กำหนดให้ค่าที่รับเป็นไปตามที่ระบบต้องการโดย Input Validation แบ่งออกได้เป็น 2 ประเภทนั่นคือ Allowlist validation และ Denylist validation
Client-side และ Server-side validation คืออะไร?
หัวข้อนี้อาจมีคนสงสัยว่าเกี่ยวข้องยังไงกับ Allowlist และ Denylist validation เราจะอธิบายให้เข้าใจกันครับจะขออธิบายในฝั่งของ Client-side validation กันก่อนจากนั้นจะอธิบายในส่วนของ Server-side validation ก่อนจะอธิบายกันอยากให้ทุกท่านดูรูปภาพของ Figure 1 จะทำให้เข้าใจว่า Clients ก็คืออุปกรณ์ต่างๆบน Internet ที่เชื่อมต่อไปหา Server จะเป็นการอธิบายแบบย่อเท่านั้น สามารถอ่านเพิ่มเติมได้จาก https://en.wikipedia.org/wiki/Client-server_model ต่อมาขอเริ่มอธิบายในส่วนของ Client-side validation
Client-side validation เป็นการตรวจสอบข้อมูลที่ผู้ใช้งานส่งเข้ามาในหน้าเว็บหรือแอปพลิเคชันของผู้ใช้เองภายในเครื่องคอมพิวเตอร์ หรือ โทรศัพท์มือถือ ก่อนที่จะส่งข้อมูลเข้าสู่ระบบ วิธีนี้เป็นวิธีการที่บังคับให้ผู้ใช้งานกรอกข้อมูลให้ถูกต้องจากหน้าเว็บหรือแอปพลิเคชันเพื่อที่ข้อมูลที่กรอกเข้ามานั้นจะได้ถูกต้อง
Server-side validation เป็นการตรวจสอบที่ฝั่ง Server นั่นคือข้อมูลที่ผู้ใช้กรอกมาจากหน้าเว็บต่างๆจะถูกส่งมาที่ระบบหลังบ้านเพื่อประมวลผลข้อมูลต่างๆ วิธีนี้เป็นวิธีที่ไม่ได้บังคับผู้ใช้งานให้กรอกข้อมูลให้ถูกต้องแต่จะเป็นการตรวจสอบว่าข้อมูลที่ส่งมาให้ถูกต้องหรือไม่และตอบกลับตามความเหมาะสม
เมื่อทุกท่านอ่านดูแล้วอาจเห็นว่าการป้องกันเพียง Client-side validation ก็พอแล้วเพราะถูกบังคับให้ใส่ข้อมูลที่ถูกต้องตั้งแต่แรกจะจริงหรือไม่ จะได้เห็นกันในส่วนของหัวข้อถัดไปครับ
ตัวอย่างการใช้งานของ Allowlist และ Denylist
หัวข้อนี้จะเป็นตัวอย่างต่างๆของ Allowlist และ Denylist ให้ผู้อ่านทุกท่านได้เห็นถึงรูปแบบของ Allowlist และ Denylist นอกจากนี้ยังแสดงให้เห็นว่าการป้องกันกันเพียง Client-side นั้นเพียงพอหรือไม่โดยสถานการณ์ตัวอย่างจะเป็นหน้าเว็บที่ให้กรอกเลขและส่งไปที่ server
***หมายเหตุ ตัวอย่างโค้ด การโจมตี หรือ การป้องกัน เป็นเพียงตัวอย่างที่ผู้เขียนสร้างขึ้นให้เข้าใจ Allowlist และ Denylist อย่างง่ายในกรณีอื่นการใช้งานอาจมีความซับซ้อนกว่านี้ได้***
เรามาเริ่มที่การทำงานของเว็บไซต์นี้กัน
- เว็บไซต์นี้จะเป็นเว็บที่ให้เรากรอกข้อมูลตัวเลขดังภาพครับ
ผู้เขียนทำการกรอกข้อมูลพบว่าสามารถใส่ได้แค่ตัวเลขเท่านั้นไม่สามารถใส่ตัวอักษรได้
2.เมื่อเรากรอกตัวเลขแล้วกดปุ่ม “ส่งข้อมูล” จะทำการส่งข้อมูลตัวเลขและแสดงที่หน้าเว็บว่าเรากรอกเลขอะไรเข้ามาดังตัวอย่าง ผู้เขียนกรอกเลข 1 ก็จะมีข้อความแสดงว่า “คุณกรอกเลขตัวเลข: 1”
ต่อมาจะดู source code(โค้ด) ของเว็บข้างต้นครับ
- ไฟล์ index.html เป็นหน้าเว็บที่เราจะใช้กรอกข้อมูลจากโค้ดจะเห็นว่ามีการกำหนด input type ให้เป็น number ซึ่งบังคับให้ผู้ใช้งานใส่เพียงตัวเลขเท่านั้นเป็นหนึ่งในการป้องกันที่ฝั่ง Client side validation และ เมื่อกดปุ่ม “ส่งข้อมูล” จะไปประมวลผลต่อที่ไฟล์ “process.php”
2.ไฟล์ “process.php” เป็นไฟล์ที่จะรับค่าต่างๆมาจากไฟล์ “index.html” และแสดงข้อความ “คุณกรอกตัวเลข: ” ตามด้วยค่าที่ใส่มาจากหน้า “index.html”
ผู้อ่านทุกท่านจะได้เห็นกระบวนการทำงานของระบบและโค้ดแล้วต่อไปเราจะลองโจมตีกันจากหน้าเว็บเดิมเราจะทำการเปิดเครื่องมือ inspect ของ browser ทำการดูโค้ดของหน้าเว็บจะเห็น “input type=“number” ” เหมือนโค้ดที่ให้ดูไปข้างต้นจากนั้นทำการเปลี่ยนจาก “number” เป็น “text” กัน
เมื่อทำการแก้ไขแล้วพบว่าสามารถพิมพ์ข้อความอะไรก็ได้แล้วในกล่องข้อความ ผู้เขียนจึงลองใส่คำว่า “test” จากนั้นทำการกดที่ปุ่ม “ส่งข้อมูล”
พบว่าสามารถส่งได้และแสดงคำว่า “test” ขึ้นมาแทนตัวเลขซึ่งผิดวัตถุประสงค์ของระบบที่ใช้เป็นตัวอย่าง
ที่นี้เราลองเปลี่ยนจาก “test” เป็น “<script>alert(“Hacked”)</script>” และ “กดส่งข้อมูล”
พบว่ามีกล่องข้อความแสดงคำว่า “Hacked” ขึ้นมาที่หน้าจอสิ่งนี้เรียกว่า “Reflected cross-site scripting (Reflected XSS)” ต่อไปนี้จะขอเรียกย่อๆว่า XSS จากหัวข้อก่อนหน้าที่ได้ทิ้งคำถามเอาไว้ว่า “การป้องกันเพียง Client-side validation ก็พอแล้ว” จะเห็นได้อย่างชัดเจนจากขั้นตอนนี้ว่าการป้องกันจาก Client-side validation ไม่เพียงพอที่จะป้องกันระบบให้ปลอดภัย
เมื่อเกิดการโจมตีขึ้นดังนั้นการป้องกันก็ควรจะป้องกันที่ Server side เพื่อให้ระบบยังคงปลอดภัยอยู่นั่นเองโดยเราจะแยกให้ดูว่าการป้องกันด้วย Allowlist และ Denylist ต่างกันอย่างไร เราจะมาดู source code ของ “process.php” ในส่วนของการป้องแบบ Denylist กันก่อน
ในส่วนของโค้ดที่เพิ่มมานั้นจะมีตัวแปรเก็บค่าของ Tag ต่างๆเอาไว้จำนวน 4 แบบ และมีการเพิ่ม foreach ลูปสำหรับกรองคำที่ตรงกับ Tag ที่อยู่ในตัวแปร
เมื่อเราลองโจมตีด้วยช่องโหว่ XSS กันอีกครั้งหนึ่งด้วยคำสั่งเดิมผลลัพธ์จะเป็นคำว่า “alert(“Hacked”)” แทนจะเห็นได้ว่าต่อให้เราใส่ Tag <script> เข้าไปก็ยังไม่ถูกโจมตี
ต่อมาผู้เขียนลองเปลี่ยนจาก “<script>alert(“Hacked”)</script>” เป็น “<Script>alert(“Hacked”)</Script>” ผลลัพธ์คือ เกิดกล่องข้อความแสดงคำว่า “Hacked” อีกแล้ว!!! ที่เกิดแบบนี้ขึ้นเพราะการที่เราแก้ไขจาก <script> เป็น <Script> นั้นไม่ตรงกับ Tag ที่เราทำการกำหนดไว้ ทำให้เห็นว่าการใช้งาน Denylist นั้นจำเป็นที่จะต้องเตรียมคำที่เราจะกำจัดทิ้งไว้มากและต้องครบทุกแบบเพื่อป้องกันได้ 100%
ต่อไปจะเป็นตัวอย่างของการใช้งานในส่วนของ Allowlist ก่อนอื่นเรามาดูกันที่โค้ดกันก่อน
จากโค้ดจะเห็นได้ว่ามีการกำหนด Type ของค่าที่รับมาเป็นค่า Integer(int) ซึ่งค่า int คือค่าของตัวเลขนั่นเอง
ผู้เขียนกรอกข้อมูลด้วยคำสั่งแรก “<script>alert(“Hacked”)</script>” ผลลัพธ์คือได้เลข “0” ออกมา
จากนั้นผู้เขียนกรอกข้อมูลด้วยคำสั่งที่ 2 “<Script>alert(“Hacked”)</Script>” ผลลัพธ์คือได้เลข “0” เช่นเดียวกัน
- **ไม่ใช่รูปเดียวกัน***
ทำไม่ถึงเป็นแบบนั้น เนื่องจากเราใส่ข้อมูลเข้าไปเป็นคำสั่ง “<Script>alert(“Hacked”)</Script>” เมื่อระบบหลังบ้านรับข้อมูลนี้มาแล้วทำให้ถูกเปลี่ยนค่าจาก String เป็น Integer ซึ่งจะถูกแก้ไขค่าให้เป็น 0 นั่นเองสามารถทดสอบได้จากเว็บ https://www.w3schools.com/php/php_casting.asp
จากขั้นตอนต่างๆที่ได้มาจะเริ่มเห็นความแตกต่างของ Denylist และ Allowlist กันแล้วในหัวข้อถัดไปเราจะไปดูกันในส่วนของการเลือกใช้ว่าเราควรจะเลือกใช้แบบไหนเพราะอะไร
*เพิ่มเติมการป้องกันด้วยวิธีต่างๆสามารถใช้ regular expression ช่วยในการกรองข้อมูลได้ แต่ต้องระวังช่องโหว่ ReDos ซึ่งสามารถตรวจสอบได้จากเว็บ https://devina.io/redos-checker
เราควรเลือกใช้ Input validation แบบไหนในการป้องกัน
จากหัวข้อก่อนหน้าจะเห็นว่าผู้เขียนยังคงสามารถหลีกเลี่ยงการป้องกันแบบ Denylist ได้ด้วยการเปลี่ยนพิมพ์เล็กพิมพ์ใหญ่โดยนอกจากวิธีนี้ยังมีอีกหลายวิธีในการหลีกเลี่ยงในส่วนของ Denylist แต่ในทางกลับกันการใช้งาน Allowlist โดยการเปลี่ยนแปลงค่าทั้งหมดเป็นตัวเลขที่ฝั่ง Server ทำให้ไม่ว่าจะใส่ข้อมูลอะไรเข้ามาจะกลายเป็นเลข 0 นั่นทำให้เป็นไปได้ยากที่จะโจมตีระบบ โดยปกติแล้วผู้เขียนแนะนำให้ใช้การป้องกันด้วย Allowlist ตามที่เห็นได้ในตัวอย่าง นอกจากตัวอย่างแล้วผู้เขียนใช้ข้อมูลจากเว็บไซต์ของ OWASP บอกเอาไว้ว่า “การใช้งาน Denylist เป็นข้อผิดพลาดพื้นฐานในการตรวจสอบข้อมูลที่ส่งเข้ามา เช่น <script> เนื่องจากผู้โจมตียังสามารถหลีกเลี่ยงการกรองข้อมูลเหล่านี้ได้ ถึงการใช้ Denylist จะมีประโยชน์ในการเป็นวิธีการเพิ่มเติมในการตรวจจับข้อมูล แต่ไม่ควรเป็นวิธีหลัก การใช้งาน Allowlist เป็นการที่ปลอดภัยกว่าในการป้องกันข้อมูล” จะเห็นได้ว่าเว็บไซต์หลักอย่าง OWASP ก็มีการบอกถึงการใช้งานของ Allowlist และ Denylist เอาไว้เช่นกันซึ่งตรงกับตัวอย่างที่ผู้เขียนได้ยกตัวอย่างให้เห็นด้วยเช่นกัน
References:
https://www.acunetix.com/websitesecurity/sql-injection/
https://bolster.ai/glossary/allowlist
https://devina.io/redos-checker
https://surveyjs.io/stay-updated/blog/client-server-data-validation