Difference between revisions of "WordPress"

From Jon's Wiki
Line 8: Line 8:
 
* CLI, wp-cli.org
 
* CLI, wp-cli.org
 
* Security scanner, wpscan.org
 
* Security scanner, wpscan.org
* LetsEncrypt SSL everywhere CSP, Strict Transport, X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Access-Control-Allow-Origin, Referrer-Policy etc.
+
 
 +
== Use SSL ==
 +
 
 +
Use [[Letsencrypt]] to enforce HTTPS everywhere and dehydrated to maintain the SSL certificate.
 +
 
 +
== Content security ==
 +
 
 +
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:
 +
 
 +
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;"
 +
 
 +
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 "*"
 +
 
 
* gzip + WP SuperCache = win
 
* gzip + WP SuperCache = win
  
Deny some stupid stuff:
+
== Lock things down ==
 +
 
 +
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:
  
  # Don't fill up the log with favicon and robots.txt
+
  <Files "xmlrpc.php">
location = /favicon.ico {
+
     Require ip 127
    log_not_found off;
+
  </Files>
     access_log off;
+
  <Files "wp-cron.php">
  }
+
     Require ip 127
  location = /robots.txt {
+
  </Files>
     allow all;
+
  RedirectMatch 404 "/readme\.html"
    log_not_found off;
+
   
    access_log off;
+
  <Location "/wp-content/uploads">
  }
+
    SetHandler none
  # Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
+
    RedirectMatch 404 ".+\.php"
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
+
  </Location>
location ~ /\. {
 
    deny all;
 
  }
 
  # Deny access to any files with a .php extension in the uploads directory
 
# Works in sub-directory installs and also in multisite network
 
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
 
location ~* /(?:uploads|files)/.*\.php$ {
 
    deny all;
 
}
 

Revision as of 03:58, 20 August 2021

Enable user-plugin installation by allowing write access to wp-content/{plugins,themes} and configure:

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

Other nifty things:

  • CLI, wp-cli.org
  • Security scanner, wpscan.org

Use SSL

Use Letsencrypt to enforce HTTPS everywhere and dehydrated to maintain the SSL certificate.

Content security

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:

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;"

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 "*"
  • gzip + WP SuperCache = win

Lock things down

As suggested by WPScan, prevent DDoS attacks on web-accessible cron and XMLRPC endpoints, but allow CLI invocation, and prevent execution of uploaded PHP content:

<Files "xmlrpc.php">
    Require ip 127
</Files>
<Files "wp-cron.php">
    Require ip 127
</Files>
RedirectMatch 404 "/readme\.html"

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