Skip to content
Insights

Securing Laravel 101

laravel

In this blog post, we're gonna take a closer look at some common Laravel security mistakes that I've encountered in projects, personally made or even came across on Stack Overflow approved answers. We'll briefly talk about file validation, mass assignment, and Laravel’s query builder. The goal of this blog is to demonstrate how small, easy to avoid mistakes can have a big impact on your application’s security. And to hopefully prevent you from making them in the future. So, let’s get started!

File validation

Laravel offers a robust and secure file system that enables developers to store and retrieve files from either local or cloud storage. Below is some code, straight from Stackoverflow, to handle a user uploading an avatar to their profile:

Can you spot how an attacker could abuse the following snippet?

1
2
3
4
5
6
7
8
9
10
11
12
public function storeImage(Request $request)
{
	$request->validate([
		'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
	]);

	$imageName = Str::random(16) . '.' . $request->image->getClientOriginalExtension();

	Storage::disk('public')->putFileAs("avatars", $request->image, $imageName);

	return back()->with('success','You have successfully uploaded your avatar!');
}

Say, an attacker wanted to upload a .html file with an XSS (Cross-site scripting) payload to your server. You’d expect Laravel’s server-side validation image and mimes:jpeg,png,jpg,gif,svg to take care of this.

Sidenote: This isn’t a bug in Laravel but simply how mime type checking works.

The crucial mistake being made is using Laravel’s getClientOriginalExtension method to retrieve the file’s extension directly from the request. Instead, in this scenario we want to use:

1
$request->image->extension();

This will guess the file’s mime type based on the contents of the actual file rather than what’s being received from the client.

Another safe way to handle this is to use Laravel’s put method to store files:

1
Storage::disk('public')->put(Str::random(16), $request->image);

This will automatically replace your filename with a random hash and append the correct extension.
The key takeaway here is to never trust user input, especially when it comes to file uploads.

 

 

Mass assignment

Can you spot the security flaw in this code?

User model

1
2
3
4
5
class User extends Model {

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

}

Register controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class RegisterController
{
  public function create(Request $request) 
  {
    $request->validate([
      'username' => 'required|string',
      'email' => 'required|email|unique:users',
      'password' => 'required|string|min:12|confirmed',
    ]);

    $user = new User();
    $user->role = 'guest'
    $user->fill($request->all());
    $user->save();

    return response()->json(['success' => true],201);
  }
}
A registration form for a user

If a malicious user were to forge the register request and add "role": "admin" to the form payload. Laravel would happily grant this user access to your entire system.

This is due to $request→all() not only taking validated data from the request but ALL data. In combination with role being added to the $fillable property of the model, the user would be created with the role "admin".

Prevent this by either using $request→validated() to only retrieve validated input from your request. Or ensure your fillable properties are set up correctly.

Bonus points if you spotted the password being stored in plain text ;)

Query builder parameter binding

SQL injection is a common attack vector for web applications, and Laravel is no exception. It occurs when an attacker is able to input malicious SQL code into a web application's input fields or query strings.

Here is an example of SQL injection using Laravel's query builder:

1
2
3
4
//User input
$search = "1; DROP TABLE users;";

DB::table('users')->whereRaw("name = " . $search)->get();

This code is vulnerable to SQL injection because it allows the attacker to execute arbitrary SQL code by setting the $search variable to a string that includes SQL commands. In this case, the attacker could delete the entire users table by setting $search to "1; DROP TABLE users;".

To prevent SQL injection attacks, always use Laravel's query builder or parameter binding when constructing SQL queries.

Example of parameter binding:

1
2
3
$search = "1; DROP TABLE users;";

DB::table('users')->whereRaw("name = ?", $search)->get();

Conclusion

Though the examples above might be quite obvious to some, I hope this article illustrates the importance of having a solid understanding of the inner workings of a framework, even when it appears to handle security seamlessly. Despite Laravel's beginner-friendly nature and minimized overhead for developers, don’t forget to remain cautious and avoid complacency.

Looking for Laravel experts? 

More insights

  • SymfonyCon 2024: code in harmony

    The 2024 edition took place in beautiful Vienna, so one of our experts went to check it out. A quick night train journey and some culture later, they were ready to focus on two days packed with Symfony. What insights did we bring back as souvenirs? You can read all about it in this report! 

    SymfonyCon 2024: code in harmony
  • Stepping into something new: Lore’s journey at Codana

    Lore Vanderlinden tells you all about her journey at Codana. She combines her technical background as a frontend developer with a passion for entrepreneurship in her role as project manager. Find out how by reading the blog!

    Stepping into something new: Lore’s journey at Codana
  • Qodo: an AI-copiloot for coding and testing

    We recently came across Qodo: a tool that uses Artificial Intelligence (AI) to help us code and test. In this blog post, you can read all about our initial experiences. 

    Qodo: an AI-copiloot for coding and testing
  • Lunar and Codana merge into one brand

    Lunar and Codana join hands and from today will continue together under the Codana brand name. This merger creates a digital product studio with more than 30 experts and a clear ambition: to become a leading player in the Belgian and European market.

    Lunar and Codana merge into one brand
  • From Intern to Digital Project Manager: My Journey at Codana

    Jelmer Krux tells you all about his journey at Codana. He joined our team fresh out of university and combines the roles of digital project manager and UX/UI Designer. How? Find out by reading his story in this blog! 

    From Intern to Digital Project Manager: My Journey at Codana
  • Cross-platform applicaties with React Native

    Never before has developing native mobile applications been as accessible as it is today. At Codana, we do this by using the React Native, an open-source framework developed by Meta.

    Cross-platform applicaties with React Native