Laravel provides a robust Query Builder and Eloquent ORM. And thanks to them most of the queries are protected in Laravel applications by default, so for example a query like
will be automatically protected, since under the hood Laravel will translate the code into a prepared statement and execute.
But developers usually make mistakes by assuming Laravel protects from all SQL injections, while there are some attack vectors that Laravel can’t protect, here are the most common causes of SQL injections that we saw in modern Laravel applications during our security checks.
1. SQL Injection via column name
The first common mistake that we see is that a lot of people think that Laravel would escape any parameter that is passed to Query Builder or Eloquent. But in reality, it’s not safe to pass user-controlled column names to the query builder. Here is a warning from Laravel’s documentation.
So the following code will be vulnerable to a SQL injection
$categoryId = $request->get('categoryId'); $orderBy = $request->get('orderBy'); Product::where('category_id', $categoryId) ->orderBy($order By)->get();
and if someone makes a request with the following “orderBy” parameter value
http://example.com/users?orderBy=id->test"' ASC, IF((SELECT count(*) FROM users ) < 10, SLEEP(20), SLEEP(0)) DESC -- "'
under the hood the following query will be executed and we will get a successful SQL injection.
select * from `users` order by `id`->'$."test"' ASC, IF((SELECT count(*) FROM users ) < 10, SLEEP(20), SLEEP(0)) DESC -- "'"' asc limit 26 offset 0
It’s important to mention that the demonstrated attack vector is fixed on the latest Laravel versions, but still, Laravel warns developers even in the latest documentation to not pass user-controlled column names to Query Builder without whitelisting.
In general, even if there is no possibility to turn a custom column to an injected SQL string, we still do not recommend allowing to sort the data by any user-provided column name, since it can introduce a security issue. Consider an example when a “users” table can have some secret column “secretAnswer”, a clever attacker possibly could deduce the value without ever needing SQL injection.
2. SQL Injection via validation rules
Let’s look at the following simplified validation code
$id = $request->route('id'); $rules = [ 'username' => 'required|unique:users,name,' . $id, ]; $validator = Validator::make($request->post(), $rules);
Since Laravel uses $id here to query that database and $id is not escaped, it will allow an attacker to perform an SQL injection. You can read more about getting to SQL Injections via validation rule injection here.
3. SQL Injection via raw queries
Another pattern worth mentioning here, but less common that we see in our security code reviews is just using old style DB::raw function and not escaping passed data. A pattern like this usually happens rarely, mostly in cases when there is a need to pass some custom query. If you have to use DB::raw function for some custom query, make sure you escape the passed data via
📝 If you have a Laravel application that is running on production, check out also our Laravel applications security testing service.