Web Hosting

IDOR (Insecure Direct Object Reference): Pengertian, Bahaya, dan Cara Mencegahnya


IDOR atau Insecure Direct Object Reference adalah salah satu kerentanan keamanan web yang paling umum namun seringkali diabaikan oleh developer. Kerentanan ini memungkinkan attacker untuk mengakses atau memodifikasi data milik user lain hanya dengan mengubah parameter dalam URL atau request.

Meskipun terdengar sederhana, IDOR dapat menyebabkan kebocoran data sensitif yang serius. Artikel ini akan membahas secara mendalam tentang IDOR, contoh-contoh kasusnya, dan bagaimana cara mencegahnya dengan efektif.

Apa Itu IDOR?

IDOR adalah kerentanan keamanan yang terjadi ketika aplikasi web memberikan akses langsung ke objek (seperti file, database record, atau user data) berdasarkan input dari user, tanpa melakukan validasi otorisasi yang memadai.

Kerentanan ini masuk dalam kategori A01:2021 – Broken Access Control dalam OWASP Top 10, yang merupakan risiko keamanan tertinggi untuk aplikasi web.

Karakteristik IDOR

  • Menggunakan referensi langsung ke objek internal (ID, nama file, key) yang dapat diprediksi
  • Tidak ada atau lemahnya validasi otorisasi untuk mengakses objek tersebut
  • Attacker dapat dengan mudah mengubah parameter untuk mengakses data user lain
  • Sering ditemukan pada URL, parameter form, cookie, atau header HTTP

Contoh Kasus IDOR dalam Real World

1. IDOR pada Profile User

Contoh paling klasik dari IDOR adalah akses ke profile user menggunakan ID yang dapat diprediksi.

https://example.com/profile?user_id=12345

User dengan ID 12345 dapat melihat profilenya sendiri.
Attacker mencoba mengubah parameter:

https://example.com/profile?user_id=12346
https://example.com/profile?user_id=12347

Jika tidak ada validasi, attacker dapat melihat profile user lain.
<?php
// ❌ KODE VULNERABLE - Tidak ada validasi otorisasi
function viewProfile() {
    $userId = $_GET['user_id'];
    
    $stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
    $stmt->execute([$userId]);
    $user = $stmt->fetch();
    
    // Langsung tampilkan data tanpa cek apakah user berhak akses
    echo json_encode($user);
}

// ✅ KODE AMAN - Dengan validasi otorisasi
function viewProfileSecure() {
    $requestedUserId = $_GET['user_id'];
    $currentUserId = $_SESSION['user_id'];
    
    // Validasi: user hanya bisa akses profilenya sendiri
    if ($requestedUserId != $currentUserId) {
        http_response_code(403);
        echo json_encode(['error' => 'Unauthorized access']);
        return;
    }
    
    $stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
    $stmt->execute([$requestedUserId]);
    $user = $stmt->fetch();
    
    echo json_encode($user);
}
?>

2. IDOR pada Dokumen atau File

Kerentanan IDOR sering terjadi pada sistem download dokumen atau file.

https://example.com/download?file_id=8901

Attacker dapat mencoba:
https://example.com/download?file_id=8900
https://example.com/download?file_id=8902

Akses ke dokumen pribadi user lain tanpa otorisasi.
<?php
// ❌ VULNERABLE - Tidak cek ownership
function downloadFile() {
    $fileId = $_GET['file_id'];
    
    $stmt = $db->prepare("SELECT * FROM files WHERE id = ?");
    $stmt->execute([$fileId]);
    $file = $stmt->fetch();
    
    if ($file) {
        header('Content-Type: application/pdf');
        readfile($file['file_path']);
    }
}

// ✅ AMAN - Cek ownership sebelum download
function downloadFileSecure() {
    $fileId = $_GET['file_id'];
    $currentUserId = $_SESSION['user_id'];
    
    $stmt = $db->prepare(
        "SELECT * FROM files WHERE id = ? AND user_id = ?"
    );
    $stmt->execute([$fileId, $currentUserId]);
    $file = $stmt->fetch();
    
    if (!$file) {
        http_response_code(404);
        echo "File not found or unauthorized";
        return;
    }
    
    header('Content-Type: application/pdf');
    readfile($file['file_path']);
}
?>

3. IDOR pada API Endpoint

API modern juga rentan terhadap IDOR, terutama pada endpoint yang mengelola resource user.

