Hello! Recently we had a client execute a security audit on their site. They approached us with a pretty verbose audit report with a long list of items to address. Going through the motions of securing and hardening a WordPress site in this context is a good reason to share the processes that an agency must undertake to ensure client site security is optimal and follows best practices.
In today’s post, we’re diving into a topic that’s both critical and often overlooked: security headers. This is part of an ongoing series of posts centred around improving your website security and recovering from a compromised wordpress website.
For WordPress website owners, implementing proper security headers can significantly enhance your site’s security posture, shielding it from a range of potential threats. In this guide, we’ll focus specifically on how to audit, report, and implement security headers directly through your functions.php
file—no plugins needed.
What Are Content Security Policy Headers and Why Should You Care?
Security headers are configurations sent by your server to browsers, instructing them on how to handle your site’s content and interactions. They play a vital role in preventing a wide range of attacks, such as cross-site scripting (XSS), clickjacking, and data sniffing.
Here’s a quick overview of some common security headers:
- Content Security Policy (CSP): Defines which resources (scripts, styles, etc.) are allowed to load on your site.
- Strict-Transport-Security (HSTS): Forces browsers to only load your site over HTTPS.
- X-Frame-Options: Prevents your site from being embedded in iframes, protecting against clickjacking.
- X-Content-Type-Options: Stops browsers from MIME-sniffing content types, reducing the risk of code execution attacks.
- Permissions-Policy: Manages access to browser features like the camera, microphone, or geolocation.
Without these headers, your site is like a house with unlocked doors and windows. Adding them strengthens your defenses against potential intrusions.
Step 1: Auditing Your Current Security Headers
Before making any changes, it’s essential to understand what’s already in place. This process involves auditing your current headers to identify gaps.
Using Browser Developer Tools:
- Open your website in a browser.
- Press
F12
or right-click and select “Inspect” to open the developer tools. - Navigate to the “Network” tab and reload the page.
- Click on the main request (usually your site’s URL) and inspect the “Headers” section.
Online Tools for Auditing:
- SecurityHeaders.io: Enter your URL, and it will generate a detailed report on your current security headers.
- Observatory by Mozilla: Another excellent tool for a comprehensive security assessment.
Take note of missing headers or misconfigurations. For instance, if CSP is absent, your site is potentially vulnerable to XSS attacks.
Step 2: Adding Security Headers to functions.php
To address identified gaps, we’ll add security headers directly to the functions.php
file in your theme. This method avoids relying on plugins, giving you complete control over the implementation.
Basic Example:
function add_custom_security_headers() {
header("Strict-Transport-Security: max-age=31536000; includeSubDomains; preload");
header("Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:");
header("X-Frame-Options: SAMEORIGIN");
header("X-Content-Type-Options: nosniff");
header("Permissions-Policy: geolocation=(), microphone=()");
header("Referrer-Policy: no-referrer-when-downgrade");
}
add_action('send_headers', 'add_custom_security_headers');
Detailed Breakdown:
- HSTS: This ensures browsers only load your site over HTTPS for a year (
max-age=31536000
). - CSP: Limits resources to trusted domains. For example,
script-src
allows scripts only fromself
andhttps://trusted.cdn.com
. - X-Frame-Options: Prevents your site from being embedded in other sites, protecting against clickjacking attacks.
- X-Content-Type-Options: Disables MIME type sniffing, ensuring browsers only execute scripts with the correct content type.
- Permissions-Policy: Disables unnecessary browser features, such as geolocation, unless explicitly needed.
- Referrer-Policy: Controls how much referrer information is sent when users navigate from your site to another.
After adding the above code, save the file and test your site for functionality.
Step 3: Testing and Reporting
Implementing security headers is only part of the process. Testing ensures your configurations work as intended without breaking functionality.
Testing Tools:
- Use browser developer tools to verify that the headers appear in the response.
- Re-run tests on SecurityHeaders.io and Mozilla Observatory to confirm improvements.
Common Issues and Fixes:
- Blocked Resources: If a script or style is blocked, update the
CSP
to include the required domain. - Mixed Content Errors: Ensure all resources (e.g., images, scripts) are loaded over HTTPS.
For example, if you’re embedding a third-party script like Google Maps, you may need to modify the script-src
directive:
header("Content-Security-Policy: script-src 'self' https://maps.googleapis.com;");
Step 4: Monitoring and Iterating
Security is an ongoing process. Regular monitoring ensures your site remains protected as new threats emerge.
Set Up Reporting: Add a report-uri
to your CSP header to collect reports on blocked resources:
header("Content-Security-Policy: default-src 'self'; report-uri /csp-violation-report-endpoint;");
You can create a PHP endpoint to log these reports:
if ($_SERVER['REQUEST_URI'] === '/csp-violation-report-endpoint') {
$data = file_get_contents('php://input');
file_put_contents('csp-violations.log', $data . PHP_EOL, FILE_APPEND);
exit;
}
Monitor Server Logs: Check your server logs periodically to identify any patterns of blocked requests or errors caused by misconfigured headers.
Stay Updated: Security headers evolve. Follow WordPress security blogs and communities to stay informed about best practices and new header directives.
Final Thoughts
Implementing these security headers via editing the functions.php as detailed above will provide a layer of protection against things like malicious code injection throughout your site. However it should be noted that should your site be compromised beyond a certain threshold of malicious activity, none of these headers should matter.
It might be more ideal to implement things like security headers earlier on in the web request layer such as through web services like nginx or apache as those are less likely to be manipulated if a website is compromised.
Remember, this process requires ongoing attention. As your site grows and changes, so too should your security configurations. With the steps outlined here, you’re well-equipped to take control of your site’s security posture and safeguard it against evolving threats.