Top 10 Laravel Audit Security Issues

Published on by

Top 10 Laravel Audit Security Issues image

Since the start of 2022, I’ve spent my time conducting Security Audits and Penetration Tests for Laravel applications. I’ve audited apps of all sizes, from tiny apps with a handful of controllers, right through to huge apps with a multitude of modules, and many different coding styles and app structures.

Amongst all of that, there has been a pretty clear trend that holds true for the apps that I audited: Laravel is pretty secure, but it’s easy to overlook the little things, the extra layers of defence, and leave a weakness hiding somewhere.

In fact, it’s fair to say that the vast majority of issues I’ve found during my audits were either something simple that was overlooked in a single route, or are extra layers of security that developers were either not aware of, or not comfortable implementing.

So let’s dive into it and check out the Top 10 most common security issues I discovered doing my security audits.

#10 → Insufficient Input Validation

It’s common to find insufficient input validation scattered throughout controllers. Often there will be a validator checking expected input keys (although sometimes there isn’t even that), and then request()->all() will be in use immediately after, submitting data into models, events, etc. Without proper validation, it’s easy to inject malicious values to cause errors, injection attacks, and more.

For example, the usual mass-assignment defence for this is considered to be $fillable on the model, but if $fillable includes the admin flag for the admin portal, then anyone can create a new admin user from the registration page… (and yes, this is a real example.)

#9 → Missing Subresource Integrity (SRI)

Subresource Integrity (SRI) adds a layer of protection against compromised 3rd party scripts affecting your site, and in my opinion is seriously underused. The risk is that 3rd party scripts and styles can be modified to include malicious code, such as Magecart, keyloggers, cryptominers, etc. If you have the compromised script included on your site, then your users are at risk even though your app wasn’t compromised.

SRI works by defining an integrity hash on each <script> & <style> tag that loads a 3rd party resource, which the browser verifies before loading the resource. If the hash doesn’t match, the resource is blocked.

Most of the sites I audited didn’t load resources from 3rd parties and thus didn’t need SRI anyway, otherwise I’d expect this to be much higher on the list!

#8 → Insufficient Rate Limiting

Rate limiting is essential for limiting bot attacks, and preventing abuse, and yet it's common to find endpoints that are lacking in rate limiting. This is of particular concern when it comes to routes like authentication, or querying for sensitive information like the existence of user accounts. For example, I discovered an SMS-based Multi-Factor Authentication (MFA) route was lacking rate limiting - and the 6-digit code expired in 5 minutes. It was trivial to brute-force a valid code and force my way in. 😈

That said, rate limiting can be tricky to get right - do you base it on IP, or username, or both? Or something else? It depends on the route, but having some is definitely better than none. So don’t overlook it!

#7 → Cross-Site Scripting (XSS)

One of the most unsurprising entries in the Top 10, but it’s probably a lot lower than you were expecting! XSS usually popped up on a single route through a single input, due to something like Markdown (which is insecure by default) or limited formatting that suffered from a subtle escaping bypass.

The best strategy here is to look out for those pesky unescaped blade tags ({!! … !!}) and raw HTML directives like v-html, and ensure all uses are properly escaped, that Markdown’s security features are enabled, and user-supplied HTML is purified. I recommend avoiding these tags as much as possible, so any uses really stand out and can be easily identified and reviewed.

#6 → Outdated & Vulnerable Dependencies

Another unsurprising entry… Just when WAS the last time you ran composer/npm update?

It’s common to delay dependency updates, to avoid breaking things and keep systems stable, but vulnerabilities in dependencies can leave your apps exposed without you doing anything. It’s also common to include a multitude of dependencies, but every dependency increases the potential vulnerabilities you need to be aware of.

I recommend keeping everything updated weekly or monthly, and using tools like composer audit --locked and npm audit in your build system to block deploys when vulnerabilities are discovered. I also suggest trimming down your dependencies to the minimum, and anything that can be easily replaced by a simple in-house middleware or wrapper, should be.

#5 → Insecure Function Use

I’ve lost count of the number of times I’ve seen md5(time()) (and md5(microtime())!) used since I started working in PHP - I probably even wrote a few of them in my early years! And disappointingly, it’s a trend that continues to this day. Worse, it’s often used for generating random tokens or unique filenames. Except it’s neither random, nor unique. It’s incredibly easy to guess and brute-force, and collision attacks can also be quite trivial too.

