SQL Injection in Laravel Applications
Laravel's Eloquent ORM and Query Builder parameterize queries automatically, but DB::raw(), whereRaw(), selectRaw(), and orderByRaw() bypass this protection. These raw expression methods are commonly used for complex queries and are the primary SQL injection vectors in Laravel apps.
Scan Your Laravel AppHow SQL Injection Manifests in Laravel
Laravel's Query Builder is safe by default, but raw methods inject unparameterized SQL: - DB::raw("COUNT(*) as total WHERE status = '$status'") in select clauses - whereRaw("price > $min AND price < $max") with variable interpolation - orderByRaw($request->sort) with user-controlled sort expressions - DB::select("SELECT * FROM users WHERE $condition") with string building Eloquent scopes that use raw expressions internally can hide injection vulnerabilities behind a clean interface.
Real-World Impact
A Laravel e-commerce API used whereRaw() with string concatenation for an advanced product filter. An attacker exploited the filter endpoint to perform UNION-based SQL injection, extracting customer addresses and order history. The data was used for targeted phishing attacks against high-value customers.
Step-by-Step Fix
Parameterize raw expressions
Always use binding parameters with raw query methods.
// UNSAFE - string interpolation
$users = DB::select("SELECT * FROM users WHERE role = '$role'");
$products = Product::whereRaw("price > $min AND price < $max")->get();
// SAFE - parameter bindings
$users = DB::select("SELECT * FROM users WHERE role = ?", [$role]);
$products = Product::whereRaw("price > ? AND price < ?", [$min, $max])->get();Use Query Builder instead of raw SQL
Replace raw queries with Query Builder methods whenever possible.
// UNSAFE - raw query
$products = DB::select(
"SELECT * FROM products WHERE category = '$cat' AND price < $max"
);
// SAFE - Query Builder
$products = DB::table('products')
->where('category', $request->category)
->where('price', '<', $request->max_price)
->get();
// SAFE - Eloquent
$products = Product::where('category', $request->category)
->where('price', '<', $request->max_price)
->get();Secure orderByRaw()
Validate sort parameters against an allowlist.
$allowedSorts = [
'price_asc' => 'price ASC',
'price_desc' => 'price DESC',
'newest' => 'created_at DESC',
'name' => 'name ASC',
];
$sortKey = $request->input('sort', 'newest');
$orderClause = $allowedSorts[$sortKey] ?? 'created_at DESC';
$products = Product::orderByRaw($orderClause)->paginate(20);Audit Eloquent scopes
Check scopes that use raw expressions for injection vulnerabilities.
// UNSAFE scope
public function scopeSearch($query, $term)
{
return $query->whereRaw("name LIKE '%{$term}%'");
}
// SAFE scope
public function scopeSearch($query, $term)
{
return $query->where('name', 'like', "%{$term}%");
}Prevention Best Practices
1. Use Eloquent and Query Builder methods which are automatically parameterized. 2. When using raw expressions, always pass bindings as the second parameter. 3. Never concatenate request input into raw SQL strings. 4. Validate and allowlist columns for orderByRaw() and dynamic queries. 5. Use Laravel's built-in validation to reject SQL-like input patterns.
How to Test
1. Search for raw query methods: grep -rn "DB::raw\|whereRaw\|selectRaw\|orderByRaw\|DB::select" --include="*.php" 2. Check if any raw methods use $request or $variable interpolation. 3. Test search and filter endpoints with ' OR 1=1 -- payloads. 4. Use Laravel Debugbar to inspect generated SQL queries. 5. Use Vibe App Scanner to detect SQL injection in your Laravel application.
Frequently Asked Questions
Is Laravel's Eloquent ORM safe from SQL injection?
Yes, Eloquent's standard methods (where(), find(), create()) automatically parameterize queries. SQL injection only occurs through raw methods (whereRaw, DB::raw, selectRaw) when they include unparameterized user input. Always pass bindings as the second argument to raw methods.
Does Laravel's validation prevent SQL injection?
Laravel's validation rules help reject malformed input but are not a reliable defense against SQL injection. Attackers can craft payloads that pass validation. Always use parameterized queries as the primary defense; validation is a supplementary layer.
Are DB::raw() expressions always dangerous?
No. DB::raw() with hardcoded SQL (like DB::raw("COUNT(*)")) is safe. It becomes dangerous when user input is interpolated into the SQL string. Always use the second parameter for bindings: DB::raw("COALESCE(?, 0)", [$value]).
Related Security Resources
Is Your Laravel App Vulnerable to SQL Injection?
VAS automatically scans for sql injection vulnerabilities in Laravel applications and provides step-by-step remediation guidance with code examples.
Scans from $5, results in minutes. Get actionable fixes tailored to your Laravel stack.