Security

SQL injection

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

PHP
Product::query()->where('category_id', $request->input('categoryId'))->get();

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.

Column name

SQL Injection via column name. It’s not safe to pass user-controlled column names to the query builder. Here is a warning fromLaravel’s documentation.

PDO does not support binding column names. Therefore, you should never allow user input to dictate the column names referenced by your queries, including "order by" columns.

Reference: Laravel Documentation

So the following code will be vulnerable to an SQL injection:

PHP
$categoryId = $request->input('categoryId');
$orderBy = $request->input('orderBy');
Product::query()
    ->where('category_id', $categoryId)
    ->orderBy($orderBy)
    ->get();

This way, someone can use a query like http://example.com/users?orderBy=id->test"' ASC, IF((SELECT count (*) FROM users ) < 10, SLEEP(20), SLEEP(0)) DESC -- "'

Resume: Do not pass user-controlled column names to Query Builder without whitelisting.

Validation rules

SQL Injection via validation rules. Let’s look at the following simplified validation code

PHP
$userId = $request->input('id');
Validator::make($request->post(), [
    'username' => ['required', "unique:users,name,$userId"],
]);

Since Laravel uses $userId here to query that database and $userId is not escaped, it will allow an attacker to perform an SQL injection.

Case 1: Making the validation rule optional

The simplest thing that we can do here is to send a request with ID = 10|sometimes, which will alter the validation rule torequired|unique:users,username,10|sometimes and will allow us to not skip the username in the request data, depending on your application business logic, a bypass like this might create a security issue.

Case 2: DDOS the server by creating an evil REGEX validation rule

Another attack vector here could be to create an evil Regex validation, that is vulnerable to ReDoS attack and DDOS the app. For example, the following request would consume a lot of CPU and if multiple requests sent concurrently can cause a big CPU spike on the server.

PHP
PUT /api/users/1,id,name,444|regex:%23(.*a){100}%23

{
    "username": "aaaaa.....ALOT_OF_REPETED_As_aaaaaaaaaa"
}

Case 3: SQL Injection

The simplest SQL injection here would be to just add an extra validation rule that is querying the database, for example

PHP
PUT /api/users/1,id,name,444|unique:users,secret_col_name_here

{
    "username": "secret_value_to_check"
}

But important to mention since using unique we are able to provide both custom column name and values (values are not going through PDO parameter binding) possibilities of SQL injection here could be not limited to just a simple attack vector that is mentioned above. For more details, check out Laravel Blog’s post "Unique Rule SQL Injection Warning".

The best prevention here is to not use user-provided data to create a validation rule.

RAW Query SQL Injection

DB::raw function is dangerous when developers don’t escape passed data. If you have to use DB::raw function for some custom query, make sure you escape the passed data via DB::getPdo()->quote() method.

XSS in Laravel Blade

Cross-Site Scripting can be very dangerous, for example an XSS attack in the admin panel can allow an attacker to inject a code like this:

HTML
Some text
<input onfocus='$.post("/admin/users", {name:"hacker", email:"hacker@example.com", password: "test123", });' autofocus />
test

Which will allow an attacker to create an admin user with his credentials and take over the admin panel.

Laravel Blade protects from most XSS attacks, so for example an attack like this will not work:

HTML
// $name = 'John Doe
<script>
    alert("xss");
</script>
';
<div>{{ $name }}</div>

Blade’s null statement automatically encodes the output. So the server will send the following properly encoded code to the browser (which will prevent the XSS attack):

HTML
<div>John Doe&lt;script&gt;alert(&quot;xss&quot;);&lt;/script&gt;</div>

But frameworks can’t handle all cases for developers.

Case 1: XSS via {!! $variable !!} Statement

Sometimes you need to output a text that contains HTML, and for it you will use {!! !!}:

BLADE
<div>{!! $htmlDescription !!}</div>

In this case Laravel can’t do anything for you and if the $htmlDescription contains JavaScript code, it will be executed as-is and we will get an XSS attack.

Prevention tips:

If you can, avoid outputting user supplied data without html encoding. If in some cases you know that the data can contain HTML, use HTML Purifier to clean the HTML from JS and unwanted tags before outputting the content.

Case 2: XSS via a.href Attribute

If you are outputting user provided value as a link, here are some examples on how it can turn into an XSS attack.

BLADE
// $user->website = "javascript:alert('Hacked!');";
<a href="{{ $user->website }}">My Website</a>

The alert(‘Hacked!’) code will get executed when a user clicks on the link.

Prevention tips:

  • Validate user provided links, in most cases, you need only to allow http/https schemas
  • As an extra layer of security, before outputting you can replace any link that is not starting with http/https schema with some “#broken-link” value.

Case 3: XSS via Custom Directive

When you write a custom directive, don’t forget to use Laravel’s e function to escape any code that is user provided. An example of vulnerable code:

PHP
// Registering the directive code
Blade::directive('hello', function ($name) {
    return "<?php echo 'Hello ' . $name; ?>";
});

// user.blade.php file
// $name = 'John Doe <script>alert("xss");</script>';
@hello($name);

Mass Assignment

Mass Assignment Vulnerabilities

Example: a User model with:

PHP
protected $fillable = ['name', 'email', 'password', 'role'];

If in a Controller a developer uses something like $user->fill($request->all()); or $user->update($request->all());, a user/attacker can add an input with a role name and submit the form ans thus, set a role, e.g. to "administrator".

Prevention tips:

  • Don’t use Mass Assignment
  • Pass to Model only fields that have been validated: $user->update($validator->validated());
  • Use whitelisting instead of blacklisting (prefer $fillable over $guarded, because it’s easy to forget to add a new column to $guarded when you add it to a Model)
  • Use $model->forceFill($data) method with caution, make sure passed data cannot be manipulated by the user