Hardening a Simple WordPress Site

This ist part 2 of my mini series on setting up a WordPress site for my daughter’s primary school. In part 1, I covered the basic setup, including TLS and various security-related HTTP headers. In this post, I outline some steps specific to WordPress to secure your site. As I already introduced Cloudflare as a comfortable means of securing a site, I will continue to refer to Cloudflare in this post as well.

Restricting Access

In order to provide just a little more security against automated attacks like botnets, you can add access restrictions using Cloudflare’s Web Application Firewall rules. Using these, you may e.g. restrict access to the admin panel depending on a request’s origin country. For WordPress, such a rule could look like this:

(ip.geoip.country ne "DE" and http.request.uri.path contains "/wp-admin/") or (ip.geoip.country ne "DE" and http.request.uri.path contains "/wp-login.php")

While a trained person can work around such a restriction fairly easily, it provides an additional layer of protection against untargeted, automated attacks. Users trying to make web requests to your WordPress admin panel just get the following page along with and HTTP 403 return code:

Error message by the Cloudflare Firewall: Error 1020 - Access denied
Error message by the Cloudflare Firewall

Multi Factor Authentication

The security of a site is determined by its weakest point. Setting up a site that is used by a number of people always comes with the risk of one user’s password being that weak point. Therefore, I was searching for a way to enable multi-factor authentication (specifically 2 factor authentication, 2FA). I settled using the plugin WP 2FA for this. It is maintained by a company that offers paid, security-related WordPress plugins, which gives me some confidence they are doing things right. Also, their support seemed quite knowledgeable, the configuration makes sense, and the plugin works as expected.

Caution: Mobile App Access

Out of curiosity, I tried logging in to my new site using the WordPress mobile app for iOS. While it worked flawlessly, I noticed it did not ask me for my second factor, but simply logged me in once I provided the correct password. Since this basically breaks the whole premise of 2FA, I contacted WP 2FA support. They quickly replied that they are aware of the issue, but have not yet found a solution to support 2FA when using the mobile app. Since we do not need the app, I decided to block it completely, so nobody can use it to circumvent 2FA.

After some network sniffing, I found the app uses a single endpoint on the site for all its requests, namely the file xmlrpc.php. A quick web search brought up several results on how to disable this functionality either via an additional WordPress plugin, or via a filter in Apache web server. But since I was already using the Cloudflare firewall, I just added another rule to block all requests to that file:

(http.request.uri.path eq "/xmlrpc.php")

This effectively blocks login via the mobile app and therefore closes yet another entry point for possible credential stuffing attacks. With this rule in place, the app just displays an error trying to connect to the site.

Wordpress app error message: "Connection failed. We received a 403 error trying to reach your site's XMLRPC endpoint. The app needs it to communicate with your site. Please contact your host to resolve this issue."

Do you have additional tips on how to secure a simple WordPress site? Feel free to leave a comment. Part 3 of this series will cover the creation of a Content Security Policy (CSP) and enable logging for issues related to that.

Updating to macOS Monterey with an Aftermarket SSD

Update 2021-11-01: It seems this error is quite common, and at this time, the only solution confirmed to be working over on StackExchange and MacRumors is the one that Worked for me, too.

I’ve spent a few days trying to install macOS Monterey on my late 2014 Mac Mini. In order for other people to save some time trying various general purpose advice, I summarize my findings here. In case you just want to know how to fix it, jump to the solution section at the end of this article. If you want to understand what is going on, keep reading.

The Error Message

Running the macOS Monterey installer, after a few minutes, and right before the first reboot („closing programs and preparing reboot“), an error message was shown (German: „Beim Vorbereiten der Installation ist ein Fehler aufgetreten. Versuche, das Programm erneut auszuführen.“, English: „An error occurred while preparing the installation. Try running this application again.“).

Screenshot of error message "An error occurred while preparing the installation. Try running this application again."

While trying many things in order to fix the issue (see below), I came across another error message:

osinstallersetupd[313]: Forcing MSU springboard for target: disk1s1 (has EFI without NVMe update support)

What did NOT Work

All of the following approaches did not work and led to the same result:

  • Running the installer from „System Settings“
  • Copying the installer bundle from another Mac
  • Checking and repairing the disk using Disk Utility
  • Creating a new admin user account and installing from there
  • Using System Restore (⌘ + R)
  • Using Internet System Restore (⌘ + ⌥ + R)
  • Using a bootable installer USB drive