<?php
// ❌ VULNERABLE API
// DELETE /api/orders/5432
function deleteOrder() {
    $orderId = $_GET['id'];
    
    $stmt = $db->prepare("DELETE FROM orders WHERE id = ?");
    $stmt->execute([$orderId]);
    
    echo json_encode(['success' => true]);
}

// ✅ SECURE API
// DELETE /api/orders/5432
function deleteOrderSecure() {
    $orderId = $_GET['id'];
    $currentUserId = $_SESSION['user_id'];
    
    // Cek ownership sebelum delete
    $stmt = $db->prepare(
        "SELECT user_id FROM orders WHERE id = ?"
    );
    $stmt->execute([$orderId]);
    $order = $stmt->fetch();
    
    if (!$order || $order['user_id'] != $currentUserId) {
        http_response_code(403);
        echo json_encode(['error' => 'Forbidden']);
        return;
    }
    
    $stmt = $db->prepare("DELETE FROM orders WHERE id = ?");
    $stmt->execute([$orderId]);
    
    echo json_encode(['success' => true]);
}
?>

4. IDOR pada Parameter Tersembunyi

IDOR tidak selalu pada URL, bisa juga pada hidden field atau request body.

<?php
// Form dengan hidden field
?>
<form method="POST" action="/update-profile">
    <input type="hidden" name="user_id" value="12345">
    <input type="text" name="email">
    <button type="submit">Update</button>
</form>

<?php
// ❌ VULNERABLE - Trust client input
function updateProfile() {
    $userId = $_POST['user_id']; // Attacker bisa ubah ini!
    $email = $_POST['email'];
    
    $stmt = $db->prepare(
        "UPDATE users SET email = ? WHERE id = ?"
    );
    $stmt->execute([$email, $userId]);
}

// ✅ SECURE - Gunakan session, bukan input user
function updateProfileSecure() {
    $userId = $_SESSION['user_id']; // Dari session, bukan POST
    $email = $_POST['email'];
    
    $stmt = $db->prepare(
        "UPDATE users SET email = ? WHERE id = ?"
    );
    $stmt->execute([$email, $userId]);
}
?>

Alur Serangan IDOR

Diagram berikut menunjukkan bagaimana attacker mengeksploitasi kerentanan IDOR:

sequenceDiagram
    participant A as Attacker
    participant W as Web Application
    participant D as Database
    
    A->>W: Login sebagai User A (ID: 100)
    W->>A: Session User A
    
    A->>W: Request: /profile?user_id=100
    W->>D: SELECT * FROM users WHERE id=100
    D->>W: Data User A
    W->>A: Tampilkan Profile User A ✓
    
    Note over A: Attacker mencoba ubah parameter
    
    A->>W: Request: /profile?user_id=101
    W->>D: SELECT * FROM users WHERE id=101
    D->>W: Data User B
    
    alt Aplikasi Vulnerable (IDOR)
        W->>A: Tampilkan Profile User B ✗
        Note over A: SUKSES! Data bocor
    else Aplikasi Secure
        W->>W: Validasi: Session(100) ≠ Request(101)
        W->>A: HTTP 403 Forbidden
        Note over A: GAGAL! Akses ditolak
    end

Metode Pencegahan IDOR

1. Implementasi Access Control yang Ketat

Selalu validasi bahwa user yang sedang login memiliki hak untuk mengakses resource yang diminta.

<?php
class AccessControl {
    
    public static function canAccessOrder($orderId, $userId) {
        global $db;
        
        $stmt = $db->prepare(
            "SELECT COUNT(*) FROM orders WHERE id = ? AND user_id = ?"
        );
        $stmt->execute([$orderId, $userId]);
        
        return $stmt->fetchColumn() > 0;
    }
    
    public static function canModifyDocument($docId, $userId) {
        global $db;
        
        $stmt = $db->prepare(
            "SELECT user_id, is_public FROM documents WHERE id = ?"
        );
        $stmt->execute([$docId]);
        $doc = $stmt->fetch();
        
        if (!$doc) return false;
        
        // Owner bisa modify, atau jika public dan user punya permission
        return $doc['user_id'] == $userId || 
               ($doc['is_public'] && self::hasEditPermission($userId));
    }
}

// Penggunaan
$orderId = $_GET['order_id'];
$currentUser = $_SESSION['user_id'];

if (!AccessControl::canAccessOrder($orderId, $currentUser)) {
    http_response_code(403);
    die('Forbidden');
}

