Table of Contents Securing Your Laravel App .............................................................................................. 4 Step 1: Monitoring and Visibility .................................................................................... 5 Bug Reporting ................................................................................................................ 5 Availability ..................................................................................................................... 5 Performance Measurement and Monitoring ................................................................... 5 What Should I Do? .......................................................................................................... 6 Step 2: Code Management and Quality ......................................................................... 7 Source Control ............................................................................................................... 7 Code Reviews ................................................................................................................. 7 Code Scanning Tools ...................................................................................................... 7 What Should I do? .......................................................................................................... 8 Step 3: Storing Secrets and Config ................................................................................ 9 What Should I do? ........................................................................................................ 10 Step 4: Rate Limiting ...................................................................................................... 11 What Should I Do? ........................................................................................................ 11 Step 5: Data Validation .................................................................................................. 12 Required Data / Types of Data ..................................................................................... 12 Data Bounds ................................................................................................................. 12 Prohibit or Exclude Data ............................................................................................... 13 Only retrieve validated data ........................................................................................ 13 What Should I Do? ........................................................................................................ 13 Step 6: Authorization ..................................................................................................... 14 Why Authorization? ...................................................................................................... 14 What’s the security implication? .................................................................................. 14 What Should I Do? ........................................................................................................ 15 Step 7: Are Eloquent and Query Builder Worth It? .................................................... 16 Prepared Statements ................................................................................................... 16 Use the Laravel Toolset ................................................................................................ 16 What if you can’t? ........................................................................................................ 16 What Should I Do? ........................................................................................................ 17 What’s the Most Important Thing in a Secure Laravel App? ................................... 18 User Input .................................................................................................................... 18 API Data ....................................................................................................................... 18 What Should I Do? ........................................................................................................ 19 End Notes ......................................................................................................................... 20 Securing Your Laravel App Laravel is already secure. Book over. Done. We kid - we joke! The Laravel framework actually is built very securely. The core team, coupled with the open source community, take responsibility for reviewing the performance, quality, accuracy and security of the framework itself. It's pretty cool that most of the features that Laravel supports are configured to be secure by default. But, your app isn't only made from the framework, though. Laravel provides the building blocks, but you must assemble them. Another way to look at it is that Laravel could be the most secure door lock for your house - but you still have to remember to actually lock it every time you leave! So what's this book about? This book is a set of 7 steps to help secure your Laravel app. It's far from an in-depth, holistic manual. Instead, it's meant to accomplish two simple things. The first is to remind you to have a secure mindset when you're building your app. Laravel helps us make fast and beautifully-coded apps as a part of its paradigm. Coding securely, however, takes practice. We'll bring forth considerations both inside your app and in the whole web software ecosystem that combine to create a secure app. The second thing is to direct you towards some tools that we always implement, trust, and have used to obtain the goal of making the most secure apps. Hopefully pointing you in the right direction will help save you some time securing your next app. This book was written by Joel Clermont and Aaron Saray. Combined, we have over 4 decades of experience programming in web and open source technologies. We've built many Laravel projects over the years focusing on performance, code quality and security. We're excited to share with you what we've learned. So, let's get started. Let's take a look at the 7 Steps to Securing Your Laravel App. 4 Step 1: Monitoring and Visibility Bug Reporting Bugs erode confidence, bugs destroy trust, and bugs expose unwanted functionality, data and secrets. It’s important to fix bugs when you see them - but how do you even know they exist? You can’t rely on a kind-hearted visitor to report issues - or even worse, wait till you get irritated customer support requests. You should be using a bug reporting system. We use Bugsnag and love it. You can also use Flare or Sentry. These are valuable tools that will alert you as soon as a bug is found in both your PHP/Laravel or Javascript code. You can filter the types of errors, ignore specific errors that you can’t get to just yet, and set up integrations with ticket systems. It’s important to be notified of bugs because there are many times when fatal errors or exceptions have been known to expose unwanted functionality or dump secrets to the screen. While you can’t prevent them with these tools, the sooner you can fix them, the better. Availability Most developers understand that having an entire site down is a big deal, so they’ve bought into the idea of availability monitoring with something like Oh Dear or UptimeRobot for their homepage. But you shouldn’t stop there. Set up monitoring on your mission-critical endpoints for availability and average response time. This is important because a slower or non-responsive end-point can be a sign of something going wrong. Perhaps there’s a denial of service you need to prevent? Someone is abusing the system? Monitoring end points besides just your homepage is a good idea and will help to give you a heads-up sooner. Performance Measurement and Monitoring When you’re developing locally, it makes a lot of sense to use something like Laravel Telescope and the Xdebug profiling functionality. You’ll be able to see where your code maybe isn’t as efficient as you’d like. Poor performing code is often preliminary pointer to system crashes or security holes. Not only will a better performing app be appreciated by 5 your visitors, it is more secure as well. And speaking of your visitors, you can use specialized performance managers in production as well. Scout provides functionality to integrate with PHP and provide real-time productiongrade performance monitoring. New Relic is also a worthy alternative. These tools help you identify situations you didn’t catch during development - or ones that are unique to your production data and configuration. What Should I Do? Get a bug tracking and reporting service like Bugsnag or Flare Do not mute the notifications - prioritize fixing the errors. You WANT to be bothered and alerted when an error occurs in your app. Errors are important to fix! Use Laravel Telescope configured for your local dev environment only. Understand what these screens are showing you and understand your performance bottlenecks. Set up a few end point monitors with Oh Dear - watch your home page, watch your API, monitor your login system. After you’ve done all these things, then you can look into the other more advanced performance options. But don’t get too bogged down. We have a whole set of other quick wins coming in the next few steps. 6 Step 2: Code Management and Quality Source Control We can’t tell you the amount of times we’ve found copies of current code in a file like index.php.bak on a website. Or, another favorite, some server-based editors will crash and then leave behind artifacts like index.php~ which are ripe for the picking. The reason this happens is because the project is probably not using source control. A secure Laravel app is in source control, most likely Git. This allows the programmers to keep a long, accurate, auditable history of all changes. This helps reduce .bak and tilde files, and provides for tooling for easy deploys as well. Code Reviews Speaking of auditing, a secure Laravel application has a code review process in place. Code review helps us with both accuracy and quality of code. Having someone else review the code we’ve written will help point out flaws and bugs - as well as provide a potential dialog that will force the developer to explain their decisions. It doesn’t matter who you are or what experience you have, the code review process is important and necessary. We've actually found bugs in each other's code during the review process. This saves the embarrassment of a buggy deploy and saves the company money. There's also been a few times that, when explaining a code block to a reviewer, we’ve realized the code needed to be cleaned up and made less complicated. If you work on a team, setting up code reviews should be easy. If you’re on your own, however, this doesn’t mean you don’t do a code review. You can find another consultant or contractor (like us!) to bring in just for some reviews, or you can work with sites like PullRequest which is a code review process as a service. Code Scanning Tools In addition to human-based code review, you can use automated scanning tools to review you code. Using PHPStan or Psalm is great for local development. You can build these into your CI environment as well. SonarPHP is a software as a service tool that can be integrated into your code development lifecycle. 7 The point of these security or static analysis tools is to look for situations or scenarios beyond what a developer might see. They can look for common security holes, configuration errors, or logical issues. What Should I do? Make sure you’re using a source control management tool like Git. And don’t worry if your project isn’t set up perfectly and you have to commit some files and things you know you shouldn’t. You can always change this later - start now! Set up code reviews with your team or a trusted consultant. If you’re set with those two things, you can investigate into using something like PHPStan. Start simple, light weight and disable scans that you don’t need or are generating too many errors at first. Then, slowly add them or configure them properly. 8 Step 3: Storing Secrets and Config If you’ve studied the rules of a secure Twelve-Factor App methodology, you know that factor number three is about config and environment variables. And that’s what this step is about. Laravel provides a robust configuration option with the config folder, the individual files, and the config() global helper method. The application itself has the config baked in as well. This means you can access the configuration values or inject them nearly anywhere. And you should be doing this. Manage your configuration using the Laravel Configuration system. This includes things like endpoints to APIs, special constants or values, or even secrets for your application. Secrets, things like API credentials, service keys and passwords, should be defined in the configuration system in Laravel but resolved from the environment. That is to say, you should ask the config system for a secret, but that secret is proxied forward from an environment variable either defined in the OS or through something like the phpdotenv .env file. Why is this all a good idea? Well first, using the configuration system you know where all of your non-code related configuration is. You don’t have to search through different classes to try to find an end point that changed, for example. Using the environment allows you to specify things like different api endpoints depending on if you’re testing locally or in a production environment. This means you don’t have to change code, only configuration. Finally, putting secrets in an environment usually separates out the access of these secrets between two parties. The first party, the programmer, doesn’t need to know or have access to production services. The least amount of privilege you can have, the better. The second party is not a coder, but maybe a network admin or someone else. They don’t need to know coding to change out and rotate secrets for you. And, as they’re environment-based, you will never have secrets in your code. In the rare case that someone had access to your source code, they still won’t have access to your other systems. 9 What Should I do? Make sure you’re using the config system for your configuration options. A common pattern we’ve seen is a constant in a class that contains an end point - move this to the config system and inject it in the constructor when you define that service in your app service provider. Make sure all secrets are in your .env file or some other environment configuration system that’s available to your config system. If you previously had them hardcoded, move them to the environment, and then ask for them to be rotated or regenerated as soon as possible. Now that you use environment-based configuration, you’ll find that unit testing also becomes easier. You can define a special .env.testing file, or even define environment variables in your phpunit.xml file. Sweet! 10 Step 4: Rate Limiting What’s worse than a website crashing because so many people are using it? Crashing when only one or two people are using it. This is why rate limiting is so important. When websites stop responding from high load, there’s more at stake than just a bad customer experience. There can be pay-per-use resources that are now being spiked causing tremendous expense. There can be fake data that will devalue real customer’s input or mix up trends. And, in some cases, high load can instigate crashes that can cause code or secrets to spill out to the world. Not good. Rate limiting is best done on a system that’s outside of the scope of your Laravel application. You should use AWS WAF if you’re using Amazon’s Web Services, or you might find value in using Rate Limiting with Cloudflare if you’re using their proxy and DNS service. Rate limiting can be done with mod_ratelimit for Apache or ngx_http_limit_req_module for NGINX on the server before PHP/Laravel is queried. If your needs are more granular than those can provide, or you don’t have access to that level, you can use the Laravel Rate Limiting functionality. You can create naive global rate limits for most endpoints. Segmented rate limits allow you to create a rate limit per segment or choice of information. Conditional rate limits also allow for returning a rate limit based on only specific environmental or request-based information. What Should I Do? Do you have any endpoints or workflows available that have expensive pay-per-use resources attached? Attach a rate limit to that. What about your login system? Are you restricting access to brute force attacks with rate limiting? You should be. Put something in place for that. Even if you force your users to pick strong passwords, those that don’t always just use the same password will rotate through a few passwords. Those same passwords are usually guessed in rapid succession against your endpoints. You can stop this. Don’t let your system get abused. 11 Step 5: Data Validation If memory serves, there was some weird buffer overflow issue in Win95(a) that allowed you to login with a username of 10,000 a characters or something like that. Somehow, that payload was too much, and allowed some memory to be overwritten and boom you were logged in. No one thought that a username should be validated - because everyone knows that a username can’t be longer than 104 characters, … or was that what windows 2000 allowed? Or was that some network limitation? Who can remember… But the incoming user data was never validated and therefore we had this tragic buffer overflow. Fast-forward to a recent project we were working on that would throw a fatal error when someone added one comment that was over 300 characters long. This was because MySQL field was limited to 255 and automatic truncation wasn’t allowed. Besides the fact that this caused a fatal error and a bad user experience, this application happened to have debug mode on - so it showed a nice set of MySQL credentials to the lucky user. Or, there was another project that we had reviewed that had a mysterious thing happening. Somehow, all of the asset records were being moved to and owned by one specific user. How were they taking ownership? That malicious user was adding an owner_id field to their requests and changing it to their own. Clever. We could go on and on with tons of examples of why data validation is necessary. Required Data / Types of Data Do not rely on the front end to validate that data exists or it is of a specific type. We need the data we need. We shouldn’t receive the word “tomorrow” when we need a YYYY-MM-DD date. If the data is not as expected, there are a couple reasons. First, there could be a bug in the front end - and we need to fix that there. Or second, it is someone trying to send malformed data - so don’t let them. Data Bounds If you only expect 200 characters for a database field, only accept up to 200 characters from the front end. Don’t allow overflows or truncation - you don’t always know what’s going to happen. 12 Keep data in a specific bounds - no one should be charging negative 10 for their cart total so validate that. Prohibit or Exclude Data If you have choices on the front end that determine the payload you should receive, either prohibit or exclude those fields that you shouldn’t receive based on those decisions. Your job is to validate that you have true, clean data before you expose it to the rest of your code. Only retrieve validated data The validated() method, provided by a Form Request class or a Validator instance, provides only validated data. Why is this important? Well, it’s only valid data, if it’s all valid, and nothing more. So, if the user mischievously sends along extra data, like an owner_id, it won’t be retrieved and possibly used in your update. What Should I Do? Every field must be either required, required_if or nullable Using that mechanism, you can use the validated() method and retrieve only data that has been defined as validatable and valid data Use validation rules like integer or string as well as using the max, min or between rules to give nice, common bounds to your system We can’t stress how important validation is. Any data you receive from anyone else, whether it’s a user or an API, should be validated against what you expect. Invalid incoming data is one of the most common pathways that are used to attack your app! 13 Step 6: Authorization It's a shame that Authentication and Authorization, two separate things, are so often referred to together or interchangeably as Auth. Authentication is the act of identifying a visitor or consumer as a specific known entity or user. Authorization is the act of determining if a visitor has permission to see something or execute a workflow. While these are commonly grouped together, these are not the same. You don’t need to have an authenticated user to authorize actions. Often you do, but it’s not required. Why Authorization? It’s important to authorize access to something instead of just check for an authenticated user for a couple of reasons. First, the authorization may change in the future independently of the authentication. We’ve worked on many projects where a particular feature was limited to authenticated users at first, but later was opened up to the public. There’s a lot of work required to undo all of the authentication checks generally. Instead, if you check authorization, you probably have only one place that you have to change that authorization calculation. Second, your authorization schemes tend to change and become more advanced as your application grows. Setting up authorization right away makes this growth easier. There have been many applications where something started out as accessible to all authenticated users that later transitioned to only specific levels or roles of users. What’s the security implication? Authorization systems tend to concentrate all of their decision making systems in one place. Because of this, it’s easier to update and to audit these systems. Instead of checking different ways of doing things - and their availability throughout the system - you have one place to go to inspect and further secure your system. 14 What Should I Do? If you’re checking for an authenticated user, maybe using something like the auth middleware, define and implement some simple Laravel Gates as well. Layer in your protection at first. Define Laravel Policies for more resource-specific or complex authorization. Choose a tested, bullet-proof tool like laravel-permission from Spatie to implement roles and permissions. Authorization can get complex. If you’re not using it now, and just checking for authenticated users instead, it’s time to pull the trigger and build this in. You’ll be glad you did. 15 Step 7: Are Eloquent and Query Builder Worth It? One of the most impactful attacks we've demonstrated to developers is how to take over an entire application with just one malformed insecure database statement. This is called SQL injection. Luckily, these days, if we’re disciplined enough, this doesn’t have to happen anymore. Prepared Statements Both Eloquent, the Laravel ORM, and the DB query builder class use prepared statements by default. SQL injection happens when unfiltered data is spliced into a string that is a SQL query. Prepared statements take away that attack vector by the way they compile the query language ahead of time before accepting user input. Originally, prepared statements were created for situations where the same query was executed over and over. Instead of splicing in user data, compiling and then executing the query thousands of times, you could compile the query ahead of time, use it almost like a function where you send in data as parameters, and then execute it. For many queries, this was faster. For a single query, though, there is a drawback: it actually takes longer. In the before times, the back in the day times, the yesteryear, this difference in time was noticeable and “a lot.” These days, the difference of time is in tenths of milliseconds - so the value we get from protecting against sql injection far outweighs the "cost" of using prepared statements - even for single queries. Use the Laravel Toolset Eloquent and the Query Builder build out efficient queries usually in the best format. We’ve seen far more programmers build worse queries by hand than any of the tradeoffs that Eloquent or Query Builder might introduce. So, if you use these tools like you’re supposed to, then you get SQL injection protection built-in. What if you can’t? In very specific scenarios where you can’t - and let us tell you these are rare - there are ways to execute the SQL without preparing it. To reiterate - it’s very rare that you have to 16 bypass the tooling of Laravel. We can count the amount of times on one hand we've had to over the last 10 years. Opt to use Eloquent or the Query builder unless you really need something else. What Should I Do? Search for any instances of raw() on any of the database statements. Are you sure you need to use raw, unprepared data here? Revisit it and see if you can come up with a different way. If you are going to bypass the built-in protections, note that Laravel now allows for scoped methods like orderByRaw() that help limit the damage that an unfiltered piece of data could cause. Use validation to make sure it’s data you’d expect and is within bounds. 17 What’s the Most Important Thing in a Secure Laravel App? Validation. Validation is the most important thing in a secure Laravel app. Whether you’re dealing directly with user input or you’re receiving third-party data from an API, you need to implement validation. User Input There are three types of user input: accurate, mistaken and malicious. Let’s break it down. Accurate user data is data that comes in from a user that makes sense and is within your boundaries. The data types are correct and any logical decisions have limited or altered the incoming fields. This is the perfect path and the one we like to concentrate on. Mistaken user data can come from a user’s misunderstanding of the input requirements. Think about an age field: should they type in 25 or 25 years? What about old enough? Perhaps the front end should explain the requirements a bit better. But, what if the validation on the front end fails? Mistaken or invalid data can flow in from the user themselves or from some failed front end process. Malicious user data comes in at two levels: the prankster and the hacker. The prankster sees a price field and they wonder can they change it to zero. They see a comment section and wonder if they can put a bad picture in there. They’re just being mischievous. The hacker is trying to actually retrieve resources, destroy things, or overtake a system. API Data Trust, but verify. That’s all we can say about API data. Likely your fellow developer has built a nice interface for you that is within bounds and documented. But things happen. Requirements change, bugs get introduced, or even worse - upstream resources get hacked. You should always validate your incoming API data and reject data outside the bounds you’re expecting. 18 What Should I Do? There are two things you should do: First, check out the Validation Documentation for Laravel. Make sure you’re making use of the built-in validation rules and using the validated() method exclusively. Review every endpoint for form requests and make sure your code that process API data is validating the 3rd party content. Second, check out the Mastering Laravel Validation Rules book. This ebook and associated exercises focuses on the Laravel validation system beyond the documentation. It focuses on the “how” and “why” using real-world examples. And, because you’re reading this book, we’re offering you a special 25% off discount code: laravelhacker. Use that during checkout to save 25% off the book or the bundle. Or, you can just use this link to save 25%: Mastering Laravel Validation Rules There’s two really unique things in this book and bundle. First, we use real world examples. Each rule we talk about has examples showing passing examples and failing examples. It’s pretty thorough. Second, the exercises are examples of things that you will run into daily in your work. We set the exercises up for you to test your knowledge writing validation rules. Then we test them with a specialized unit test framework. If tests pass, you can move on. If they doesn’t, we give you hints to show you where you have room to grow and how to fix them. 19 End Notes Thanks for reading our 7 Steps to Securing Your Laravel App book. We hope you've learned a few things along the way and made some real positive changes to your app. In fact, we'd love to hear about your success stories or things you've learned. You can email us at hello@nocompromises.io or tweet Joel (@jclermont) or Aaron (@aaronsaray) directly. Keep an eye out for news and updates from us, too. We continue to launch new and cool Laravel educational resources and tools, and we'd love you to be part of the community. ps... when you read a book, do you "hear" a voice for the author? Like, make one up and sort of hear that in your head as you're reading? Or is that just something weird Aaron does... Anyway, if you want to hear the sweet, dulcet tones of the two authors, check out the No Compromises Podcast. 20