HummingbirdUK main logo

Coding solutions to business problems

About us

We use code to create solutions to business challenges, bottle-necks and headaches.

If you think your business has a problem that can be solved through code, we are happy to chat things through without any obligation.

Get in touch

Manage a CSP with Laravel

Home / Blog / Manage a CSP with Laravel

Written by Giles Bennett

Content Security Policies have been around for years - the idea was first put forward in 2004, but it seems like they've only come to the forefront in the last five years or so.

In brief, they prevent various different types of code injection attacks by telling the browser which sources it should load scripts, images, stylesheets, and other elements of the page from. If the site is hacked in some way, by the insertion of malicious code loaded from a third party website, then as the third party website is not on the 'allowed' list, the browser doesn't execute it and the hack fails.

Server-side implementation

The simplest method of implementation is server-side, by adding the relevant configuration to the web server to add the CSP header to all responses sent back to the browser.

Whilst this is fine for resources served up from the website's own domain, or third party domains (Google Analytics, for example, or YouTube) which are permitted by the CSP, the problem comes with inline scripts, stylesheets, or even style tags within an HTML element.

To allow those requires either loosening the CSP to allow 'unsafe-inline' scripts and stylesheets, or some method of indicating to the browser that the inline script or style is valid, and should not be blocked. There are two method for this - either a hash, or a nonce.

Hashes

To allow an inline script, or stylesheet, via a hash, first the SHA256 hash of the script must be obtained. That hash can then be added to the script-src section of the CSP in the web server's configuration - as long as the script does not change at all, then the hash in the CSP will remain valid, and the script will be allowed to execute. The problem, however, is that if the script rendered to the page changes - even with just the addition of a single space - then its hash will change completely. The hash in the CSP will be invalid, and the script will not be executed by the browser.

Nonces

A nonce (pronounced 'en once') is a one time use word or phrase - is a long, randomly-generated, value which is unique to each HTTP response. It's sent in the headers of the response, and in the body of the response within the relevant script or style tag. Only if the two match will the browser execute the script or render the style - someone trying to hack the site by including a malicious script would fail because they would be unable to guess what the nonce header will be (as it's generated randomly).

Implementation in Laravel

By far the best way to implement a CSP within a Laravel application is by use of the Spatie / Laravel-CSP package.

You can install the package via Composer :

composer require spatie/laravel-csp

and then publish the configuration file with Artisan :

php artisan vendor:publish --tag=csp-config

Whilst you can use the basic policy that is shipped with the package, we prefer to set up our own. So create a new PHP class at App\Policies\CSP that initially looks like this :


namespace App;

use Spatie\Csp\Directive;
use Spatie\Csp\Keyword;
use Spatie\Csp\Policies\Basic;

class ContentPolicy extends Basic
{
    public function configure()
    {
        parent::configure();

    }
}

Next, after the call to the parent::configure() method, you can start outlining your policy. This takes the form of various Directives (which correspond to the various CSP section - img-src, script-src, etc.) which can be passed either a single value or an array of values. So, for example, to allow images to be loaded from your own site, and Google, you would add :

$this
    ->addDirective(Directive::IMG, [
        Keyword::SELF,
        'www.google.co.uk'
    ]);

Additional directives can then be chained, so to add in the ability to load Youtube videos in an iframe, you would add in a new directive :

$this
    ->addDirective(Directive::IMG, [
        Keyword::SELF,
        'www.google.co.uk'
    ])
	->addDirective(Directive::FRAME, [
        'www.youtube.com'
    ]);

If you are using the hashing method to validate stylesheets (or scripts) then the hash you calculate can be added in as follows :

->addDirective(Directive::STYLE, [
    'sha256-longrandomSHAstringwillgohere'
])

And similarly, if you wish to allow the use of nonces for scripts and styles, you could add chain those directives too :

->addNonceForDirective(Directive::SCRIPT)
->addNonceForDirective(Directive::STYLE);

If you do wish to use nonces, then you also need to tell Blade to output the nonce within your script or style tag :


<link rel="stylesheet" href="{{ mix('/css/highlight.min.css') }}" nonce="n23Z5ttmKYXeR5zUiBlZD4inPtCUCy1B">

Once you have finalised your policy, within config/csp.php you should change the value of policy to match the class you creeated :

'policy' => \App\Policies\CSP::class,

and ensure that the CSP is enabled further down that file :

'enabled' => env('CSP_ENABLED', true),

Once done, reload your configuration, and check the front of your site using the Developer Tools console to ensure that everything's loaded that should be being loaded. And you're away.

Author : Giles Bennett

About the author

Giles Bennett built his first website in 1996, and is old enough to miss Netscape Navigator. Initially a lawyer, he jumped ship to IT in 2008, and after 5 years as a freelancer, he founded HummingbirdUK in 2013. He can be reached by email at giles@hummingbirduk.com.