// Lanjutkan proses...
?>

2. Gunakan Indirect Reference Maps

Alih-alih menggunakan ID database secara langsung, gunakan reference map atau UUID yang tidak dapat diprediksi.

<?php
// ❌ VULNERABLE - Predictable ID
// URL: /invoice?id=1001

// ✅ LEBIH AMAN - UUID
// URL: /invoice?id=a3f2c8b9-4e7d-4a1c-9f6e-8d2c1b5a7e9f

class SecureReference {
    
    public static function generateUUID() {
        return sprintf(
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0x0fff) | 0x4000,
            mt_rand(0, 0x3fff) | 0x8000,
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
        );
    }
    
    public static function createInvoice($userId, $data) {
        global $db;
        
        $uuid = self::generateUUID();
        
        $stmt = $db->prepare(
            "INSERT INTO invoices (uuid, user_id, data) VALUES (?, ?, ?)"
        );
        $stmt->execute([$uuid, $userId, json_encode($data)]);
        
        return $uuid;
    }
    
    public static function getInvoice($uuid, $userId) {
        global $db;
        
        $stmt = $db->prepare(
            "SELECT * FROM invoices WHERE uuid = ? AND user_id = ?"
        );
        $stmt->execute([$uuid, $userId]);
        
        return $stmt->fetch();
    }
}
?>

3. Implementasi Role-Based Access Control (RBAC)

Untuk aplikasi yang kompleks, implementasikan sistem role dan permission yang terstruktur.

<?php
class RBAC {
    private $userRoles = [];
    private $rolePermissions = [
        'admin' => ['view_all', 'edit_all', 'delete_all'],
        'editor' => ['view_own', 'edit_own', 'view_published'],
        'viewer' => ['view_published']
    ];
    
    public function __construct($userId) {
        $this->loadUserRoles($userId);
    }
    
    private function loadUserRoles($userId) {
        global $db;
        $stmt = $db->prepare(
            "SELECT role FROM user_roles WHERE user_id = ?"
        );
        $stmt->execute([$userId]);
        $this->userRoles = $stmt->fetchAll(PDO::FETCH_COLUMN);
    }
    
    public function hasPermission($permission) {
        foreach ($this->userRoles as $role) {
            if (in_array($permission, $this->rolePermissions[$role] ?? [])) {
                return true;
            }
        }
        return false;
    }
    
    public function canAccessResource($resourceId, $resourceType) {
        if ($this->hasPermission('view_all')) {
            return true;
        }
        
        if ($this->hasPermission('view_own')) {
            return $this->isOwner($resourceId, $resourceType);
        }
        
        return false;
    }
    
    private function isOwner($resourceId, $resourceType) {
        global $db;
        $stmt = $db->prepare(
            "SELECT user_id FROM $resourceType WHERE id = ?"
        );
        $stmt->execute([$resourceId]);
        $owner = $stmt->fetchColumn();
        
        return $owner == $_SESSION['user_id'];
    }
}

// Penggunaan
$rbac = new RBAC($_SESSION['user_id']);

if (!$rbac->canAccessResource($_GET['document_id'], 'documents')) {
    http_response_code(403);
    die('Access Denied');
}
?>

4. Validasi di Sisi Server

Jangan pernah mengandalkan validasi client-side atau data yang dikirim dari client.

<?php
// ❌ JANGAN LAKUKAN INI
function badPractice() {
    // Percaya data dari client
    $userId = $_POST['user_id'];
    $role = $_COOKIE['user_role'];
}

// ✅ LAKUKAN INI
function goodPractice() {
    // Ambil dari session yang dikelola server
    $userId = $_SESSION['user_id'] ?? null;
    
    if (!$userId) {
        http_response_code(401);
        die('Not authenticated');
    }
    
    // Load role dari database
    global $db;
    $stmt = $db->prepare(
        "SELECT role FROM users WHERE id = ?"
    );
    $stmt->execute([$userId]);
    $role = $stmt->fetchColumn();
    
    return ['userId' => $userId, 'role' => $role];
}
?>

Testing untuk Menemukan IDOR

Manual Testing

Beberapa langkah untuk menguji kerentanan IDOR secara manual:

  • Buat dua akun user yang berbeda
  • Login sebagai User A, catat semua endpoint dan ID yang digunakan
  • Login sebagai User B, coba akses resource milik User A dengan mengubah parameter
  • Perhatikan response code: 200 (vulnerable), 403/404 (aman)
  • Test pada semua HTTP method: GET, POST, PUT, DELETE

