คลัง
web

IDOR (Insecure Direct Object Reference)

IDOR เกิดเมื่อแอปอ้างถึงทรัพยากร (เช่น id ของ object) จาก input ผู้ใช้โดยตรง แต่ตรวจแค่ 'ล็อกอินหรือยัง' (authentication) โดยไม่ตรวจ 'เป็นเจ้าของหรือมีสิทธิ์ไหม' (authorization) ทำให้เปลี่ยน id แล้วเข้าถึง/แก้ข้อมูลของคนอื่นได้ บทนี้ไล่ครบทั้ง horizontal/vertical, การเดา UUID/hash, mass assignment, การ enumerate และ workflow การทดสอบด้วย 2 บัญชี + Burp Autorize

BeginnerIntermediate#idor#access-control#authorization#bola#mass-assignment#uuid#web#ctf

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 สูงกว่า

IDOR: server เชื่อ id ที่ส่งมาโดยไม่ตรวจ ownership
attacker session ของตัวเอง GET /api/orders/1001 id ของตัวเอง → 200 OK (ปกติ) GET /api/orders/1002 id ของเหยื่อ → ควร 403 server ไม่เช็ค owner → 200 = IDOR
กฎเหล็ก: การซ่อน id (UUID, hash) เป็น obscurity ไม่ใช่ security — ถ้า server ไม่ตรวจ ownership ก็ยังเป็น IDOR อยู่ดี แค่หา id ยากขึ้น การป้องกันที่ถูกคือตรวจสิทธิ์ระดับ object ทุก request ฝั่ง server เสมอ

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. 1สร้างบัญชี A และ B — จดทุก id ที่แต่ละบัญชีเห็น (order id, user id, doc id)
  2. 2ทำงานปกติด้วยบัญชี A แล้วดักทุก request ที่มี identifier ผ่าน Burp Proxy
  3. 3ส่ง request ของ A เข้า Repeater แล้วสลับ session cookie/token เป็นของ B
  4. 4แก้ id ใน request ให้ชี้ทรัพยากรของ A (ที่ B ไม่ควรเห็น) แล้วยิงด้วย session B
  5. 5ดูผล: 200 + ข้อมูลของ A = IDOR; 403/404 = ป้องกันถูก (แต่เช็ค 404 ว่าใช่การซ่อนจริงไหม)
  6. 6ทดสอบทั้ง read (GET) และ write (POST/PUT/PATCH/DELETE) — สิทธิ์อ่านกับเขียนมักเช็คไม่เท่ากัน
  7. 7ทดสอบซ้ำแบบไขว้: เข้าของ B ด้วย session A ด้วย เพื่อกันผลบวกลวงจากข้อมูล cache
เจอ endpoint ที่มี id — ไล่ตามนี้
endpoint นี้มี identifier ไหม?
path / query / body / header / cookie
สร้าง 2 บัญชี A,B + จด id ของแต่ละคน
replay request ของ A ด้วย session ของ B
Burp Repeater / Autorize
ผลลัพธ์เป็นอะไร?
200 + ข้อมูลของ AIDOR ยืนยัน → ทดสอบ write ต่อ
403/401ป้องกันถูก → ลอง bypass (method, pollution, body)
404อาจซ่อนของจริง → หา id leak / enumerate
id เป็นแบบไหน?
เลขเรียงenumerate ±1 → หมด range
UUID/hashหา leak ในหน้าอื่น/response/referer
encodeddecode (base64/hex) แล้วดู pattern
ลอง write + mass assignment
PUT /users/{id} {role:admin}
IDOR สำเร็จ → เข้าถึง/แก้ข้อมูลคนอื่น

4. ตัวอย่าง request จริง

Horizontal IDOR — อ่าน invoice ของคนอื่น
# บัญชี 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"}
เทียบกับตอนยิง id 1002 (ของ B เอง) — ถ้าทั้งคู่ตอบ 200 แสดงว่าไม่มีการเช็ค owner
Blind IDOR (write) — เปลี่ยนอีเมลของ user อื่น
# 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 แล้วดูว่าเมลไปที่ไหน
Bypass — HTTP Parameter Pollution + method swap
# 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}}
ลองทีละอย่าง เทียบ response length — บาง WAF/validator กรองแค่ตำแหน่งเดียว

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'

ยกสิทธิ์ตัวเองผ่าน mass assignment
# 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 / เติมเงิน
หา field ที่ซ่อนได้จาก response ของ GET /users/me, จาก JS bundle, หรือจาก error message ที่หลุด schema
เทคนิคหา field: ดู response ของ GET object เดียวกัน (server มักส่ง field ที่รับ input ได้กลับมา) แล้วลองยัดกลับใน request update ทีละตัว — role, is_admin, owner_id, status, price เป็นชุดคลาสสิก

6. เมื่อ id เป็น UUID / hash / random

ถ้า id ไม่ใช่เลขเรียง อย่าเพิ่งยอมแพ้ — เป้าหมายคือ หา id ของเหยื่อมาจากที่อื่น (leak) หรือหาว่า id นั้น predictable กว่าที่คิด

