Discover more from Securing Laravel
Security Tip: Getting Started with Content Security Policies
[Tip#47] Setting up a CSP doesn't have to be a daunting task! Let's take a look at a tips for getting started with CSPs, without breaking anything!
Greetings my friends! We’re on the home stretch now, onto #3 in the Top 10 security issues I’ve found during audits. This week is Missing Content Security Policy (CSP)! I split it from #4’s Missing Security Headers in my list since it’s such a big topic on it’s own. I’ve covered CSPs before, so today we’re looking at getting started with CSPS, with links to other resources.
Here’s our current Top 10:
#10 → Insufficient Input Validation
#9 → Missing Subresource Integrity (SRI)
#8 → Insufficient Rate Limiting
#7 → Cross-Site Scripting (XSS)
#6 → Outdated & Vulnerable Dependencies
#5 → Insecure Function Use
#4 → Missing Security Headers
#3 → Missing Content Security Policy (CSP)
🛡️ Do you remember when your last annual security audit was?
🕵️ Book a security audit now to keep your app security up-to-date!
Securing Laravel is 100% reader-supported. Please consider becoming a free or paid subscribe to receive new posts and support my security work in the Laravel community!
Getting Started with Content Security Policies
I talk about Content Security Policies (CSPs) a fair bit, but if you haven’t heard of them before, CSPs are a way to instruct the web browser that resources your app is allowed to interact with. It was designed as a layer of defence to prevent Cross-Site Scripting (XSS) and clickjacking attacks, and consists of a set of directives and rules which define the allowed behaviour. When fully configured, a CSP can prevent any unwanted scripts, styles, fonts, forms, etc, from being executed - locking a site down completely.
As an example, this is the CSP from practicallaravelsecurity.app:
Content-Security-Policy: report-uri https://valorin.report-uri.com/r/d/csp/enforce ; default-src 'none' ; connect-src 'self' https://cdn.usefathom.com ; font-src 'none' ; frame-src https://practicallaravelsecurity.app https://*.practicallaravelsecurity.dev ; img-src 'self' data: https://cdn.usefathom.com ; manifest-src 'self' ; script-src 'report-sample' 'self' https://cdn.usefathom.com 'nonce-cH48M...' ; style-src 'self' 'nonce-cH48M...' ; form-action 'self' ; frame-ancestors 'none'
There are two modes you can implement a CSP in:
`Content-Security-Policy`→ Blocks and reports all policy violations.
`Content-Security-Policy-Report-Only`→ Reports all policy violations only.
You can use either or both modes on the same site, a making CSPs incredibly flexible. You can deploy a flexible blocking CSP alongside a strict Report-Only CSP, to test out new directives without breaking anything, and still retain the protection of your flexible CSP.
I covered CSPs in one of my early In Depth articles, so go check that out to learn more about how CSPs work and how to configure them:
Implementing CSPs in Laravel
My recommended approach is to use a simple middleware class and a config file to configure and load your CSP. This gives you full control over the directives and makes it easy to add in support for nonces, etc1.
You can use my middleware in this Gist: https://gist.github.com/valorin/d4cb9daa190fdee90603efaa8cbc5886.
If you’re after something with a lot more features or wish to define your CSPs within classes, Spatie have a CSP package: https://github.com/spatie/laravel-csp.
My favourite reporting tool for CSPs (and many other security things) is Report URI. Report URI have a free tier, which is great for getting started. I have a paid account, which allows me to monitor a number of sites easily.
If you’re a user of Honeybadger, they can receive CSP reports as well, which is handy for handling CSP violations alongside app errors, etc.
CSPs in New Projects
CSPs are relatively easy to add into new projects. You can build the directives as you add in dependencies, test then in local dev, and roll them out, knowing that there will be nothing unexpected you’ve missed.
Just start with a basic policy that blocks everything, and start allowing things as required:
Content-Security-Policy: default-src 'none'; form-action 'none'; frame-ancestors 'none'; report-uri <reporting_url>
I recommend configuring your CSP middleware to run Report-Only mode in local dev, and non-report-only in production. This allows you to track violations in local dev, without it breaking things when you’re testing new features. It also prevents Laravel’s Error page from breaking, since it uses a bunch of inline scripts and styles.
CSPs in Existing Projects
Adding CSPs to existing projects can seem like a daunting task, but there are a couple of steps you can take to make it relatively painless.
First, sign up for Report URI and configure their CSP Wizard for your site. The wizard receives all violations, reduces them down into the required directives, and them helps you build your CSP header.
Secondly, deploy a Report-Only header to your site, blocking everything:
Content-Security-Policy: default-src 'none'; form-action 'none'; frame-ancestors 'none'; report-uri https://<subdomain>.report-uri.com/r/d/csp/wizard
Browse around your app and wait for your users to use it.
You’ll see the reports landing in the wizard, where you can add directives to the compiled policy, and block unwanted directives. Once you’re starting to build your policy, you can periodically update the policy in your app to reduce the number of violations hitting the wizard.
When you’re happy, you can disable the wizard and do one of two things:
Stay on Report-Only mode.
Report-Only mode gives you complete awareness of what’s happening in your app, without the risk of breaking anything. This is a fantastic option for complex or legacy apps. This is what I run on WordPress sites, do to their unique nature.
Switch off Report-Only mode.
If you’re confident in your policy, switching off report-only mode will block any violations, and keep your site protected. This is how I run a CSP on practicallaravelsecurity.app.
To wrap up this one, I want to see where everyone is at in regards to CSPs…
I recommend implementing all security headers via custom middleware, to avoid needless complexity and adding extra dependencies.