Protecting WordPress with Suricata

Protecting WordPress with Suricata

There aren’t any silver bullets that will protect a WordPress installation against every single attack, but adding a full featured IDPS solution like Suricata, is a good step in protecting not only that “all too many times vulnerable” WordPress installation but also other services like SSH.

Most WordPress installations are run as a single machine with a complete middleware stack from the web server down to the database. As such, what follows, is based on the following assumptions:

  • Middleware stack is already installed, fully functional and based on LEMP
  • OS is Enterprise Linux 7 based (minimum version for the Linux Kernel is 3.14)
  • HTTP traffic is received using TLS
  • Nftables user space tools are installed
  • Suricata is already installed (minimum version is 2.1beta3)
  • ETOpen rule set as been installed in order for Suricata to use it
  • Everything is running in the same host
  • The host is a dedicated server or a VPS
 There are some VPS’s, mostly those based on OpenVZ, that will not allow for Kernel changes. Check with the current or future VPS provider before hand.

The first step is to replace the location section of the server block that listens to HTTPS and passes the requests to PHP, in order to reverse proxy them instead.

 1# Replace the following
 2location ~ \.php$ {
 3  (...)
 4}
 5
 6# With
 7location / {
 8  proxy_set_header X-Real-IP $remote_addr;
 9  proxy_set_header X-Forwarded-Host $server_name;
10  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
11  proxy_set_header X-Forwarded-Proto $scheme;
12  proxy_pass http://localhost;
13}
 Do not use the $host variable in the header X-Forwarded-Host since its client/attacker controlled, possibly making your WordPress installation susceptible to host header poisoning. Use the $server_name variable instead.

Next we need to configure Nginx to listen locally for the decrypted traffic and to pass it to PHP.

 The WordPress installation will need to be configured for a reverse proxy environment and paths may need to be adjusted depending on the reader specific WordPress installation.
 1server {
 2  listen localhost:80 default_server;
 3
 4  # WordPress installation root directory
 5  root /usr/share/nginx/html;
 6
 7  # Index files
 8  index index.php;
 9
10  # Make nginx play nice with WordPress permanent links
11  location / {
12    try_files $uri $uri/ /index.php?$args;
13  }
14
15  # Pass the requests to PHP
16  location ~ \.php$ {
17    (...)
18  }
19}

After checking that the configuration is correct, restart Nginx. From this point onwards, there should be plain text HTTP traffic flowing to the socket listening locally (generate some traffic if needed). You can confirm this, by using tcpdump.

1#!/bin/bash
2tcpdump -i lo "port 80"

The next step is to configure Netfilter using Nftables, in order for it to send traffic to Suricata. To do that, create a file with the rules extension under /etc/nftables/ directory.

 If Nftables is already configured in the system, edit the existing rules by adding the IPS related chains with a priority lower than the ones used for the firewall (the higher the number the lower the priority).
 The following configuration only takes into account IPv4, for IPv6 extra configurations will be needed.
 1#!/usr/sbin/nft -f
 2table ip filter {
 3  # Firewall
 4  chain firewall-input {
 5    type filter hook input priority 0;
 6    (...)
 7  }
 8  chain firewall-output {
 9    type filter hook output priority 0;
10    (...)
11  }
12  # IPS
13  chain ips-input {
14    type filter hook input priority 10;
15
16    # Queue input packets to Suricata
17    counter queue num 0-1 fanout, bypass
18  }
19  chain ips-output {
20    type filter hook output priority 10;
21
22    # Queue output packets to Suricata
23    counter queue num 0-1 fanout, bypass
24  }
25}
 Make sure firewalld is disabled.

After starting the Nftables service, the next step is to configure Suricata. First edit the main Suricata configuration file (/etc/suricata/suricata.yaml).

 The Netfilter queue fail open setting may have performance impacts, be sure to read Suricata documentation on it.
 The Suricata configurations, are for a host with two CPU’s, as such, affinity settings and the number of queues that Suricata listens to may need to be adjusted.

 1# Activate workers run mode
 2runmode: workers
 3
 4# Enable EVE logging with X-Forward-For support
 5- eve-log:
 6    enabled: yes
 7    # ...
 8    types:
 9      - alert:
10          # ...
11          xff:
12            enabled: yes
13            # Two operation modes are available, "extra-data" and "overwrite".
14            mode: overwrite
15            # Two proxy deployments are supported, "reverse" and "forward". In
16            # a "reverse" deployment the IP address used is the last one, in a
17            # "forward" deployment the first IP address is used.
18            deployment: reverse
19            # Header name where the actual IP address will be reported, if more
20            # than one IP address is present, the last IP address will be the
21            # one taken into consideration.
22            header: X-Forwarded-For
23
24# Disable Netfilter queue fail open
25nfq:
26  fail-open: no
27
28# Configure CPU affinity
29threading:
30  # ...
31  set-cpu-affinity: yes
32  # Tune cpu affinity of suricata threads. Each family of threads can be bound
33  # on specific CPUs.
34  cpu-affinity:
35    - management-cpu-set:
36        cpu: [ 0, 1 ]  # include only these cpus in affinity settings
37        mode: "balanced"
38        prio:
39          default: "high"
40    # ...
41    - detect-cpu-set:
42        cpu: [ 0, 1 ]
43        mode: "exclusive" # run detect threads in these cpus
44        # Use explicitely 3 threads and don't compute number by using
45        # detect-thread-ratio variable:
46        # threads: 3
47        prio:
48          # low: [ 0 ]
49          # medium: [ "1-2" ]
50          # high: [ 3 ]
51          default: "high"
52
53# Edit the HOME_NET to contain the localhost address
54vars:
55  # ...
56  address-groups:
57    HOME_NET: "[127.0.0.1,(...)]"
58
59# Edit the host OS policy to contain the localhost address
60host-os-policy:
61  # ...
62  linux: [127.0.0.1]

Second, edit the system Suricata configuration file (/etc/sysconfig/suricata).

1# Make Suricata listen for packets in the Netfilter queues
2OPTIONS="-q 0 -q 1 "

After starting Suricata, check that everything worked out without errors and that packets are being received (check the /var/log/suricata/stats.log file). To test the installation, use the following SQLi vector.

1https://external.domain/?p=')) UNION SELECT 1--

If everything worked as planned, Suricata should have created an entry in the EVE log (/var/log/suricata/eve.json) reporting the attack.

 Configuring the ETOpen rule set can be easily done using Oinkmaster.

The next step is to configure the rules to disable false positives:

  • 2003508 - ET WEB_SPECIFIC_APPS WordPress wp-login.php redirect_to credentials stealing attempt
  • 2012843 - ET POLICY Cleartext WordPress Login
  • 2012998 - ET WEB_SERVER PHP Possible https Local File Inclusion Attempt
  • 2013505 - ET POLICY GNU\Linux YUM User-Agent Outbound likely related to package management

And change some useful rules from alert to drop:

  • 2001219 - ET SCAN Potential SSH Scan
  • 2006546 - ET SCAN LibSSH Based Frequent SSH Connections Likely BruteForce Attack
  • 2019876 - ET SCAN SSH BruteForce Tool with fake PUTTY version
  • Every rule from:
    • ciarmy.rules
    • compromised.rules
    • drop.rules
    • dshield.rules
    • emerging-web_server.rules
    • emerging-web_specific_apps.rules

Restart Suricata and that’s it. It’s a good idea to update the rules every now and then so that Suricata can better protect WordPress :)