รูปแบบ idแนวทาง
เลขเรียง 1,2,3enumerate ±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 encodedecode แล้วอาจเจอเลขเรียง/JSON ข้างใน → กลับไปเดาต่อ
JWT/ signedดู payload; ถ้า secret อ่อน ปลอมได้ (ดู JWT Attacks)
Enumerate เลขเรียงด้วย ffuf (พร้อม session)Linux
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-limit
การ enumerate ข้อมูลผู้ใช้จริงเป็นการเข้าถึงข้อมูลส่วนบุคคล — ทำได้เฉพาะบน lab/CTF หรือ scope ที่มีหนังสืออนุญาตชัดเจนเท่านั้น อย่าดึงข้อมูลจริงเกินที่จำเป็นในการพิสูจน์ช่องโหว่

7. เครื่องมือ — 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
ตั้งค่า Autorize อย่างย่อ
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/... เข้าได้ด้วย uservertical — ดู 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
การป้องกัน (blue team): ตรวจ authorization ระดับ object ทุก request เทียบ subject ของ session กับ owner ของทรัพยากรฝั่ง server — ไม่ใช่ฝั่ง client ใช้ deny-by-default, ผูก id เข้ากับ session (ดึงจาก token ไม่ใช่จาก input), ใช้ allowlist field กัน mass assignment, และ log/alert เมื่อ user เข้าถึง object นอก scope ถี่ผิดปกติ (สัญญาณ enumeration)

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. 1เปิด Burp Suite Community (มากับ Kali) → ตั้งเบราว์เซอร์ให้ผ่าน proxy 127.0.0.1:8080 แล้วติดตั้ง Burp CA cert
  2. 2ถ้าโจทย์ให้สมัครได้ สร้าง 2 บัญชี A และ B แล้วจดทุก id ที่แต่ละบัญชีเห็น (order id, user id, doc id)
  3. 3login ด้วยบัญชี A ใช้งานแอปตามปกติ ให้ Burp เก็บ Proxy > HTTP history ไว้ครบ
  4. 4หา request ที่มี id ใน path/query/body เช่น GET /api/orders/1001 → คลิกขวา Send to Repeater
  5. 5ใน Repeater เปลี่ยน id เป็นเลขข้างเคียง (1002, 1000) ด้วย session A เดิม แล้วกด Send — ดูว่า response เปลี่ยนไปเป็นของคนอื่นไหม
  6. 6login บัญชี B แยก คัด header Cookie/Authorization มาแปะทับใน Repeater ของ request A แล้วยิงซ้ำด้วย session B
  7. 7เทียบผล: 200 + เห็นข้อมูลจริงของ A = IDOR ยืนยัน; 403/401 = กันสิทธิ์ถูก; 404 = อาจซ่อน ต้องเช็คต่อ
  8. 8ถ้า id เป็น UUID/hash วางใน CyberChef (From Base64 / From Hex) ดูว่าซ่อนเลขเรียงหรือ pattern อยู่ข้างในไหม
  9. 9ติดตั้ง extension Autorize (Burp → Extender/BApp Store) ใส่ Cookie ของ session สิทธิ์ต่ำ แล้วเดินแอปด้วยสิทธิ์สูง ให้มัน replay ทุก request อัตโนมัติ
  10. 10ถ้า id เป็นเลขเรียง ลอง enumerate ทั้ง range ด้วย ffuf พร้อม session ของ B
  11. 11ลอง mass assignment: เติม field เช่น role/isAdmin/owner_id ใน body ของ PUT/PATCH แล้วดูว่า server ยอมรับไหม
  12. 12ยังไม่ได้ผล → ลอง parameter pollution (?id=1&id=2), method override (X-HTTP-Method-Override), หรือย้าย id จาก path ไป body
ตัดสินใจ: เจอ IDOR จริงไหม แล้วไปต่อทางไหน
เปลี่ยน id เป็นของคนอื่น (ใช้ session ตัวเอง)
ผลลัพธ์?
✅ เห็นข้อมูลคนอื่น (200+data)→ ยืนยันด้วย 2 บัญชี (A/B) แล้ว enumerate ต่อ
❌ 403/404/ไม่เห็นข้อมูล→ ลอง encode/UUID predict/parameter pollution
ยืนยันด้วย session ของ B จริง + ลอง enumerate/mass assignment
ลอง base64/hex decode (CyberChef), UUID sandwich, ?id=1&id=2, id ใน body แทน path, method override
✅ ผ่านสักท่า→ กลับไปยืนยันด้วย session ของ B
❌ ยังไม่ได้เลย→ ข้ามไปดู access control / auth bypass ของทั้งเว็บ
ยืนยัน IDOR สำเร็จ → เก็บ request/response เป็นหลักฐาน
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ดักจับ/แก้ไข requestBurp Suite Community--
ตรวจ authz หลาย session อัตโนมัติBurp ExtenderBApp 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_tooljwt.io
crack weak secret/hash ของ tokenhashcat-crackstation.net
🚑 ถ้าตันสนิท ลองท่าถัดไป: Access Control — ถ้า id เดายากมากหรือ enumerate ไม่ได้ผล อาจกันด้วย role ไม่ใช่ ownership ลองหา endpoint admin ที่ควรเข้าไม่ได้แทน · JWT Attacks — ถ้า id/สิทธิ์ฝังอยู่ใน JWT ที่ปลอมได้ (alg=none/weak secret) · Business Logic — ถ้า id ถูกต้องแต่ endpoint ทำงานเกินสิทธิ์ในเชิงตรรกะ (เช่น เปลี่ยนสถานะ order) · Race Condition — ถ้า resource ใช้ครั้งเดียว (coupon/ตั๋ว) แล้วยิงพร้อมกันได้ผลกว่า

หัวข้อที่เชื่อมโยง

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้