I bet if you go into your codebase right now and search for md5(, you’ll find it used insecurely somewhere… Seriously, I have trauma over this one!

It’s not just md5(time()) specifically, but other functions like rand() and array_rand() which aren’t cryptographically secure and should not be trusted for anything that requires security.

Always use proper secure random number generators, random_int() and Str::random(), to safely generate random values.

#4 → Missing Security Headers

Now we’re getting into the additional layers of protection that aren’t well understood. Web browsers include a bunch of really awesome security features, you just need to enable them on your site through the use of response headers. While their absence doesn’t directly open your site to be hacked, they help prevent things like clickjacking, referrer information leaking, XSS and other injection attacks, HTTPS downgrade attacks, etc. Enabling many of these security headers is trivial, but most sites don’t even enable the easy ones… 😭

The best suggestion I can give you is to head over to securityheaders.com and scan your site. It’ll list all of the headers you’re missing, and give you links to resources to learn more about adding them.

#3 → Missing Content Security Policy (CSP)

Content Security Policies are so important I’ve given them their own spot in the Top 10. CSPs are a secondary line of defence against XSS and clickjacking, and give you visibility over what scripts, styles, fonts, forms, frames, etc, run on your app. The CSP tells the browser what resources are allowed to be used on the site, and it will block and/or report any violations of the policy - preventing XSS attacks and providing you visibility of what’s happening on your site.

They are often dismissed as too hard or “break stuff”, however deploying a Report-Only policy provides you with full visibility without breaking anything. So this is definitely the way to go when you’re starting out! The easiest way to set one up is to use the CSP Wizard in Report URI.

I’ve published my CSP middleware as a simple way to get started with CSPs: https://gist.github.com/valorin/d4cb9daa190fdee90603efaa8cbc5886

#2 → Missing Authorisation

Oh boy, this one encompasses a bunch of stuff around Authorisation (and authentication in a way). I’ve seen: Insecure Direct Object References (IDOR), missing signed, auth, and policy middleware, forgotten authorize() calls, webhooks without validation, etc… Ultimately, the code wasn’t actually checking if the requestor was allowed to do what it wanted to do.

I was actually surprised to see this so high on the list, but it turns out that it’s quite common for most projects to have a single one of these hiding somewhere, waiting to be exploited. It’s usually a simple case of the developer forgetting to add in a line, but it potentially leaves a massive hole.

My best recommendation here is to include tests for authentication and authorization on each route, alongside your other tests. That way you’re checking valid and invalid permissions as part of your standard testing flow, and you’ll notice if authorization is missing because a test will pass when it’s supposed to fail.

#1 → Exposed API Keys & Passwords

How many times do I have to say this? Don’t commit secrets into Git! 😡

This was the unsurprising champion of the list: API keys and passwords committed into version control and scattered across codebases. It is so ridiculously common, I’m honestly surprised when I don’t come across this during an audit.

Consider where your code goes… It’s on all of your developers machines, shared with contractors, up on GitHub, Bitbucket, GitLab, etc, in 3rd party services and build tools. It’s scattered across many different places. While your API keys unlock Billing, file storage, Personally Identifying Information, backups, infrastructure, etc. So many pieces of sensitive information and access, and if it falls into the wrong hands, your reputation is gone.

I know there are tutorials out there which recommend committing these things, so I can see why it happens. But it’s something we need to work hard to stop as a community.

If you do accidentally commit credentials, make sure you revoke it at the source. This will prevent anyone from using it if they find it. I also suggest using tools like Gitleaks and TruffleHog to find secrets in your codebases.

BONUS → Missing security.txt file!

It's not a security issue as such, so I’ve included it as a bonus, but adding a security.txt file to your app is one of the simplest and best things you can do for your security.

security.txt is a text file that you put at /.well-known/security.txt, which provides your (security) contact details in case someone finds a security issue with your site. This makes it super easy for security folks to contact you, reducing the time and effort it takes to report issues so you can get them fixed faster.

Generate one here: https://securitytxt.org

Summary

Ok my friends, that’s my top 10! Were there any surprises? Things you need to check in your own apps? Maybe there was something you disagree with? Head over to the thread on Twitter, Fediverse, or Substack Notes to join the discussion!

The key takeaway for me is that while Laravel is secure, it’s all the little things which are overlooked and missed. It’s easy to assume you’ve checked authorization on every route, and your output is fully escaped, but there might be that one instance you’ve missed. This is the benefit of security audits - getting someone who doesn’t make assumptions to review your code. It’s also what I teach over in my Practical Laravel Security course.

Stephen Rees-Carter photo

Stephen is a security consultant who specialises in security audits and pentests of Laravel apps, he is the creator of Laravel Security in Depth, where he teaches Laravel developers about security concepts and how to think like a hacker. His Laracon talks have been described as "terrifying magic tricks", that show just how easy it is to hack into a vulnerable site and cause mayhem.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project.

Visit No Compromises
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
Larafast: Laravel SaaS Starter Kit logo

Larafast: Laravel SaaS Starter Kit

Larafast is a Laravel SaaS Starter Kit with ready-to-go features for Payments, Auth, Admin, Blog, SEO, and beautiful themes. Available with VILT and TALL stacks.

Larafast: Laravel SaaS Starter Kit
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
Apply Dynamic Filters to Eloquent Models with the Filterable Package image

Apply Dynamic Filters to Eloquent Models with the Filterable Package

Read article
Property Hooks Get Closer to Becoming a Reality in PHP 8.4 image

Property Hooks Get Closer to Becoming a Reality in PHP 8.4

Read article
Asserting Exceptions in Laravel Tests image

Asserting Exceptions in Laravel Tests

Read article
Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4 image

Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4

Read article
Basset is an alternative way to load CSS & JS assets image

Basset is an alternative way to load CSS & JS assets

Read article
Integrate Laravel with Stripe Connect Using This Package image

Integrate Laravel with Stripe Connect Using This Package

Read article