In this post I’ll explain how to detect a bruteforce on WordPress.
For this post, I assume you’ve read my SIEM 100 series where I explain the basics of Logz.io. I’ll be using the stack I created in this series to demonstrate what to do.
Why we need to detect WordPress bruteforce attack
WordPress is one of the most popular CMS and website server in the world. It handles “more than 30%” of the websites on the internet. Because of this, bad actors are really interested in finding ways to get control of them.
One of the most easy way for them to achieve this is by bruteforcing the login for common username and password combinaisons, including from past leaks. This is a working strategy because by default WordPress doesn’t protect against bruteforce attacks.
How do we detect a WordPress bruteforce attack
In the WordPress world, there are many ways to handle bruteforce attacks, as this is rather simple to detect. But because we are in the SIEM series, I’ll talk about how to detect the attack using a SIEM.
For this post, I assume you have WordPress installed on linux using Apache and that you are sending Apache’s logs, as defined in the filebeat configuration I provided in the “Initial Setup” post.
To understand the next section, we need to understand how WordPress works when someone tries to login and how we can see this in the logs.
There are two ways to login to wordpress:
- Through the standard login page (wp-login.php)
- Through xmlrpc when not disabled
After a successful logon, we get redirected (302 Found) to the WordPress dashboard. When a failed logon occures, the response is different (we get a 200 OK and the page contains an error message).
The detection pattern
Now that we know how WordPress behaves when logging in, we know what to look for to detect a bruteforce attack.
The idea is to count the number of times the same IP sent a request to one of the login pages and got something else than a successful logon response. When the count reaches a defined threshold (like 10) in a defined time window (like 5 minutes), we consider it as being a bruteforce attack. In our case, we’ll simply send an email, but we could automatically block the attacking IP.
Here are the useful elements:
The query: request: (/.*wp-login.*/ OR /.*xmlrpc.*/) AND NOT response: 302
The field “request” is the field, after parsing, that stores the page being accessed. The field “response” is the field, after parsing, that stores the response code of the request.
First group by: beat_agent.hostname
This group by is necessary if we plan, or do, have more than 1 WordPress website, or any website with these login pages.
Second group by: geoip.ip
(or simply “ip”, depending on your config)
This group by is necessary because we want to know what IP is attacking us so we can eventually take an action. This info could also be usefull in an investigation.
For the sensitivity (10 failed login in 5 minutes), I find it to be sensitive enough for most attacks and it has a low level of false positives. But you can always tweak it to fit your context.
For those of you who tells me that “an attacker could make 9 attemps every 5 minutes to bypass your measure”, that is true, but a bruteforce attacks that does 9 attemps every 5 minutes will probably never succeed and the attacker will go elsewhere, to a more juicy target.
For the next part, head over to Logz.io and login to your account.
The first thing to do is simulate a bruteforce. Because we are only looking for 10 tries in 5 minutes, it’s feasible manually. If you had a more agressive threshold, you would need to develop a script to send login requests.
Head over to the login page of your WordPress website. For me that would be https://www.tristandostaler.com/wp-login.php.
Note: because I have some security plugins, this link is not normally working. I disabled the plugin while writing this post.
You can write anything in the fields Username and Password, then press “Log In”. Do this 10 times. You should now have 10 failed logon visible in the logs.
In Kibana, search the query: request: (/.wp-login./ OR /.xmlrpc./) AND NOT response: 302
You can add the following fields as columns: beat_agent.hostname, geoip.ip (or simply ip) and response (this one is not useful for the alert but we’ll add it to see the behavior I described). You will get something similar to this:
Here, we can notice a few things:
- My hostname is “novutech.ca” and not “tristandostaler.com”. This is because I am hosting my site on the same server as the website I use to advertise my hosting services (the website needs to be updated, I know!)
- The responses are “200”. This is, as I explained, the behavior that happens when a bad logon occurs.
- We can see the IP 188.8.131.52 trying to bruteforce my site at the same time!
You can now click on Create alert and enter the alert name “WordPress bruteforce”.
In the Group by field, add geoip.ip (or ip) and beat_agent.hostname.
In the Trigger if section, change the values to match the following image:
In the Notify section, add your email and then save your new alert.
Managing false positives
After creating the alert, the number of false positive should be pretty low. But if you happen to get too much false positives in your environment, there are a few strategies we can use to reduce the level of false positives:
- We can reduce the sensitivity. Increase the threshold by reducing the time frame we look at, or increase the number of failed attempt. A smaller timeframe will detect aggressive bruteforce but can be bypassed more easily. A higher number of failed attempts will increase the risk that an attacker guesses the right username / password combination before being locked out.
- If you do have geoip enabled, you can have more sensitive alerts for some countries and less sensitive alerts for other countries.
- Whatever fits your environment. When creating an alert, it’s important to be creative and understand the usage patterns in our environments to reduce the level of false positive.
That’s it, you can now detect a WordPress bruteforce and know how to manage false positives!
Don’t hesitate to write to me if you have any questions. This could also help me better understand how to write a good blog that answers most questions.