Panduan Lab Lengkap: SQLi (Real DB), XSS, dan IDOR

Panduan ini berisi 3 lab terpisah. Anda akan berperan sebagai Blue Team (membangun server target di VM Ubuntu) dan Red Team (menyerang server dari VM Kali).

Penting: Setiap lab akan menggunakan port yang berbeda (8080, 8081, 8082) sehingga Anda dapat menjalankan ketiga kontainer Docker secara bersamaan.


🚀 Lab 1: SQL Injection (Error-Based & Boolean-Based dengan Real DB)

Tujuan: Memicu error database asli SQLite dan mem-bypass halaman login menggunakan SQL Injection.

Bagian 1A: Setup Target (Host / Blue Team)

Di Host Ubuntu Anda, buat server yang rentan terhadap SQLi.

1. Buat Folder & File Lab:

mkdir ~/lab-sqli
cd ~/lab-sqli

2. Buat Dockerfile (Diperbarui):

nano Dockerfile

Salin-tempel konten berikut:

# Gunakan base image PHP 8.1 dengan Apache
FROM php:8.1-apache

# [PERBAIKAN] Instal dependensi sistem (libsqlite3-dev) terlebih dahulu
RUN apt-get update && apt-get install -y \
    libsqlite3-dev \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

# Instal ekstensi PDO dan SQLite (untuk database nyata)
RUN docker-php-ext-install pdo pdo_sqlite

# Buat file DB dan berikan izin agar Apache (www-data) bisa menulis
RUN touch /var/www/html/users.db && chown www-data:www-data /var/www/html/users.db && chmod 664 /var/www/html/users.db

# Salin file web kita ke dalam container
COPY index.php /var/www/html/index.php
COPY product.php /var/www/html/product.php

# Expose port 80
EXPOSE 80

Simpan dan keluar (Ctrl+O, Enter, Ctrl+X).

3. Buat Halaman index.php (Rentan Bypass, Real DB):

nano index.php

Salin-tempel kode PHP berikut. Ini akan membuat dan mengisi DB saat pertama kali dijalankan.

<?php
// --- DB Setup ---
$db_file = '/var/www/html/users.db';
$db_exists = file_exists($db_file);

try {
    $db = new PDO('sqlite:' . $db_file);
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // Buat tabel jika belum ada
    $db->exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)");
    $db->exec("CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, name TEXT, description TEXT)");

    if ($db->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) {
         // Isi data default jika tabel users kosong
         // Kita hash passwordnya untuk perbandingan di lab perbaikan
        $hashed_pass = password_hash('password123', PASSWORD_DEFAULT);
        $db->exec("INSERT INTO users (username, password) VALUES ('admin', '$hashed_pass')");
    }
    if ($db->query("SELECT COUNT(*) FROM products")->fetchColumn() == 0) {
        // Isi data default jika tabel products kosong
        $db->exec("INSERT INTO products (id, name, description) VALUES (1, 'Laptop', 'Laptop Canggih'), (2, 'Mouse', 'Mouse Optik')");
    }

} catch (PDOException $e) {
    die("Error setting up database: " . $e->getMessage());
}

// --- Login Logic ---
$login_message = '';
$sql_query_display = '';

