Difference between revisions of "WordPress"

From Jon's Wiki
 
(16 intermediate revisions by the same user not shown)
Line 1: Line 1:
Enable user-plugin installation by allowing write access to wp-content/{plugins,themes} and configure:
+
WordPress is popular, feature-rich and useful, despite the awful spaghetti state of its PHP code. It is also slow, without a bunch of extra help. Luckily we can speed it up and lock it all down pretty well as follows.
 +
 
 +
== Optimise PHP ==
 +
 
 +
Use PHP-FPM and use the Opcache. See the [[PHP]] page.
 +
 
 +
== Cache all the things ==
 +
 
 +
WordPress is slow. Fortunately, there are things we can do to make it behave more like a static site, which for 99% of the time, it may as well be.
 +
 
 +
First, install and configure [https://wordpress.org/plugins/wp-super-cache/ WP Super Cache] to cache page content.
 +
 
 +
Second, install a Redis server, and get the [https://wordpress.org/plugins/wp-redis/#installation WP-Redis] plugin and follow its installation instructions. This hugely reduces load times by caching PHP object data and reducing calls to the database.
 +
apt get install redis-server
 +
 
 +
Also, configure Apache to gzip all the static assets and use a long cache expiry time.
 +
 
 +
== Allow user-installed plugins and themes ==
 +
 
 +
I do not recommend it, but if you're game you can enable WordPress users with admin access to install their own plugins and themes by allowing write access to wp-content/{plugins,themes} and configuring as follows:
  
 
  # in wp-config.php
 
  # in wp-config.php
 
  define('FS_METHOD', 'direct');
 
  define('FS_METHOD', 'direct');
  
Other nifty things:
+
The obvious disadvantage to this approach is you lose control of what god-awful unaudited third-party code they might install.
 +
 
 +
It is however useful as a temporary measure when starting out, and once the website is nearing a stable production-ready state, you can audit it all, and switch back to maintaining the selection of themes and plugins separately (e.g. in git, using submodules, or using wp-cli on the server) and disallowing user installs.
 +
 
 +
== Command line tool ==
 +
 
 +
Use [https://wp-cli.org wp-cli], a really useful tool for managing WordPress sites from the command line. Once downloaded you can keep it updated with:
 +
 
 +
wp cli update
 +
 
 +
Manage users, pages, posts and so on. Plugins and themes can be installed, enabled or disabled, removed, and kept up to date:
 +
 
 +
wp plugin install wp-super-cache wp-redis
 +
wp theme install astra
 +
wp plugin update --all
  
* CLI, wp-cli.org
+
== Security hardening ==
* Security scanner, wpscan.org
 
  
== Use SSL ==
+
Treat the internet like it is: an invading horde of zombie bots, script kiddies, crypto miners and identity thieves swarming outside relentlessly trying to break into your site.
  
Use [[Letsencrypt]] to enforce HTTPS everywhere and dehydrated to maintain the SSL certificate, and use the latest Mozilla SSL Guideline from their [https://ssl-config.mozilla.org/ configurator] to set up strong SSL.
+
=== Use SSL everywhere ===
  
== Content security ==
+
Use [[Letsencrypt]] to get free SSL certificates, and enforce it everywhere using HTTP redirection to HTTPS. This is probably the most important thing you can do to increase the attack difficulty of your site. Use dehydrated to automatically renew the SSL certificate, and grab the latest [https://ssl-config.mozilla.org/ Mozilla SSL Guideline] to set up strong SSL.
  
Use a content security policy. There is a plugin for managing one from within WordPress, but it is straightforward to cobble one and add it to the headers. Here's an example content security policy that allows JavaScript, images and CSS from only certain origins, and denies everything else:
+
=== Use a content security policy ===
  
 +
Use a content security policy to reduce or even eliminate several methods of attack. There is a plugin for managing one from within WordPress, but it is straightforward to cobble one and add it to the headers. Here's an example content security policy that allows JavaScript, images and CSS from only certain origins, and denies everything else:
 +
 +
# Expanded for readability; needs to be on one line:
 
  Header always set Content-Security-Policy <nowiki>"base-uri 'self';
 
  Header always set Content-Security-Policy <nowiki>"base-uri 'self';
 
     default-src 'none'; media-src 'self';
 
     default-src 'none'; media-src 'self';
     connect-src 'self' https://*.wp.com wss://public-api.wordpress.com https://api.stripe.com https://checkout.stripe.com;
+
     connect-src 'self' https://*.wp.com wss://public-api.wordpress.com https://api.stripe.com
     font-src 'self' data: https://*.wp.com https://wordpress.com https://fonts.googleapis.com https://fonts.gstatic.com;
+
        https://checkout.stripe.com;
 +
     font-src 'self' data: https://*.wp.com https://wordpress.com https://fonts.googleapis.com
 +
        https://fonts.gstatic.com;
 
     form-action 'self';
 
     form-action 'self';
 
     frame-ancestors 'self';
 
     frame-ancestors 'self';
     frame-src 'self' https://jetpack.wordpress.com https://*.wp.com https://www.google.com https://*.stripe.com
+
     frame-src 'self' https://jetpack.wordpress.com https://*.wp.com https://www.google.com
        https://player.vimeo.com https://www.youtube.com https://www.surveymonkey.com;
+
        https://*.stripe.com https://player.vimeo.com https://www.youtube.com
 +
        https://www.surveymonkey.com;
 
     img-src 'self' data: https://*.tile.openstreetmap.org https://*.gravatar.com https://*.wp.com
 
     img-src 'self' data: https://*.tile.openstreetmap.org https://*.gravatar.com https://*.wp.com
 
         https://*.wordpress.com https://s.w.org https://maps.googleapis.com https://maps.gstatic.com
 
         https://*.wordpress.com https://s.w.org https://maps.googleapis.com https://maps.gstatic.com
 
         https://q.stripe.com https://secure.surveymonkey.com;
 
         https://q.stripe.com https://secure.surveymonkey.com;
 
     script-src 'self' 'unsafe-inline' 'unsafe-eval' https://widget.surveymonkey.com https://*.wp.com
 
     script-src 'self' 'unsafe-inline' 'unsafe-eval' https://widget.surveymonkey.com https://*.wp.com
         https://public-api.wordpress.com https://*.stripe.com https://maps.googleapis.com https://ajax.googleapis.com;
+
         https://public-api.wordpress.com https://*.stripe.com https://maps.googleapis.com
     style-src 'self' 'unsafe-inline' https://*.wp.com https://ajax.googleapis.com https://fonts.googleapis.com;"</nowiki>
+
        https://ajax.googleapis.com;
 +
     style-src 'self' 'unsafe-inline' https://*.wp.com https://ajax.googleapis.com
 +
        https://fonts.googleapis.com;"</nowiki>
 +
 
 +
=== Quick win HTTP headers ===
  
 
Other headers can be used to further enforce some secure behaviour:
 
Other headers can be used to further enforce some secure behaviour:
Line 39: Line 81:
 
  Header always set Access-Control-Allow-Origin "*"
 
  Header always set Access-Control-Allow-Origin "*"
  
* gzip + WP SuperCache = win
+
=== Mandate strong user passwords ===
 +
 
 +
Consider installing a password policy plugin, such as [https://wordpress.org/plugins/password-policy-manager/ this one] from [https://www.miniorange.com/ Mini Orange].
 +
 
 +
=== Prevent server-side execution of uploaded content ===
 +
 
 +
Users can upload images and documents to the <code>wp-content/uploads</code> directory. This directory is therefore writeable by the webserver, including any of its processes hijacked by an attacker. This is a major avenue of attack, where an attacker can place malicious code in a site's upload directory and then run it on the server. So, we simply prevent server-side execution of anything uploaded this way:
  
== Lock things down ==
+
<Location "/wp-content/uploads">
 +
    SetHandler none
 +
    RedirectMatch 404 ".+\.php"
 +
</Location>
  
As suggested by [https://wpscan.com/wordpress-security-scanner WPScan], prevent DDoS attacks on web-accessible cron and XMLRPC endpoints, but allow CLI invocation, and prevent execution of uploaded PHP content:
+
=== Periodically scan your site  ===
  
 +
There are a few online scanning tools available which can be useful for keeping up with SecOps developments. For example, [https://wpscan.com/wordpress-security-scanner WPScan] recommends preventing DDoS attacks on web-accessible cron and XMLRPC endpoints, but allowing CLI invocation:
 
  <Files "xmlrpc.php">
 
  <Files "xmlrpc.php">
 +
    # Note: this breaks Jetpack
 
     Require ip 127
 
     Require ip 127
 
  </Files>
 
  </Files>
 
  <Files "wp-cron.php">
 
  <Files "wp-cron.php">
 +
    # Note: this breaks Jetpack
 
     Require ip 127
 
     Require ip 127
 
  </Files>
 
  </Files>
 +
 
  RedirectMatch 404 "/readme\.html"
 
  RedirectMatch 404 "/readme\.html"
 
   
 
   
<Location "/wp-content/uploads">
+
== Do you need an online shop? ==
    SetHandler none
+
 
    RedirectMatch 404 ".+\.php"
+
Do you need to pay $50 a month to Shopify or any of the other pay-through-the-nose shop services? No. Unless you are actually running some giant operation or department store, you can do it yourself for free in WordPress. You will need:
  </Location>
+
 
 +
* The [https://wordpress.org/plugins/woocommerce/ WooCommerce] plugin
 +
* The [https://wordpress.org/plugins/woocommerce-gateway-stripe/ WooCommerce Stripe Payment Gateway] plugin
 +
* A [https://dashboard.stripe.com/ Stripe account] connected to your bank account
 +
* Some products to sell
 +
* An evening of fiddling with shipping settings and setting up your product pages.
 +
 
 +
I have certainly found it sufficiently useful for a small non-profit selling merchandise, a community orchestra selling tickets, or a home business flogging chilli sauce.
 +
 
 +
If your phone has a NFC chip, then the Stripe app will also let you use your phone as a mobile EFTPOS machine that accepts credit cards for when you're out and about, and there is also a handy [https://woocommerce.com/mobile/ WooCommerce app] that lets you track orders and sales from the website.

Latest revision as of 21:53, 11 October 2021

WordPress is popular, feature-rich and useful, despite the awful spaghetti state of its PHP code. It is also slow, without a bunch of extra help. Luckily we can speed it up and lock it all down pretty well as follows.

Optimise PHP

Use PHP-FPM and use the Opcache. See the PHP page.

Cache all the things

WordPress is slow. Fortunately, there are things we can do to make it behave more like a static site, which for 99% of the time, it may as well be.

First, install and configure WP Super Cache to cache page content.

Second, install a Redis server, and get the WP-Redis plugin and follow its installation instructions. This hugely reduces load times by caching PHP object data and reducing calls to the database.

apt get install redis-server

Also, configure Apache to gzip all the static assets and use a long cache expiry time.

Allow user-installed plugins and themes

I do not recommend it, but if you're game you can enable WordPress users with admin access to install their own plugins and themes by allowing write access to wp-content/{plugins,themes} and configuring as follows:

# in wp-config.php
define('FS_METHOD', 'direct');

The obvious disadvantage to this approach is you lose control of what god-awful unaudited third-party code they might install.

It is however useful as a temporary measure when starting out, and once the website is nearing a stable production-ready state, you can audit it all, and switch back to maintaining the selection of themes and plugins separately (e.g. in git, using submodules, or using wp-cli on the server) and disallowing user installs.

Command line tool

Use wp-cli, a really useful tool for managing WordPress sites from the command line. Once downloaded you can keep it updated with:

wp cli update

Manage users, pages, posts and so on. Plugins and themes can be installed, enabled or disabled, removed, and kept up to date:

wp plugin install wp-super-cache wp-redis
wp theme install astra
wp plugin update --all

Security hardening

Treat the internet like it is: an invading horde of zombie bots, script kiddies, crypto miners and identity thieves swarming outside relentlessly trying to break into your site.

Use SSL everywhere

Use Letsencrypt to get free SSL certificates, and enforce it everywhere using HTTP redirection to HTTPS. This is probably the most important thing you can do to increase the attack difficulty of your site. Use dehydrated to automatically renew the SSL certificate, and grab the latest Mozilla SSL Guideline to set up strong SSL.

Use a content security policy

Use a content security policy to reduce or even eliminate several methods of attack. There is a plugin for managing one from within WordPress, but it is straightforward to cobble one and add it to the headers. Here's an example content security policy that allows JavaScript, images and CSS from only certain origins, and denies everything else:

# Expanded for readability; needs to be on one line:
Header always set Content-Security-Policy "base-uri 'self';
     default-src 'none'; media-src 'self';
     connect-src 'self' https://*.wp.com wss://public-api.wordpress.com https://api.stripe.com
         https://checkout.stripe.com;
     font-src 'self' data: https://*.wp.com https://wordpress.com https://fonts.googleapis.com
         https://fonts.gstatic.com;
     form-action 'self';
     frame-ancestors 'self';
     frame-src 'self' https://jetpack.wordpress.com https://*.wp.com https://www.google.com
         https://*.stripe.com https://player.vimeo.com https://www.youtube.com
         https://www.surveymonkey.com;
     img-src 'self' data: https://*.tile.openstreetmap.org https://*.gravatar.com https://*.wp.com
         https://*.wordpress.com https://s.w.org https://maps.googleapis.com https://maps.gstatic.com
         https://q.stripe.com https://secure.surveymonkey.com;
     script-src 'self' 'unsafe-inline' 'unsafe-eval' https://widget.surveymonkey.com https://*.wp.com
         https://public-api.wordpress.com https://*.stripe.com https://maps.googleapis.com
         https://ajax.googleapis.com;
     style-src 'self' 'unsafe-inline' https://*.wp.com https://ajax.googleapis.com
         https://fonts.googleapis.com;"

Quick win HTTP headers

Other headers can be used to further enforce some secure behaviour:

Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Access-Control-Allow-Origin "*"

Mandate strong user passwords

Consider installing a password policy plugin, such as this one from Mini Orange.

Prevent server-side execution of uploaded content

Users can upload images and documents to the wp-content/uploads directory. This directory is therefore writeable by the webserver, including any of its processes hijacked by an attacker. This is a major avenue of attack, where an attacker can place malicious code in a site's upload directory and then run it on the server. So, we simply prevent server-side execution of anything uploaded this way:

<Location "/wp-content/uploads">
    SetHandler none
    RedirectMatch 404 ".+\.php"
</Location>

Periodically scan your site

There are a few online scanning tools available which can be useful for keeping up with SecOps developments. For example, WPScan recommends preventing DDoS attacks on web-accessible cron and XMLRPC endpoints, but allowing CLI invocation:

<Files "xmlrpc.php">
    # Note: this breaks Jetpack
    Require ip 127
</Files>
<Files "wp-cron.php">
    # Note: this breaks Jetpack
    Require ip 127
</Files>

RedirectMatch 404 "/readme\.html"

Do you need an online shop?

Do you need to pay $50 a month to Shopify or any of the other pay-through-the-nose shop services? No. Unless you are actually running some giant operation or department store, you can do it yourself for free in WordPress. You will need:

I have certainly found it sufficiently useful for a small non-profit selling merchandise, a community orchestra selling tickets, or a home business flogging chilli sauce.

If your phone has a NFC chip, then the Stripe app will also let you use your phone as a mobile EFTPOS machine that accepts credit cards for when you're out and about, and there is also a handy WooCommerce app that lets you track orders and sales from the website.