The Reason

A few days later, a web search finally brought up a thread in Apple’s support forum, which contained an explanation as well as a proposed solution. The argument goes like this:

  • The issue is most likely caused by an outdated EFI / boot ROM
  • Boot ROM updates are not provided as standalone updates, but are included in macOS installers
  • The Monterey installer needs a new boot ROM in order to run successfully
  • Apple just updates the boot ROM if an original, Apple-produced system drive is present
  • On Macs with third-party system drives, the boot ROM update fails, causing the error message

The Solution

In order to verify that your Mac has an outdated boot ROM, you can check your current version – either using an app such as SilentKngiht, or by running the following Terminal command and checking the result against this list.

/usr/libexec/firmwarecheckers/eficheck/eficheck --integrity-check

If your boot ROM is indeed outdated, and you are lucky to have the original Apple hard drive or SSD around, you can upgrade to macOS Monterey by following these steps:

  1. Remove your current SSD, put in the original SSD
  2. Install or update to Monterey on that drive
  3. Verify you got the latest boot ROM
  4. Power down, swap your main SSD back in
  5. Install Monterey on your main SSD

For me, this completely fixed the issue, and I now run macOS Monterey on my Mac Mini with its third-party SSD.

Further Reading

Here is another resource I discovered during my quest (thanks to @AppleSupport): If an error occurred while updating or installing macOS

Securely Setting Up a Simple Website

This post aims at providing simple step-by-step guidance on how to set up a small, cost-efficient, yet secure website as an individual or organization. Being written by a single person using a single web site as an example, this post is highly opinionated. In order to put some of the decisions into perspective, the following paragraph outlines the project context, before we take a deeper look into the decisions themselves and the particular implementations.

Example: Primary School Website

As mentioned in an earlier post, I volunteer in the support association („Förderverein“) of my daughters‘ primary school. Currently, neither the association nor the school itself have their own website, let alone an IT department. Because I work in tech, I was approached by the school’s principal who asked for advice on how to get started building a website. They had already taken several attempts on putting up a site, but never got very far. There was a free offering available, which was provided by the state administration, but that seemed cumbersome to work with. Commercial options were considered as well, but it was hard to judge which of those were best suited for them.

Our Choice: Managed WordPress

Driven by personal interest, I have been following cyber security news for a couple of years, including blogs like troyhunt.com, scotthelme.co.uk, and podcasts like Security Now, Cyber Security Sauna, Random but Memorable, or InnoQ Security, so I wanted the site to be configured as securely as possible, but still require low maintenance effort and be cost-efficient.

While WordPress has made the cyber security headlines quite some times in the recent past, many of those incidents have been due to vulnerabilities in plugins, and lack of updates. So in order to operate a WordPress site, my priorities were clearly keeping the number of plugins to the absolute minimum, and (similar to this blog) selecting a managed offering, in which securing and updating WordPress is not primarily my own responsibility.

Since we are a (very) small non-profit organization in Germany, completely run by volunteers, responsibilities might change in the future, and therefore we voted against choosing one of the US-based heavyweights in the market, but went for a local, specialized vendor instead. They offer German customer service, including phone support, which gives us the confidence of being able to transfer responsibilities to less tech-savvy person in the future, who might also not be as comfortable communicating in English.

Regardless of this decision, the following paragraphs universally apply to any web hosting solution, given that it comes with a dedicated domain and allows for name server changes.

Optimizing the TLS Configuration

One fundamental advice given by security professionals is that a lot of security comes with secure default settings. Therefore, I tried to achieve a sensible, secure default configuration for the site. The first step was enabling HTTPS on the site itself. This could be done by activating Let’s Encrypt in the admin panel.

Since settings were limited in terms of protocol versions, HSTS headers etc, I chose to follow Troy Hunt’s advice on httpsiseasy.com and put Cloudflare in front of the site. This is basically achieved by changing the name servers of the domain over to Cloudflare’s servers, allowing them to route all of the traffic to your site though their network. Cloudflare offers many features, like caching of requests or protecting agains DDoS attacks, but we’ll focus on those features that are relevant for improving the security of your site.

After completing the TLS setup on Cloudflare as suggested over at httpsiseasy.com, our site scores an A+ on ssllabs.com.

