Getting WordPress to play along with HTTP Content Security Policy (CSP) can be challenging.
Security headers play an important role in protecting your web site against malicious attacks, but they can also cause trouble for legitimate server applications.
I’ve written a set of non-intrusive patches for WordPress 5.5.1 that simplifies the task of deploying CSP for my site. The patches should be regarded as a temporary fix. Eventually the WordPress developers will get around to fix the parts that cause problems with CSP – I hope.
Content Security Policy
In essence, security headers are a set of self-imposed rules or precautions that informs the client (typically the browser) of what kind of behavior to expect from the web application.
If an application steps out of line, the client will react by blocking or disregarding the offending action and in some circumstances notify the user that a possible security breach was detected.
Headers are typically added by editing the server configuration file for your site. For Nginx, an excerpt from a security header specification may look something like this:
add_header Referrer-Policy no-referrer; add_header X-Frame-Options DENY; ....
I assume that the reader is familiar with setting up a web site with Nginx. While configuring web servers is out of scope for this posting, many good tutorials can be found online, for example this one.
One important header is Content Security Policy (CSP). CSP defines rules for from where different types of content may originate. CSP protects against the sort of unauthorized manipulation of your site that typically occurs when an attacker exploits vulnerabilities and coding mishaps in server-side programs. Cross Site Scripting and Data Injection Attacks are two major threats CSP offers protection against.
Properly deploying a content security policy can be complicated, especially if you are not in complete control over the HTML rendering process.
Even though the code base for WordPress has improved dramatically over time, turning on strict CSP for any WordPress driven site is destined for trouble.
Blocking inline code
One key feature of CSP is that it can be configured to disallow so-called inline scripts and styles. The browser will block content that is embedded inline in <script> or <style> elements.
While that makes sense from a security point of view, it causes some issues for WordPress. Inline is the middle name of many WordPress plugins and themes. Even WordPress’ core templates emit inline scripts/styles. Generally, it is frequently encountered all over the place. It is bad practice because it inhibits deployment of a full-fledged Content Security Policy.
The problem is often worked around by simply allowing unsafe code. By specifying the ‘unsafe-inline’ keyword in the CSP header, inline scripts/styles are let through. However, bailing out like this defeats the very purpose of CSP and should be avoided.
As an alternative to ‘unsafe-inline’, there are a couple of mechanisms for whitelisting inline styling and scripting in CSP. These methods enable the client to verify that the code in question actually originates from the web application itself, and not from some evil hacker attempting to exploit program vulnerabilities by sending specially crafted request parameters to the server.
One method for whitelisting inline <style>- and <script> elements is to tag them with a random, unique code, or nonce, that changes between each server request. Since an attacker has no way of knowing what the next code will be, injecting and executing code through request parameters becomes intractable. Any compliant browser will refuse to execute the attacker’s code, as long as it lacks the correct nonce.
In a blog posting from 2017, security researcher Scott Helme pointed out that Nginx can be configured to generate a new random nonce for each request it handles. The nonce is then added to the relevant DOM elements before the final response is dispatched. He has written a great description on how to implement CSP support in Nginx by compiling Nginx with the two additional modules.
By applying my patches to WordPress, a nonce attribute is added to inline <script> and <style> tags in WordPress’ source files. The attribute’s value is a secret placeholder that Nginx will replace with a random nonce before sending the reponse to the client.
Setting up Nginx
Two modules need to be installed with Nginx for this to work:
The first one,
ngx_set_misc, takes care of random nonce generation, while the second one,
http_sub_module, provides search and replace functionality in order for Nginx to swap the secret placeholder value with the actual nonce.
Here’s what the nonce setup may look like in Nginx’ site configuration file:
set_secure_random_alphanum $NONCE 32; sub_filter_once off; sub_filter_types text/html; sub_filter 'Ykggh14DVCF7' $NONCE; sub_filter ' style="' ' data-inl-style="';
The nonce is generated by the
set_secure_random_alphanum function in line 1 above. The nonce is now stored in the variable
$NONCE. We use it in the search and replace operation in line 4. All instances of the secret placeholder
Ykggh14DVCF7 is replaced with the newly generated nonce.
In addition, I removed all style attributes by running the crude search and replace operation in line 5. These cannot be whitelisted at all in CSP, and will have to go. All
style attributes are renamed to
data-inl-style. This may affect certain aspects of the page design. To me, that is a minor concern.
Finally, we can construct the CSP header directive:
add_header Content-Security-Policy \ "img-src 'self'; \ style-src 'self' 'nonce-$NONCE'; \ script-src 'self' 'nonce-$NONCE'; \ font-src 'self' data:; \ default-src 'self'"; \ always;
It is very important that the placeholder code is kept secret, otherwise an attacker could use it to trick your web server into whitelisting hers or his injection attacks (!).
After applying the patches and testing your server headers, you may still have blocked content. If you have third-party plugins in your WordPress rig, they should be investigated an possibly patched as well. Themes may also violate CSP. You should consider creating a child theme where you can correct the markup without touching the original theme.
Happy inline hunting!