December 2, 2012

PHP: ระบบสมาชิก - สมัครสมาชิก

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

ตอนแรกขอพูดถึงการสมัครสมาชิกก่อน แล้วตอนต่อไปจะว่ากันเรื่องการ log in และ log out เพราะถ้ายังไม่สมัครสมาชิกก่อน ก็ log in ไม่ได้...

ในระบบสมาชิก สิ่งที่จำเป็นจริง ๆ มีอยู่ 2 อันคือ ชื่อผู้ใช้ และ รหัสผ่าน แต่ควรจะมี อีเมล เพื่อใช้ในการติดต่อกับสมาชิกไว้ด้วย

จะขอข้ามไม่พูดถึงเรื่องการสร้างฐานข้อมูล และตาราง แต่จะบอกแค่ชื่อ และโครงสร้างของตาราง นอกจากนั้น ใช้ความรู้เรื่อง PHP และ PDO เขียนสคริปท์ PHP กันเอาเอง หรือไม่ก็ใช้เครื่องมือจัดการฐานข้อมูลอย่าง phpMyAdmin (MySQL), phpPgAdmin (PostgreSQL) หรือตัวอื่น ๆ ตามแต่ละชนิดฐานข้อมูลกันตามสะดวก
เมื่อฐานข้อมูลพร้อมแล้ว ก็สร้างแบบฟอร์มสำหรับสมัครสมาชิกขึ้นมาก่อน เมื่อมีคนมาสมัครสมาชิก ข้อมูลจะถูกส่งมายัง regis.php ก็ต้องตรวจสอบค่าให้ถูกต้องเรียบร้อยก่อนว่าผู้ใช้กรอกข้อมูลครบถ้วนสมบูรณ์ดี หากผิดพลาดก็แจ้งเตือนกลับไป เมื่อตรวจสอบข้อมูลเรียบร้อยแล้ว หากไม่มีอะไรผิดพลาดก็เชื่อมต่อกับฐานข้อมูล และตรวจสอบว่ามีใครใช้ชื่อนี้ไปก่อนหรือยัง เพราะชื่อบัญชีห้ามซ้ำกัน อันนี้ใช้ความสามารถของ PDO::prepare() ในการป้องกัน SQL Injection นอกจากนี้ ถ้าต้องการตรวจสอบว่าใช้ อีเมลเดียวกันในการสมัครหรือไม่ ก็อาจจะเพิ่มการตรวจสอบเข้าไปอีกครั้งก็ได้ โดยใช้วิธีเดียวกับตอนตรวจสอบชื่อบัญชี

