คำอธิบายข้อสอบ Registered Information Security Specialist ฤดูใบไม้ผลิ 2024 (เรวะ 6) ภาคบ่าย ข้อ 1 — JWT alg=none, การอนุญาต API และการบรรเทาชั่วคราวด้วย WAF
· Go Komura · Registered Information Security Specialist, ผู้เชี่ยวชาญความมั่นคงปลอดภัยที่ขึ้นทะเบียน, API, ความมั่นคงปลอดภัยของ API, JWT, การยืนยันตัวตน, การอนุญาต, WAF, Log4Shell, ความมั่นคงปลอดภัยสารสนเทศ, ช่องโหว่, IPA
“เราตรวจลายเซ็น JWT ดังนั้นรหัสผู้ใช้จึงเชื่อถือได้”
ประโยคนั้นถูกเพียงครึ่งเดียว
ข้อ 1 ของภาคบ่าย (PM) ข้อสอบ Registered Information Security Specialist ฤดูใบไม้ผลิ 2024 (เรวะ 6) สร้างรอบ API ที่แอปสมาร์ตโฟนเรียก1 เมื่อยืนยันตัวตนสำเร็จจะออก JWT และ JWT นั้นถูกแนบไปกับการเรียก API ที่ดึงและอัปเดตข้อมูลผู้ใช้ ดูแวบแรกแล้วเป็นโครงธรรมดาทั้งหมด
อย่างไรก็ดี การประเมินพบสี่ประเด็นต่อไปนี้
- เปลี่ยน
algในส่วนหัว JWT เป็นnoneแล้ว JWT ที่ไม่มีลายเซ็นผ่านได้ - คง JWT ที่ถูกต้องไว้ แต่เปลี่ยน
midเป็นรหัสผู้ใช้อื่น ทำให้읽หรืออัปเดตข้อมูลของคนอื่นได้ - เติม
status=paidที่ไม่อยู่ในสเปก ทำให้ผู้ใช้ฟรีกลายเป็นผู้ใช้ที่ชำระเงิน - รหัสยืนยันตัวตน 4 หลักที่ส่งทางอีเมลถูกบรูทฟอร์ซได้โดยไม่จำกัดจำนวนครั้ง
ทั้งสี่ดูเหมือน “ช่องโหว่ใกล้การยืนยันตัวตน” แต่สาเหตุไม่เหมือนกัน สิ่งที่ถูกทำลายคือขอบเขตคนละอย่าง: ความครบถ้วนของโทเค็น, การอนุญาตระดับออบเจกต์, การอนุญาตระดับคุณสมบัติ และการจำกัดอัตราการลอง
ครึ่งหลังของข้อยังเพิ่มหัวข้ออีกข้อ ช่องโหว่ร้ายแรงถูกเปิดเผยในไลบรารีโอเพนซอร์สที่ใช้กันกว้าง ซึ่งให้ผู้โจมตีรันโค้ดจากระยะไกลด้วยการใช้อำนาจ JNDI Lookup ผิด ทั้งแพตช์และกฎ WAF ที่เสร็จแล้วยังไม่มี ในระหว่างนั้น ข้อถามว่าจะยืนยันผลกระทบอย่างไร WAF ควรดูที่ไหน และทำไมโหมด WAF แรกจึงควรเป็น “ตรวจจับ” ไม่ใช่ “บล็อก”
บทความนี้ใช้เฉลยทางการ2 และคำอธิบายการให้คะแนน3 เป็นฐาน และไล่ไม่ใช่แค่คำตอบของแต่ละข้อ แต่ทำไมนั่นถึงเป็นคำตอบ และในทางปฏิบัติควรออกแบบเข้มแค่ไหน
flowchart TB
accTitle: ภาพรวมของข้อสอบ
accDescr: แสดงขอบเขตความเชื่อถือที่ถูกทำลายในแต่ละขั้น รหัสยืนยันตัวตน JWT การอนุญาต API และช่องโหว่ไลบรารี
user[แอปผู้ใช้]
auth[รหัสยืนยันตัวตน 4 หลัก]
jwt[การออก JWT]
api[API ผู้ใช้]
log[การเขียนล็อก]
vuln[ไลบรารีที่มีช่องโหว่]
ext[การรันโค้ดจากระยะไกล]
user --> auth
auth -->|ไม่มีจำกัดการลอง| jwt
jwt -->|ยอมรับ alg=none| api
api -->|เชื่อ mid| db1[อ่าน/อัปเดตข้อมูลผู้ใช้อื่น]
api -->|status=paid| db2[เปลี่ยนสถานะการเรียกเก็บเงิน]
api --> log
log --> vuln
vuln -->|JNDI/LDAP/HTTP| ext
ภาพ 1: ภาพรวมของข้อสอบ ขอบเขตความเชื่อถือคนละขอบถูกทำลายในแต่ละขั้น
1. สรุปก่อนเลย
- คุณสมบัติของ RESTful API ที่ไม่ถือสถานะเซสชันเรียกว่า สเตตเลส นี่ไม่ได้แปลว่าเซิร์ฟเวอร์ไม่ถือฐานข้อมูลหรือสถานะผู้ใช้เลย
- รหัสยืนยันตัวตน 4 หลักมีค่าที่เป็นไปได้ 10,000 ค่า ที่ 10 ครั้งต่อวินาที ผู้โจมตีสำเร็จหลังเฉลี่ย 5,000 ครั้ง คือ 500 วินาที สั้นกว่าอายุ 10 นาที ดังนั้นเวลาหมดอายุอย่างเดียวหยุดไม่ได้
- มาตรการขั้นต่ำต่อ
alg=noneคือยืนยันว่าalgในส่วนหัว JWT ไม่ใช่NONEแต่ในทางปฏิบัติควรตรึงชุดอัลกอริทึมที่อนุญาตฝั่งเซิร์ฟเวอร์ - แม้มี JWT ที่ถูกต้อง ก็ห้ามเชื่อ
midของคำขอ ให้เทียบรหัสผู้ใช้ใน JWT กับmidหรือปลอดภัยกว่าคือไม่รับmidจากไคลเอนต์เลย แล้วกำหนดเป้าหมายจาก JWT - การเติม
status=paidเป็นปัญหา Mass Assignment ที่คุณสมตินอกสเปกถูกผูกตรงเข้าออบเจกต์ภายใน ใช้ DTO อัปเดตเป็นรายการอนุญาต และอย่าให้ผู้ใช้เปลี่ยนสถานะการเรียกเก็บเงิน - เฉลยมาตรการบรูทฟอร์ซคือ ตรรกะที่ล็อกบัญชีเมื่อจำนวนล้มเหลวติดต่อกันเกินเกณฑ์ ในทางปฏิบัติให้ซ้อนดีเลย์แบบขั้นบันไดและการควบคุมตามแหล่งที่มาด้วย
- เพื่อยืนยันผลกระทบของช่องโหว่ร้ายแรงที่เพิ่งเปิดเผย แทนที่จะออกคำสั่งทำลาย ให้บันทึกการเข้าถึง
index.htmlของเซิร์ฟเวอร์ทดสอบ เพื่อยืนยันว่ารันโค้ดจากระยะไกลไปถึงจริง - สตริงโจมตีถูกพาในส่วนหัว HTTP ดังนั้นเป้าหมายตรวจของ WAF คือ
Headerในฐานะนิพจน์ปรกติที่รับการสลับตัวพิมพ์ ใช้แบบ\W[jJ][nN][dD][iI]\W - ข้อดีของการเริ่ม WAF ในโหมด “ตรวจจับ” คือ ป้องกันไม่ให้ทราฟฟิกธุรกิจที่ถูกต้องถูกบล็อกจากผลบวกลวง เมื่อมีแจ้งเตือน ให้ตรวจว่าเป็นการโจมตีจริง แล้วย้ายไปโหมดบล็อกเมื่อปรับกฎแล้ว
- WAF เป็นเพียงมาตรการชั่วคราว การแก้ที่ต้นเหตุคือ อัปเดตไลบรารีที่ได้รับผลกระทบเป็นเวอร์ชันที่มีแพตช์
2. สถานการณ์จับคู่กับข้ออย่างไร
สถานการณ์อยู่ที่บริษัท G ซึ่งกำลังเปิดบริการสุขภาพใหม่ ผู้ใช้ป้อนข้อมูลเช่นมื้ออาหารและน้ำหนักผ่านแอปสมาร์ตโฟน แล้วได้รับประเมินความเสี่ยงสุขภาพและคำแนะนำอาหาร ระบบสร้างบนคลาวด์ รวมเกตเวย์ API, การประมวลผลตามเหตุการณ์ และฐานข้อมูลแบบจัดการ
ข้อสอบตัดชื่อผลิตภัณฑ์และบริการเฉพาะออก บทความนี้ก็ไม่ทำซ้ำแผนภาพหรือข้อความของ IPA แต่ถอดความเฉพาะโครงที่จำเป็นต่อความเข้าใจข้อ
| ข้อ | หัวข้อ | บทของบทความนี้ |
|---|---|---|
| ข้อ 1 | ธรรมชาติของ RESTful API | บทที่ 4 |
| ข้อ 2(1) | เวลาบรูทฟอร์ซรหัส 4 หลัก | บทที่ 5 |
| ข้อ 2(2) | JWT alg=none |
บทที่ 6 |
| ข้อ 2(3) | เข้าถึงผู้ใช้อื่นผ่าน mid |
บทที่ 7 |
| ข้อ 2(4) | ข้อบกพร่องที่รับ status นอกสเปก |
บทที่ 8 |
| ข้อ 2(5) | มาตรการบรูทฟอร์ซ | บทที่ 9 |
| ข้อ 3(1) | ยืนยันว่ามีช่องโหว่โดยปลอดภัย | บทที่ 11 |
| ข้อ 3(2)(3) | WAF ดูที่ไหน และนิพจน์ปรกติ | บทที่ 12 |
| ข้อ 3(4) | ข้อดีของโหมดตรวจจับและวิธีปฏิบัติ | บทที่ 13 |
คำอธิบายการให้คะแนนระบุว่าอัตราถูกโดยรวมอยู่ในระดับปานกลาง แต่ก็ชี้ว่าอัตราถูกค่อนข้างต่ำสำหรับมาตรการดัดแปลง JWT ในข้อ 2(2) และกลไกที่เซิร์ฟเวอร์ตรวจต้องการในข้อ 3(1) ไม่มีข้อใดตอบจากคำศัพท์อย่างเดียวได้ ต้องไล่ว่าผู้โจมตีเปลี่ยนค่าใด ค่าไหลเข้ากระบวนการใด และไปถูกเชื่อที่ไหน
3. นี่ไม่ใช่แค่ “ปัญหาการยืนยันตัวตน” ข้อเดียว
จัดทั้งข้อตามขอบเขตความเชื่อถือได้ดังนี้
[รหัสผู้ใช้ / รหัสผ่าน]
|
v
[ตรวจรหัส 4 หลัก] ---- ไม่จำกัดการลอง ----> บรูทฟอร์ซ
|
v
[ออก JWT]
|
v
[ไลบรารี JWT] ------- ยอมรับ alg=none ------> ดัดแปลงรหัสผู้ใช้
|
v
[API ผู้ใช้]
| |
| +-- ส่ง status ทั้งก้อน ----> ข้อบกพร่องการอนุญาตระดับคุณสมบัติ
|
+-- เชื่อ mid -----------------------> ข้อบกพร่องการอนุญาตระดับออบเจกต์
[บันทึกอินพุตภายนอกในล็อก]
|
v
[ไลบรารีที่มีช่องโหว่] ---- JNDI/LDAP/HTTP ------> รันโค้ดจากระยะไกล
ข้อแยกที่สำคัญที่สุดคือต่อไปนี้
| การตรวจ | คำถามที่ถาม | ตัวอย่างที่ถูกทำลายในสถานการณ์นี้ |
|---|---|---|
| การยืนยันตัวตน | คุณเป็นใคร | บรูทฟอร์ซรหัส 4 หลัก |
| การตรวจโทเค็น | ข้อมูลอัตลักษณ์นั้นถูกดัดแปลงหรือไม่ | alg=none |
| การอนุญาตระดับออบเจกต์ | ผู้ใช้นี้เข้าถึงข้อมูลของผู้ใช้นี้ได้หรือไม่ | สลับ mid |
| การอนุญาตระดับคุณสมบัติ | ฟิลด์นี้เปลี่ยนได้หรือไม่ | status=paid |
| ขอบเขตจากอินพุตสู่การรัน | อินพุตภายนอกถูกตีความเป็นคำสั่งหรือไม่ | JNDI Lookup |
การผ่านการตรวจข้อหนึ่งไม่มีวันเป็นเหตุให้ข้ามข้อถัดไป ผู้ใช้ที่มี JWT ถูกต้องไม่จำเป็นต้องอ่านข้อมูลของคนอื่นได้ ผู้ใช้ที่อัปเดตข้อมูลของตนได้ไม่จำเป็นต้องเปลี่ยนสถานะการเรียกเก็บเงินด้วย
เมื่อแยกขั้นเหล่านี้ได้ คำตอบของแต่ละข้อจะไม่ใช่สิ่งที่ท่องจำ
flowchart LR
accTitle: ความต่างระหว่างการยืนยันตัวตนกับการอนุญาต
accDescr: การยืนยันตัวตนยืนยันตัวตน การอนุญาตยืนยันว่าตัวตนนั้นทำอะไรได้
auth[การยืนยันตัวตน<br/>คุณเป็นใคร]
authz[การอนุญาต<br/>คุณทำอะไรได้]
auth --> authz
ภาพ 2: ความต่างระหว่างการยืนยันตัวตนกับการอนุญาต การยืนยันตัวตนมาก่อน การอนุญาตเป็นการตรวจแยก
4. ข้อ 1 — “สเตตเลส” หมายความว่าอะไร
ข้อ 1 ถามถึงหลักออกแบบอย่างหนึ่งของ RESTful API คือคุณสมบัติที่ไม่จัดการเซสชัน
คำตอบคือ สเตตเลส
สเตตเลสหมายความว่าเซิร์ฟเวอร์ไม่ต้องจำสถานะสนทนาของคำขอก่อนหน้า เพราะแต่ละคำขอแบกทุกอย่างที่ต้องใช้ประมวลผลด้วยตัวเอง ในข้อนี้แอปสมาร์ตโฟนแนบ JWT ในส่วนหัว Authorization ทุกคำขอ เซิร์ฟเวอร์ตรวจ JWT นั้นแล้วระบุผู้ใช้ของคำขอนั้นจากมัน
การอ่านผิดที่พบบ่อยคือถือว่า “สเตตเลส” แปลว่า “เซิร์ฟเวอร์ไม่ถือสถานะเลย” ในความเป็นจริงมักถือสถานะต่อไปนี้
- ฐานข้อมูลที่เก็บข้อมูลผู้ใช้และข้อมูลสุขภาพ
- สถานะการเรียกเก็บเงิน
- ค่า วันหมดอายุ และตัวนับความล้มเหลวของรหัสยืนยันตัวตน
- คีย์ลงลายเซ็น JWT
- ข้อมูลเพิกถอน หากออกแบบใช้รายการเพิกถอน
- ล็อกและบันทึกตรวจสอบ
สิ่งที่ไม่ถือคือ สถานะเซสชันฝั่งเซิร์ฟเวอร์ที่มีอยู่เพียงเพื่อต่อบทสนทนา เป็นเงื่อนไขที่แต่ละการเรียก API พึ่งพา
การเป็นสเตตเลสก็ไม่ยกระดับความมั่นคงปลอดภัยอัตโนมัติ การส่ง JWT ทุกคำขอทำให้สเกลแนวนอนง่ายขึ้น แต่ถ้าตรวจ JWT ผิด ความผิดนั้นก็กระจายสม่ำเสมอไปทุกโหนดเช่นกัน คุณสมบัติทางสถาปัตยกรรมกับความถูกต้องด้านความมั่นคงปลอดภัยเป็นคนละเรื่อง
5. ข้อ 2(1) — รหัส 4 หลักถูกเจาะเฉลี่ยใน 500 วินาที
API ยืนยันตัวตนส่งตัวเลข 4 หลักทางอีเมลเมื่อรหัสผู้ใช้และรหัสผ่านตรง แล้วออก JWT เมื่อรหัสผู้ใช้กับรหัส 4 หลักตรง รหัสมีอายุ 10 นาทีนับจากสร้าง
ในการประเมิน ลองได้ 10 ครั้งต่อวินาที ข้อถามว่าโดยเฉลี่ยใช้กี่วินาทีจึงทะลุ
การคำนวณคือ “ครึ่งหนึ่งของปริภูมิผู้สมัคร”
ตัวเลข 4 หลัก รวมศูนย์นำหน้า มีความเป็นไปได้ 10,000 แบบดังนี้
0000, 0001, 0002, ... , 9999
หากคำตอบถูกสุ่มอย่างสม่ำเสมอ ผู้โจมตีที่ลองผู้สมัครตามลำดับโดยไม่ซ้ำ จะถึงค่าที่ถูกโดยเฉลี่ยหลังครึ่งหนึ่งของปริภูมิผู้สมัคร
จำนวนครั้งเฉลี่ย = 10,000 / 2 = 5,000
เวลาเฉลี่ย = 5,000 / 10 ครั้งต่อวินาที = 500 วินาที
ดังนั้นช่อง b คือ 500
กรณีแย่สุดใช้ถึง 1,000 วินาที แต่ข้อถามค่าเฉลี่ย และอายุของรหัสคือ 600 วินาที ยาวกว่าเวลาเจาะเฉลี่ย 500 วินาที นั่นจึงถูกตัดสินว่า “มีแนวโน้มถูกเจาะ”
flowchart LR
accTitle: ขนาดของรหัสยืนยันตัวตน 4 หลัก
accDescr: ลอง 10,000 ผู้สมัครที่ 10 ครั้งต่อวินาที เฉลี่ย 5,000 ครั้งและ 500 วินาที น้อยกว่าอายุ 600 วินาที
A[ผู้สมัคร 10,000] -->|เฉลี่ย 10,000 / 2 = 5,000 ครั้ง| B[เวลาเจาะเฉลี่ย 500 วินาที]
C[อายุ 600 วินาที] -->|500 วินาทีน้อยกว่า 600| D[เจาะได้ภายในอายุ]
ภาพ 9: ขนาดของรหัสยืนยันตัวตน 4 หลัก การลองโดยเฉลี่ยครึ่งหนึ่งของปริภูมิผู้สมัครเจาะได้ภายในอายุ
การย่นเฉพาะเวลาหมดอายุแพ้หากปริภูมิผู้สมัครเล็ก
ความแข็งของรหัสยืนยันตัวตนไม่ได้กำหนดจากจำนวนหลักอย่างเดียวหรือจากอายุอย่างเดียว
จำนวนครั้งที่ลองได้ในช่วงอายุ
= ครั้งต่อวินาที x อายุ
= 10 x 600
= 6,000 ครั้ง
การลองค่าไม่ซ้ำตามลำดับ ผู้โจมตีตรวจได้ 60% ของ 10,000 ความเป็นไปได้ภายในอายุ การตั้งเวลาหมดอายุอย่างเดียวไม่พอ หากไม่จำกัดจำนวนครั้ง
NIST SP 800-63B ปัจจุบันกำหนดให้ความลับระยะสั้นที่ใช้ยืนยันตัวตนนอกแถบมีอย่างน้อยหกหลัก และบังคับจำกัดอัตราการลองเมื่อความลับมีเอนโทรปีต่ำกว่า 64 บิต และยังระบุไม่ให้ใช้อีเมลสำหรับการยืนยันตัวตนนอกแถบ4 คำตอบข้อสอบทำงานในสเปกที่ให้มาคือรหัส 4 หลักส่งทางอีเมล แต่สำหรับการออกแบบใหม่ในทางปฏิบัติ ควรทบทวนข้อสมมตินั้นเอง
6. ข้อ 2(2) — alg=none คือปัญหา “ให้ผู้โจมตีเลือกวิธีตรวจ”
JWT ในข้อนี้มีสามส่วน คือส่วนหัว เพย์โหลด และลายเซ็น
base64url(header).base64url(payload).base64url(signature)
ส่วนหัวบันทึก RS256 เป็นอัลกอริทึมที่ใช้ลงลายเซ็น เพย์โหลดมีรหัสผู้ใช้ เวลาออก และวันหมดอายุ
ผู้ประเมินเปลี่ยนสองอย่างต่อไปนี้
- เปลี่ยน
algในส่วนหัวจากRS256เป็นNONE - เปลี่ยนรหัสผู้ใช้ในเพย์โหลดเป็นผู้ใช้อื่น
เมื่อส่ง JWT นั้น การตรวจสำเร็จและทำให้แอบอ้างเป็นคนอื่นได้
flowchart LR
accTitle: การไหลของการโจมตี JWT alg=none
accDescr: เปลี่ยน alg เป็น none ใน JWT ที่ถูกต้องแล้วเขียนทับรหัสผู้ใช้ ทำให้คำขอผ่าน
A[JWT ที่ถูกต้อง<br/>alg=RS256<br/>user=user01] -->|เปลี่ยน alg ส่วนหัวเป็น none| B[JWT ที่ถูกดัดแปลง<br/>alg=none<br/>user=user02]
B -->|ข้ามการตรวจลายเซ็น| C[เซิร์ฟเวอร์รับเป็น<br/>user02]
ภาพ 3: การไหลของการโจมตี JWT alg=none ผู้โจมตีเป็นคนเลือกอัลกอริทึมตรวจ
none ไม่ใช่การสะกดผิด
RFC 7519 นิยาม “Unsecured JWT” คือ JWT ที่ไม่มีทั้งลายเซ็นและการเข้ารหัส ซึ่ง alg เป็น none5 ดังนั้นค่า none ไม่ใช่สิ่งที่ไม่มีในสเปก
ปัญหาคือ API ที่ควรรับเฉพาะ JWT ที่มีลายเซ็นกลับรับ none ที่ผู้โจมตีระบุ
เขียนกระบวนการที่มีช่องโหว่เชิงแนวคิดได้ดังนี้
1. อ่านส่วนหัว JWT
2. ดู alg ที่เขียนในส่วนหัว แล้วเลือกวิธีตรวจ
3. ถ้า alg เป็น none ไม่ต้องตรวจลายเซ็น
4. เชื่อรหัสผู้ใช้ในเพย์โหลด
ความแข็งของความมั่นคงปลอดภัยเองกำลังถูกเลือกจากอินพุตที่ผู้โจมตีควบคุม
คำตอบของข้อสอบ
ข้อถามภายใน 20 ตัวอักษรต่อช่อง ว่าไลบรารี Q ที่แก้แล้วควรตรวจข้อมูลใด และควรตรวจอะไร
เฉลยมีดังนี้
| รายการ | สาระของคำตอบ |
|---|---|
| ข้อมูลที่ตรวจ | ค่าที่ระบุใน alg ของส่วนหัว JWT |
| สิ่งที่ตรวจ | ว่าไม่ใช่ NONE |
ในฐานะการแก้ตรงต่อช่องโหว่ที่ข้อบรรยาย นี่ถูกต้อง
ในทางปฏิบัติอย่ายอมอยู่ที่ “อะไรก็ได้ที่ไม่ใช่ NONE”
ตรงนี้ต้องแยกคำตอบข้อสอบกับคำแนะนำในทางปฏิบัติ
RFC 8725 ระบุว่าไลบรารี JWT ควรให้ผู้เรียกระบุชุดอัลกอริทึมที่อนุญาต และห้ามใช้นอกชุดนั้น6 กล่าวคือแนวคิดคือแบบนี้
แนวทางไม่ดี:
รับถ้า token.header.alg != "none"
แนวทางดี:
รับเฉพาะเมื่ออยู่ใน serverConfig.allowedAlgorithms
เช่น allowedAlgorithms = ["RS256"]
การปฏิเสธแค่ none ยังอาจเหลืออัลกอริทึมอ่อนอื่น หรือความสับสนอัลกอริทึมที่แผนคีย์สาธารณะถูกเข้าใจผิดเป็นคีย์สมมาตร หลักคือ อย่าเพิ่มเงื่อนไขปฏิเสธว่าจะรับอะไร แต่ให้ตรึงชุดบวกที่แคบของสิ่งที่อนุญาต
การตรวจ JWT ควรยืนยันไม่ใช่แค่อัลกอริทึม แต่ตามกรณีใช้งานอย่างน้อยต่อไปนี้ด้วย
| รายการ | สิ่งที่ยืนยัน |
|---|---|
| ลายเซ็น | ตรวจด้วยคีย์และอัลกอริทึมที่คาดได้หรือไม่ |
iss |
เป็นผู้ออกที่เชื่อถือได้หรือไม่ |
aud |
โทเค็นออกให้ API นี้หรือไม่ |
exp |
ยังอยู่ในอายุหรือไม่ |
nbf |
ยังไม่ถึงเวลาก่อนที่จะใช้ได้หรือไม่ |
sub หรือรหัสผู้ใช้ |
เป็นตัวตนที่ถูกต้องในแอปหรือไม่ |
| ชนิดโทเค็น | สับสน ID โทเค็นกับโทเค็นเข้าถึงหรือไม่ ฯลฯ |
ในข้อนี้ชื่อคีย์เพย์โหลดคือ user แต่ในทางปฏิบัติควรใช้ sub มาตรฐาน หรือนิยามความหมายของเคลมกำหนดเองให้ชัด
flowchart TB
accTitle: การตรวจ JWT ที่ปลอดภัยกับที่ไม่ปลอดภัย
accDescr: การตรวจที่ไม่ปลอดภัยพึ่ง alg การตรวจที่ปลอดภัยใช้รายการอนุญาตฝั่งเซิร์ฟเวอร์
subgraph "การตรวจที่ไม่ปลอดภัย"
D1[อ่าน alg จากส่วนหัว JWT]
D2[รับถ้า alg เป็น none]
D1 --> D2
end
subgraph "การตรวจที่ปลอดภัย"
S1[อัลกอริทึมที่อนุญาตในตั้งค่าเซิร์ฟเวอร์<br/>เช่น RS256]
S2[ยืนยันว่า alg ในส่วนหัว JWT<br/>อยู่ในรายการอนุญาต]
S3[ตรวจลายเซ็น iss aud exp]
S1 --> S2 --> S3
end
ภาพ 4: การตรวจที่ปลอดภัยกับที่ไม่ปลอดภัย ในทางปฏิบัติให้ตรึงชุดอัลกอริทึมที่อนุญาตให้แคบ
Base64url ไม่ใช่การเข้ารหัส
ยังมีความเข้าใจผิดที่พบบ่อยอีกข้อเกี่ยวกับ JWT ส่วนหัวและเพย์โหลดถูกแทนด้วย base64url แต่นั่นไม่ใช่การเข้ารหัส ใครก็ถอดรหัสแล้วอ่านได้
สิ่งที่ลายเซ็นรับประกัน และเฉพาะเมื่อตรวจผ่าน คือเนื้อหาไม่ถูกดัดแปลงนับจากออก ไม่ได้แปลว่าข้อมูลส่วนบุคคลที่อยากเก็บเป็นความลับใส่ในเพย์โหลดของ JWT ที่มีลายเซ็นได้
7. ข้อ 2(3) — แม้มี JWT ที่ถูกต้อง การเปลี่ยน mid อ่านข้อมูลคนอื่นได้
ถัดไปคือการโจมตีที่ไม่ดัดแปลง JWT เอง
API ผู้ใช้รับรหัสผู้ใช้ชื่อ mid บน GET หรือ PUT โมดูลร่วม P ดึงหรืออัปเดตข้อมูลผู้ใช้ที่ผูกกับ mid นั้นในฐานข้อมูล
โครงของการโจมตีเรียบง่าย
รหัสผู้ใช้ใน JWT: user01 <- JWT ที่ลงลายเซ็นถูกต้อง
mid ของคำขอ: user02 <- ผู้โจมตีเปลี่ยน
ลายเซ็น JWT ถูกต้อง ดังนั้นการยืนยันตัวตนสำเร็จ แต่ API เชื่อ mid=user02 ตามที่ให้มา และคืนข้อมูลของ user02
นี่เป็นกรณีตำราของสิ่งที่ OWASP API Security Top 10 2023 เรียกว่า Broken Object Level Authorization (BOLA) เมื่อเข้าถึงข้อมูลด้วยรหัสออบเจกต์ที่ผู้ใช้ระบุ ต้องตรวจการอนุญาตของออบเจกต์นั้นทุกครั้ง7
flowchart LR
accTitle: การโจมตี BOLA
accDescr: ใช้ JWT ที่ถูกต้องขณะเปลี่ยน mid ของคำขอเป็นรหัสผู้ใช้อื่น
A[ผู้โจมตี] -->|JWT user01<br/>mid user02| B[API ผู้ใช้]
B -->|เชื่อ mid| C[คืนข้อมูล user02 จาก DB]
ภาพ 5: การโจมตี BOLA การยืนยันตัวตนผ่าน แต่การอนุญาตไม่เคยถูกตรวจ
คำตอบของข้อ
ขีดเส้นใต้ 2 ในตาราง 5 ถามภายใน 40 ตัวอักษร ถึงกระบวนการที่ต้องเพิ่มในการเรียกโมดูลร่วม P
เฉลยคือ:
ตรรกะที่ตรวจว่ารหัสผู้ใช้ใน JWT ตรงกับค่าของ
midหรือไม่
ข้อดีของการตรวจในโมดูลร่วม P คือการตรวจอนุญาตชุดเดียวกันใช้ง่ายทั้ง GET และ PUT และกับ API ในอนาคตที่ใช้ P ด้วย การคัดลอกการเทียบเดียวกันไปแต่ละหน้าจอหรือเอนด์พอยต์หมายความว่าจะหายไปที่ไหนสักแห่ง
การออกแบบที่ปลอดภัยกว่าคือไม่รับ mid เลย
สำหรับ API ที่ดึงหรืออัปเดตเฉพาะข้อมูลของผู้เรียกเอง ไม่จำเป็นต้องรับรหัสผู้ใช้จากไคลเอนต์เลย
GET /users/me
Authorization: Bearer <JWT>
ฝั่งเซิร์ฟเวอร์ ดึงตัวตนจาก JWT ที่ตรวจแล้ว
principal = validateJwt(request.authorization)
userId = principal.subject
return repository.getUser(userId)
การอัปเดตก็เช่นกัน
principal = validateJwt(request.authorization)
input = validateProfileUpdate(request.body)
repository.updateProfile(
userId = principal.subject,
name = input.name,
age = input.age
)
การตรวจเทียบจะปกป้องคุณถ้าคุณเขียน แต่การออกแบบที่ไม่รับรหัสเป้าหมายจากภายนอกเลย ลดการมีอยู่ของคลาสบั๊กที่ลืมเขียนการเทียบนั้นเอง
หากผู้ดูแลระบบต้องทำงานกับข้อมูลของผู้ใช้อื่น ให้แยกดังนี้
PUT /users/me สำหรับผู้ใช้ทั่วไป
PUT /admin/users/{userId} สำหรับผู้ดูแลระบบ
สำหรับเส้นทางผู้ดูแลระบบ ให้ขอสิทธิ์แยก บันทึกตรวจสอบ และหากจำเป็น การยืนยันตัวตนซ้ำ สิ่งนี้ทำให้ขอบเขตนโยบายอนุญาตเห็นชัดกว่า “เพิ่มข้อยกเว้นให้ผู้ดูแลระบบใน API ผู้ใช้ทั่วไป”
flowchart LR
accTitle: วิธีป้องกัน BOLA
accDescr: แทนการใช้ mid ของคำขอ ให้ตัดสินหรือตรวจเป้าหมายจาก subject ของ JWT
A[ผู้ใช้] -->|GET /users/me + JWT| B[API]
B -->|เอา sub จาก JWT| C{ถ้ามี mid<br/>ตรงกับ sub หรือไม่}
C -->|ตรง| D[คืนข้อมูลของตน]
C -->|ไม่ตรง| E[ปฏิเสธ 403]
B -->|ไม่มี mid| F[ค้น DB ด้วย JWT sub]
ภาพ 6: วิธีป้องกัน BOLA ไม่รับ mid หรือเทียบกับ subject ของ JWT
แยกการยืนยันตัวตนกับการอนุญาตในประโยคเดียว
ทั้งในข้อสอบและในทางปฏิบัติ วลีต่อไปนี้ช่วยได้
- การยืนยันตัวตน: คุณเป็นใคร
- การอนุญาต: คนนั้นทำอะไรได้
การตรวจลายเซ็น JWT สำเร็จพาคุณไปได้แค่ “ตัวตนที่โทเค็นนี้แทนเชื่อถือได้” ว่า “ตัวตนนั้นอ่าน user02 ได้หรือไม่” ต้องยืนยันแยก
8. ข้อ 2(4) — status=paid คือข้อบกพร่องการอนุญาตระดับคุณสมบัติ
สเปกของ API ผู้ใช้กำหนดพารามิเตอร์อัปเดตดังนี้
mid รหัสผู้ใช้
name ชื่อ
age อายุ
อย่างไรก็ดี ผู้ประเมินเติมค่าต่อไปนี้ซึ่งไม่อยู่ในสเปก
status=paid
สถานะของผู้ใช้ฟรีจึงเปลี่ยนเป็นผู้ใช้ที่ชำระเงิน
ตามข้อ บริการ L ไม่ได้ตรวจพารามิเตอร์ที่รับ แต่ส่งทั้งหมดตรงไปยังโมดูลร่วม P ซึ่งถูกสร้างให้สามารถอัปเดตฐานข้อมูลได้โดยตรง
คำตอบของช่อง c คือ โมดูลร่วม P
flowchart TB
accTitle: Mass Assignment
accDescr: status=paid นอกสเปกถูกเติมแล้วถูกนำไปใช้ทั้งก้อนบนออบเจกต์ภายใน
A[สเปก API<br/>mid / name / age] -->|ผู้โจมตีเติม status=paid| B[เนื้อหาคำขอ]
B -->|ผูกอัตโนมัติ| C[โมดูลร่วม P]
C -->|บันทึกใน DB| D[สถานะการเรียกเก็บเงินเปลี่ยนเป็น paid]
ภาพ 7: Mass Assignment คุณสมตินอกสเปกถูกนำไปใช้ทั้งก้อนบนออบเจกต์ภายใน
ความต่างจาก BOLA
การสลับ mid จากบทก่อนกับ การเติม status ครั้งนี้ดูคล้าย แต่ความละเอียดที่ปกป้องต่างกัน
| ช่องโหว่ | สิ่งที่ผู้โจมตีเปลี่ยน | สิ่งที่ควรตรวจจริง |
|---|---|---|
สลับ mid |
ออบเจกต์เป้าหมาย | ผู้ใช้นี้เข้าถึงระเบียนผู้ใช้นี้ได้หรือไม่ |
เติม status |
คุณสมบัติภายในออบเจกต์ | ผู้ใช้นี้เปลี่ยนฟิลด์นี้ได้หรือไม่ |
OWASP API Security Top 10 2023 ถืออย่างหลังเป็น Broken Object Property Level Authorization โดยพับสิ่งที่เคยเรียกว่า Mass Assignment เข้าหมวดนี้8
“ยัด JSON ตรงเข้าเอนทิตี” อันตราย
การอิมพลีเมนต์ที่มีช่องโหว่เชิงแนวคิดมีลักษณะดังนี้
entity = repository.find(body.mid)
bindAllProperties(entity, body)
repository.save(entity)
แม้หน้าจอมีช่องกรอกแค่ name และ age ผู้โจมตีสร้างคำขอ HTTP ได้โดยตรง การที่ฟิลด์ไม่มีใน UI ไม่ใช่ขอบเขตความมั่นคงปลอดภัย
การอิมพลีเมนต์ที่ปลอดภัยทำให้ฟิลด์ที่อัปเดตได้ชัดเจน
input = parseExactSchema(body, fields = ["name", "age"])
entity = repository.find(authenticatedUserId)
entity.name = input.name
entity.age = input.age
repository.save(entity)
สองอย่างสำคัญที่นี่
- ชนิดอินพุตอัปเดตควรถือเฉพาะฟิลด์ที่ผู้ใช้เปลี่ยนได้
- แทนที่จะเมินฟิลด์นอกสเปกที่ไม่รู้จักอย่างเงียบ ให้ปฏิเสธเป็นข้อผิดพลาดถ้าทำได้
การเมินฟิลด์ที่ไม่รู้จักอย่างเงียบซ่อนว่าการโจมตีล้มเหลว แต่ก็ทำให้พลาดความผิดพลาดของไคลเอนต์และสัญญาณการโจมตี หากไม่มีเหตุผลด้านความเข้ากันได้ การปฏิเสธด้วยสคีมาเข้มงวดทำให้สอบสวนง่ายขึ้น
flowchart TB
accTitle: การอนุญาตระดับคุณสมบัติ
accDescr: DTO อัปเดตถือเฉพาะรายการอนุญาต และปฏิเสธคุณสมบัติที่ไม่รู้จัก
subgraph "DTO อัปเดต - รายการอนุญาต"
D1["name"]
D2["age"]
end
A[เนื้อหาคำขอ] -->|ตรวจสคีมา| B{มีเฉพาะฟิลด์<br/>ที่อนุญาต}
B -->|ใช่| C[อัปเดต name/age ของเอนทิตี]
B -->|ไม่| D[คืนข้อผิดพลาด]
E[บริการชำระเงิน<br/>การแจ้งที่ยืนยันแล้ว] -->|เส้นทางเฉพาะ| F[อัปเดต status=paid]
ภาพ 8: การอนุญาตระดับคุณสมบัติ จำกัดฟิลด์ที่อัปเดตได้ด้วยรายการอนุญาต และเปลี่ยนสถานะการเรียกเก็บเงินเฉพาะผ่านเส้นทางแยก
status ควรเปลี่ยนเฉพาะจากผลการชำระเงิน
status=paid ไม่ใช่ส่วนของโปรไฟล์ผู้ใช้ เป็นสถานะที่ได้จากข้อเท็จจริงฝั่งเซิร์ฟเวอร์ว่าการชำระเงินสำเร็จ
อัปเดตโปรไฟล์ผู้ใช้
-> เปลี่ยนได้เฉพาะ name / age
การแจ้งที่ยืนยันแล้วจากบริการชำระเงิน
-> เทียบ paymentId
-> ป้องกันการประมวลผลซ้ำ
-> เปลี่ยน status เป็น paid
แม้เก็บในคอลัมน์ฐานข้อมูลเดียวกัน อำนาจในการเปลี่ยนกับเส้นทางที่ใช้เปลี่ยนเป็นคนละเรื่อง การใช้เอนทิตีภายในตรงเป็นชนิดอินพุตของ API ภายนอกลบขอบเขตนั้น
9. ข้อ 2(5) — มาตรการบรูทฟอร์ซถือตัวนับความล้มเหลวเป็นสถานะ
สำหรับการบรูทฟอร์ซรหัส 4 หลัก ช่อง d ในตาราง 5 ถามภายใน 30 ตัวอักษรถึงกระบวนการที่อยู่ตรงนั้น เกณฑ์คือ 10
เฉลยคือ:
ตรรกะที่ล็อกบัญชีเมื่อจำนวนล้มเหลวติดต่อกันเกินเกณฑ์
นี่ไม่ขัดกับสเตตเลสจากข้อ 1 การไม่ถือสถานะสนทนาของการเรียก API เป็นเซสชันเซิร์ฟเวอร์ กับการคงตัวนับความล้มเหลวที่ต้องใช้ตัดสินด้านความมั่นคงปลอดภัย เป็นคนละเรื่อง
flowchart LR
accTitle: มีและไม่มีจำกัดอัตราการลอง
accDescr: หากไม่มีจำกัด รหัสถูกเจาะเฉลี่ยใน 500 วินาที แต่จำกัดตัวนับความล้มเหลวทำให้การโจมตีช้าลงมาก
subgraph "ไม่มีจำกัด"
A1[10 ครั้งต่อวินาที] -->|ราว 500 วินาที| B1[ยืนยันตัวตนสำเร็จ]
end
subgraph "มีจำกัด"
A2[ล็อกหลังล้มเหลว 10 ครั้ง] -->|ความเร็วโจมตีพัง| B2[บัญชีถูกล็อก]
C2[ดีเลย์แบบขั้นบันได] --> B2
end
ภาพ 10: มีและไม่มีจำกัดอัตราการลอง การจำกัดตัวนับความล้มเหลวหยุดบรูทฟอร์ซได้ในทางปฏิบัติ
ในทางปฏิบัติอย่าพึ่งการล็อกถาวรอย่างเดียว
จำกัดการลองต่อบัญชีจำเป็น แต่หากผู้โจมตีรู้รหัสผู้ใช้ของคนอื่น สามารถจงใจล้มเหลว 10 ครั้งเพื่อล็อกผู้ใช้ที่ถูกต้องออก ในทางปฏิบัติจึงรวมสิ่งต่อไปนี้
| การควบคุม | บทบาท |
|---|---|
| ตัวนับความล้มเหลวต่อบัญชี | หยุดบรูทฟอร์ซต่อบัญชีเดียว |
| เวลารอแบบขั้นบันได | ทนความผิดพลาดของผู้ใช้ที่ถูกต้องขณะชะลอการโจมตี |
| ควบคุมตาม IP ต้นทาง อุปกรณ์ ASN ฯลฯ | กดการโจมตีที่ลองน้อยครั้งกับหลายบัญชี |
| การตัดสินตามความเสี่ยง | จำกัดแรงขึ้นสำหรับภูมิภาค อุปกรณ์ หรือความเร็วที่ผิดปกติ |
| แจ้งผู้ใช้ | ให้ผู้ใช้รู้การโจมตีหรือความผิดของตน |
| ขั้นตอนกู้คืนที่ปลอดภัย | ไม่ให้ช่องปลดล็อกกลายเป็นเส้นทางโจมตีเอง |
ยิ่งกว่านั้น เมื่อส่งรหัสใหม่ ตัวนับความล้มเหลวต้องไม่ถูกรีเซ็ตเป็นศูนย์ มิฉะนั้นผู้โจมตีเติมโควตาการลองได้ทุกครั้งที่เรียก API ส่งซ้ำ NIST SP 800-63B ปัจจุบันก็กำหนดไม่ให้รีเซ็ตตัวนับความล้มเหลวแม้สร้างความลับยืนยันตัวตนใหม่4
ทำให้รหัสยืนยันตัวตนใช้ครั้งเดียว
ข้อเน้นเวลาหมดอายุ แต่ในทางปฏิบัติยังต้องการสิ่งต่อไปนี้
- ยกเลิกรหัสที่สำเร็จทันที
- ปฏิเสธการใช้รหัสเดิมซ้ำ
- อย่าทิ้งรหัสเองไว้ในล็อก
- ทำให้การตอบไม่ให้ใช้ผลสำเร็จหรือล้มเหลวของการตรวจรหัสเพื่อเดาว่ามีผู้ใช้หรือไม่
- ใส่จำกัดการลองบน API ส่งรหัสด้วย
ตราบที่ใช้ความลับสั้น ความมั่นคงปลอดภัยปล่อยให้การสุ่มสร้างอย่างเดียวไม่ได้
flowchart TB
accTitle: มาตรการรหัสยืนยันตัวตน
accDescr: นอกจากจำนวนหลักและอายุ ให้ปกป้องด้วยจำกัดการลอง ปฏิเสธการใช้ซ้ำ การแจ้งเตือน และอื่น ๆ
A[รหัสยืนยันตัวตน] --> B[เพิ่มจำนวนหลัก]
A --> C[ย่นอายุ]
A --> D[จำกัดอัตราการลอง]
A --> E[ยกเลิกหลังสำเร็จ]
A --> F[อย่ารีเซ็ตตัวนับความล้มเหลวตอนส่งซ้ำ]
A --> G[อย่าทิ้งรหัสในล็อก]
A --> H[ควบคุมตามแหล่งที่มา]
ภาพ 11: มาตรการรหัสยืนยันตัวตน รวมจำนวนหลักและอายุกับการควบคุมการลองและวิธีปฏิบัติ
10. แยกสี่ส่วนของข้อ 2 ในหน้าเดียว
จุดในข้อ 2 ที่ปนกันง่าย จัดตามค่าที่ผู้โจมตีควบคุม
| การโจมตี | ค่าที่ผู้โจมตีเปลี่ยน | สิ่งที่ไม่ควรเชื่อ | การแก้ที่ต้นเหตุ |
|---|---|---|---|
| ดัดแปลง JWT | alg ในส่วนหัว JWT, รหัสผู้ใช้ในเพย์โหลด |
อัลกอริทึมตรวจที่โทเค็นประกาศเอง | ตรึงอัลกอริทึมที่อนุญาตฝั่งเซิร์ฟเวอร์ |
| อ่านข้อมูลผู้ใช้อื่น | mid ของคำขอ |
รหัสเป้าหมายที่ไคลเอนต์ระบุ | เทียบกับ subject ของ JWT หรือกำหนดรหัสเป้าหมายจาก JWT |
| เลื่อนเป็นผู้ใช้ที่ชำระเงิน | status นอกสเปก |
คุณสมบัติที่ผูกอัตโนมัติทั้งหมด | ทำให้คุณสมบัติที่อัปเดตได้เป็นรายการอนุญาต |
| เจาะรหัส 4 หลัก | ผู้สมัครของ otp |
การลองยืนยันตัวตนไม่จำกัด | ใส่จำกัดอัตราการลอง ดีเลย์ และการตัดสินความเสี่ยง |
สำคัญคืออย่ารวมทั้งหมดเป็น “ตรวจอินพุต”
algคือนโยบายเข้ารหัสmidคือการอนุญาตระดับออบเจกต์statusคือการอนุญาตระดับคุณสมบัติotpคือความทนต่อการเดาออนไลน์
แม้ในคำขอ HTTP เดียวกัน เหตุผลที่แต่ละอย่างต้องปกป้องต่างกัน
11. ข้อ 3(1) — ยืนยันการรันโค้ดจากระยะไกลโดยไม่ก่อความเสียหาย
หลังบริการเปิด ช่องโหว่ร้ายแรง V ถูกเปิดเผยในไลบรารี H ซึ่งเป็นไลบรารีโอเพนซอร์สที่ใช้กันกว้าง ลำดับเหตุการณ์ของข้อมีดังนี้
- ผู้โจมตีใส่สตริงที่มี JNDI Lookup ในส่วนหัว HTTP แล้วส่ง
- เซิร์ฟเวอร์เป้าหมายล็อกค่านั้น
- ไลบรารีที่มีช่องโหว่ประเมิน JNDI Lookup แล้วสอบถามเซิร์ฟเวอร์ LDAP ของผู้โจมตี
- การตอบ LDAP คืน URL ของเซิร์ฟเวอร์ HTTP ของผู้โจมตี
- เซิร์ฟเวอร์เป้าหมายดึงไฟล์คลาสแล้วรันคำสั่ง
เมื่อปิดชื่อผลิตภัณฑ์ นี่อ่านเป็นการโจมตีแบบ Log4Shell (CVE-2021-44228) คำอธิบายของ Apache เองก็บรรยายช่องโหว่ว่า หากผู้โจมตีควบคุมข้อความล็อกหรือพารามิเตอร์ จะรันโค้ดใดก็ได้ที่โหลดจากเซิร์ฟเวอร์ LDAP9
flowchart LR
accTitle: การไหลยืนยันช่องโหว่แบบ Log4Shell
accDescr: ใช้คอลแบ็กที่ไร้อันตรายเพื่อยืนยันว่าโซ่จาก JNDI ถึงการรันโค้ดจากระยะไกลผ่านจริง
A[ผู้โจมตี] -->|ฉีดเพย์โหลด jndi/ldap<br/>ใน x-api-version| B[เซิร์ฟเวอร์ที่มีช่องโหว่]
B --> C[ประมวลผลล็อก]
C -->|JNDI Lookup| D[เซิร์ฟเวอร์ LDAP ที่มุ่งร้าย]
D -->|ตอบ URL HTTP| E[เซิร์ฟเวอร์ HTTP ที่มุ่งร้าย<br/>index.html]
E -->|บันทึก GET| F[เซิร์ฟเวอร์ทดสอบ<br/>ล็อกการเข้าถึง]
F -->|ยืนยันว่าถึง| G[ยืนยันช่องโหว่]
ภาพ 12: การไหลยืนยันช่องโหว่แบบ Log4Shell การถึงถูกยืนยันด้วยการบันทึกการเข้าถึง HTTP ไม่ใช่การออกคำสั่งทำลาย
โค้ดตรวจทำให้เกิดเฉพาะการเข้าถึง HTTP ที่ไร้อันตราย
บริษัท G รันโค้ดตรวจที่ไม่มีผลกระทบต่อระบบ เพื่อยืนยันว่าช่องโหว่ V ถูกใช้จากภายนอกได้หรือไม่ คำสั่งเดียวที่โค้ดตรวจออกคือดึง index.html ของเซิร์ฟเวอร์ทดสอบ
ข้อ 3(1) ถามว่าต้องอิมพลีเมนต์อะไรบนเซิร์ฟเวอร์ทดสอบเพื่อยืนยันว่าคำสั่งถูกรัน
เฉลยคือ:
กลไกที่บันทึกและให้ยืนยันการเข้าถึง index.html ของเซิร์ฟเวอร์ทดสอบ
หากล็อกการเข้าถึงของเว็บเซิร์ฟเวอร์บันทึก GET จากเซิร์ฟเวอร์เป้าหมาย นั่นยืนยันอย่างน้อยว่าโซ่ต่อไปนี้ผ่าน
คำขอ HTTP ภายนอก
-> ประมวลผลล็อก
-> JNDI Lookup
-> การตอบ LDAP
-> ดึงคลาส
-> รันคำสั่งตรวจ
-> การเข้าถึง HTTP ไปยังเซิร์ฟเวอร์ทดสอบ
ทำไม “แสดงข้อความบนจอ” จึงไม่พอ
เป้าหมายของการโจมตีคือเซิร์ฟเวอร์ ไม่มีการรับประกันว่าอะไรจะเปลี่ยนบนจอบราวเซอร์ของผู้ใช้ และแม้ช่องโหว่มีอยู่ การสื่อสารขาออกกลางทางอาจถูกไฟร์วอลล์บล็อก
การบันทึกการเข้าถึงฝั่งเซิร์ฟเวอร์ทดสอบให้หลักฐานที่สังเกตได้ว่าเซิร์ฟเวอร์เป้าหมายไปถึงโลกภายนอกจริง
เมื่อทำการตรวจแบบนี้ในทางปฏิบัติ ให้ยึดสิ่งต่อไปนี้เสมอ
- ขออนุมัติชัดเจนจากเจ้าของระบบเป้าหมาย
- ใช้วิธีตรวจที่ไม่มีผลกระทบต่อโปรดักชัน หรือเล็กจนยอมรับได้
- อย่าใช้คำสั่งทำลาย เช่น เขียน ลบ หรือเปลี่ยนการตั้งค่า
- จัดการโดเมนและเซิร์ฟเวอร์ตรวจเอง
- บันทึกเวลาตรวจ แหล่งที่มา เป้าหมาย และคอลแบ็กที่คาด
- รื้อเซิร์ฟเวอร์ LDAP หรือ HTTP ชั่วคราวและข้อมูลรับรองหลังตรวจ
“ยืนยันว่ารันโค้ดใดก็ได้เป็นไปได้” กับ “รันโค้ดอันตรายใดก็ได้” ไม่ใช่เรื่องเดียวกัน ให้ผลข้างเคียงเหลือน้อยที่สุดที่บรรลุเป้าหมาย
12. ข้อ 3(2)(3) — WAF ตรวจส่วนหัว HTTP
WAF ของบริการ N ให้เลือก GET, POST, PUT, ANY, Header, COOKIE หรือ Multipart เป็นเป้าหมายตรวจ
โค้ดโจมตีอยู่ในค่าของส่วนหัว HTTP ชื่อ x-api-version ดังนั้นช่อง e และ f ในตาราง 6 ทั้งคู่คือ Header
แมปตำแหน่งที่ข้อความให้ตรงไปยังเป้าหมายตรวจของ WAF
นี่ไม่ใช่ความรู้ทั่วไปเท่าการอ่านการไหลของข้อมูลในข้อ
ตำแหน่งที่วางสตริงโจมตี:
ส่วนหัว x-api-version
|
v
เป้าหมายตรวจของ WAF:
Header
ไม่ใช่พารามิเตอร์ GET หรือเนื้อหา POST แทนที่จะดูรายการความสามารถของ WAF แล้วเลือก ANY เพราะ “ดูเหมือนการโจมตี” ให้ตอบด้วยตำแหน่งที่ข้อบอกว่าผู้โจมตีใส่ค่า
รับมือการสลับตัวพิมพ์
ข้อเสนอแรกเชิงแนวคิดคือกฎต่อไปนี้
Header \Wjndi\W บล็อก
Header \Wldap\W บล็อก
แต่การสลับตัวพิมพ์อย่าง jNdI หลบแพตเทิร์นที่จับเฉพาะตัวพิมพ์เล็ก
เฉลยข้อ 3(3) คืออย่างใดอย่างหนึ่งต่อไปนี้
\W[jJ][nN][dD][iI]\W
\W(j|J)(n|N)(d|D)(i|I)\W
ในเล่มข้อสอบ แบ็กสแลชอาจดูเหมือนเครื่องหมายเยนในรูปอักษรโลเคลญี่ปุ่น แต่ในฐานะนิพจน์ปรกติคือ \W \W จับอักขระใดก็ได้ที่ไม่ใช่ตัวอักษรตัวเลขและขีดล่าง ในวากยสัมพันธ์ JNDI Lookup อักขระที่ไม่ใช่คำอย่าง ${ และ : ปรากฏทันทีก่อนและหลัง jndi และแพตเทิร์นถูกเขียนให้จับสิ่งนั้นด้วย
แนวคิดเดียวกันทำให้ฝั่ง ldap ไม่สนตัวพิมพ์ได้เช่นกัน
\W[lL][dD][aA][pP]\W
อย่าถือว่ารีเจ็กซ์นี้เป็น “มาตรการ Log4Shell ที่สมบูรณ์”
ข้อสอบถามนิพจน์ปรกติที่รับมือเทคนิคหลบที่แสดงในข้อ การโจมตีจริงอาจมีการแยกสตริง Lookup ทางเลือก การเข้ารหัส โปรโตคอลอื่น และรูปแบบอื่นที่ลายเซ็นอย่างเดียวครอบคลุมยาก
ดังนั้นตำแหน่งในทางปฏิบัติมีดังนี้
- บล็อกชั่วคราวแพตเทิร์นโจมตีที่รู้จักปัจจุบันด้วย WAF
- สอบสวนว่าไลบรารีที่ได้รับผลกระทบมีจริงหรือไม่
- จำกัด LDAP, RMI และ HTTP ขาออกที่ไม่จำเป็น
- อัปเดตเป็นเวอร์ชันที่มีแพตช์
- หลังอัปเดต ยังตรวจล็อกและสอบสวนว่ามีการบุกรุกหรือไม่
WAF เป็นชั้นที่ซื้อเวลาจนกว่าเวอร์ชันที่มีแพตช์จะพร้อม
flowchart TB
accTitle: ตำแหน่งของ WAF
accDescr: WAF เป็นชั้นบรรเทาชั่วคราว การแก้ที่ต้นเหตุคืออัปเดตไลบรารีเป็นเวอร์ชันที่มีแพตช์
A[เปิดเผยช่องโหว่ร้ายแรง] --> B[ยืนยันผลกระทบ]
B --> C[บรรเทาชั่วคราว]
C -->|กฎ WAF<br/>ตรวจจับ/บล็อก| D[หยุดแพตเทิร์นโจมตีชั่วคราว]
C -->|จำกัดทราฟฟิกขาออก| E[ปิดเส้นทางใช้อำนาจผิด]
D --> F[อัปเดตเป็นไลบรารีที่มีแพตช์]
E --> F
F --> G[ทบทวนภายหลังและป้องกัน]
ภาพ 13: ตำแหน่งของ WAF WAF ซื้อเวลาจนกว่าแพตช์จะมา การแก้ที่ต้นเหตุคือการอัปเดต
13. ข้อ 3(4) — ทำไมเริ่มด้วย “ตรวจจับ”
สำหรับกฎ WAF ที่อัปเดตแล้ว Z ผู้เชี่ยวชาญความมั่นคงปลอดภัยที่ขึ้นทะเบียน แนะนำให้ตั้งโหมดเป็น “ตรวจจับ” ไม่ใช่ “บล็อก” เป็นเวลาหนึ่งหลังขึ้นโปรดักชัน
ข้อถามภายใน 25 ตัวอักษรต่อช่อง ถึงข้อดีของการใช้โหมดตรวจจับ และสิ่งที่ควรทำเพื่อลดความเสียหาย
เฉลยคือ:
| รายการ | สาระของคำตอบ |
|---|---|
| ข้อดี | ป้องกันการบล็อกจากผลบวกลวงได้ |
| สิ่งที่ควรทำ | ตรวจว่าเป็นการโจมตีหรือไม่ทุกครั้งที่ได้รับแจ้งเตือน |
โหมดตรวจจับไม่ใช่โหมด “ไม่ทำอะไร”
ในโหมดตรวจจับ ทราฟฟิกที่ตรงกฎยังถูกปล่อยผ่าน แต่ถูกบันทึกล็อกและยกแจ้งเตือน แม้สตริง jndi หรือ ldap บังเอิญปรากฏในคำขอ API ที่ถูกต้อง ธุรกิจก็ไม่ถูกหยุดทันที
แลกกับนั้น ฝั่งปฏิบัติการต้องทำสิ่งต่อไปนี้
ได้รับแจ้งเตือน
|
v
ตรวจคำขอที่เกี่ยวข้อง
|
+-- ทราฟฟิกที่ถูกต้อง -> บีบกฎ พิจารณาข้อยกเว้น
|
+-- การโจมตี -> แยกเป้าหมาย รักษาล็อก สอบสวนผลกระทบ ย้ายไปบล็อก
หากไม่มีใครดูแจ้งเตือน โหมดตรวจจับไม่มีผลป้องกันเลย การตรวจจับทำงานคู่กับกระบวนการปฏิบัติการที่สังเกตและตัดสิน
เส้นทางจากตรวจจับสู่บล็อก
ขั้นตอนการเปิดใช้ทั่วไปมีดังนี้
- รันโหมดตรวจจับกับทราฟฟิกจริง
- จำแนกการชนเป็นผลบวกลวงหรือผลบวกจริง
- ปรับส่วนหัวเป้าหมาย เส้นทาง API ขอบคำ ฯลฯ
- ยืนยันว่าผลกระทบต่อทราฟฟิกที่ถูกต้องยอมรับได้
- สลับเป็นโหมดบล็อก
- เฝ้าจำนวนบล็อกและผลกระทบทางธุรกิจ
อย่างไรก็ดี นี่คือหลักยามปกติ เมื่อช่องโหว่ร้ายแรง กำลังถูกใช้จริง และไม่มีทางเลือกอื่น ความเสียหายจากการบุกรุกอาจถูกตัดสินว่าหนักกว่าความเสียหายจากผลบวกลวง และเลือกบล็อกตั้งแต่ต้นแทน ในสถานการณ์ของข้อสอบ เลือกโหมดตรวจจับก่อนเพื่อยืนยันว่าบริการเดินต่อได้เหมือนเดิม
flowchart LR
accTitle: จากโหมดตรวจจับ WAF สู่โหมดบล็อก
accDescr: สังเกตแจ้งเตือนในโหมดตรวจจับ ปรับผลบวกลวงออก แล้วย้ายไปโหมดบล็อก
A[โหมดตรวจจับ] -->|ทราฟฟิกจริง| B[เกิดแจ้งเตือน]
B --> C{โจมตีหรือ<br/>ผลบวกลวง}
C -->|ผลบวกลวง| D[ปรับกฎ]
D --> A
C -->|โจมตี| E[สลับเป็นโหมดบล็อก]
E --> F[เฝ้าจำนวนบล็อกและผลกระทบทางธุรกิจ]
ภาพ 14: จากตรวจจับสู่บล็อก สังเกตและปรับก่อน ยืนยันว่าผลกระทบยอมรับได้ แล้วค่อยบล็อก
14. WAF เป็นมาตรการชั่วคราว การอัปเดตคือการแก้ที่ต้นเหตุ
ในข้อ เว็บไซต์ทางการของไลบรารี H ยังไม่มีทั้งแพตช์และทางแก้ชั่วคราว และแม้กฎ WAF ครอบคลุมของผู้ให้บริการคลาวด์ก็ใช้เวลาถึง 72 ชั่วโมง ดังนั้นบริษัท G ยืนยันผลกระทบเองและบล็อกชั่วคราวอย่างน้อยแพตเทิร์นที่ระบุแล้ว
ลำดับนี้คือรูปพื้นฐานของการตอบสนองเหตุการณ์
| ขั้น | จุดประสงค์ | การตอบในข้อนี้ |
|---|---|---|
| ยืนยันผลกระทบ | ตัดสินว่าองค์กรตนเสี่ยงจริงหรือไม่ | ยืนยันการใช้จากภายนอกด้วยคอลแบ็กที่ไร้อันตราย |
| บรรเทาชั่วคราว | ซื้อเวลาจนกว่าแพตช์จะมา | กฎ WAF ตรวจจับ/บล็อก จำกัดทราฟฟิกขาออก |
| แก้ที่ต้นเหตุ | เอาสาเหตุที่มีช่องโหว่ออก | อัปเดตเป็นไลบรารีที่มีแพตช์ |
| ทบทวนภายหลัง | ตรวจว่าถูกใช้ไปแล้วหรือไม่ | สอบสวนล็อก WAF แอป DNS พร็อกซี และอื่น ๆ |
| ป้องกันซ้ำ | ทำให้การตัดสินครั้งหน้าเร็วขึ้น | สำรวจการพึ่งพา SBOM ขั้นตอนอัปเดต เส้นทางติดต่อ |
“ไม่รู้ว่าเราใช้หรือไม่” คือแหล่งหน่วงที่ใหญ่ที่สุด
ในข้อ แม้บริษัท G ถามบริษัท F ว่าใช้ไลบรารี H หรือไม่ คำตอบก็ใช้เวลาเพราะต้องวิเคราะห์การตั้งค่าละเอียด
ในทางปฏิบัติ หากเริ่มค้นไฟล์ JAR หลังช่องโหว่ร้ายแรงถูกเปิดเผย การตอบจะช้า ควรมีอย่างน้อยสิ่งต่อไปนี้ยามสงบ
- สำรวจการพึ่งพาตรงและการพึ่งพาทางอ้อม
- ส่วนประกอบและเวอร์ชันที่รวมจริงในอาร์ติแฟกต์
- บริการ คอนเทนเนอร์ และอุปกรณ์ที่ถูกนำไปใช้
- ขั้นตอนอัปเดตไลบรารีที่พึ่งพาแล้วสร้างใหม่/แจกจ่ายใหม่
- เส้นทางติดต่อสำหรับอนุมัติการเปลี่ยนฉุกเฉิน
- จุดหมายที่อนุญาตของทราฟฟิกขาออก และผลกระทบของการบล็อก
- ที่เก็บล็อกและวิธีค้น
SBOM ไม่ใช่เป้าหมายในตัวเอง แต่เป็นดัชนีสำหรับตอบในเวลาสั้นว่า “ช่องโหว่นี้กระทบระบบที่รันอยู่ระบบใด”
อย่าหยุดสอบสวนเมื่ออัปเดตแล้ว
คุณอาจถูกโจมตีไปแล้วราวเวลาที่ช่องโหว่ถูกเปิดเผย การอัปเดตเป็นเวอร์ชันที่มีแพตช์หยุดการใช้ในอนาคต แต่ไม่ลบข้อมูลรับรองที่รั่วไปแล้วหรือแบ็กดอร์ที่ปลูกไว้แล้ว
สำหรับช่องโหว่แบบ Log4Shell ให้สอบสวนอย่างน้อยมุมต่อไปนี้
- คำขอ HTTP ที่มีสตริงน่าสงสัยที่ชี้ JNDI หรือ LDAP
- การสื่อสารจากเซิร์ฟเวอร์แอปไปยัง LDAP, RMI หรือ HTTP ภายนอก
- การเปิดโปรเซสลูกที่ผิดปกติ
- การสร้าง JAR คลาส สคริปต์ หรือไฟล์ปฏิบัติการที่น่าสงสัย
- การเข้าถึงข้อมูลรับรองคลาวด์หรือตัวแปรสภาพแวดล้อม
- การยืนยันตัวตน การเปลี่ยนสิทธิ์ และการส่งออกนอก ราวเวลาอัปเดต
สำคัญคืออย่าสรุปว่า “เราไม่ถูกโจมตี” จากล็อก WAF อย่างเดียว มีเส้นทางภายในที่ไม่ผ่าน WAF และล็อกที่ไม่ได้ถูกเก็บในอดีต
15. วิธีอ่านที่เก็บคะแนนได้ง่ายขึ้นในข้อสอบ
ข้อนี้ไม่ใช่แบบทดสอบความรู้เท่าการฝึกอ่านช่องว่างระหว่างสเปกกับการอิมพลีเมนต์
15.1 แยก “สเปก” กับ “การอิมพลีเมนต์” ในตาราง
ในปัญหา status ค่าที่ไม่มีในสเปก API ผ่านในการอิมพลีเมนต์
สเปก:
mid / name / age
การอิมพลีเมนต์:
ส่งพารามิเตอร์ที่รับทั้งหมดไปยัง P
เมื่อเห็นช่องว่างนี้ จะชัดว่าช่อง c คือโมดูลร่วม P
15.2 ขีดเส้นใต้ค่าที่ผู้โจมตีเปลี่ยน
ค่าที่เปลี่ยนในแต่ละการโจมตีมีดังนี้
algในส่วนหัว JWT- รหัสผู้ใช้ในเพย์โหลด JWT
- พารามิเตอร์ API
mid statusนอกสเปกotpของ API ยืนยันตัวตน- ส่วนหัว HTTP
x-api-version
เกือบทุกข้อถามว่า “ค่าควรถูกตรวจที่ไหน”
15.3 ดึงคำตอบกลับสู่คำของข้อเอง
ในทางปฏิบัติเรียกสิ่งเหล่านี้ได้ว่า “BOLA” “Mass Assignment” และ “rate limiting” แต่สิ่งที่ข้อต้องการคือกระบวนการรูปธรรมที่จับคู่กับโครงของข้อ
ตัวอย่างไม่ดี:
ทำการอนุญาตอย่างเหมาะสม
ตัวอย่างดี:
ตรวจว่ารหัสผู้ใช้ใน JWT ตรงกับค่าของ mid หรือไม่
ตัวอย่างไม่ดี:
ใช้มาตรการบรูทฟอร์ซ
ตัวอย่างดี:
ล็อกบัญชีเมื่อจำนวนล้มเหลวติดต่อกันเกินเกณฑ์
รู้ชื่อนามธรรมอย่างเดียวไม่ให้คำตอบที่ให้คะแนนได้ภายในจำกัดตัวอักษร
15.4 สำหรับ WAF ให้ไล่ “ถูกใส่ที่ไหน”
เป้าหมายตรวจของ WAF ไม่ได้ตัดสินจากการเดาชนิดการโจมตี แต่จากตำแหน่งที่วางสตริงโจมตี
ใส่ในส่วนหัว x-api-version
↓
เป้าหมายตรวจคือ Header
บันทึกในคำอธิบายการให้คะแนนว่าอัตราถูกของข้อ 3(1) ค่อนข้างต่ำ ก็เพราะคำตอบจำนวนมากไม่ตรงกับการไหลของการโจมตีในรูป 6 การวาดลำดับการโจมตีด้วยลูกศรใหม่ก็เผยแล้วว่าควรสังเกตอะไร
16. รายการตรวจสำหรับรีวิว API ในโลกจริง
รายการตรวจสำหรับนำข้อนี้กลับไปสู่การออกแบบและรีวิวโค้ดจริง
การตรวจ JWT
- อัลกอริทึมลายเซ็นที่อนุญาตถูกตรึงในการตั้งค่าเซิร์ฟเวอร์
noneและอัลกอริทึมที่ไม่คาดถูกปฏิเสธ- ลายเซ็น,
iss,aud,expและnbfถูกตรวจตามกรณีใช้งาน - ID โทเค็น โทเค็นเข้าถึง และโทเค็นรีเฟรชไม่ถูกสับสนกัน
- มีขั้นตอนหมุนคีย์และเพิกถอน
- ข้อมูลที่ต้องเป็นความลับไม่ถูกใส่ในเพย์โหลด JWT
การอนุญาตระดับออบเจกต์
- การเปลี่ยนรหัสในคำขอไปไม่ถึงข้อมูลของผู้ใช้อื่น
- การอนุญาตถูกบังคับทั้งรายการ รายละเอียด อัปเดต ลบ และดาวน์โหลด
- การอนุญาตถูกบังคับในชั้นร่วมที่ถึงข้อมูล ไม่ใช่ที่หน้าจอ
- สำหรับ API ที่เป็นของตนอย่างเดียว ได้พิจารณาดึงรหัสเป้าหมายจากโทเค็นแทน
- การดำเนินการของผู้ดูแลระบบใช้นโยบายแยกจาก API ผู้ใช้ทั่วไป
การอนุญาตระดับคุณสมบัติ
- ชนิดอินพุตภายนอกกับเอนทิตีฐานข้อมูลถูกแยก
- ฟิลด์ที่อัปเดตได้ถูกแจงเป็นรายการอนุญาต
- คุณสมตินอกสเปกถูกปฏิเสธหรือตรวจสอบ
- สถานะอย่างสิทธิ์ การเรียกเก็บเงิน การอนุมัติ และความเป็นเจ้าของเปลี่ยนจากอินพุตผู้ใช้ไม่ได้
- การตอบก็ตัดคุณสมบัติความลับที่ไม่จำเป็นออก
การลองยืนยันตัวตน
- มีจำกัดตัวนับความล้มเหลวต่อบัญชี
- มีดีเลย์แบบขั้นบันไดและการควบคุมตามแหล่งที่มา
- ตัวนับความล้มเหลวไม่ถูกรีเซ็ตเมื่อออกรหัสใหม่
- รหัสยืนยันตัวตนใช้ได้ครั้งเดียว
- รหัสยืนยันตัวตนและรหัสผ่านไม่ถูกทิ้งในล็อก
- ขั้นตอนปลดล็อก/กู้คืนไม่ใช่เส้นทางยืนยันตัวตนที่อ่อนกว่าเอง
ช่องโหว่ร้ายแรงในไลบรารีที่พึ่งพา
- บริการที่รันสามารถจับคู่กับเวอร์ชันการพึ่งพาได้
- มีขั้นตอนยืนยันผลกระทบด้วยวิธีที่ไร้อันตราย
- มาตรการชั่วคราวอย่างกฎ WAF และจำกัดขาออกใช้ได้
- มีกระบวนการปฏิบัติการให้ผู้รับผิดชอบทบทวนแจ้งเตือนการตรวจจับ
- มีเส้นทางปล่อยฉุกเฉินสำหรับอัปเดตเป็นเวอร์ชันที่มีแพตช์
- ล็อกถูกสอบสวนถึงการใช้ที่เป็นไปได้ก่อนอัปเดต
17. สองหน้าของชิ้นส่วนร่วม ที่เห็นผ่านข้อนี้
ข้อนี้มีชิ้นส่วนร่วมสองชิ้น คือไลบรารีจัดการ JWT Q และโมดูลร่วม P
ชิ้นส่วนร่วมมีข้อดีใหญ่
- แก้ที่เดียวแล้วการแก้กระจายไปทุก API ที่ใช้
- ตรรกะการอนุญาตและการตรวจไม่ต้องทำซ้ำในแต่ละฟีเจอร์
- เป้าหมายทดสอบรวมได้
- รูปแบบล็อกและตรวจสอบรวมได้
ในทางกลับกัน ความผิดก็กระจายทั่วระบบ
- หากไลบรารี Q รับ
alg=noneทุก API ที่ใช้ JWT จะมีช่องโหว่ - หากโมดูลร่วม P รับ
midหรือstatusใดก็ได้ ทั้ง GET และ PUT จะมีช่องโหว่ - หากไลบรารี H ที่มีช่องโหว่ถูกใช้ในฐาน ทุกเส้นทางที่ล็อกส่วนหัว HTTP จะเป็นส่วนของพื้นผิวโจมตี
ดังนั้นสิ่งที่ควรร่วมกันไม่ใช่แค่การเข้าถึงข้อมูล จำเป็นต้องร่วมเงื่อนไขคงที่ด้านความมั่นคงปลอดภัยเอง และตรวจชิ้นส่วนร่วมนั้นอย่างเข้มงวดแยกต่างหาก
ตัวอย่างเช่น ทำให้สัญญาของ P เป็นดังนี้
P.getOwnUser(authenticatedSubject)
P.updateOwnProfile(authenticatedSubject, ProfileUpdate{name, age})
ปลอดภัยกว่าที่จะไม่เปิด API ระดับต่ำแบบต่อไปนี้ให้ผู้เรียกทั่วไปโดยตรง
P.getUser(arbitraryMid)
P.updateUser(arbitraryMap)
อย่างหลังจำเป็นเฉพาะเส้นทางจำกัด เช่น การประมวลผลของผู้ดูแลระบบ การมอบเสรีภาพระดับต่ำนั้นให้ทุก API ทำให้ได้การออกแบบที่พึ่งว่าผู้เรียกทุกคนใช้ถูกต้องทุกครั้ง
18. สรุป
ข้อ 1 ภาคบ่ายฤดูใบไม้ผลิ 2024 (เรวะ 6) เป็นข้อที่อ่านแยกหัวข้อความมั่นคงปลอดภัยของ API ทีละข้อ
การใช้ JWT ไม่ได้แปลว่าการยืนยันตัวตนปลอดภัย การให้ผู้โจมตีเลือกอัลกอริทึมลายเซ็นทำให้เขียนทับรหัสผู้ใช้ได้
ลายเซ็น JWT ที่ถูกต้องไม่ได้แปลว่าการอนุญาตถูกต้อง การเชื่อ mid ของคำขอทำให้ผู้ใช้ที่ถูกต้องเข้าถึงข้อมูลของคนอื่นได้
การอัปเดตออบเจกต์ของตนได้ไม่ได้แปลว่าทุกคุณสมบัติเปลี่ยนได้ การผูกอัตโนมัติสถานะภายในอย่าง status ทำให้เขียนทับสิทธิ์หรือสถานะการเรียกเก็บเงินได้
การมีอายุบนรหัสยืนยันตัวตนไม่ได้แปลว่าทนบรูทฟอร์ซ ต้องคำนวณปริภูมิผู้สมัครและความเร็วการลอง และจำกัดจำนวนความล้มเหลว
การใส่กฎใน WAF ไม่ได้แปลว่าช่องโหว่ถูกแก้ การตรวจจับและบล็อกซื้อเวลาเท่านั้น คุณยืนยันผลกระทบแล้วท้ายที่สุดอัปเดตไลบรารี
flowchart TB
accTitle: การจับคู่ช่องโหว่กับมาตรการ
accDescr: จับคู่แต่ละช่องโหว่กับขอบเขตความเชื่อถือและมาตรการของมัน
A[ดัดแปลง JWT] -->|การตรวจโทเค็น| B[ตรึงอัลกอริทึมที่อนุญาต]
C[สลับ mid] -->|การอนุญาตระดับออบเจกต์| D[เทียบกับ JWT subject/ไม่ต้องมี mid]
E[status=paid] -->|การอนุญาตระดับคุณสมบัติ| F[ทำให้ DTO อัปเดตเป็นรายการอนุญาต]
G[บรูทฟอร์ซรหัส 4 หลัก] -->|ควบคุมการลองยืนยันตัวตน| H[จำกัดความล้มเหลว/ดีเลย์]
I[ช่องโหว่แบบ Log4Shell] -->|จากอินพุตสู่การรัน| J[อัปเดตไลบรารี/WAF]
ภาพ 15: การจับคู่ช่องโหว่กับมาตรการ แยกการแก้ตามขอบเขตที่ถูกทำลาย
หลักการหนึ่งไหลผ่านข้อนี้ทั้งหมด
อย่าให้ความสำเร็จของการตรวจก่อนหน้าเป็นเหตุให้ข้ามขอบเขตความเชื่อถือถัดไป
บทความก่อนหน้าในซีรีส์นี้ครอบคลุม ช่องโหว่ XSS แบบเก็บในข้อ 1 ภาคบ่ายฤดูใบไม้ร่วง 2023 (เรวะ 5) และ การนำข้อมูลออกผ่าน Wi-Fi สำหรับแขกในข้อ 2 ภาคบ่ายฤดูใบไม้ร่วง 2023 (เรวะ 5) สำหรับมุมมองสิ่งที่ควรตรวจทั่วทั้งเว็บไซต์ ดูเพิ่มที่ ใช้ “วิธีทำเว็บไซต์ให้ปลอดภัย” ของ IPA เป็นรายการตรวจ
flowchart TB
accTitle: สรุปสุดท้าย
accDescr: แสดงว่าความสำเร็จของการตรวจก่อนหน้าไม่มีวันเป็นเหตุให้ข้ามขอบเขตความเชื่อถือถัดไป
A[ยืนยันตัวตนสำเร็จ] --> B[ตรวจลายเซ็น JWT]
B --> C[การอนุญาตระดับออบเจกต์]
C --> D[การอนุญาตระดับคุณสมบัติ]
D --> E[จำกัดอัตราการลอง]
E --> F[ขอบเขตจากอินพุตสู่การรัน]
F --> G[WAF/อัปเดตไลบรารี]
ภาพ 16: สรุปสุดท้าย ขอบเขตความเชื่อถือถูกตรวจเป็นขั้น และห้ามข้ามขั้นใด
ลิงก์อ้างอิง
-
IPA, Spring 2024 (Reiwa 6) Registered Information Security Specialist Examination, PM Question Booklet. ข้อความข้อที่บทความนี้อิง ↩
-
IPA, Spring 2024 (Reiwa 6) Registered Information Security Specialist Examination, PM Model Answers. เฉลยทางการของแต่ละข้อ ↩
-
IPA, Spring 2024 (Reiwa 6) Registered Information Security Specialist Examination, PM Grading Commentary. คำอธิบายอัตราถูกและความผิดพลาดที่พบบ่อย ↩
-
NIST, SP 800-63B: Authentication and Authenticator Management. กำหนดจำนวนหลักของความลับระยะสั้น จำกัดอัตราการลอง ตัวนับความล้มเหลวตอนออกใหม่ และการไม่ใช้อีเมลสำหรับการยืนยันตัวตนนอกแถบ รวมถึงข้อกำหนดอื่น ↩ ↩2
-
RFC Editor, RFC 7519: JSON Web Token (JWT). สเปกของ JWT รวม Unsecured JWT และ
alg=none↩ -
RFC Editor, RFC 8725: JSON Web Token Best Current Practices. BCP ที่กำหนดการตรึงชุดอัลกอริทึมที่อนุญาต และการตรวจผู้ออก ตัวตน และ audience รวมถึงแนวปฏิบัติอื่น ↩
-
OWASP, API1:2023 Broken Object Level Authorization. อธิบายความจำเป็นต้องตรวจการอนุญาตสำหรับทุกรหัสออบเจกต์ที่ผู้ใช้ระบุ ↩
-
OWASP, API3:2023 Broken Object Property Level Authorization. อธิบายข้อบกพร่องการอนุญาตระดับคุณสมบัติ รวม Mass Assignment และมาตรการ ↩
-
Apache Logging Services, Security. อธิบายผลกระทบของ CVE-2021-44228 การรันโค้ดผ่าน JNDI และ LDAP และเวอร์ชันที่แก้แล้ว ↩
บทความที่เกี่ยวข้อง
บทความล่าสุดที่มีแท็กเดียวกัน เพื่อเจาะลึกหัวข้อใกล้เคียง
เชิงลึกของการจำลองเสมือนบน Windows (ตอนที่ 3) — เครื่องเสมือนที่บูตในไม่กี่วินาที: ทำไม WSL2, Windows Sandbox และคอนเทนเนอร์จึงเบา
ทำไม WSL2 และ Windows Sandbox จึงเริ่มในไม่กี่วินาทีและรู้สึกเบา? บทความนี้อธิบายกลไก ตั้งแต่ dynamic base image และ direct map ไปจนถึงกา...
เชิงลึกของการจำลองเสมือนบน Windows (ตอนที่ 2) — หน่วยความจำที่แม้เคอร์เนลก็มองไม่เห็น: กลไกของ VBS, HVCI และ Credential Guard
เมื่อติดตั้งใหม่บนฮาร์ดแวร์ที่รองรับ VBS จะเปิดตามค่าเริ่มต้น และใช้ hypervisor กับ SLAT สร้างการแยกที่แข็งกว่าเคอร์เนล บทความนี้อธิบายโค...
เชิงลึกของการจำลองเสมือนบน Windows (ตอนที่ 1) — Windows ของคุณรันอยู่ที่ไหนจริง ๆ? Hypervisor และพาร์ติชัน
เมื่อเปิด Hyper-V แล้ว Windows ฝั่งโฮสต์เองก็รันบน hypervisor ในฐานะ root partition บทความนี้อธิบายรากฐานของการจำลองเสมือนผ่านบทบาทของ VT...
Win32 Thread Pool API — คอนเคอร์เรนซีโดยไม่สร้างเธรดเอง ด้วย CreateThreadpoolWork
โค้ดเนทีฟของคุณกระจายการเรียก CreateThread ไปทั่วหรือไม่ บทความนี้อธิบาย Win32 thread pool API ที่ออกแบบใหม่ใน Vista — ออบเจ็กต์สี่ชนิด w...
Named Pipe ในทางปฏิบัติ — IPC มาตรฐานของ Windows ตั้งแต่การออกแบบถึงความปลอดภัย
คู่มือเชิงปฏิบัติเรื่อง named pipe ซึ่งเป็นการสื่อสารระหว่างโปรเซสมาตรฐานของ Windows บทความนี้จัดระเบียบจากแหล่งปฐมภูมิ ทั้งการเลือกระหว่...
หัวข้อที่เกี่ยวข้อง
หน้าเหล่านี้วางหัวข้อไว้ในบริบทที่กว้างขึ้นของบริการและการตัดสินใจ
หัวข้อเทคนิคของ Windows
ประตูสู่การพัฒนา Windows การตรวจสอบบั๊ก และการใช้ประโยชน์จากสินทรัพย์เดิม
บริการที่เกี่ยวข้องกับหัวข้อนี้
บทความนี้เกี่ยวข้องโดยตรงกับบริการต่อไปนี้
พัฒนาเว็บไซต์
ใน API สำหรับสมาชิกและการเชื่อมต่อสมาร์ตโฟน การตรวจ JWT, การอนุญาตระดับออบเจกต์ และการจำกัดว่าคุณสมบัติใดอัปเดตได้ เชื่อมตรงกับความมั่นคงปลอดภัยของระบบเว็บเอง
ที่ปรึกษาเทคนิคและรีวิวการออกแบบ
การหาช่องว่างการอนุญาตใน API ที่มีอยู่ การประเมินขอบเขตผลกระทบของไลบรารีที่พึ่งพา และการออกแบบกฎ WAF ชั่วคราวผ่านรีวิวการออกแบบ ล้วนอยู่ในขอบเขตของที่ปรึกษาเทคนิค
คำถามที่พบบ่อย
คำถามที่มักพบในการปรึกษาเกี่ยวกับหัวข้อของบทความนี้
- ทำไมผู้โจมตีแอบอ้างเป็นคนอื่นได้ ทั้งที่การตรวจลายเซ็น JWT สำเร็จ
- ในข้อนี้ไลบรารีจัดการ JWT รับ alg ในส่วนหัว JWT ตามที่ผู้โจมตีระบุเลย และถือว่า JWT ที่ alg=none ถูกต้องโดยไม่มีลายเซ็น ดังนั้นแม้เขียนทับรหัสผู้ใช้ในเพย์โหลด การตรวจก็ผ่าน คำตอบของข้อสอบคือให้ตรวจ alg ในส่วนหัว JWT และยืนยันว่าค่าไม่ใช่ NONE แต่ในทางปฏิบัติ การปฏิเสธแค่ NONE ยังไม่พอ ต้องตรึงอัลกอริทึมที่อนุญาต เช่น RS256 ไว้ในการตั้งค่าฝั่งเซิร์ฟเวอร์ เพื่อไม่ให้อัลกอริทึมที่โทเค็นประกาศเองถูกใช้เลือกโดยตรง ควรตรวจ issuer, audience, วันหมดอายุ, subject ฯลฯ ตามกรณีใช้งานด้วย
- การเทียบรหัสผู้ใช้ใน JWT กับ mid ของคำขอเพียงพอเป็นการป้องกันด้านการอนุญาตหรือไม่
- สำหรับคำตอบของข้อสอบนี้ เพียงพอ การตรวจในโมดูลร่วม P ว่ารหัสผู้ใช้ใน JWT ตรงกับ mid จะหยุดการโจมตีที่ระบุ mid ของคนอื่น แต่สำหรับ API ที่จัดการเฉพาะข้อมูลของผู้เรียกเอง ในทางปฏิบัติปลอดภัยกว่าที่จะไม่รับ mid จากไคลเอนต์เลย และกำหนดรหัสผู้ใช้จาก subject ของ JWT ที่ตรวจแล้ว การใช้ GET /users/me หรือ PUT /users/me ทำให้ตรรกะเทียบหายไปได้ยากขึ้น API ที่ผู้ดูแลระบบทำงานกับผู้ใช้อื่นควรแยกเป็นเอนด์พอยต์ต่างหากพร้อมนโยบายอนุญาตของตนเอง
- ทำไมการตรวจอินพุตทั่วไปอย่างเดียวจึงหยุดการโจมตีที่เติม status ไม่ได้
- เพราะแม้ตรวจความยาวของ name หรือช่วงของ age ก็ไม่ช่วยอะไร หาก status ซึ่งเป็นฟิลด์ที่ไม่ควรรับตั้งแต่แรกถูกผูกอัตโนมัติแล้วส่งตรงเข้าออบเจกต์ภายใน ปัญหาไม่ใช่รูปแบบของค่า แต่เป็นการอนุญาตระดับคุณสมบัติ คือผู้ใช้มีสิทธิ์เปลี่ยนคุณสมบัตินั้นหรือไม่ ให้นิยามเฉพาะ name และ age ในชนิดอินพุตสำหรับอัปเดต และปฏิเสธคุณสมบัติที่ไม่รู้จัก สถานะการเรียกเก็บเงินต้องเปลี่ยนได้เฉพาะจากเหตุการณ์ที่เซิร์ฟเวอร์เชื่อถือได้ เช่น ผลสำเร็จจากบริการชำระเงิน
- รหัสยืนยันตัวตน 4 หลักหมดอายุใน 10 นาที ทำไมยังอันตราย
- เพราะมีผู้สมัครเพียง 10,000 รายจาก 0000 ถึง 9999 และที่ 10 ครั้งต่อวินาที ผู้โจมตีสำเร็จหลังเฉลี่ย 5,000 ครั้ง คือ 500 วินาที ช่วงอายุ 10 นาทีคือ 600 วินาที ดังนั้นการลองผู้สมัครไม่ซ้ำตามลำดับทำให้ตรวจได้ 6,000 รายในหน้าต่างนั้น เวลาหมดอายุอย่างเดียวหยุดบรูทฟอร์ซไม่ได้ ต้องออกแบบจำนวนผู้สมัคร ความเร็วในการลอง และเพดานจำนวนครั้งร่วมกัน
- มาตรการในข้อสอบคือการล็อกบัญชี การล็อกทันทีอย่างเดียวในทางปฏิบัติพอหรือไม่
- ไม่พอ ช่องว่างในข้อเรียกตรรกะที่ล็อกบัญชีเมื่อจำนวนล้มเหลวติดต่อกันเกินเกณฑ์ แต่การล็อกถาวรแบบตายตัวอย่างเดียวเปิดทางให้ผู้โจมตีตั้งใจล็อกบัญชีของคนอื่นเป็นการปฏิเสธบริการ ในทางปฏิบัติให้รวมนับความล้มเหลวต่อบัญชี เวลารอแบบขั้นบันได การประเมินความเสี่ยงของแหล่งที่มาและอุปกรณ์ การแจ้งเตือน และขั้นตอนกู้คืน สิ่งสำคัญอีกอย่างคือการออกรหัสใหม่ต้องไม่รีเซ็ตตัวนับความล้มเหลวกลับเป็นศูนย์
- จุดของการตั้ง WAF เป็นตรวจจับแทนบล็อกคืออะไร
- หมายความว่าทราฟฟิกธุรกิจที่ถูกต้องไม่ถูกหยุด แม้สตริงปกติถูกตัดสินผิดว่าเป็นการโจมตี ในเฉลย ข้อดีคือป้องกันการบล็อกจากผลบวกลวง และสิ่งที่ควรทำคือตรวจว่าเป็นการโจมตีหรือไม่ทุกครั้งที่ได้รับแจ้งเตือน โหมดตรวจจับไม่ใช่การตั้งค่าที่ปล่อยทิ้ง ใช้เป็นช่วงสังเกตเพื่อตรวจล็อก กรองผลบวกลวง ปรับกฎ แล้วค่อยย้ายไปบล็อก ในภาวะฉุกเฉินที่ช่องโหว่ร้ายแรงที่รู้จักกำลังถูกใช้จริง อาจสมเหตุสมผลที่จะชั่งกับความเสี่ยงด้านความพร้อมใช้ แล้วเลือกบล็อกตั้งแต่ต้นแทน
- ไลบรารี H ในข้อนี้คือ Log4j หรือไม่
- ข้อสอบปิดชื่อผลิตภัณฑ์ แต่ลำดับการโจมตี คือ JNDI Lookup, เซิร์ฟเวอร์ LDAP, การดึงคลาสจากเซิร์ฟเวอร์ HTTP, สตริงฝังในส่วนหัว HTTP และคะแนนฐาน CVSS v3.1 ที่สูง อ่านได้เป็นนามธรรมของ CVE-2021-44228 ที่รู้จักในชื่อ Log4Shell บทความนี้อธิบายความสอดคล้องนั้น แต่ข้อสอบไม่ต้องการให้ระบุชื่อผลิตภัณฑ์ ตอบได้จากขั้นตอนการโจมตีและสเปก WAF ที่ให้มา
- ควรนำอะไรจากข้อนี้กลับไปใช้จริง
- ว่าการยืนยันตัวตนสำเร็จ, JWT ไม่ถูกดัดแปลง, ได้รับอนุญาตให้เข้าถึงออบเจกต์เป้าหมาย และได้รับอนุญาตให้เปลี่ยนคุณสมบัติเป้าหมาย เป็นการตรวจคนละเรื่อง นอกจากนั้นรหัสยืนยันตัวตนสั้นต้องมีจำกัดอัตราการลอง และสำหรับช่องโหว่ไลบรารีร้ายแรงให้ทำยืนยันผลกระทบ ป้องกันชั่วคราว และแก้ที่ต้นเหตุขนานกัน จุดที่นำกลับไปใช้จริงคือ รวมการอนุญาตไว้ในชิ้นส่วนร่วม ทำให้สคีมาอินพุตเป็นรายการอนุญาต ตรึงเงื่อนไขตรวจ JWT ฝั่งเซิร์ฟเวอร์ และติดตามไลบรารีที่พึ่งพาให้พร้อมอัปเดต