TR-36 Example setup of WordPress with static export

You can report incidents via our official contact including e-mail, phone or use the Anonymous reporting form.

Search


CIRCL is accredited TI CIRCL is FIRST member

Overview

CIRCL warns very often about security issues in Content Management Systems (e.g. Wordpress, Joomla, Typo3, Drupal) or their plug-ins 1. To minimize the risk, the best practice is to always keep track of the installed plug-ins, uninstall unnecessary components and keep the plug-ins and main software components up-to-date. Unfortunately Content Management Systems are sometimes vulnerable to 0-day exploits for which by definition no protection is available. Also, sometimes plug-ins are no longer maintained, or the administrators are just busy with other important tasks so that they cannot react to new known threats in a timely manner.

“the only winning move is not to play” - Joshua, Wargames 2

So another advice from CIRCL is: whenever it is possible, the Content Management System component should be removed from the attack surface.

This article gives an example about how to set up a system that exposes only static websites without dynamically generated content to the user and attacker.

Pros & Cons

Benefits

What are the benefits of a static exported website? First of all, the security impact cannot be greater. No dynamic pages are created on access, only static pages are served. That also means, no database queries are made, which themselves are also prone to various attacks. Also, the speed and scalability of a static website is much better than dynamically generated pages.

Drawbacks

On the negative side, not all options of a CMS can be transposed to static content. The built-in comments system, search or contact forms need to be replaced by other means.

Setup

This technical document gives just an example for the following environment, demonstrating that such a setup is possible and feasible to implement for many installations.

Components

Design

  • Static website will be accessible on
    • http://example.com
    • http://www.example.com
    • https://example.com
    • https://www.example.com
  • Dynamic website hosting with WordPress will be accessible on
    • http://admin.example.com (redirecting to https)
    • https://admin.example.com

The dynamic website will be password protected with additional Basic access authentication.

The maintainer of the website can prepare and preview the content on https://admin.example.com and when satisfied with the editorial result, the website can be exported to the static website.

Assumptions

For this text it is assumed that the WordPress instance for example.com is installed at /srv/www/example.com/

Installation

Assuming that Apache and WordPress are already installed, the only missing component is ‘staticpress’, which comes either as a plug-in from WordPress or the more recent version from github.

cd /srv/www/example.com/wp-content/plugins
git clone git@github.com:megumiteam/staticpress.git

Activate the plug-in in the admin interface of WordPress.

Configuration

WordPress Configuration:

WordPress Admin interface -> Settings -> General:

WordPress-Adresse (URL): http://admin.example.com
Website-Adresse (URL): http://admin.example.com

StaticPress Configuration:

WordPress Admin interface -> StaticPress -> StaticPress Options:

Static URL: http://example.com/
Save DIR (Document root): /srv/www/example.com/static/
(OPTION) BASIC Auth User: [YOUR BASIC AUTH USER]
(OPTION) BASIC Auth Password: [YOUR BASIC AUTH PASSWORD]
(OPTION) Request Timeout: 5

Apache Configuration

Apache vhost config (/etc/apache2/sites-available/example.com)

<VirtualHost *:443>
        ServerName admin.example.com
        SSLEngine on
        SSLCertificateFile    /etc/ssl/certs/ssl-cert-example.com.pem
        SSLCertificateKeyFile /etc/ssl/private/ssl-cert-example.com.key
        SSLCertificateChainFile /etc/ssl/certs/ssl-intermediate.pem
        ErrorLog /var/log/apache2/example.com-ssl-error.log
        CustomLog /var/log/apache2/example.com-ssl-access.log combined
        VirtualDocumentRoot /srv/www/example.com
        Options -Indexes
        <Location />
                AuthType Basic
                AuthName "Admin Area"
                AuthUserFile "/etc/apache2/htpasswd-example.com"
                require valid-user
                RewriteEngine On
                RewriteBase /
                RewriteRule ^index\.php$ - [L]
                RewriteCond %{HTTP_HOST} ^admin.example.com$ [NC]
                RewriteCond %{REQUEST_FILENAME} !-f
                RewriteCond %{REQUEST_FILENAME} !-d
                RewriteRule . /index.php [L]
        </Location>
</VirtualHost>

<VirtualHost *:443>
        ServerName example.com
        ServerAlias www.example.com
        SSLEngine on
        SSLCertificateFile    /etc/ssl/certs/ssl-cert-example.com.pem
        SSLCertificateKeyFile /etc/ssl/private/ssl-cert-example.com.key
        SSLCertificateChainFile /etc/ssl/certs/ssl-intermediate.pem
        ErrorLog /var/log/apache2/example.com-ssl-error.log
        CustomLog /var/log/apache2/example.com-ssl-access.log combined
        VirtualDocumentRoot /srv/www/example.com/static
        Options -Indexes
        <Location />
                Satisfy any
        </Location>
</VirtualHost>

<VirtualHost *:80>
        NameVirtualHost *:80
        ServerName admin.example.com
        UseCanonicalName    Off
        VirtualDocumentRoot /srv/www/example.com
        ErrorLog /var/log/apache2/example.com-error.log
        CustomLog /var/log/apache2/example.com-access.log combined
        Options -Indexes
        RewriteEngine On
        RewriteCond %{HTTPS} !=on
        # This doesn't seem to work:
        #RewriteCond %{REMOTE_ADDR} !%{SERVER_ADDR}
        # Instead, specify the IP address of the server:
        RewriteCond %{REMOTE_ADDR} !10\.0\.0\.1
        RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L]
        <Location />
                AuthType Basic
                AuthName "Admin Area"
                AuthUserFile "/etc/apache2/htpasswd-example.com"
                require valid-user
                RewriteEngine On
                RewriteBase /
                RewriteRule ^index\.php$ - [L]
                RewriteCond %{HTTP_HOST} ^admin.example.com$ [NC]
                RewriteCond %{REQUEST_FILENAME} !-f
                RewriteCond %{REQUEST_FILENAME} !-d
                RewriteRule . /index.php [L]
        </Location>
</VirtualHost>

<VirtualHost *:80>
        NameVirtualHost *:80
        ServerName example.com
        ServerAlias www.example.com
        UseCanonicalName    Off
        VirtualDocumentRoot /srv/www/example.com/static
        ErrorLog /var/log/apache2/example.com-error.log
        CustomLog /var/log/apache2/example.com-access.log combined
        Options -Indexes
        <Location />
                Satisfy any
        </Location>
</VirtualHost>

Password

The password file for the additional authentication must be created once finished:

htpasswd -c /etc/apache2/htpasswd-example.com [YOUR BASIC AUTH USER]

The password is being prompted on the command line.

These credentials must be in line with the settings of StaticPress.

Restart of Apache and test

A restart of Apache is required:

sudo service apache2 restart

From now on, everything should be correctly setup.

To test the configuration, got to the WordPress -> StaticPress -> Rebuild This takes a few minutes to execute, since now all the pages are being rebuilt. If everything is correct, the static export of the website should have been generated in

/srv/www/example.com/static/

and consequently should be able to be accessed via http://example.com

Classification of this document

TLP:WHITE information may be distributed without restriction, subject to copyright controls.

References

Revision

  • Version 1.1 April 29, 2015 (TLP:WHITE) - got rid of all .htaccess files previously mentioned.
  • Version 1.0 April 28, 2015 (TLP:WHITE)