CSP and Other Headers

One thing I wanted to do for the site was create a Content-Security Policy (CSP). Basically, a CSP is a way to define which other sources your site should be allowed to pull stuff from, with „stuff“ being images, font, style sheets, scripts, and the like. Some time ago, I came across a site selling learning toys for kids, which immediately forwarded me to a malicious site showing loads of ads. This was achieved by someone (probably a script or a botnet) using a vulnerability on their site to inject a malicious script into the page. Had they used a CSP, my browser would have just refused to load that script, since it was hosted on another domain. Also, CSP provides a means of reporting such events, but I’ll cover this in another post.

In order to find out which other relevant headers exist, I recommend checking securityheaders.com by Scott Helme, which checks your site for common, security-related headers and recommends changes, along with great explanations of what they mean and how to set them. All these features are controlled by sending HTTP headers in your site’s responses. These headers are interpreted by the browser to e.g. block certain content. Depending on your web host, you may not get full control over the HTTP headers sent by the server. This is where Cloudflare workers come into play: Workers are basically code snippets that run each time a certain request is made to your site. While there is lots of use cases for workers, we’ll focus on a very simple one: On every request, add some statically defined HTTP headers to the result. This is achieved by the following worker code (adapted from this example in Cloudflare’s documentation):

addEventListener('fetch', event => {

async function handleRequest(request) {
  const response = await fetch(request)
  // Clone the response so that it's no longer immutable
  const newResponse = new Response(response.body, response)
  // Add securirty headers
  newResponse.headers.append("Upgrade-Insecure-Requests", "1")
  newResponse.headers.append("X-Frame-Options", "SAMEORIGIN")
  newResponse.headers.append("Referrer-Policy", "strict-origin-when-cross-origin")
    "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), interest-cohort=()")

  return newResponse

Once the worker is deployed and active, we have to make sure it runs on every request, which is achieved by adding a route and assigning the worker to it. In our case, we want to run this worker on every request, so we just copy the default route and assign our new worker.

Cloudflare’s free plan includes 100.000 worker invocations per day, which should be plenty for a small website. Still, you’ll have to decide what should happen in the rare occasion that you hit the limit. Your options are failing open, meaning that your site will still be reachable, but the worker will not run, and hence security headers will not be added anymore for the rest of the day, or failing closed, meaning your site will not be reachable for the rest of the day, but you can be sure it will always have security headers when it is.

Using these modifications, our site now gets an A rating on securityheaders.com, giving us the confidence that we do our best to protect visitors from malicious activity, even in case our site gets compromised.

Part 2: Hardening WordPress

So far, all of the above settings apply to any website, wether you use WordPress or any other offering. The next post will cover additional steps specific to WordPress.

An Accidental IPv6 Experiment

I just took over some new voluntary responsibility of managing the paperwork for the support association („Förderverein“) of my daughter’s primary school. Trying to get an overview over the current topics, I flipped through the recent documents, when I found a printed e-mail from the school’s director. The most surprising thing about that e-mail was that I had never seen it before, although my name and correct e-mail address was clearly visible in the To: field.

I started to wonder: Had I received the message, archived and forgot about it? Was it blocked by the spam filter? Was there something wrong with my mail client? This is how the search started.

„An Accidental IPv6 Experiment“ weiterlesen

DoorBird – Installation

Geht man von den Reaktionen auf den Instagram-Post meiner Frau, meinen Gesprächen mit den Kollegen und die verwunderten Blicke unserer Gäste aus, wenn in unserer Wohnung plötzlich Pferdewiehern erklingt, nur weil draußen jemand vor der Tür steht, weckt die Wahl unserer Türsprechanlage (und das, was wir daraus gemacht haben) einiges Interesse. Dies ist der erste Teil einer kleinen Reihe von Beiträgen dazu, in dem ich unsere Auswahlkriterien, den Einbau und die Ersteinrichtung beschreibe.

„DoorBird – Installation“ weiterlesen

A Special Support Request

Working at a software consulting company and dealing with customers on a daily basis, I have seen a lot of customer support requests over the recent years. As I am also in charge of customer success, I always try to help the customer achieve what they have in mind, no matter how common or uncommon their use case may be. This time, I was the customer, and my use case was as uncommon as it could possibly be. So I reached out to customer support and eagerly waited for their reaction.

„A Special Support Request“ weiterlesen