Automated Testing dengan Script

<?php
// Script sederhana untuk test IDOR
class IDORTester {
    
    public function testEndpoint($url, $token, $startId, $endId) {
        $vulnerable = [];
        
        for ($id = $startId; $id <= $endId; $id++) {
            $testUrl = str_replace('{ID}', $id, $url);
            
            $ch = curl_init($testUrl);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                "Authorization: Bearer $token"
            ]);
            
            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            
            if ($httpCode == 200) {
                $vulnerable[] = [
                    'id' => $id,
                    'url' => $testUrl,
                    'response' => substr($response, 0, 100)
                ];
            }
        }
        
        return $vulnerable;
    }
}

// Penggunaan
$tester = new IDORTester();
$results = $tester->testEndpoint(
    'https://api.example.com/orders/{ID}',
    'user_token_here',
    1000,
    1100
);

if (!empty($results)) {
    echo "VULNERABLE! Found " . count($results) . " accessible IDs\n";
    print_r($results);
}
?>

Checklist Pencegahan IDOR

Gunakan checklist berikut untuk memastikan aplikasi Anda terlindungi dari IDOR:

graph TD
    A[Terima Request dengan ID] --> B{User Terauthentikasi?}
    B -->|Tidak| C[Return 401 Unauthorized]
    B -->|Ya| D{ID Valid di Database?}
    D -->|Tidak| E[Return 404 Not Found]
    D -->|Ya| F{User = Owner Resource?}
    F -->|Ya| G[Izinkan Akses]
    F -->|Tidak| H{User Punya Role Special?}
    H -->|Ya| I{Role Punya Permission?}
    H -->|Tidak| J[Return 403 Forbidden]
    I -->|Ya| G
    I -->|Tidak| J
    G --> K[Log Aktivitas]
    K --> L[Return Resource]
☐ Validasi authentication setiap request
☐ Validasi authorization sebelum akses resource
☐ Gunakan user ID dari session, bukan dari input
☐ Implementasi access control di semua endpoint
☐ Gunakan UUID atau indirect reference jika memungkinkan
☐ Log semua akses ke resource sensitif
☐ Test dengan multiple user accounts
☐ Review code secara berkala untuk celah IDOR
☐ Implementasi rate limiting untuk mencegah enumeration
☐ Gunakan RBAC untuk aplikasi kompleks

Studi Kasus: IDOR di Platform Terkenal

Kasus 1: Instagram IDOR (2019)

Peneliti keamanan menemukan IDOR pada API Instagram yang memungkinkan akses ke data private account hanya dengan mengetahui user ID. Bug ini memungkinkan attacker melihat email dan nomor telepon user lain.

Kasus 2: IDOR pada E-commerce

Sebuah platform e-commerce mengalami kebocoran data pelanggan karena endpoint order history tidak memvalidasi ownership. Attacker dapat mengakses detail pesanan, alamat pengiriman, dan informasi pembayaran user lain hanya dengan mengiterasi order ID.

Kasus 3: Healthcare App IDOR

Aplikasi healthcare mengalami kebocoran rekam medis pasien karena IDOR pada endpoint download hasil lab. Ini merupakan pelanggaran serius terhadap privacy dan regulasi HIPAA.

Tools untuk Deteksi IDOR

  • Burp Suite - Proxy tool dengan Autorize extension untuk automated IDOR testing
  • OWASP ZAP - Open source security scanner dengan plugin untuk access control testing
  • Postman - Untuk manual testing API dengan collections dan environment variables
  • Custom Scripts - Python atau PHP scripts untuk automated enumeration testing

Kesimpulan

IDOR adalah kerentanan serius yang dapat menyebabkan kebocoran data massal, namun relatif mudah dicegah dengan implementasi access control yang benar. Kunci pencegahan IDOR meliputi:

  • Selalu validasi otorisasi di server-side untuk setiap akses resource
  • Jangan pernah mengandalkan data dari client untuk menentukan akses
  • Gunakan session atau token yang tervalidasi untuk identifik
Rendi Julianto

Experienced programming developer with a passion for creating efficient, scalable solutions. Proficient in Python, JavaScript, and PHP, with expertise in web development, API integration, and software optimization. Adept at problem-solving and committed to delivering high-quality, user-centric applications.

Posting Komentar (0)
Lebih baru Lebih lama