หลังจากทุกอย่างเป็นปกติ ก็นำข้อมูลผู้ใช้เข้าสู่ฐานข้อมูล (เพิ่มโค้ดตรง // TODO: ... บรรทัดที่ 35) แค่นี้ระบบลงทะเบียนก็จะสมบูรณ์ แต่มันยังมีปัญหาด้านความปลอดภัยอยู่ หากมีผู้ไม่ประสงค์ดี สามารถเจาะเข้าฐานข้อมูล เขาจะได้รหัสผ่านไปใช้ได้ทันที และในกรณีที่เลวร้ายกว่า หากสมาชิกใช้ ชื่อบัญชี อีเมล และรหัสผ่านเดียวกันกับหลาย ๆ เว็บ คนร้ายก็จะเอาข้อมูลเหล่านี้ไปใช้กับเว็บอื่น ๆ ได้ด้วย ดังนั้นวิธีนี้จึงไม่เหมาะสมอย่างยิ่งอย่างยิ่งในการใช้งานจริง

ทางออกที่ดีกว่าสำหรับเรื่องนี้ คือการเก็บ hash ของรหัสผ่าน แทนที่จะเก็บตัวรหัสผ่านตรง ๆ ซึ่ง PHP มีฟังชั่นพื้นฐานที่นิยมใช้กับรหัสผ่านอยู่ 2 ตัวคือ md5 และ sha1

หากต้องการใช้ sha1 ก็ให้แก้ไขค่าของ pass ในอาเรย์ $user เสียใหม่ รหัสที่เข้ามาจะถูกแปลงเป็นรหัสที่อ่านไม่ออกอย่างเช่น 7c4a8d09ca3762af61e59520943dc26494f8941b และเก็บเข้าฐานข้อมูล ซึ่งหากข้อมูลที่เข้ามาเหมือนกัน ค่าที่ได้จะเหมือนกันทุกครั้ง

แม้ว่าตอนนี้รหัสผ่านจะเดาไม่ได้ด้วยตาเปล่าแล้ว แต่ปัญหาอีกอย่างคือ หากเป็นรหัสผ่านง่าย ๆ เพียงนำ hash ที่ได้ไปค้นหากับ Google เราก็อาจจะได้รหัสผ่านตัวจริงออกมาอย่างง่ายดาย

แต่ถึงแม้จะหาจาก Google ไม่เจอ คนร้ายยังสามารถใช้วิธี brute-force เพื่อหาข้อความที่มีค่า hash ตรงกับที่ได้มาได้ หรือหากไม่เจอ ก็สามารถใช้วิธี brute-force ค้นหาข้อความที่ได้ค่า hash ตรงกับที่ได้มาได้อยู่ดี

เพื่อเพิ่มความยุ่งยากอีกนิด จึงมีเทคนิคที่เรียกว่า salt ขึ้นมา โดยการเพิ่มข้อความต่อเข้ากับรหัสผ่าน (ไม่ว่าจะเป็นด้านหน้า ด้านหลัง หรือตรงกลาง) เพื่อให้รหัสผ่านยาวขึ้น และซับซ้อนมากขึ้น และป้องกัน rainbow table (ตาราง ฐานข้อมูล หรือเว็บบางเว็บ ที่เก็บผลของการ hash ข้อความต่าง ๆ เอาไว้ และใช้ตรวจสอบหาค่า hash โดยไม่ต้องคำนวนใหม่ทุกครั้ง — อ่านเพิ่มเติมจาก wiki, ThaiCERT) ทั้งนี้ salt ที่ใช้ควรจะมีความยาว และซับซ้อนพอสมควร

salt มีอยู่ 2 แบบคือ salt ที่ใช้ค่าคงที่ และแบบ dynamic หรือรวมกัน แม้ว่าจะปลอดภัยขึ้น แต่หากคนร้ายสามารถคาดเดาว่าใช้ salt ค่าของ salt รวมถึงวิธีสร้าง salt ได้ถูกต้อง เขาก็ยังสามารถ brute-force เพื่อหารหัสผ่านได้อยู่ดี แต่ยังมีวิธีการ และเทคนิคอื่น ๆ เพื่อช่วยให้เว็บของเราปลอดภัยขึ้น

หากเรียนรู้เรื่องความปลอดภัย เราจะเข้าใจว่า "ในโลกของความปลอดภัย ไม่มีอะไรปลอดภัย" แม้ว่าจะใช้เทคนิคด้านความปลอดภัยชั้นสูง หากมันคุ้มค่าที่จะเจาะข้อมูล คนร้ายก็ยอมเสียเวลา และทรัพยากร เพื่อจะเจาะมันให้ได้ แต่หากมันไม่คุ้มค่า ต่อให้ไม่มีความปลอดภัยใด ๆ ก็ไม่มีใครสนใจจะเจาะอยู่ดี (เรียนรู้จากความเห็นของ @lewcpe แห่ง blognone)

แต่ถึงอย่างไรก็ตาม เราควรทำให้ระบบของเราปลอดภัยระดับหนึ่ง และควรจะปรับปรุง และเพิ่มระดับความปลอดภัยขึ้นเรื่อย ๆ เมื่อมีฐานผู้ใช้มากขึ้น เพื่อไม่ให้เกิดปัญหาอย่างกรณีของ Tuts+ ที่แนะนำเกี่ยวกับการเก็บรหัสผ่านอย่างปลอดภัย แต่กลับเก็บรหัสผ่านแบบ plain text เอาไว้ และไม่แก้ไข ทั้ง ๆ ที่รู้อยู่ว่าไม่ปลอดภัย จนกระทั่งโดนแฮ็ค...

2 comments: