As mentioned by others, in general, using -m string
rules in your server is not very effective since it checks every single packet for that string, which is slow. But that's not all. Data can be broken up in separate packets on purpose (by hackers) to have say "Word" in the first packet and "press" in the second packet. So that wouldn't help. Not only that, we now have HTTP/3, which uses UDP packets... which can be encrypted.
On top of that, a real DDoS tends to happen really fast, so I think that you need to block the IP addresses as fast as possible, if you think it is really happening.
That means you want to send the IP to iptables
as quickly as possible. There is a _new interface_¹ to the firewall which allows you to add up to 65535 IP addresses in a sorted table (very fast search). Since it is unlikely that the attacker has that many computers, it will work as expected.
First, in the Apache2 settings, you capture the word Wordpress
in the User-Agent
header:
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} Wordpress
RewriteRule !^/?scripts/ scripts/script-to-run.cgi [L]
Then in script-to-run.cgi
you use ipset
like so:
#!/bin/bash
ipset add unwanted $REMOTE_ADDR -exist
Source: How to run some code or command in Apache2 on condition?
Then you need a rule in your firewall to check the unwanted
list of IPs, it looks like so:
iptables -A unwanted -m set --match-set unwanted src -j DROP
Note: Please make sure that your SSH users do not get blocked by accepting traffic on port 22 before that unwanted
rule--this is safe if all your users have a static IP address.
However, you are likely to run in a problem trying to run the ipset
command. It will be running as the Apache2 user and not root. One simple solution is to write a C++ process to which you can give root permissions. Something like this:
int main(int argc, char *argv[])
{
if(setuid(0) != 0) return 1; // could not become root?!
if(argc != 2) return 1; // exactly one parameter: IP address
std::string ip(argv[1]);
if(ip.find(' ') != std::string::npos) return 1; // IP can't include spaces
if(ip.find('\'') != std::string::npos) return 1; // IP can't include quotes
std::string cmd("ipset add unwanted '");
cmd += ip;
cmd += "' -exist";
return system(cmd.c_str());
}
Note: It is safe because it only allows an "add" to the specific table "unwanted". Offering all the options that ipset
itself supports would not be safe.
Compile with:
g++ safeipset.cpp -o safeipset
Copy result to /usr/bin
and make sure it can become root:
sudo cp safeipset /usr/bin/.
sudo chmod u+s /usr/bin/safeipset
Use in your CGI script like so:
#!/bin/bash
safeipset $REMOTE_ADDR
The ipset
table can be given a default timeout so the IP addresses that get blocked are removed after a given amount of time. You could first test without that timeout to see how many IPs get added.
Here you setup the time to 5 minutes:
ipset create unwanted hash:ip timeout 300
Please check the man ipset
manual page for more info.
¹ ipset
has been around in Linux 2.4.x which was released in 2001. So it's not that new but it was not used by anyone for a long time.