One of our servers is being hit daily by a large number of spam bots. They're not able to post any spam, but they still try and end up slowing down the server for real users.

To counter this, I have a PHP script running on the site which detects their IP addresses. However, I want this script to automatically add these IP addresses to the firewall (rather than me having to do that manually). Running something like this:

iptables -I INPUT -j DROP -s

However, it looks like iptables commands can only be run as root. Any way around this? I guess I could have the commands added to a file which root runs via cron, but don't really want any delay between the php script making the decision to ban an IP and the ban being added.

  • 1
    Why don't you run the php script as root? – terdon Mar 11 '13 at 12:48
  • The script is being run at the end of page load, by the web server. It does various logic to work out if the page load was by a spam bot or not. – Ross McLellan Mar 11 '13 at 12:53
  • 1
    Make a suid root shell script which calls out to iptables? Just make sure to sanitize your inputs very carefully. Something like s/[^0-9.:]// on the IP address and not accepting anything else as input. I'd tighten it down even further in such a scenario. – a CVn Mar 11 '13 at 13:01

Have your PHP script write the offending IP addresses to a log file. Next, install Fail2Ban and configure it to read your log file.

  • 1
    Thanks. Am giving this method a try for now. I do prefer to not rely on 3rd party scripts when a simple custom script will do the job, but fail2ban seems to have quite a lot of useful capabilities, like unbanning after a certain period of time. Was bit of a pain to get setup and seems to lack documentation for some bits, but seems to be running as desired now. Will just see how it copes the next time an attack hits. – Ross McLellan Mar 11 '13 at 16:16

The easiest way would be, as you suggest, having your script write the offending IPs in a log file. You can then have another script running (as root) in a loop that reads the file and sends the iptables command. Something like:

while true
  while read IP
      ## Check if this IP is already banned
      if ! iptables -L INPUT -v -n | grep $IP >/dev/null
      ## If it is not banned, ban it
         iptables -I INPUT -j DROP -s $IP
  done < $1
  ## Sleeping to avoid spamming, if even a one second
  ## delay is too slow for you(!) adjust as needed.
  sleep 1

You can then have this script run as root on start up, either as a service or by creating this crontab for root:

@reboot /path/to/banips.sh logfile.txt

I assume the delay you are worried about is cron's one minute interval minimum. Since the script will run in an infinite loop, you don't need to run it every minute using cron and the delay is up to you. As it stands, the script will read the logfile every second.

  • There are canned and proven solutions for the problem at hand, why reinvent the wheel? – 0xC0000022L Mar 11 '13 at 14:33
  • 1
    @0xC0000022L because in this case the wheel is simple to invent, because software recommendations are considered off topic and this is a solution that requires no third party software and because personally I prefer having small scripts doing things since I can control their behavior perfectly. – terdon Mar 11 '13 at 14:58
  • fair enough, even mentioned your script in my own answer. Suppose you have a point :) – 0xC0000022L Mar 11 '13 at 14:59
  • Thank you @terdon, I hadn't realised a cron job could run like that in a while loop. I'm trying fail2ban at the moment, but will probably try this method out at some point too, even if just to see how it works. – Ross McLellan Mar 11 '13 at 16:20

As an alternative to the existing answers and adding sshguard (not so well-named for its scope by far exceeds what the name suggests) to the mentioned Fail2Ban I would like to suggest to you sudo.

The file /etc/sudoers can be configured to control tightly who can run what as who and from which machine. Therefore you can write a little wrapper much like in terdon's answer and then chown root: it and make sure its permissions are set tight. Throw it into a folder such as /usr/sbin and allow it to be run as root from whatever user account your PHP script runs as. Make sure that if your script runs in the context of the web server and it's chrooted that the script is inside the chroot. Keep in mind that this could be used to compromise the chroot "jail" if your script is badly written. An example line in /etc/sudoers could be:

www-data   ALL= (root) NOPASSWD: /usr/sbin/yourscript.sh

which allows user www-data to run /usr/sbin/yourscript.sh as root without giving a password on all machines (sudoers can be a central one for all machines in a network, that's why ...).

sudo is considered safer than setting the suid bit on your script, but I suppose there are good reasons for using this alternative, e.g. when no sudo is available.

You can also check out this answer I gave over at the sister SE site askubuntu.com.

  • Thanks. I was slightly reluctant to let my web server user run all iptables commands, I knew sudoers could limit the commands you can run, but didn't realise I would be able to limit it to a specific .sh file (one which runs the specific iptables command). – Ross McLellan Mar 11 '13 at 16:24

A very simple method...

I wrote a php script that writes the unwanted ip to a text file on each line (insert wherever needed)

$banlist = file_get_contents( '/var/www/banlist.txt' );
$remoteip = $_SERVER['REMOTE_ADDR'];
if( strpos( $banlist, $remoteip ) === false ){
    file_put_contents( '/var/www/banlist.txt', $remoteip."\n", FILE_APPEND );

Then I created a custom iptables Chain and called it "banlist" (call it what you like) using the command...

sudo iptables --new-chain banlist

Also installing iptables-persistent using...

sudo apt-get install iptables-persistent

Lastly using the command...

sudo crontab -e

I added this line to the root cronjob as follows to run every 5 minutes (change to your time liking) that reads each ip, adds them to the iptable chain I created with a DROP target, and then clears the text file...

*/5 * * * * while read in; do sudo iptables -A banlist -s "$in" -j DROP; done < /var/www/banlist.txt && > /var/www/banlist.txt

I also added this line to make sure the rules are getting saved every 5 minutes...

*/5 * * * * iptables-save > /etc/iptables/rules.v4 && ip6tables-save > /etc/iptables/rules.v6

That's it! Sweet and simple, test it out by adding a random ip (or ip's each on a new line) to the text file and waiting for your cronjob to run at your chosen interval... Be careful not to ban yourself by testing the php script from your own network...

Note: To clear all the ip's in the iptables Chain you can use "sudo iptables -F banlist"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.