IDOR (Insecure Direct Object Reference)
IDOR เกิดเมื่อแอปอ้างถึงทรัพยากร (เช่น id ของ object) จาก input ผู้ใช้โดยตรง แต่ตรวจแค่ 'ล็อกอินหรือยัง' (authentication) โดยไม่ตรวจ 'เป็นเจ้าของหรือมีสิทธิ์ไหม' (authorization) ทำให้เปลี่ยน id แล้วเข้าถึง/แก้ข้อมูลของคนอื่นได้ บทนี้ไล่ครบทั้ง horizontal/vertical, การเดา UUID/hash, mass assignment, การ enumerate และ workflow การทดสอบด้วย 2 บัญชี + Burp Autorize
1. หลักการ — Authentication ≠ Authorization
หัวใจของ IDOR คือความสับสนระหว่างสองสิ่ง: Authentication (คุณเป็นใคร — ล็อกอินแล้ว) กับ Authorization (คุณมีสิทธิ์ทำสิ่งนี้ไหม) เมื่อ endpoint รับ identifier ของทรัพยากรจากผู้ใช้ เช่น /api/orders/1001 หรือ ?user_id=55 แล้วคืนข้อมูลโดยเชื่อ id ที่ส่งมาทันทีโดยไม่ตรวจว่า session ปัจจุบันเป็นเจ้าของ ผู้โจมตีเปลี่ยนค่า id เป็นของคนอื่น (1002, 56) เพื่อดู/แก้/ลบข้อมูลที่ไม่ใช่ของตน
OWASP API Security เรียกช่องนี้ว่า BOLA (Broken Object Level Authorization) ซึ่งเป็นอันดับ 1 ของ API Top 10 มายาวนาน เพราะทุก object ที่ผูกกับ id ล้วนเป็นเป้าได้ IDOR เป็น subset ของ Broken Access Control แบบ horizontal — ต่างจาก vertical (privilege escalation) ที่พยายามขึ้นเป็น role สูงกว่า
2. ประเภทและจุดที่ identifier ซ่อนอยู่
| ประเภท | ความหมาย | ตัวอย่าง |
|---|---|---|
| Horizontal IDOR | เข้าข้อมูลผู้ใช้ระดับเดียวกัน | ดู order/invoice/message ของ user อื่น |
| Vertical (function-level) | เข้าถึงฟังก์ชัน role สูงกว่า | user เรียก /api/admin/users/{id} |
| Object-property (mass assignment) | แก้ field ที่ไม่ควรแก้ | ส่ง role=admin, isVerified=true |
| Blind IDOR | แก้สำเร็จแต่ไม่มี response ให้เห็น | เปลี่ยนอีเมลคนอื่นแต่หน้าไม่แสดง |
identifier ไม่ได้อยู่แค่ใน URL — ต้องกวาดหาให้ครบทุกช่องทางที่ผู้ใช้ควบคุมได้ ลืมจุดใดจุดหนึ่งคือพลาด IDOR ทั้งดวง
- Path:
/orders/1001,/users/42/profile,/files/report.pdf - Query string:
?id=55,?account=ACC-99,?doc=inv_2024 - Body (form/JSON):
{"user_id":55,"order":1001}— บาง backend เชื่อ body มากกว่า session - Cookie:
uid=55,cart=abcที่ถอดรหัสได้ - Custom header:
X-User-Id: 55,X-Account: 99 - GraphQL variable:
{ user(id: 55) { email } }— ดูหัวข้อ GraphQL Attacks - Embedded/derived: id ใน JWT payload, ใน filename ของ export, ใน WebSocket message
3. Workflow การทดสอบด้วย 2 บัญชี
วิธีที่เชื่อถือได้ที่สุดคือใช้ สองบัญชี A และ B ที่คุณรู้ id ของทั้งคู่ แล้วพยายามเข้าถึงทรัพยากรของ A ด้วย session ของ B ถ้าทำได้ = IDOR ยืนยันได้แน่นอนโดยไม่ต้องเดา
- 1สร้างบัญชี A และ B — จดทุก id ที่แต่ละบัญชีเห็น (order id, user id, doc id)
- 2ทำงานปกติด้วยบัญชี A แล้วดักทุก request ที่มี identifier ผ่าน Burp Proxy
- 3ส่ง request ของ A เข้า Repeater แล้วสลับ session cookie/token เป็นของ B
- 4แก้ id ใน request ให้ชี้ทรัพยากรของ A (ที่ B ไม่ควรเห็น) แล้วยิงด้วย session B
- 5ดูผล: 200 + ข้อมูลของ A = IDOR; 403/404 = ป้องกันถูก (แต่เช็ค 404 ว่าใช่การซ่อนจริงไหม)
- 6ทดสอบทั้ง read (GET) และ write (POST/PUT/PATCH/DELETE) — สิทธิ์อ่านกับเขียนมักเช็คไม่เท่ากัน
- 7ทดสอบซ้ำแบบไขว้: เข้าของ B ด้วย session A ด้วย เพื่อกันผลบวกลวงจากข้อมูล cache
4. ตัวอย่าง request จริง
# บัญชี B (session ของ B) พยายามอ่าน invoice ของ A (id 1001)
GET /api/v1/invoices/1001 HTTP/1.1
Host: shop.example.com
Cookie: session=B_SESSION_TOKEN
# ถ้าตอบ 200 พร้อมข้อมูลของ A → IDOR
HTTP/1.1 200 OK
Content-Type: application/json
{"id":1001,"user":"alice","total":499,"card_last4":"4242"}# session ของ attacker แต่ชี้ user_id ของเหยื่อใน body
POST /api/account/update HTTP/1.1
Host: app.example.com
Cookie: session=ATTACKER_SESSION
Content-Type: application/json
{"user_id":42,"email":"[email protected]"}
# ตอบ 200 {"status":"ok"} แม้ไม่แสดงข้อมูล = blind IDOR
# ยืนยันด้วยการ trigger password-reset ให้ user 42 แล้วดูว่าเมลไปที่ไหน# 1) ส่ง id ทั้งของเราและของเหยื่อ — backend อาจอ่านตัวหลัง
GET /api/orders?id=1002&id=1001 HTTP/1.1
# 2) endpoint เดียวกันแต่เปลี่ยน method (GET กันไว้ POST ไม่กัน)
POST /api/orders/1001 HTTP/1.1
X-HTTP-Method-Override: GET
# 3) ย้าย id จาก path ไป body / เพิ่มนามสกุล
GET /api/orders/1001.json HTTP/1.1
POST /api/orders {"id":1001} # ค่าใน body ชนะ path บาง framework
# 4) array wrapping ให้ validator งง
{"id":[1001]} {"id":{"$ne":1002}}5. Mass Assignment (Object-property IDOR)
หลาย framework (Rails, Spring, Laravel, Node ORMs) bind ทุก field ใน JSON เข้ากับ model โดยอัตโนมัติ ถ้า dev ไม่กำหนด allowlist ผู้โจมตีเพิ่ม field ที่ไม่ควรตั้งเองได้ เช่น role, isAdmin, balance, verified — เป็น IDOR ที่เป้าหมายเป็น 'property ของ object' แทน 'id ของ object'
# request ปกติของ profile update มีแค่ name, bio
PATCH /api/users/me HTTP/1.1
Content-Type: application/json
{"name":"attacker","bio":"hi","role":"admin","email_verified":true,"credit":999999}
# ถ้า backend ยอมรับ field เกิน → ยกตัวเองเป็น admin / เติมเงินrole, is_admin, owner_id, status, price เป็นชุดคลาสสิก6. เมื่อ id เป็น UUID / hash / random
ถ้า id ไม่ใช่เลขเรียง อย่าเพิ่งยอมแพ้ — เป้าหมายคือ หา id ของเหยื่อมาจากที่อื่น (leak) หรือหาว่า id นั้น predictable กว่าที่คิด
| รูปแบบ id | แนวทาง |
|---|---|
| เลขเรียง 1,2,3 | enumerate ±1 ด้วย Intruder/ffuf จนหมด range |
| UUIDv1 (time-based) | เดาได้จาก timestamp+MAC (ใช้ sandwich attack ระหว่างสอง id ที่เรารู้) |
| UUIDv4 (random) | ยากมาก — หันไปหา leak: หน้า share, response อื่น, Referer, autocomplete |
| MD5/SHA ของ id | ถ้าเดา input ได้ (เช่น md5(email/username)) ก็คำนวณเองได้ |
| base64/hex encode | decode แล้วอาจเจอเลขเรียง/JSON ข้างใน → กลับไปเดาต่อ |
| JWT/ signed | ดู payload; ถ้า secret อ่อน ปลอมได้ (ดู JWT Attacks) |
ffuf -u 'https://target/api/orders/FUZZ' \
-H 'Cookie: session=YOUR_SESSION' \
-w <(seq 1000 2000) \
-mc 200 -fr '"error"' -o idor.json
# -mc 200 = เก็บเฉพาะที่ตอบ 200, -fr = ตัด response ที่มีคำว่า error
# ปรับ -rate เพื่อไม่ให้โดน rate-limit7. เครื่องมือ — Burp Autorize / AuthMatrix
การทดสอบ IDOR ทีละ endpoint ด้วยมือช้ามาก extension ที่ replay ทุก request ด้วย session อื่นให้อัตโนมัติคือของจำเป็น
- Autorize (Burp): ใส่ cookie/header ของ user สิทธิ์ต่ำ แล้ว browse ด้วย user สิทธิ์สูง — Autorize จะ replay ทุก request ด้วย session ต่ำและไฮไลต์เป็น Bypassed!/Enforced!/Is enforced??? ให้อัตโนมัติ
- AuthMatrix (Burp): สร้างตาราง role × request แล้วรันทั้งหมด เหมาะเวลามีหลาย role (guest/user/manager/admin)
- Match & Replace: ตั้งกฎสลับ session cookie อัตโนมัติเวลา replay
- Intruder / ffuf: enumerate id เป็นชุด
- Arjun / param-miner: ค้น hidden parameter ที่อาจรับ id หรือ mass-assignment field
1. เปิด user สิทธิ์สูง (admin) ใน browser ที่ต่อ Burp
2. Autorize tab → วาง Cookie/Authorization ของ user 'สิทธิ์ต่ำ' ลงช่อง headers
3. เปิด "Intercept requests from repeater" + ตั้ง Enforcement Detector
4. Browse แอปด้วย admin ตามปกติ
5. Autorize replay ทุก request ด้วย session ต่ำ:
- "Bypassed!" → IDOR/broken access control (สนใจ)
- "Enforced!" → ป้องกันถูก
- "Is enforced???" → ต้องเช็คด้วยตา8. ตัวอย่าง CTF & Real-world
CTF (PortSwigger-style): หน้า account มี GET /my-account?id=wiener เปลี่ยน id=wiener เป็น id=carlos แล้วเห็นข้อมูล carlos ทันที — IDOR ตรงๆ จาก username ที่เดาได้ ต่อยอดเป็นการอ่าน API key ของ admin เพื่อ solve
Real-world pattern: โมบายแอปเรียก GET /api/v2/users/{uuid}/documents โดย uuid อยู่ใน JWT แต่ endpoint ไม่ผูก uuid ใน path กับ subject ของ token — เปลี่ยน uuid ใน path เป็นของคนอื่นก็ดึงเอกสารได้ (BOLA คลาสสิกใน API) พบบ่อยเพราะ dev เชื่อว่า 'ต้องล็อกอินถึงเรียกได้' = ปลอดภัยแล้ว
| สัญญาณที่เจอ | ลองทำ |
|---|---|
| id เป็นเลขใน URL/JSON | ±1, enumerate, replay ด้วย session อื่น |
| endpoint /admin/... เข้าได้ด้วย user | vertical — ดู Access Control |
| response ส่ง field เกินที่หน้าจอใช้ | ลอง mass assignment field เดียวกันกลับไป |
| UUIDv4 กันแน่น | หา leak: share link, referer, response อื่น |
| GET กัน 403 แต่ PUT ไม่กัน | ทดสอบทุก method แยกกัน |
| JWT มี user_id ใน payload | ลองแก้ (ถ้า sig อ่อน) หรือชี้ id คนอื่นใน path |
9. ข้อผิดพลาดที่พบบ่อย & การป้องกัน
- ทดสอบแค่ read: ลืมลอง write/delete ทั้งที่สิทธิ์แยกกัน — พลาด IDOR ครึ่งหนึ่ง
- เชื่อ 404 ว่าปลอดภัย: บางระบบตอบ 404 แทน 403 เพื่อซ่อน — object อาจมีจริง ให้เทียบ timing/ขนาด response
- ผลบวกลวงจาก cache/CDN: ตอบ 200 จาก cache ของ user เดิม — เพิ่ม cache-buster และทดสอบไขว้
- ทดสอบด้วยบัญชีเดียว: เดา id มั่วโดยไม่รู้ว่าเป็นของใคร — ใช้ 2 บัญชีที่รู้ id เสมอ
- ลืม header/cookie identifier: โฟกัสแค่ URL แต่ id จริงอยู่ใน X-User-Id
10. Quick Reference
- IDOR = auth แต่ไม่ authz — server เชื่อ id ที่ส่งมา
- หา identifier ทุกที่: path, query, body, cookie, header, JWT, GraphQL
- ทดสอบด้วย 2 บัญชี A/B: เข้าของ A ด้วย session B
- ทั้ง read + write: GET/POST/PUT/PATCH/DELETE แยกกัน
- เลขเรียง → enumerate; UUIDv4 → หา leak; encoded → decode
- mass assignment: ยัด role/isAdmin/price/owner_id ใน body
- bypass: parameter pollution, method swap, id ใน body, array wrap
- เครื่องมือ: Burp Autorize / AuthMatrix / ffuf / Arjun
- ป้องกัน: ตรวจ ownership ระดับ object ฝั่ง server ทุก request, deny-by-default
🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ
สมมติเพิ่งเจอโจทย์ที่มี endpoint คล้าย /api/orders/1001 หรือ ?id=55 มีแค่ Kali เปล่าๆ ยังไม่รู้ว่าเป็น IDOR จริงไหม — ทำตามนี้ทีละขั้น
- 1เปิด Burp Suite Community (มากับ Kali) → ตั้งเบราว์เซอร์ให้ผ่าน proxy 127.0.0.1:8080 แล้วติดตั้ง Burp CA cert
- 2ถ้าโจทย์ให้สมัครได้ สร้าง 2 บัญชี A และ B แล้วจดทุก id ที่แต่ละบัญชีเห็น (order id, user id, doc id)
- 3login ด้วยบัญชี A ใช้งานแอปตามปกติ ให้ Burp เก็บ Proxy > HTTP history ไว้ครบ
- 4หา request ที่มี id ใน path/query/body เช่น GET /api/orders/1001 → คลิกขวา Send to Repeater
- 5ใน Repeater เปลี่ยน id เป็นเลขข้างเคียง (1002, 1000) ด้วย session A เดิม แล้วกด Send — ดูว่า response เปลี่ยนไปเป็นของคนอื่นไหม
- 6login บัญชี B แยก คัด header Cookie/Authorization มาแปะทับใน Repeater ของ request A แล้วยิงซ้ำด้วย session B
- 7เทียบผล: 200 + เห็นข้อมูลจริงของ A = IDOR ยืนยัน; 403/401 = กันสิทธิ์ถูก; 404 = อาจซ่อน ต้องเช็คต่อ
- 8ถ้า id เป็น UUID/hash วางใน CyberChef (From Base64 / From Hex) ดูว่าซ่อนเลขเรียงหรือ pattern อยู่ข้างในไหม
- 9ติดตั้ง extension Autorize (Burp → Extender/BApp Store) ใส่ Cookie ของ session สิทธิ์ต่ำ แล้วเดินแอปด้วยสิทธิ์สูง ให้มัน replay ทุก request อัตโนมัติ
- 10ถ้า id เป็นเลขเรียง ลอง enumerate ทั้ง range ด้วย ffuf พร้อม session ของ B
- 11ลอง mass assignment: เติม field เช่น role/isAdmin/owner_id ใน body ของ PUT/PATCH แล้วดูว่า server ยอมรับไหม
- 12ยังไม่ได้ผล → ลอง parameter pollution (?id=1&id=2), method override (X-HTTP-Method-Override), หรือย้าย id จาก path ไป body
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| ดักจับ/แก้ไข request | Burp Suite Community | - | - |
| ตรวจ authz หลาย session อัตโนมัติ | Burp Extender | BApp Store: Autorize / AuthMatrix | - |
| enumerate id ตัวเลข | ffuf | - | - |
| decode base64/hex/UUID | - | - | CyberChef (gchq.github.io/CyberChef) |
| หา hidden parameter | - | pipx install arjun | - |
| ทดสอบ JWT ที่มี user_id ฝัง | - | git clone https://github.com/ticarpi/jwt_tool | jwt.io |
| crack weak secret/hash ของ token | hashcat | - | crackstation.net |
หัวข้อที่เชื่อมโยง
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้