if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['username'])) {
    $user = $_POST['username'];
    $pass = $_POST['password'];

    // --- VULNERABLE CODE ---
    $sql = "SELECT * FROM users WHERE username = '$user' AND password = '$pass'";
    $sql_query_display = $sql; 

    try {
        $stmt = $db->query($sql);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($result) {
            $login_message = "<h3 style='color:green;'>Login Berhasil!</h3><p>Selamat datang, " . htmlspecialchars($result['username']) . "</p>";
            // Payload bypass sederhana
            if (strpos($user, "'") !== false) {
                $login_message .= "<p>Token Rahasia (dari bypass): flag{real_sqli_bypass_sukses_31337}</p>";
            }
        } else {
            $login_message = "<h3 style='color:red;'>Login Gagal. Kredensial salah.</h3>";
        }

    } catch (PDOException $e) {
        $login_message = "<h3 style='color:red;'>SQL Error:</h3><pre>" . htmlspecialchars($e->getMessage()) . "</pre>";
    }
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <title>Lab SQLi (Real DB) - Login</title>
    <style>
        body { font-family: sans-serif; margin: 20px; background: #f4f4f4; }
        .container { max-width: 400px; margin: 50px auto; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
        input[type="text"], input[type="password"] { width: 100%; padding: 8px; margin-bottom: 10px; box-sizing: border-box; }
        input[type="submit"] { background: #007bff; color: white; padding: 10px; border: none; border-radius: 4px; cursor: pointer; }
        pre { background: #eee; padding: 10px; border-radius: 4px; white-space: pre-wrap; word-wrap: break-word; }
    </style>
</head>
<body>
    <div class="container">
        <h2>Halaman Login (Boolean-Based, Real DB)</h2>
        <form method="POST" action="index.php">
            <input type="text" name="username" placeholder="Username">
            <input type="password" name="password" placeholder="Password">
            <input type="submit" name="Login" value="Login">
        </form>
        <p>Coba juga halaman produk: <a href="product.php?id=1">product.php?id=1</a></p>
        
        <?php
        if (!empty($sql_query_display)) {
            echo "<hr><p><b>Query SQL yang dijalankan:</b><pre>" . htmlspecialchars($sql_query_display) . "</pre></p>";
        }
        if (!empty($login_message)) {
            echo $login_message;
        }
        ?>
    </div>
</body>
</html>

Simpan dan keluar.

4. Buat Halaman product.php (Rentan Error-Based, Real DB):

nano product.php

Salin-tempel kode ini. Halaman ini akan memicu **error SQLite asli**.

<!DOCTYPE html>
<html><head><title>Lab SQLi - Produk (Real DB)</title></head>
<body>
<h2>Detail Produk (Error-Based, Real DB)</h2>
<p><a href="index.php">Kembali ke Login</a></p>
<?php
    // --- DB Setup ---
    $db_file = '/var/www/html/users.db'; // Gunakan DB yang sama
    try {
        $db = new PDO('sqlite:' . $db_file);
        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
        die("Error connecting to database: " . $e->getMessage());
    }

    if (isset($_GET['id'])) {
        $id = $_GET['id']; // Vulnerable
        
        // --- VULNERABLE CODE ---
        $sql = "SELECT * FROM products WHERE id = $id LIMIT 1"; 
        echo "Menjalankan kueri: <pre>" . htmlspecialchars($sql) . "</pre>";

        try {
            $stmt = $db->query($sql);
            $product = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($product) {
                echo "<p>Menampilkan produk: " . htmlspecialchars($product['name']) . "</p>";
                echo "<p>Deskripsi: " . htmlspecialchars($product['description']) . "</p>";
            } else {
                echo "<p>Produk tidak ditemukan.</p>";
            }
        } catch (PDOException $e) {
            // --- ERROR-BASED ---
            // Ini akan menampilkan error SQLite asli
            echo "<hr><b style='color:red;'>SQLite Error:</b> <pre>" . htmlspecialchars($e->getMessage()) . "</pre>";
        }

    } else {
        echo "<p>Silakan berikan ID produk, cth: <a href='product.php?id=1'>product.php?id=1</a></p>";
    }
?>
</body></html>

Simpan dan keluar.

5. Build & Jalankan Container:

sudo docker build -t lab-sqli-target .
sudo docker run -d -p 8080:80 --name target-sqli-lab lab-sqli-target

Target Anda sekarang berjalan di http://[IP_UBUNTU_ANDA]:8080.

Bagian 1B: Skenario Penyerangan (Attacker / Red Team)

Di VM Kali Linux Anda, buka terminal.

1. Serangan Error-Based (Manual):

Buka browser di Kali dan kunjungi URL berikut. Tambahkan tanda kutip (') di akhir.

http://[IP_UBUNTU_ANDA]:8080/product.php?id=1'

Hasil: Halaman akan menampilkan error SQLite asli, contoh: SQLite Error: ... unrecognized token: "'1'".

2. Serangan Error-Based (sqlmap):

Gunakan sqlmap (dari toolkit Anda) untuk mengeksploitasi error ini.

sqlmap -u "http://[IP_UBUNTU_ANDA]:8080/product.php?id=1" --batch --level=5 --risk=3 --dbms=sqlite

Hasil: sqlmap akan mengidentifikasi kerentanan dan bahkan bisa men-dump nama tabel (`users`, `products`).

3. Serangan Boolean-Based (Manual):

Buka http://[IP_UBUNTU_ANDA]:8080/index.php. Coba login dengan payload klasik SQLite di field Username. Password bisa diisi apa saja.

admin' --

Hasil: Halaman akan memuat ulang dan menampilkan "Login Berhasil!" serta flagnya.

Bagian 1C: Remediasi & Verifikasi (Blue Team)

Perbaikan yang **benar** adalah menggunakan **Prepared Statements** (Query berparameter).

1. Hentikan & Hapus Container Lama:

sudo docker stop target-sqli-lab
sudo docker rm target-sqli-lab
cd ~/lab-sqli

2. Perbaiki index.php (Halaman Login):

Buka file `index.php` dan ganti **SELURUH ISINYA** dengan kode aman di bawah ini.

nano index.php
<?php
// --- DB Setup ---
$db_file = '/var/www/html/users.db';
try {
    $db = new PDO('sqlite:' . $db_file);
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $db->exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)");
    if ($db->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) {
        $hashed_pass = password_hash('password123', PASSWORD_DEFAULT);
        $db->exec("INSERT INTO users (username, password) VALUES ('admin', '$hashed_pass')");
    }
} catch (PDOException $e) {
    die("Error setting up database: " . $e->getMessage());
}

// --- Login Logic ---
$login_message = '';
$sql_query_display = '';

if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['username'])) {
    $user = $_POST['username'];
    $pass = $_POST['password'];

    // --- FIXED CODE (Prepared Statements) ---
    $sql = "SELECT * FROM users WHERE username = ?"; // Hanya username
    $sql_query_display = "SELECT * FROM users WHERE username = ?";

    try {
        $stmt = $db->prepare($sql);
        $stmt->execute([$user]); // Bind parameter
        $result = $stmt->fetch(PDO::FETCH_ASSOC);

        // Verifikasi password yang benar
        if ($result && password_verify($pass, $result['password'])) {
            $login_message = "<h3 style='color:green;'>Login Berhasil!</h3><p>Selamat datang, " . htmlspecialchars($result['username']) . "</p>";
        } else {
            $login_message = "<h3 style='color:red;'>Login Gagal. Kredensial salah.</h3>";
        }
    } catch (PDOException $e) {
        $login_message = "<h3 style='color:red;'>SQL Error:</h3><pre>" . htmlspecialchars($e->getMessage()) . "</pre>";
    }
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <title>Lab SQLi (AMAN)</title>
    <style>
        body { font-family: sans-serif; margin: 20px; background: #f4f4f4; }
        .container { max-width: 400px; margin: 50px auto; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
        input[type="text"], input[type="password"] { width: 100%; padding: 8px; margin-bottom: 10px; box-sizing: border-box; }
        input[type="submit"] { background: #007bff; color: white; padding: 10px; border: none; border-radius: 4px; cursor: pointer; }
        pre { background: #eee; padding: 10px; border-radius: 4px; white-space: pre-wrap; word-wrap: break-word; }
    </style>
</head>
<body>
    <div class="container">
        <h2>Halaman Login (Aman)</h2>
        <form method="POST" action="index.php">
            <input type="text" name="username" placeholder="Username">
            <input type="password" name="password" placeholder="Password">
            <input type="submit" name="Login" value="Login">
        </form>
        <p>Coba juga halaman produk: <a href="product.php?id=1">product.php?id=1</a></p>
        
        <?php
        if (!empty($sql_query_display)) {
            echo "<hr><p><b>Query SQL yang dijalankan:</b><pre>" . htmlspecialchars($sql_query_display) . "</pre></p>";
        }
        if (!empty($login_message)) {
            echo $login_message;
        }
        ?>
    </div>
</body>
</html>

Simpan dan keluar.

3. Perbaiki product.php (Halaman Produk):

Buka file `product.php` dan ganti **SELURUH ISINYA** dengan kode aman di bawah ini.

nano product.php
<!DOCTYPE html>
<html><head><title>Lab SQLi - Produk (Aman)</title></head>
<body>
<h2>Detail Produk (Aman)</h2>
<p><a href="index.php">Kembali ke Login</a></p>
<?php
    // --- DB Setup ---
    $db_file = '/var/www/html/users.db'; // Gunakan DB yang sama
    try {
        $db = new PDO('sqlite:' . $db_file);
        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        // Pastikan tabel ada jika file ini diakses lebih dulu
        $db->exec("CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, name TEXT, description TEXT)");
        if ($db->query("SELECT COUNT(*) FROM products")->fetchColumn() == 0) {
            $db->exec("INSERT INTO products (id, name, description) VALUES (1, 'Laptop', 'Laptop Canggih'), (2, 'Mouse', 'Mouse Optik')");
        }
    } catch (PDOException $e) {
        die("Error connecting to database: " . $e->getMessage());
    }

    if (isset($_GET['id'])) {
        $id = $_GET['id'];
        
        // --- FIXED CODE (Prepared Statements) ---
        $sql = "SELECT * FROM products WHERE id = ? LIMIT 1"; 
        echo "Menjalankan kueri aman: <pre>" . htmlspecialchars($sql) . "</pre>";
        
        try {
            $stmt = $db->prepare($sql);
            $stmt->execute([$id]); // Bind parameter
            $product = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($product) {
                echo "<p>Menampilkan produk: " . htmlspecialchars($product['name']) . "</p>";
                echo "<p>Deskripsi: " . htmlspecialchars($product['description']) . "</p>";
            } else {
                echo "<p>Produk tidak ditemukan atau ID tidak valid.</p>";
            }
        } catch (PDOException $e) {
            // Ini seharusnya tidak terjadi lagi, tapi sebagai penjaga
            echo "<hr><b style='color:red;'>Error:</b> <pre>" . htmlspecialchars($e->getMessage()) . "</pre>";
        }

    } else {
        echo "<p>Silakan berikan ID produk, cth: <a href='product.php?id=1'>product.php?id=1</a></p>";
    }
?>
</body></html>

Simpan dan keluar.

4. Build Ulang Container yang Aman:

sudo docker build -t lab-sqli-target-aman .
sudo docker run -d -p 8080:80 --name target-sqli-lab-aman lab-sqli-target-aman

5. Verifikasi (Red Team):

Buka http://[IP_ANDA]:8080/product.php?id=1'. Hasilnya: Halaman tidak akan error. Akan tertulis "Produk tidak ditemukan...".

Buka http://[IP_ANDA]:8080/index.php dan masukkan admin' --. Hasilnya: Login Gagal. Serangan SQLi telah diperbaiki.


🎨 Lab 2: Cross-Site Scripting (Reflected & Stored)

Tujuan: Mengeksploitasi Reflected XSS di pencarian dan Stored XSS di buku tamu.

Bagian 2A: Setup Target (Host / Blue Team)

Di Host Ubuntu Anda, buat server yang rentan terhadap XSS.

1. Buat Folder & File Lab:

mkdir ~/lab-xss
cd ~/lab-xss

2. Buat Dockerfile (Diperbarui):

nano Dockerfile

Salin-tempel konten berikut (kita menambahkan libsqlite3-dev dan pdo_sqlite):

FROM php:8.1-apache

# [PERBAIKAN] Instal dependensi sistem (libsqlite3-dev) terlebih dahulu
RUN apt-get update && apt-get install -y \
    libsqlite3-dev \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

# Instal PDO dan SQLite untuk database buku tamu
RUN docker-php-ext-install pdo pdo_sqlite

# Atur izin agar Apache bisa menulis ke database
RUN touch /var/www/html/comments.db && chown www-data:www-data /var/www/html/comments.db && chmod 664 /var/www/html/comments.db
COPY . /var/www/html/
EXPOSE 80

Simpan dan keluar.

3. Buat Halaman search.php (Rentan Reflected XSS):

nano search.php

Salin-tempel kode ini:

<!DOCTYPE html><html><head><title>Lab XSS - Pencarian</title></head>
<body>
    <h2>Pencarian Produk (Reflected)</h2>
    <form method="GET" action="search.php">
        <input type="text" name="q" placeholder="Cari...">
        <input type="submit" value="Cari">
    </form>
    <div class="results">
        <?php
            if (isset($_GET['q'])) {
                // --- VULNERABLE CODE ---
                echo "Menampilkan hasil pencarian untuk: " . $_GET['q'];
            }
        ?>
    </div>
    <a href="guestbook.php">Pergi ke Buku Tamu</a>
</body></html>

Simpan dan keluar.

4. Buat Halaman guestbook.php (Rentan Stored XSS):

nano guestbook.php

Salin-tempel kode ini. Halaman ini akan menyimpan dan menampilkan komentar tanpa sanitasi.

<?php
// Setup Database SQLite
$db_file = '/var/www/html/comments.db';
try {
    $db = new PDO('sqlite:' . $db_file);
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    // Buat tabel jika belum ada
    $db->exec("CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, comment TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)");
} catch (PDOException $e) {
    die("Error: " . $e->getMessage());
}

// Logika POST (Menyimpan data)
if ($_SERVER["REQUEST_METHOD"] == "POST" && !empty($_POST['comment'])) {
    // --- VULNERABLE CODE (Input) ---
    // Menyimpan data langsung ke database
    $stmt = $db->prepare("INSERT INTO comments (name, comment) VALUES (?, ?)");
    $stmt->execute([$_POST['name'], $_POST['comment']]);
    header("Location: guestbook.php"); // Refresh halaman
    exit;
}
?>

<!DOCTYPE html><html><head><title>Lab XSS - Buku Tamu</title></head>
<body>
    <h2>Buku Tamu (Stored XSS)</h2>
    <form method="POST" action="guestbook.php">
        Nama: <input type="text" name="name"><br>
        Komentar: <textarea name="comment"></textarea><br>
        <input type="submit" value="Kirim">
    </form>
    <a href="search.php">Pergi ke Pencarian</a>
    <hr>
    <h3>Komentar:</h3>
    <?php
        // Logika GET (Menampilkan data)
        $result = $db->query("SELECT * FROM comments ORDER BY timestamp DESC");
        foreach ($result as $row) {
            // --- VULNERABLE CODE (Output) ---
            // Menampilkan data langsung dari DB tanpa sanitasi
            echo "<div style='border:1px solid #ccc; padding:10px; margin-bottom:10px;'>";
            echo "<b>" . $row['name'] . "</b> menulis:<br>";
            echo "<p>" . $row['comment'] . "</p>";
            echo "<small>" . $row['timestamp'] . "</small>";
            echo "</div>";
        }
    ?>
</body></html>

Simpan dan keluar.

5. Build & Jalankan Container:

sudo docker build -t lab-xss-target .
sudo docker run -d -p 8081:80 --name target-xss-lab lab-xss-target

Target Anda sekarang berjalan di port 8081.

Bagian 2B: Skenario Penyerangan (Attacker / Red Team)

Di VM Kali Linux Anda.

1. Serangan Reflected XSS (Browser):

Buka browser di Kali dan kunjungi URL berikut. Ganti IP-nya.

http://[IP_UBUNTU_ANDA]:8081/search.php?q=<script>alert('Reflected XSS')</script>

Hasil: Browser Anda akan langsung memunculkan kotak "alert". Ini hanya memengaruhi Anda.

2. Serangan Stored XSS (Browser):

  1. Buka http://[IP_UBUNTU_ANDA]:8081/guestbook.php.
  2. Isi Nama (cth: "Attacker").
  3. Di kotak Komentar, masukkan payload: <script>alert('Stored XSS by Root Bakar')</script>
  4. Klik "Kirim".

Hasil: Halaman akan me-refresh, dan kotak "alert" akan muncul. Tutup alert-nya, lalu refresh halaman (F5) lagi. Alert akan muncul **LAGI**. Ini karena payload tersimpan di database.

Bagian 2C: Remediasi & Verifikasi (Blue Team)

Perbaikan untuk XSS (Reflected dan Stored) adalah sama: **selalu lakukan *escaping* (sanitasi) data pada saat menampilkannya (output)**.

1. Hentikan & Hapus Container Lama:

sudo docker stop target-xss-lab
sudo docker rm target-xss-lab
cd ~/lab-xss

2. Perbaiki search.php (Sanitasi Output):

nano search.php

Ubah baris PHP yang rentan:

UBAH DARI: echo "Menampilkan hasil pencarian untuk: " . $_GET['q'];

UBAH MENJADI:

// --- FIXED CODE ---
echo "Menampilkan hasil pencarian untuk: " . htmlspecialchars($_GET['q'], ENT_QUOTES, 'UTF-8');

Simpan dan keluar.

3. Perbaiki guestbook.php (Sanitasi Output):

nano guestbook.php

Temukan bagian `// Logika GET (Menampilkan data)`. Ubah blok `echo` yang rentan:

UBAH DARI:

            echo "<b>" . $row['name'] . "</b> menulis:<br>";
            echo "<p>" . $row['comment'] . "</p>";

UBAH MENJADI (kode aman):

            // --- FIXED CODE (Output Sanitization) ---
            echo "<b>" . htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8') . "</b> menulis:<br>";
            echo "<p>" . htmlspecialchars($row['comment'], ENT_QUOTES, 'UTF-8') . "</p>";

Simpan dan keluar.

4. Build & Jalankan Container Aman:

sudo docker build -t lab-xss-target-aman .
sudo docker run -d -p 8081:80 --name target-xss-lab-aman lab-xss-target-aman

5. Verifikasi (Red Team):

Buka http://[IP_ANDA]:8081/search.php?q=<script>alert(1)</script>. Hasil: Tidak ada alert, teks payload tercetak di layar.

Buka http://[IP_ANDA]:8081/guestbook.php. Hasil: Tidak ada alert, komentar lama Anda (jika masih ada) akan ditampilkan sebagai teks <script>..., bukan dieksekusi.


🔑 Lab 3: Insecure Direct Object Reference (IDOR)

Tujuan: Mengakses data pengguna lain dengan memanipulasi parameter ID di URL.

Bagian 3A: Setup Target (Host / Blue Team)

Di Host Ubuntu Anda, buat server yang rentan terhadap IDOR.

1. Buat Folder & File Lab:

mkdir ~/lab-idor
cd ~/lab-idor

2. Buat Dockerfile:

nano Dockerfile

Salin-tempel konten berikut:

FROM php:8.1-apache
COPY . /var/www/html/
# Aktifkan session
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
EXPOSE 80

Simpan dan keluar.

3. Buat Halaman index.php (Halaman Login):

nano index.php

Salin-tempel kode ini. Ini adalah halaman login palsu.

<?php session_start(); ?>
<!DOCTYPE html>
<html>
<head><title>Lab IDOR - Login</title></head>
<body>
    <h2>Login sebagai Pengguna</h2>
    <p>Klik untuk login sebagai 'Pengguna Anda' (ID: 1) atau 'User Lain' (ID: 2).</p>
    <ul>
        <li><a href="login_process.php?id=1" style="font-size: 1.2em;">Login sebagai 'Pengguna Anda' (ID 1)</a></li>
        <li><a href="login_process.php?id=2" style="font-size: 1.2em;">Login sebagai 'User Lain' (ID 2)</a></li>
    </ul>
</body>
</html>

Simpan dan keluar.

4. Buat login_process.php (Penyetel Sesi):

nano login_process.php

Salin-tempel kode ini. Ini mensimulasikan login.

<?php
session_start();
$user_id = $_GET['id'] ?? 1;
$_SESSION['user_id'] = $user_id;
$_SESSION['username'] = ($user_id == 1) ? 'Pengguna Anda' : 'User Lain';

// Arahkan ke halaman profil
header("Location: profile.php?id=" . $user_id);
exit;

Simpan dan keluar.

5. Buat Halaman profile.php yang Rentan:

nano profile.php

Salin-tempel kode PHP berikut. Halaman ini **rentan** karena tidak memeriksa sesi.

<?php
session_start();

// Database Palsu
$users_data = [
    '1' => [
        'nama' => 'Pengguna Anda',
        'email_privat' => 'anda@gmail.com',
        'no_hp_rahasia' => '081234567890'
    ],
    '2' => [
        'nama' => 'User Lain',
        'email_privat' => 'userlain@gmail.com',
        'no_hp_rahasia' => '089876543210'
    ]
];

// --- VULNERABLE CODE ---
// Mengambil data HANYA berdasarkan parameter 'id' dari URL.
// TIDAK memvalidasi siapa yang sedang login (dari $_SESSION)
$requested_id = $_GET['id'] ?? 0;

if (!array_key_exists($requested_id, $users_data)) {
    die("Error: User tidak ditemukan.");
}

$user = $users_data[$requested_id];
?>

<!DOCTYPE html>
<html>
<head>
    <title>Profil Pengguna</title>
    <style>
        body { font-family: sans-serif; margin: 20px; }
        .profile { background: #fff; border: 1px solid #ccc; padding: 20px; border-radius: 8px; }
        .welcome { font-size: 1.2em; }
        .secret { color: red; font-weight: bold; background: #fff8c4; padding: 10px; }
    </style>
</head>
<body>
    <p class="welcome">
        Anda login sebagai: <b><?php echo htmlspecialchars($_SESSION['username'] ?? 'Tamu'); ?></b> (ID: <?php echo htmlspecialchars($_SESSION['user_id'] ?? 'N/A'); ?>)
    </p>
    <hr>

    <div class="profile">
        <h2>Profil Milik: <?php echo htmlspecialchars($user['nama']); ?></h2>
        <p class="secret">Email Privat: <?php echo htmlspecialchars($user['email_privat']); ?></p>
        <p class="secret">No. HP Rahasia: <?php echo htmlspecialchars($user['no_hp_rahasia']); ?></p>
    </div>
</body>
</html>

Simpan dan keluar.

6. Build & Jalankan Container:

sudo docker build -t lab-idor-target .
sudo docker run -d -p 8082:80 --name target-idor-lab lab-idor-target

Target Anda sekarang berjalan di http://[IP_UBUNTU_ANDA]:8082.

Bagian 3B: Skenario Penyerangan (Attacker / Red Team)

Di VM Kali Linux Anda.

1. Skenario Login Normal:

  1. Buka http://[IP_UBUNTU_ANDA]:8082 di browser Anda.
  2. Klik link "Login sebagai 'Pengguna Anda' (ID 1)".
  3. Anda akan diarahkan ke profile.php?id=1.
  4. Perhatikan: Halaman menampilkan info pribadi Anda ("email_privat: anda@gmail.com").

2. Eksploitasi IDOR (Manual):

  1. Sekarang, di address bar browser Anda, ubah URL secara manual.
  2. Ubah profile.php?id=1 menjadi profile.php?id=2 dan tekan Enter.

Hasil: Meskipun Anda login sebagai "Pengguna Anda (ID 1)", halaman sekarang menampilkan data sensitif milik "User Lain (ID 2)", yaitu "email_privat: userlain@gmail.com". Ini adalah IDOR.

Bagian 3C: Remediasi & Verifikasi (Blue Team)

Cara memperbaiki ini adalah dengan **memvalidasi ID sesi** terhadap ID yang diminta.

1. Hentikan & Hapus Container Lama:

sudo docker stop target-idor-lab
sudo docker rm target-idor-lab
cd ~/lab-idor

2. Perbaiki profile.php (Sanitasi Sesi):

nano profile.php

Tambahkan blok kode validasi ini tepat setelah `session_start();` di bagian atas file.

<?php
session_start();

// --- FIXED CODE (VALIDASI SESI) ---
// Cek apakah pengguna sudah login
if (!isset($_SESSION['user_id'])) {
    // Arahkan ke halaman login jika belum login
    header("Location: index.php");
    exit;
}

// Cek apakah ID yang diminta sama dengan ID yang login
if ($_GET['id'] != $_SESSION['user_id']) {
    // Jika tidak sama, tampilkan pesan error dan hentikan eksekusi
    die("Akses Ditolak! Anda tidak diizinkan melihat profil ini.");
}
// --- AKHIR PERBAIKAN ---


// Database Palsu
$users_data = [
// ... (sisa file biarkan sama) ...

Simpan dan keluar.

3. Build & Jalankan Container Aman:

sudo docker build -t lab-idor-target-aman .
sudo docker run -d -p 8082:80 --name target-idor-lab-aman lab-idor-target-aman

4. Verifikasi (Red Team):

  1. Buka http://[IP_ANDA]:8082 dan login sebagai "Pengguna Anda (ID 1)".
  2. Anda akan diarahkan ke profile.php?id=1 (Ini akan berhasil).
  3. Sekarang, ubah URL secara manual ke profile.php?id=2.

Hasil: Halaman akan menampilkan "Akses Ditolak! Anda tidak diizinkan melihat profil ini." Kerentanan IDOR telah berhasil ditutup.