ความปลอดภัย API ของเว็บไซต์อิสระ: การยืนยันตัวตน จำกัดอัตรา CORS ตรวจสอบอินพุต และป้องกันช่องโหว่
หนึ่ง ทำไม “เปิด HTTPS” ไม่เท่ากับ API ปลอดภัย
เว็บมาสเตอร์หลายคนถือว่า HTTPS เป็นคำพ้องของความปลอดภัย คิดว่าพอเข้ารหัสระหว่างเบราว์เซอร์กับเซิร์ฟเวอร์แล้ว API ก็มั่นคง นี่คือความเข้าใจผิดข้อแรก: HTTPS แก้ปัญหา “ถูกดักฟังระหว่างทาง” แต่แก้ไม่ได้ “ตัวตนที่ถูกต้องถูกนำไปยิง” “สคริปต์อันตรายยิง API ล็อกอินของคุณเป็นชุด” “ยัด SQL ลงในคำขอเพื่อดึงตารางออกไป”
การตัดสินที่ขัดกับสัญชาตญาณอยู่ตรงนี้:สิ่งที่ทำให้เว็บไซต์อิสระเกิดเรื่องจริงๆ เกือบทั้งหมดคือช่องโหว่ระดับแอปพลิเคชัน ไม่ใช่ระดับการรับส่ง การดึงฐานข้อมูล ลบฐานข้อมูล API ถูกยิงจนล่ม เก้าในสิบมาจากอินพุตไม่ถูกตรวจสอบ การยืนยันตัวตนไม่แน่นหนา ไม่ได้ตั้งจำกัดอัตรา WAF สกัดบางลายเซ็นการโจมตีที่รู้จักได้ แต่สกัดรูที่เกิดจากตรรกะธุรกิจที่คุณเขียนเองไม่ได้ เรื่องนี้สอดคล้องกับ หลังเว็บไซต์ถูกแฮก ข้อสรุปจากการทบทวน: เคลียร์โค้ดก่อน แล้วค่อยพูดถึงการเสริมความแข็งแรง
ห้าชั้นป้องกันต่างดูแลคนละช่วง ขาดชั้นใดชั้นหนึ่งก็มีการโจมตีประเภทหนึ่งทะลุเข้ามาได้ทันที ตารางด้านล่างสรุปแต่ละชั้นว่า “กันอะไร ถ้าพลาดจะเกิดอะไร” ไว้ในครั้งเดียว
| ชั้นป้องกัน | การโจมตีที่กันได้ | การใช้งานทั่วไป | ผลที่ตามมาถ้าละเลย |
|---|---|---|---|
| การรับรอง | เรียกแบบไม่ระบุตัวตน สวมรอยผู้อื่น | API Key, JWT, OAuth2 | API ข้อมูลถูกใครก็ดึงตรงๆ |
| จำกัดอัตรา | เดารหัสแบบ brute force ยิง API เป็นชุด | sliding window ตาม IP/Key | API ล็อกอินถูก credential stuffing ฐานข้อมูลถูกดึง |
| CORS | เว็บไซต์อันตรายอ่านข้อมูลข้ามโดเมน | whitelist ต้นทาง | ข้อมูลถูกเว็บไซต์บุคคลที่สามอ่านขณะผู้ใช้ล็อกอินอยู่ |
| ตรวจสอบอินพุต | SQL injection, XSS, เขียนข้อมูลสกปรก | ตรวจสอบ whitelist + parameterized query | ฐานข้อมูลถูกดึงทั้งก้อน หลังบ้านถูก XSS hijack session |
| สแกนช่องโหว่ | หลังบ้านที่รู้จัก dependency หมดอายุ | สแกน dependency + สแกนช่องโหว่เป็นระยะ | ใช้ปลั๊กอินเก่าที่มี EXP เปิดเผย |
สอง การยืนยันตัวตน: เลือกสามวิธีอย่างไร จัดการคีย์อย่างไร
API ภายนอกของเว็บไซต์อิสระแบ่งเป็นสองประเภท: สำหรับเบราว์เซอร์ส่วนหน้า (สถานะล็อกอิน ส่งฟอร์ม) และสำหรับเชื่อมต่อระบบภายนอก (Webhook ซิงค์บุคคลที่สาม) สองประเภทใช้วิธี ยืนยันตัวตน ต่างกัน อย่าปนกัน
สถานะล็อกอินฝั่งฟรอนต์เอนด์ใช้ JWT โดยโทเคนมีเวลาหมดอายุ ดีฟอลต์ 2 ชั่วโมง พอหมดอายุก็ใช้ refresh token แลกอันใหม่ ส่วนการเชื่อมต่อกับระบบภายนอกใช้ API Key แบบอายุยาว ใส่ใน request header ไม่ใช่ใน URL parameter เพราะ URL จะถูกเก็บใน log และประวัติเบราว์เซอร์ OAuth2 ใช้เฉพาะตอนที่ต้อง “ให้ผู้ใช้อนุญาตให้แอปของบุคคลที่สามเข้าถึงข้อมูลแทนเขา” เท่านั้น ร้านเดี่ยวที่ทำคนเดียว 90% ของกรณีไม่จำเป็น ใช้แล้วเพิ่มความซับซ้อนให้ตัวเองเปล่าๆ
เกณฑ์ตัดสิน: ถ้า API นี้ให้บริการแค่ฟรอนต์เอนด์ของตัวเอง ใช้ JWT ก็พอ ถ้าต้องให้โปรแกรมของพาร์ตเนอร์เรียกใช้ ให้ออก API Key แยก และสร้าง Key แยกตามพาร์ตเนอร์แต่ละราย เพื่อให้เพิกถอนทีละรายได้ทันที หมุนเวียนคีย์ไตรมาสละครั้ง ตอนหมุนให้ Key เก่าและใหม่ทำงานคู่กัน 7 วัน เพื่อไม่ให้ฝั่งที่เชื่อมต่อล่มทันที
| วิธีตรวจสอบสิทธิ์ | เหมาะกับ | อายุโทเคน | หมุนเวียน / เพิกถอน |
|---|---|---|---|
| Session Cookie | หลังบ้านล็อกอินด้วยฟอร์มแบบเดิม | เซสชันเบราว์เซอร์ | ออกจากระบบแล้วหมดอายุทันที |
| JWT | แยกฟรอนต์เอนด์กับแบ็กเอนด์ ฟรอนต์เรียก API | Access token 2 ชั่วโมง | refresh 7 วัน แบล็กลิสต์ฝั่งเซิร์ฟเวอร์ |
| API Key | ระบบภายนอก, Webhook callback | อายุยาว ออกให้ตามพาร์ตเนอร์ | หมุนเวียนรายไตรมาส เพิกถอนแยกได้ |
| OAuth2 | แอปบุคคลที่สามเข้าถึงแทนผู้ใช้ | access อายุสั้น + refresh | ผู้ใช้ยกเลิกการอนุญาตได้ทุกเมื่อ |
สาม การจำกัดอัตรา: จำกัดตามใคร ตั้งเกณฑ์เท่าไร
ข้อผิดพลาดที่พบบ่อยที่สุดของการจำกัดอัตราคือ “ใช้เกณฑ์เดียวทั้งเว็บ” ความถี่การเรียกปกติของ API ล็อกอินกับ API รายการสินค้าต่างกันเป็นสิบเท่า ใช้เกณฑ์เดียวกันก็ได้ผลแค่สองอย่าง คือไปตัดคนจริงหรือไม่ก็กันการโจมตีไม่อยู่ วิธีที่ถูกคือแบ่งระดับตาม API และจำกัดตามมิติ
ใช้สามมิติจำกัดอัตราซ้อนกัน: ตาม IP กันผู้โจมตีรายเดียวถล่มเต็มกำลัง ตามบัญชีกันการวนลองพจนานุกรมรหัสผ่านที่ถูกเจาะ ตาม API Key กันโปรแกรมของพาร์ตเนอร์รายใดรายหนึ่งหลุดควบคุม เกณฑ์ต้องเป็นตัวเลขชัดเจน ตารางข้างล่างนี้เป็นค่าเริ่มต้นของ API ที่พบบ่อยในร้านเดี่ยว หลังเปิดใช้แล้วค่อยปรับตามทราฟฟิกจริง
ตัวอย่างสถานการณ์: API ล็อกอินถูก credential stuffing ตั้งค่าเป็นตาม IP 10 ครั้งต่อนาที ตามบัญชีล้มเหลว 5 ครั้งต่อนาที ถ้าเกินเกณฑ์ต่อเนื่อง 5 นาทีให้แบล็กลิสต์ IP นั้น 1 ชั่วโมง วิธีนี้ได้ผลกว่าการ “ล็อกบัญชีเมื่อล็อกอินล้มเหลว 5 ครั้ง” เฉยๆ เพราะผู้โจมตีจะใช้รหัสผ่านเดียวกันลองหลายบัญชี เกณฑ์ต่อบัญชีเดียวดักไม่อยู่ ส่วนการจัดการทราฟฟิกขยะที่เกี่ยวกับฟอร์ม เทียบได้กับ ความน่าเชื่อถือของฟอร์มสอบถาม วิธีจัดการสแปมในนี้
| ประเภท API | มิติที่จำกัด | เกณฑ์เริ่มต้น | การดำเนินการเมื่อเกิน |
|---|---|---|---|
| ล็อกอิน / กู้คืนรหัสผ่าน | IP + บัญชี | 10 ครั้ง/นาที | แบล็กลิสต์ชั่วคราว 1 ชั่วโมง |
| ส่งฟอร์มสอบถาม | IP | 3 ครั้ง/นาที | เด้งแคปต์ชา |
| รายการสินค้าสาธารณะ | IP | 60 ครั้ง/นาที | คืนค่า 429 |
| เชื่อมต่อ API ภายนอก | API Key | ตาม QPS ที่ตกลงในสัญญา | 429 + แจ้งพาร์ตเนอร์ |
| ค้นหาในเว็บ | เซสชัน | 20 ครั้ง/นาที | คืนค่า 429 |
สี่ CORS: เปิดเมื่อไร เปิดถึงระดับไหน
CORS (Cross-Origin Resource Sharing) คือระบบป้องกันที่มากับเบราว์เซอร์ โดยค่าเริ่มต้นสคริปต์บนหน้าเว็บของคุณจะอ่านข้อมูลตอบกลับจาก API ของโดเมนอื่นไม่ได้ มันไม่ใช่เรื่อง “เปิดแล้วไม่ปลอดภัย” แต่เป็น “ตั้งค่าผิดจึงไม่ปลอดภัย” ข้อผิดพลาดที่พบบ่อยที่สุดคือเขียนแบบมักง่ายเป็น Access-Control-Allow-Origin: * แล้วใส่ Allow-Credentials: true ควบคู่ไปด้วย ซึ่งเท่ากับเปิดช่องข้ามโดเมนให้ทั้งอินเทอร์เน็ต
หลักการตัดสิน: ถ้า API ของคุณมีแค่ frontend ของคุณเองเรียกใช้ ไวท์ลิสต์ CORS ก็ใส่แค่โดเมนของคุณเอง (ทั้งแบบมี www และไม่มี www) อย่าใส่ * ถ้ามี frontend ของบุคคลที่สามเรียกใช้ ให้เพิ่มทีละโดเมน อย่าใช้ regex แบบ *.yourdomain.com เพราะถ้าวันหนึ่งมีคนจดโดเมนย่อยหรือถูกโจมตี มันจะกลายเป็นช่องโหว่ทันที
API สาธารณะล้วนที่ไม่ใช้ Cookie (เช่น รายการสินค้า) จริงๆ ไม่จำเป็นต้องเปิด CORS แบบมี credentials เบราว์เซอร์ไม่ได้เข้มงวดกับ simple request ขนาดนั้น ยิ่งตั้งค่าน้อยข้อ ก็ยิ่งลดความเสี่ยงได้หนึ่งจุด ขอบเขตระหว่าง credentials กับการปฏิบัติตามกฎข้อมูลข้ามพรมแดน ดูได้ที่ฐานรากด้านการปฏิบัติตามกฎสำหรับเว็บออกตลาดต่างประเทศ คำอธิบายเรื่อง Cookie และการส่งข้อมูลข้ามพรมแดน
| สถานการณ์ | การตั้งค่า Allow-Origin | Allow-Credentials | ปลอดภัยหรือไม่ |
|---|---|---|---|
| มีแค่ frontend ตัวเองเรียกใช้ | ไวท์ลิสต์ระบุโดเมนตัวเองแบบเจาะจง | true (ต้องมีสถานะล็อกอิน) | ความปลอดภัย |
| API ข้อมูลสินค้าสาธารณะ | ไม่ตั้งค่าหรือใส่ * | false | ความปลอดภัย |
| มักง่ายใส่ * + credentials true | * | true | อันตราย ต้องแก้ |
| regex *.yourdomain.com | จับแบบคลุมเครือ | true | อันตราย โดเมนย่อยถูกโจมตีก็แตก |
| frontend ของพาร์ทเนอร์หลายราย | ใส่ไวท์ลิสต์ทีละโดเมน | true | ปลอดภัย ล้างรายการที่ไม่ใช้ออกเป็นประจำ |
ห้า: การตรวจสอบอินพุตและการป้องกันช่องโหว่ — กันข้อมูลสกปรกไว้ที่ประตู
การตรวจสอบอินพุตเป็นชั้นที่ทีมคนเดียวพลาดบ่อยที่สุดในห้าชั้น และผลกระทบหนักที่สุด มีหลักการเดียว:อย่าเชื่อข้อมูลจาก frontend และ API ภายนอกเด็ดขาด ตรวจสอบพารามิเตอร์ทุกตัวด้วยไวท์ลิสต์ ทั้งชนิดข้อมูล ความยาว รูปแบบ และค่าที่อนุญาต ถ้าไม่ตรงให้ปฏิเสธไปเลย อย่าพยายาม “ทำความสะอาดแล้วใช้ต่อ”
SQL injection แก้ที่ต้นเหตุด้วย parameterized query อย่าต่อสตริง SQL เอง XSS แก้ที่ต้นเหตุด้วยการ escape ตอน output ตอนเก็บข้อมูลที่ผู้ใช้ส่งเข้าฐานข้อมูลไม่ต้อง escape แต่ตอนแสดงผลบนหน้าต้อง escape ตามบริบท (บริบท HTML, JS และ URL มีกฎการ escape ต่างกัน) การอัปโหลดไฟล์ต้องจำกัดนามสกุลด้วยไวท์ลิสต์ เปลี่ยนชื่อไฟล์ และเก็บไว้นอกโฟลเดอร์เว็บ สิ่งเหล่านี้เป็นพื้นฐานใน OWASP Top 10 ไม่มี “เทคนิคขั้นสูง” ใดๆ ทั้งนั้น
จัดตารางสแกนช่องโหว่ให้เป็นจังหวะประจำ สแกน dependency lock file อัตโนมัติสัปดาห์ละครั้ง ถ้าพบระดับสูง (คะแนน CVE 9 ขึ้นไป) ให้อัปเกรดหรือปิดฟีเจอร์นั้นชั่วคราวภายใน 72 ชั่วโมง สแกนช่องโหว่แบบ passive ทั้งเว็บเดือนละครั้ง แล้วเคลียร์ปลั๊กอินที่หมดอายุตามผลที่ได้ วิธีเชื่อมต่อการแจ้งเตือนมอนิเตอร์ ดูได้ที่การมอนิเตอร์และแจ้งเตือนเว็บไซต์ ส่วนการตัดสินใจด้านความปลอดภัยของ tech stack เอง เทียบได้กับ เทคโนโลยีสแตกของ crater。
| รายการป้องกัน | แนวทาง | ความถี่ / เกณฑ์ | เส้นแดง |
|---|---|---|---|
| ตรวจสอบพารามิเตอร์ | ชนิด + ความยาว + ไวท์ลิสต์ค่าที่อนุญาต | ทุก API ต้องทำ | ห้ามตรวจแค่ที่ frontend |
| SQL injection | พารามิเตอร์ไรซ์คิวรี / พรีคอมไพล์ | ต้องตรวจทุกครั้งในโค้ดรีวิว | ห้ามต่อสตริงเป็น SQL |
| XSS | escape ตามบริบทของ output | ทุกจุดที่แสดงข้อมูลผู้ใช้ | ห้าม raw output rich text ที่ไม่ได้ทำความสะอาด |
| อัปโหลดไฟล์ | ไวท์ลิสต์นามสกุล + เปลี่ยนชื่อไฟล์ | ต้องมีทุก API อัปโหลด | ห้ามให้โฟลเดอร์อัปโหลดรันสคริปต์ได้ |
| สแกนช่องโหว่ dependencies | สแกนอัตโนมัติจาก lock file | สัปดาห์ละครั้ง | ระดับสูง 9 คะแนนขึ้นไป แก้ภายใน 72 ชั่วโมง |
FAQ คำถามที่พบบ่อย
เว็บไซต์ของตัวเองจำเป็นต้องมี WAF ถึงจะปลอดภัยจริงหรือ?
JWT กับ API Key ควรใช้ตัวไหน?
ตั้ง threshold ของ rate limit ยังไงไม่ให้กระทบผู้ใช้ปกติ?
การเขียน CORS เป็น Access-Control-Allow-Origin: * มีปัญหาอะไร?
ทำ input validation ฝั่ง frontend แล้วทำไมยังต้องทำฝั่ง backend อีก?
ควรสแกนช่องโหว่บ่อยแค่ไหน?
ทิ้งท้าย
หัวใจของความปลอดภัย API ของเว็บไซต์แบรนด์ตัวเองไม่ใช่การซื้อเครื่องมือมากองไว้ แต่คือการแบ่งเป็นชั้น ๆ ถ้าชั้นหนึ่งถูกเจาะ ชั้นถัดไปยังกันไว้ได้ การป้องกันจุดเดียวเปราะบางที่สุด ต่อให้ตั้งค่า WAF สวยแค่ไหน แต่ถ้าไม่ตรวจสอบข้อมูลนำเข้า SQL injection ก็เข้าไปถึงฐานข้อมูลอยู่ดี ในห้าชั้น ให้ทำ input validation กับ authentication ก่อน เพราะสองชั้นนี้ลงทุนน้อยที่สุดแต่ได้ผลมากที่สุด ส่วน rate limiting กับ CORS เป็นงานตั้งค่า ครึ่งวันก็เสร็จ และการสแกนช่องโหว่ให้ทำเป็นงานประจำที่รันอัตโนมัติทุกสัปดาห์
ลงมือสามขั้น ตอนแรก เติม authentication ให้ API ภายนอกทุกตัว โดยออก Key แยกตามพาร์ตเนอร์แต่ละราย ขั้นที่สอง ตั้ง rate limiting แบบแบ่งระดับให้ API ล็อกอินและฟอร์ม ส่วน API สาธารณะปล่อยหลวมไว้ก่อน ขั้นที่สาม ตรวจสอบพารามิเตอร์ที่ส่งเข้ามาทุกตัวด้วย whitelist ฝั่งแบ็กเอนด์ และผูกการสแกน dependency เข้ากับงานรายสัปดาห์ ทำสามขั้นนี้เสร็จ API ของคุณจะเจอแค่ “ถูกทดลองเป็นครั้งคราว” ไม่ใช่ “เปิดประตูทิ้งไว้” จังหวะดูแลประจำวันเทียบได้กับ ช่วงดูแลเว็บไซต์ จัดตารางไปพร้อมกันได้เลย
ประโยคเดียวที่จำง่าย ๆ ความปลอดภัย API ไม่ใช่กำแพงหนึ่งบาน แต่คือห้าประตู ทุกบานต้องล็อก ถ้าลืมล็อกบานเดียว อีกสี่บานที่เหลือก็แทบไม่มีประโยชน์
