Blog นี้เป็นการจดบันทึกไว้สั้นๆ สำหรับคนที่จะทำ API ฝั่ง Server ไม่ว่าจะใช้เองให้ตัวเองเรียก หรือจะเปิดให้ชาวบ้านเรียกก็ตาม จะทำยังไงให้ไม่เกิดปัญหาขั้นพื้นฐาน เช่น ถูกเรียกซ้ำหรือถูกแฮคได้ง่ายๆ
ซึ่งตัวเราก็ทำมาหลายรูปแบบ เริ่มมาก็ท่ายากเลยคือใช้ท่า OAuth 1.0a ก็ทำให้เราได้เข้าใจมากทีเดียวว่าความปลอดภัยคืออะไร แต่ OAuth 1.0a มันซับซ้อนมาก มากๆๆๆ เข้าใจยากมากและบางทีก็ยากเกินไปที่จะเอามาใช้กับบริการเล็กๆส่วนใหญ่ที่มนุษย์ส่วนใหญ่ทำกัน
ดังนั้น Blog นี้เราจะ Scope Down ลงมา โฟกัสไปที่การทำ API ระดับพื้นฐาน เพื่อป้องกันสองเหตุได้แก่
1) โดนแฮคสร้าง Request ปลอม
2) ป้องกันปัญหาอันเกิดจากเนตมือถือไม่ดีแล้วเกิด Duplicated Request
ซึ่ง API ถึงจะพื้นฐานยังไง ควรต้องประกอบด้วย 3 ส่วนเหล่านี้ทุก API System ที่ทำ ... ลุยหละ
nonce
เป็นคำที่อ่านแล้วงง มันคืออะไรฟระ จริงๆมันแปลว่า 'Number Used Once' หรือ ตัวเลขที่ใช้ครั้งเดียว
เอ่า งง งง งงเข้าไปใหญ่
ในโลกของ Internet การ "Sniff" เป็นสิ่งที่ทำได้ และให้จินตนาการดูว่าถ้ามี Hacker ไป Sniff Request ที่สำคัญๆ อย่างเช่นการโอนเงินได้ คน Sniff ก็ยิง Replay Request นั้นดู ผลปรากฎว่าก็จะเกิดการโอนเงินซ้ำๆๆๆๆทุกครั้งที่เรียก
สาเหตุนั่นก็เพราะว่า API ไม่มีการเช็ค "การซ้ำของ Request" ไว้เลย
เจ้า nonce จึงเกิดมาเพื่อการณ์นี้ เอาไว้เช็คว่า Request ที่ถูกส่งมา เป็น Request เดิมหรือเปล่า ถ้า nonce เป็นตัวเดิม แปลว่าเป็น Request ซ้ำนั่นเอง
เหตุผลการใช้ nonce มีอยู่สองข้อ
1) ป้องกันคน Replay Request แบบไม่ประสงค์ดีตามที่ได้บอกไป
2) ป้องกันเหตุการณ์ที่แอพฯเป็นคน Replay Request เองโดยไม่ตั้งใจ - บางทีฝั่ง Server ตอบกลับมา แต่ฝั่ง Client อาจจะไม่ได้รับคำตอบ เนตมีปัญหาไรงี้ ทั้งๆที่ Request ทำงานเสร็จแล้ว (ยกตัวอย่างเช่นโอนเงินเลยละกัน) ฝั่ง Client ก็จะพยายามส่งไปหา Server อีก และก็ส่งอีก ส่งอีก ส่งอีก ถ้าไม่ได้กันไว้ แน่นอน ก็จะเป็นการทำซ้ำ n รอบได้เลย
หากจำได้ เมื่อสักครึ่งปีที่แล้ว มีเหตุการณ์ธนาคารแห่งหนึ่งโอนเงินซ้ำไป 25 ครั้ง ครั้งละ 20,000 บาท จดหมดบัญชี ทำเอาคนใช้ Mobile Banking เหวอไปเลย ก็มีโอกาสเข้าข่ายปัญหาจาก Duplicated Request เช่นกันครับ ถ้ามี nonce ก็จะไม่เกิดปัญหาเหล่านี้ขึ้น
nonce เป็นตัวเลขที่ไม่ต้องซับซ้อนอะไรมาก แค่จำเป็นต้องไม่ซ้ำกัน โดยปกติเราจะ Generate nonce ด้วยท่า
$nonce = microtime() . rand(0,100000);
แต่ก็ต้องลองพิจารณาจากจำนวน Request ต่อวินาทีดูด้วย เพราะถ้าเข้ามาเป็นล้าน Request ต่อวินาที การ Generate nonce จากคนละเครื่องก็อาจจะเกิดซ้ำกันได้ (microtime เดียวกันโดยบังเอิญ และ random ออกมาได้ตัวเลขเดียวกันโดยบังเอิญ)
ก็ถ้าถึงสเกลนั้นแล้ว ลองคิดท่าดู ไม่ยากหรอก
ส่วนฝั่ง Server ก็เก็บ nonce สำหรับทุก Request ที่ส่งมา ก่อนจะทำ Request ใดๆก็เช็คว่า nonce นี้มีคนเคยใช้รึยัง ถ้ามีก็ Reject Request ทันทีจ๊ะ
Signature a.k.a. Request Signing
โจทย์ต่อไปคือ จะทำยังไงให้มั่นใจได้ว่า Request ที่ส่งมา เป็น Request ที่ถูกต้อง ส่งจากแอพฯ ไม่ใช่ถูกแฮคแล้วส่ง Parameter มาทำงานดื้อๆ?
ก็ขึ้นชื่อว่า Internet แหละนะ Request ทุกอย่างเป็นรูปแบบเปิด คนจะปลอม HTTP Request แก้ Parameter เปลี่ยน User Agent ก็ทำได้ง่ายๆ ถามว่า nonce ช่วยตรงนี้มั้ย? ก็ตอบว่าไม่ช่วย เพราะเราก็เปลี่ยน nonce ได้เช่นกัน
name=nuuneoi&action=delete&id=10&nonce=1234567
สิ่งที่สำคัญอีกส่วนคือการทำ API คือการ Sign Request
วิธีมีหลายแบบ แล้วแต่ศรัทธา Key หลักๆง่ายคือต้องพ่วง Signature มาใน Request ด้วย โดยวิธีที่จะสร้าง Signature นั้น เกิดจากเอา Parameter ต่างๆที่ส่งไปมายำกัน อารมณ์เหมือนเป็น Hash และต้องมั่นใจว่ามีเราคนเดียวที่จะทำขึ้นมาได้
ยกตัวอย่างเช่น โค้ด PHP ด้านล่างนี้ ตบมาจาก phpvs เป็นการเอา Parameter ทุกตัวที่ส่งไปทาง GET/POST มาเรียงตามตัวอักษร แล้วเอามาต่อ(Concat)กัน ก่อนจะเข้า hmac sha1 ด้วย Secret Key เฉพาะของแอพฯเรา
function generateSignature($data,$secretKey){ //sort data array alphabetically by key ksort($data); //combine keys and values into one long string $dataString = "; foreach($data as $key => $value) { $dataString .= $key.$value; } //lowercase everything $dataString = strtolower($dataString); //generate signature using the SHA256 hashing algorithm return hash_hmac("sha256",$dataString,$secretKey);}$data = array( "name" => 'nuuneoi', "action" => 'delete', "nonce" => '1234567');$signature = generateSignature($data, '(UOIFJO*@#!');
สังเกตดูว่าถ้าคนไม่รู้ Algorithm ในการ Generate และไม่รู้ Secret Key ที่ใช้ HMACSHA1 ก็ไม่มีทางจะสร้าง Signature ตัวนี้ขึ้นมาได้
ทางฝั่ง Client ก็ต้องใช้ Algorithm และ Secret Key เดียวกันนี้ สร้าง Signature ขึ้นมา ส่งไปให้ Server ด้วยเพื่อตรวจสอบ ที่สำคัญอีกอย่างหนึ่งคือต้องใช้ประกอบกับ nonce ด้วย เพราะนั่นจะทำให้ Parameter เปลี่ยนไปทุก Request และ Signature ก็จะเปลี่ยนตามไปด้วย
เมื่อ nonce และ Request Signing มาเจอกัน จะเป็นความปลอดภัยสองชั้นซ้อนที่ยากที่จะแฮค
https
ตอนนี้มีความพยายามผลักดันทุก API Request ให้ไปวิ่งอยู่บน https เพื่อให้ยากต่อการ Sniff ขึ้น
ก็แนะนำว่าไปซื้อ SSL มาติดตั้งบน Server แล้ววิ่ง API บน https ดีกว่า ปีนึงก็ถูกๆ $9.99 ไรงี้ บางเว็บก็ฟรี จัดปายยย
แต่ถามว่าแฮคได้มั้ย
คนจะแฮค ยังไงมันก็แฮคได้แหละนะ ...
อย่าง Instagram ใช้ทั้งสามวิธีด้านบน แต่เราก็แฮคมาแล้ว ตอนนี้เราสามารถโพสต์ภาพและวีดีโอจากโค้ด PHP ได้โดยตรง
Algorithm ในการ Sign เราก็ Decompile ออกมา ส่วน Secret Key ก็ Reverse Engineer ออกมาจากแอพฯ สุดท้าย https ก็ใช้วิธีพิเศษ Sniff ออกมาได้อยู่ดี (ซึ่งจริงๆวิธีป้องกันการ Sniff ก็มีอยู่ ไว้ดูในบล็อกถัดไปนะจ๊ะ ลิงก์ด้านล่างๆ)
ดังนั้นข้อแนะนำตรงนี้คือ
1) เก็บ Algorithm ไว้ให้ดี อย่าให้ใครเห็น
2) Secret Key ถ้าต้องเก็บไว้ใน Client อย่าลืม Encrypt ไว้ด้วย อย่าเก็บดิบๆ เปล่าๆ อย่าง Instagram ก็ถือว่าเก็บดีพอสมควร เอาไว้ใน JNI เวลาดึงออกมาก็ต้อง Decrypt อีก ... (แต่สุดท้ายก็ไม่พ้นเงื้อมมือ...)
ที่ลิสต์มาด้านบนเป็น 3 อย่างที่ขาดไม่ได้อย่างพื้นฐาน จริงก็มีวิธีอื่นๆที่เพิ่มความปลอดภัยเข้าไปอีก เช่น Verify SSL ลองพิจารณาดูจากความซีเรียสของระบบครับ
พื้นฐานการรับส่งข้อมูลฝั่งแอป
ฝั่ง Server แบบเบาๆพื้นท้านพื้นฐานจบไปแล้ว (จริงๆแบบ Advance ยังมีอีกเยอะ) คราวนี้ก็ไปดูกันต่อกับพื้นฐานทางด้านแอปบ้างครับว่าจะรับส่งข้อมูลยังไงให้ปลอดภัย >> [Geek] พื้นฐานความปลอดภัยในการเก็บข้อมูลและรับส่งข้อมูลบนแอพฯมือถือ
ลากันไปก่อน บายยยยย