Layered WordPress Defense with Fail2Ban, NGINX, and Real Client IPs

Layered WordPress Defense with Fail2Ban, NGINX, and Real Client IPs

Fail2Ban, the Swiss army knife of quick rules to block aggressive intruders. As part of a layered defense, fail2ban is quick and easy to deploy to stop aggressive attackers from compromising your site with automated and brute force approaches.

In this blog we are going to give you a quick recipe for making a defense on WordPress sites you can easily deploy whether you are behind a load balancer or directly connected to the internet.

Some notes on how we host WordPress websites

In our setup, we have a mix of deployment strategies. However, the stack on the machine is generally always the same. This is Varnish → NGINX → PHP-FPM.

For staging and testing websites, we have a separate server that has HAProxy sitting infront and the server is NAT’d. This allows us to spawn new servers and move them around quite quickly for testing needs. We also do this for production websites that require HA.

So when writing a Fail2Ban rule, we need to

  1. Block the real IP of the attacker
  2. Not block ourselves (obviously, but we’ve all done it)
  3. Make sure they are blocked even when we are NAT’d

See where your traffic is coming from

The following is a quick way to see if a ban will work at IPtables or you need our NGINX rules.

sudo ss -tnp | grep ':443' | head -n 20
FIN-WAIT-2 0      0               <machine>:53758           <external-ip>:443

If the above is only your gateway, then your IPTables rule likely won’t help. We do it anyway though. If you are getting a collection of public IP’s in here, then you are directly connected to the internet and IPTables is the best choice.

Install Fail2ban

sudo apt install fail2ban

As easy as that.

Fail2Ban WordPress NGINX Action

This is our custom action for banning in an NGINX

[Definition]
actionstart =
actionstop =
actioncheck =
actionban = echo "deny <ip>;" >> /etc/nginx/fail2ban/wordpress.conf && nginx -t && nginx -s reload
actionunban = sed -i '\#deny <ip>;#d' /etc/nginx/fail2ban/wordpress.conf && nginx -t && nginx -s reload

It requires the following in your /etc/nginx/nginx.conf inside the http { block.

...

http {
  include /etc/nginx/fail2ban/*.conf;

...

This will be where Fail2Ban adds/removes rules.

You will need to make sure the fail2ban folder exists.

Once done you can reload with

nginx -s reload

Fail2Ban Filters

Filters are the rules that tell Fail2Ban how to detect a threat from a log file or some other form.

Our filters use the GELF format, but you can adapt them to standard NGINX log rules like below. We also need the real IP address, so we use the following rules to get a JSON log and set the real IP recursively. You should only do ones that are applicable to your network!

  set_real_ip_from  127.0.0.1;
  set_real_ip_from  10.0.0.0/8;
  set_real_ip_from  172.16.0.0/12;
  set_real_ip_from  192.168.0.0/16;
  real_ip_header    X-Forwarded-For;
  real_ip_recursive on;
  
  log_format gelf_json escape=json '{ "timestamp": "$time_iso8601", '
     '"real_ip":"$real_client_ip", '
     '"remote_addr": "$remote_addr", '
     '"connection": "$connection", '
     '"connection_requests": $connection_requests, '
     '"pipe": "$pipe", '
     '"body_bytes_sent": $body_bytes_sent, '
     '"request_length": $request_length, '
     '"request_time": $request_time, '
     '"response_status": $status, '
     '"request": "$request", '
     '"request_method": "$request_method", '
     '"host": "$host", '
     '"upstream_cache_status": "$upstream_cache_status", '
     '"upstream_addr": "$upstream_addr", '
     '"http_x_forwarded_for": "$http_x_forwarded_for", '
     '"http_referrer": "$http_referer", '
     '"http_user_agent": "$http_user_agent", '
     '"http_version": "$server_protocol", '
     '"remote_user": "$remote_user", '
     '"http_x_forwarded_proto": "$http_x_forwarded_proto", '
     '"upstream_response_time": "$upstream_response_time", '
     '"nginx_access": true,'
   '"environment": "transient" }';

XML-RPC Filter

/etc/fail2ban/filter.d/wordpress-xmlrpc.conf

Standard log

[Definition]
# Match POST requests to xmlrpc.php in standard nginx access logs

failregex =
    ^<HOST> .*"POST\s+/xmlrpc\.php\s+HTTP/.*"

ignoreregex =

or with GELF JSON

[Definition]
# Match POST requests to xmlrpc.php in nginx JSON logs

failregex =
    ^.*"remote_addr"\s*:\s*"<HOST>".*"request"\s*:\s*"POST\s+//xmlrpc\.php(?:\s|\\).*$
    ^.*"remote_addr"\s*:\s*"<HOST>".*"request"\s*:\s*"POST\s+/xmlrpc\.php(?:\s|\\).*$

ignoreregex =

WP-LOGIN Filter

/etc/fail2ban/filter.d/wordpress.conf

Standard log

[Definition]
# Match POST requests to wp-login.php in standard nginx access logs

failregex =
    ^<HOST> .*"POST\s+/wp-login\.php\s+HTTP/.*"

ignoreregex =

Or with GELF JSON

[Definition]
# Match POST requests to wp-login.php in nginx JSON logs

failregex = ^.*"remote_addr"\s*:\s*"<HOST>".*"request"\s*:\s*"POST\s+//wp-login\.php(?:\s|\\).*$

ignoreregex =

Fail2Ban Jail

This is where the actual banning happens.

You will need to adjust the log location depending how you log. We use independent site logs, but you may have one large one.

In these Jail’s we use both IPTables for banning and NGINX. Iptables is still useful for direct connections or future topology changes, but NGINX is the authoritative enforcement point when NAT’d.

XML-RPC Jail

[wordpress-xmlrpc-site]
enabled  = true
filter   = wordpress-xmlrpc
action = nginx-deny
         iptables-multiport[name=Wordpress, port="http,https"]
logpath  = /var/log/nginx/site_name.access.log

# XML-RPC abuse is usually brute-force or amplification
maxretry = 3
findtime = 300
bantime  = 86400

backend  = auto

# Never ban LB or localhost
ignoreip = 127.0.0.1/8 ::1 10.245.0.0/16

WP-LOGIN Jail

[wordpress-nginx-site]
enabled = true
filter = wordpress
action = nginx-deny
         iptables-multiport[name=Wordpress, port="http,https"]
logpath = /var/log/nginx/site_name.access.log
maxretry = 15
findtime = 600
bantime = 3600
backend = auto

# Never ban LB or localhost
ignoreip = 127.0.0.1/8 ::1 10.245.0.0/16

Conlusion

Now you will see anyone brute forcing those endpoints banned from your site. Well done!

Related articles

Custom Software Application Development: Empowering Your Business

Empower your business with our custom software solutions. Designed to streamline operations, enhance productivity, and drive growth, our software is tailored to your unique business needs. Experience the transformative power of custom software that not only meets your current requirements but also scales with your future ambitions.

Read More

3 Types of System Integration

Unlock the power of system integration. Streamline operations, enhance efficiency, and make informed decisions with customised integration software. Discover how today!

Read More

Why Select Ruby on Rails Developers for Your Web Development Project

Choose Ruby on Rails for your web development needs and experience the power of a robust, flexible, and efficient framework. With its emphasis on convention over configuration, Rails accelerates development time, allowing you to launch faster. Harness the power of Ruby on Rails and bring your web application ideas to life with ease and efficiency.

Read More