What Is SQL Injection?
A vulnerability where an attacker inserts malicious SQL code into a query through user input. If the application passes user input directly into SQL queries without sanitization, the attacker can read, modify, or delete data, and in some cases execute commands on the database server.
How It Works
SQL injection works by inserting SQL grammar into user-supplied input that reaches a database query, causing the database to interpret the input as commands rather than data.
Step 1: Identifying the injection point — The attacker probes inputs that are likely to reach the database: search fields, URL ID parameters (/users/123), filter dropdowns, and sort parameters. They submit a single apostrophe ' and observe the response — a SQL syntax error, a different result set, or a change in behavior confirms the parameter reaches a query without parameterization.
Step 2: Determining the query structure — To construct a UNION SELECT exploit, the attacker must know how many columns the original query selects. They submit 1 ORDER BY 1--, then 1 ORDER BY 2--, incrementing until a SQL error appears, indicating the column count has been exceeded. With five columns confirmed, they proceed.
Step 3: UNION-based extraction — The attacker constructs: 1 UNION SELECT username, password, email, null, null FROM users--. The database executes both the original query and the attacker's SELECT, returning all rows from the users table merged with the original result set. The application renders both, revealing every username, hashed password, and email.
Step 4: Blind injection (when no output is visible) — If the application does not display query results directly, the attacker uses boolean-based blind injection. 1 AND SUBSTRING((SELECT password FROM users WHERE id=1),1,1)='a' returns true (normal response) or false (empty/error response). By iterating through characters, the attacker extracts data one character at a time. Time-based blind uses AND SLEEP(5)-- to infer data from response timing when even response differences are invisible.
Step 5: Escalation — In MySQL with FILE privilege, LOAD_FILE('/etc/passwd') reads server files. INTO OUTFILE writes webshells to disk. In some configurations, this enables full server compromise from a single SQL injection vulnerability.
Types of SQL Injection
UNION-Based Injection
Attacker appends a UNION SELECT statement to extract data from arbitrary tables in the same database — results are returned directly in the application response.
GET /products?category=1 UNION SELECT username,password,email,null FROM users-- HTTP/1.1
Error-Based Injection
Database error messages are engineered to contain query output — effective when the application displays database errors (which `APP_DEBUG=true` does in Laravel).
GET /users?id=1 AND extractvalue(1,concat(0x7e,(SELECT password FROM users LIMIT 1))) HTTP/1.1
Boolean-Based Blind Injection
Attacker infers data one bit at a time by observing whether the page returns a normal response (true condition) or an empty/error response (false condition).
# True: page loads normally
GET /users?id=1 AND 1=1--
# False: page returns no results
GET /users?id=1 AND 1=2--
Time-Based Blind Injection
Attacker extracts data by measuring server response time — a `SLEEP(5)` that causes a 5-second delay confirms the condition is true.
GET /users?id=1 AND IF(1=1,SLEEP(5),0)-- HTTP/1.1
# 5-second delay confirms injection; adjust condition to extract data
In Laravel Applications
Laravel's Eloquent ORM uses parameterized queries by default, which prevents SQL injection. Vulnerabilities are introduced when using DB::raw(), DB::statement(), or whereRaw() with unescaped user input. Always use parameter binding: DB::select("SELECT * FROM users WHERE email = ?", [$email]).
Code Examples
Raw Query Interpolation vs. Parameterized Binding
Vulnerable
// VULNERABLE: User input concatenated into SQL string
// Attacker sends: id=1 UNION SELECT email,password,null FROM users--
public function show(Request $request): JsonResponse
{
$results = DB::select(
"SELECT * FROM products WHERE id = " . $request->input('id')
);
return response()->json($results);
}
// Also vulnerable with whereRaw:
Product::whereRaw("name = '" . $request->input('name') . "'")->get();
Secure
// SECURE: User input passed as bound parameter — treated as data, not SQL
public function show(Request $request): JsonResponse
{
// Option 1: Parameterized DB::select with array binding
$results = DB::select(
"SELECT * FROM products WHERE id = ?",
[$request->input('id')]
);
// Option 2: Eloquent (uses PDO prepared statements by default)
$product = Product::findOrFail($request->input('id'));
// Option 3: whereRaw with bound parameters (safe)
$products = Product::whereRaw("name = ?", [$request->input('name')])->get();
return response()->json($results);
}
Real-World Example
DB::select("SELECT * FROM users WHERE id = " . $request->id) is vulnerable. An attacker can send id=1 OR 1=1 to dump all users. Use DB::select("SELECT * FROM users WHERE id = ?", [$request->id]) instead.
Why It Matters
SQL injection remains one of the most critical vulnerabilities in web applications because the consequences are severe: complete database extraction, authentication bypass, and in some database configurations, operating system command execution. Despite being one of the oldest known vulnerabilities, it continues to appear in Laravel applications where developers bypass the ORM's protections.
Eloquent's Query Builder uses PDO prepared statements and parameterized queries by default, which is the correct defense against SQL injection. The vulnerability is introduced specifically in four "raw" methods: DB::raw(), whereRaw(), orderByRaw(), and selectRaw(). When user input is string-concatenated into these methods rather than passed as a bound parameter, SQL injection becomes possible.
The impact in Laravel is compounded by the fact that the database user configured in config/database.php often has broad permissions (SELECT, INSERT, UPDATE, DELETE on all tables) for development convenience. In production, principle of least privilege dictates separate database users for different application roles, but this is rarely implemented.
Laravel's $fillable and $guarded properties on Eloquent models prevent mass assignment vulnerabilities, which are a related but distinct class of injection. The $guarded = [] shortcut — used to disable mass assignment protection entirely — combined with Model::create($request->all()) can allow attackers to set arbitrary model attributes.
Common Misconceptions
Myth: We use Laravel's query builder, so SQL injection is impossible.
Reality: The query builder is safe when used correctly, but `whereRaw()`, `orderByRaw()`, `selectRaw()`, and `DB::raw()` all bypass parameterization if you string-interpolate user input directly into the expression.
Myth: Escaping user input with `addslashes()` or `htmlspecialchars()` prevents SQL injection.
Reality: Input escaping is an unreliable defense against SQL injection. Parameterized queries (which Eloquent uses by default) are the correct and only reliable defense. Escaping functions have known bypasses and do not cover all edge cases.
Myth: SQL injection only lets attackers read data, not modify it.
Reality: SQL injection can enable SELECT (read all data), INSERT/UPDATE/DELETE (modify data), and in some configurations, file system access via `LOAD_FILE` or `INTO OUTFILE` on MySQL.
How to Detect This
Search your codebase for raw query usage with `grep -rn "whereRaw\|orderByRaw\|selectRaw\|DB::raw\|DB::statement\|DB::select" app/` and review each occurrence to verify user input is passed as bound parameters (second array argument) rather than interpolated into the string. OWASP ZAP's active scan automatically tests for SQL injection by submitting malicious payloads in form fields and URL parameters. StackShield monitors for unusual error patterns in your application that may indicate SQL injection probing. In your database server logs, look for queries containing SQL keywords like `UNION SELECT`, `OR 1=1`, or `DROP TABLE` in the query text.
How to Prevent This in Laravel
-
1
Run `grep -rn "whereRaw\|orderByRaw\|selectRaw\|DB::raw\|DB::select\|DB::statement" app/` and review every result — verify user input is in the second parameter array, never interpolated into the SQL string.
-
2
Use Eloquent ORM for all standard CRUD operations — `User::find()`, `User::where()->get()`, `User::create()` all use PDO prepared statements automatically.
-
3
When you must use raw methods, always pass user input as bound parameters: `whereRaw("column = ?", [$userInput])` not `whereRaw("column = '$userInput'")`.
-
4
Configure the database user in `config/database.php` with minimum required permissions — `SELECT`, `INSERT`, `UPDATE`, `DELETE` on your application's tables only, never `FILE` or `SUPER` privileges.
-
5
Enable query logging in development with `DB::enableQueryLog()` and periodically review `DB::getQueryLog()` to verify user input never appears embedded in the SQL string — it should always appear as a separate binding.
Frequently Asked Questions
Does using Eloquent ORM prevent all SQL injection in Laravel?
Eloquent's standard query methods (where, find, first, get, create, update, delete) use PDO prepared statements and are safe against SQL injection. The vulnerability is introduced specifically through the raw methods: `DB::raw()`, `whereRaw()`, `orderByRaw()`, `selectRaw()`, `DB::select()`, and `DB::statement()`. When you use any of these with string-concatenated user input instead of bound parameter arrays, SQL injection is possible.
Is `whereRaw()` ever safe to use?
`whereRaw()` is safe when user input is passed as the second parameter (bound parameters array): `->whereRaw("status = ? AND priority > ?", [$status, $priority])`. It is vulnerable when user input is string-interpolated into the first parameter (the SQL expression): `->whereRaw("name = '$name'")`. The rule is simple: if you see a PHP variable inside the SQL string argument, it is vulnerable.
Can stored procedures in MySQL prevent SQL injection in Laravel?
Stored procedures help only if they also use parameterized queries internally — a stored procedure that concatenates user input into dynamic SQL is still vulnerable. In Laravel, calling a stored procedure via `DB::statement("CALL my_proc(?)", [$param])` is safe because the parameter is bound. The binding happens at the PHP/PDO level, before any SQL reaches the database engine, regardless of whether the target is a table query or a stored procedure.
How do I test my Laravel application for SQL injection?
Start with a code review: `grep -rn "whereRaw\|DB::raw\|DB::select" app/` and manually inspect each call. For dynamic testing, run OWASP ZAP's active scan against your staging environment — it automatically submits SQL injection payloads to every discovered form field and URL parameter. For comprehensive testing, use sqlmap against specific endpoints: `sqlmap -u "https://staging.yourapp.com/search?q=test" --dbs` which attempts dozens of injection techniques automatically.
Related Terms
OWASP (Open Worldwide Application Security Project)
A nonprofit foundation that produces freely available tools, documentation, and standards for web application security. OWASP is best known for the OWASP Top 10, a list of the ten most critical web application security risks, updated every few years based on real-world data.
Vulnerability
A weakness in a system that can be exploited by an attacker to perform unauthorized actions. Vulnerabilities can exist in code, configuration, infrastructure, or processes. They range in severity from informational to critical.
Cross-Site Scripting (XSS)
A vulnerability where an attacker injects malicious JavaScript into web pages viewed by other users. The injected script runs in the victim's browser with the same privileges as legitimate scripts, allowing the attacker to steal session tokens, redirect users, or modify page content.
Related Articles
ISO 27001 for Laravel Applications: Controls, Annex A, and What Developers Must Implement
ISO 27001:2022 defines 93 Annex A controls across four domains. This guide maps the technological controls that directly affect Laravel developers to specific implementations: access control, authentication, logging, cryptography, secure development, and continuous monitoring.
PCI DSS v4.0 for Laravel Developers: What You Actually Need to Implement
PCI DSS v4.0 became mandatory in March 2025. If your Laravel application touches payment card data, you need to know exactly which of the 12 PCI requirements apply to you and what they mean in PHP terms. This guide cuts through the compliance jargon.
HIPAA Technical Safeguards for Laravel Applications Handling PHI
If your Laravel application stores or processes Protected Health Information, you need specific technical safeguards. This guide covers the HIPAA Security Rule requirements that PHP developers must implement, with Laravel-specific code examples.
Related Fix Guides
Monitor Your Laravel Application's Security
StackShield continuously checks your Laravel application from the outside, catching security issues before attackers find them.
Start Free Trial