7 Security Concerns to Keep in Mind When Developing Laravel Apps

Wojciech Gabryś - Senior Full Stack Developer
3 minutes read

Laravel's maintainers strive to make the framework as secure as possible, but at the end of the day, it's up to developers to build their applications with security in mind.

Let's investigate the most common ways developers unknowingly introduce security vulnerabilities. Each of the issues discussed is followed by an actionable solution that will increase the security of your Laravel-based application.

1. Running your staging instance with debugging mode enabled

When running your Laravel application with debugging enabled, you're running a risk of leaking your implementation & configuration details. Whenever Laravel stumbles upon an unhandled exception, it renders a page that contains a full stack trace (with unobfuscated pieces of source code that lead to the error), specific versions of Laravel & PHP (used when looking for CVEs), and in some cases the username & address of your database server. You might think that running the staging instance on a cryptic subdomain will take you off the hook. Think again – your DNS records are public and easily scannable by tools like dig.

A makeshift solution could be security by obscurity, thanks to obtaining an inconspicuous domain name to deploy your staging environment (remember to block all crawlers to prevent accidental search engine indexing). Depending on the complexity of your application, it's a surefire way to introduce many other issues (like sharing cookies or CORS).

(A proper) Solution: Set APP_DEBUG=false in your .env file and reach for an error tracking & application monitoring service (e.g. Sentry, Bugsnag, Flare, or Honey Badger) to make your staging environment as similar to production as possible. While you're at it, be sure to generate a separate APP_KEY by running php artisan key:generate. This will ensure that the key leaked from staging cannot be used to decrypt the data from the production environment.

2. Enumeration attacks

By default, Laravel is using auto-incremented unsigned integers as primary keys for models and their relationships. While this readability is great for development and debugging, it opens your app to enumeration attacks (the most obvious of which is changing the ID in the URL to be able to view another resource). That can lead to heavy resource usage due to scraping, or vulnerabilities caused by sending modified form data (or even tampering with network packets) when the endpoint isn't secured enough. A less obvious threat would be open-source intelligence (OSINT), where a third party would create two separate accounts (or other kinds of entries) just to compare the IDs and estimate how many entries have been created within a certain period.

To solve this problem, replace regular IDs with UUIDs or Ordered UUIDs (to preserve the ability to order them by the time they've been generated at). If you're afraid that storing alphanumeric keys will slow down your large application, you can store UUIDs as binary data.

3. Rate limiting unwanted requests

Enumeration and data scraping attacks can be very damaging.

The easiest and most flexible way to counteract them is by reaching for the built-in RateLimiter class. Thanks to its versatility, you'll be able to automatically limit the number of tries (in a given period) per IP address, user, or the whole organization. After a certain point, this solution might not be enough, and you'll need to block the requests on WAF (Web Application Firewall) level.

4. Additional validation when turning off CSRF protection

Some of the external APIs (like Stripe) rely on sending sensitive data to a specific route of your application (usually via a POST method, to be able to use SSL encryption). This forces us to poke a hole in Laravel's VerifyCsrfToken Middleware – otherwise, these requests would have been ignored. Aside from setting an URL for this endpoint that's not easily guessable, it's a good practice to check against a whitelist of IP addresses (that will be allowed to send requests) before devoting any time & resources to processing them.

5. Creating your own authentication and cryptography

Whenever possible, try to fight this urge. You might not like the specific way it implements the default password requirements, or how it doesn't use your favorite CSS framework out of the box.

In moments like these, comb through the documentation and check if it's something that you can easily change (and based on the Laravel team's track record it should be). The underlying code was collaborated on & reviewed by a vast amount of developers, security professionals and hackers. Plus, it's heavily tested (both phpunit and battle-wise). Always try to stand on the shoulders of giants.

6. Sensitive data exposure

Laravel makes it effortless to respond with whole Eloquent models (and their relationships), which makes it a perfect solution for rapid prototyping of APIs or SPA-like applications (thanks to Inertia.js & Livewire). You must be aware that with great power comes great responsibility. Every time you're returning an Eloquent model, or a list of related models, Laravel will serialize all of their properties (making them visible to the end-user). Depending on how you've defined your models, you might be leaking sensitive information, such as personal details, email addresses, hashed passwords, Stripe keys, etc.

There are various ways of mitigating this issue:

Limiting what's serialized by explicitly setting your models' $visible & $hidden properties,

Using Eloquent's API Resources to massage the incoming data into the desired format,

Taking advantage of Invisible Columns (introduced in MySQL 8.0.23 & natively supported since Laravel 8.76.0), which are omitted during SELECT queries.

7. Code injection

Laravel does a tremendous job of sanitizing any user input, like query strings, form data, and (often overlooked) contents of cookies. All of the magic done behind the scenes can lull developers into a false sense of security when working with low-level SQL queries and unescaped data in Blade views.

When Eloquent is not enough and you'll need to drop to the Query Builder & raw SQL, never pass user input directly into a query (by concatenation). Instead, take advantage of placeholders and add the arguments as an array passed as the second argument to Query Builder's methods.

There might be times when you'll need to pass some HTML into Blade and have them printed without escaping. In moments like these, try to keep the entirety of related logic outside of views (to be able to test it), and be very strict about the tags that can go through. Have a look at PHP's strip_tags(), which allows you to explicitly specify which tags are allowed (and strips everything else).

To summarize: always pay special attention to the parts of Laravel's documentation marked with red exclamation points, be mindful of the data you're returning to the public, and never trust the data coming from your user (no matter how nice they might seem).

And that's a wrap!

***

As a software house with more than 10 years of experience in delivering top-notch and robust Laravel solutions, Polcode knows how to keep applications stable and secure. Let's talk!

On-demand webinar: Moving Forward From Legacy Systems

We’ll walk you through how to think about an upgrade, refactor, or migration project to your codebase. By the end of this webinar, you’ll have a step-by-step plan to move away from the legacy system.

moving forward from legacy systems - webinar

Latest blog posts

See more

Ready to talk about your project?

1.

Tell us more

Fill out a quick form describing your needs. You can always add details later on and we’ll reply within a day!

2.

Strategic Planning

We go through recommended tools, technologies and frameworks that best fit the challenges you face.

3.

Workshop Kickoff

Once we arrange the formalities, you can meet your Polcode team members and we’ll begin developing your next project.