This weekend, I changed the design of this blog whilst doing so I wanted to add the security headers for content security policies, these tell the application what it can and cannot run, There's a great website called https://securityheaders.com which will scan a URL and tell you what your level is.
If you have no headers set up you'll get an F grade, which is bad!
Before I started my rating:
Once I finished I have an A rating:
For detailed information about security headers read Daniel DuĊĦek blog that explains this really well https://danieldusek.com/enabling-security-headers-for-your-website-with-php-and-laravel.html
Still here? great I'll go over what I've done.
I created a middleware class called SecurityHeaders.php inside App\Http\Middleware of my Laravel application
Add this middleware to the Middleware group inside App\Http\Kernal.php
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\SecurityHeaders::class,
];
Set the headers to be turned off, this provide would be attackers information about the server, you don't need to advertise these to better to turn them off.
private $unwantedHeaders = ['X-Powered-By', 'server', 'Server'];
Referrer Policy
Sets how much information should be sent with requests, in my case I chose the option to not send the referer header for requests to less secure destinations.
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
XSS Protection
Stops loading of pages when they detect reflected cross-site scripting (XSS) attacks
I set: Enables XSS filtering. Rather than sanitizing the page, the browser will prevent rendering of the page if an attack is detected.
$response->headers->set('X-XSS-Protection', '1; mode=block');
Strict Transport Security
informs browsers that the site should only be accessed using HTTPS
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
Content Security Policy
The content security policy sets weather a browser can run JS / CSS on page load.
You will want to either det a URL specifically or a wildcard like *.domain to allow the subdomain of the given domain to run
$response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'self' platform.twitter.com 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' * data:; font-src 'self' data: ; connect-src 'self'; media-src 'self'; frame-src 'self' platform.twitter.com github.com *.youtube.com *.vimeo.com; object-src 'none'; base-uri 'self'; report-uri ");
Expect-CT
The Expect-CT header lets sites opt in to reporting and/or enforcement of Certificate Transparency requirements
$response->headers->set('Expect-CT', 'enforce, max-age=30');
Permissions-Policy
Sets what permissions the device load the page is given
$response->headers->set('Permissions-Policy', 'autoplay=(self), camera=(), encrypted-media=(self), fullscreen=(), geolocation=(self), gyroscope=(self), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=(self), usb=()');
The complete class:
With this class in place upload your changes to your server and re-run https://securityheaders.com
<?php
namespace App\Http\Middleware;
use Closure;
class SecurityHeaders
{
private $unwantedHeaders = ['X-Powered-By', 'server', 'Server'];
/**
* @param $request
* @param Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);
if (!app()->environment('testing')) {
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
$response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'self' platform.twitter.com plausible.io utteranc.es *.cloudflare.com 'unsafe-inline' 'unsafe-eval' plausible.io/js/plausible.js utteranc.es/client.js; style-src 'self' *.cloudflare.com 'unsafe-inline'; img-src 'self' * data:; font-src 'self' data: ; connect-src 'self' plausible.io/api/event; media-src 'self'; frame-src 'self' platform.twitter.com plausible.io utteranc.es github.com *.youtube.com *.vimeo.com; object-src 'none'; base-uri 'self';");
$response->headers->set('Expect-CT', 'enforce, max-age=30');
$response->headers->set('Permissions-Policy', 'autoplay=(self), camera=(), encrypted-media=(self), fullscreen=(), geolocation=(self), gyroscope=(self), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=(self), usb=()');
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->headers->set('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
$response->headers->set('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Requested-With,X-CSRF-Token');
$this->removeUnwantedHeaders($this->unwantedHeaders);
}
return $response;
}
/**
* @param $headers
*/
private function removeUnwantedHeaders($headers): void
{
foreach ($headers as $header) {
header_remove($header);
}
}
}