Some users have been using Wordfence to protect their sites. It detects nasty people trying to hack into other people's network.
I thought of harvesting these IP's and wrote a script that collects these and use them in iptables.
Each hour it scans all the WordPress sites on my server and adds IP's to the firewall. I have been running that script for a half year and works great.
But the script I wrote was not something to publish as it needed a special layout of the firewall file and had some other specifics. These prerequisites would for sure stop people from using it.
Only recently I discovered "ipset" which changed all this. I have been using scripts to manipulate iptables for years and if I discovered this ipset sooner it would have made that so much easier.
The code can now be much cleaner. Most of the original code was about housekeeping the live firewall and the file which keeps the firewall (used with iptables-restore)
It only needs to have one entry in iptables which tells it what to do with an IP coming from that list. I tell it to log it with a specific text and block it.
I would use some whitelisting first (to prevent getting locked out yourself) and then use this somewhere reasonably high up in the chain to actually block them.
Because it doesn't have to execute many iptables checks this is much faster than seperate entries. It is also much easier to dynamically manipulate the firewall with this.
You also need to expand MySQL with some special functions so it can extract the IP's out of the Wordfence tables. More information on how to install that can be found here:
watchmouse / mysql-udf-ipv6 — Bitbucket
I suggest to run the script as an hourly cronjob:
And watch it fill your firewall with all those nasty perpetrators.
After a reboot all the IP's are lost. For now I will not make any efforts to keep them. I assume most of you have uptimes of more than months and years. Maybe those perpetrators come back another time, but then they will be blocked out soon again.
EDIT:
I made a change to the code, so it now does not rely on an entry already in iptables.
It inserts a row itself in iptables and does this after the line that matches "$REFERENCE_ROW".
This is necessary as you can't use iptables-restore with an ipset line in it if that set has not been created yet. This would be after a reboot.
It also makes it easier to implement this code for those that don't understand iptables.
# cat /usr/local/sbin/ipset-for-wordfence
I thought of harvesting these IP's and wrote a script that collects these and use them in iptables.
Each hour it scans all the WordPress sites on my server and adds IP's to the firewall. I have been running that script for a half year and works great.
But the script I wrote was not something to publish as it needed a special layout of the firewall file and had some other specifics. These prerequisites would for sure stop people from using it.
Only recently I discovered "ipset" which changed all this. I have been using scripts to manipulate iptables for years and if I discovered this ipset sooner it would have made that so much easier.
The code can now be much cleaner. Most of the original code was about housekeeping the live firewall and the file which keeps the firewall (used with iptables-restore)
Code:
sudo apt-get install ipset
It only needs to have one entry in iptables which tells it what to do with an IP coming from that list. I tell it to log it with a specific text and block it.
Code:
:WordFenceDrop - [0:0]
-A WordFenceDrop -j LOG --log-prefix "[WordFence DROP] : " --log-uid --log-tcp-options --log-ip-options
-A WordFenceDrop -j DROP
I would use some whitelisting first (to prevent getting locked out yourself) and then use this somewhere reasonably high up in the chain to actually block them.
Code:
-A INPUT -m set --match-set WordFence src -j WordFenceDrop
You also need to expand MySQL with some special functions so it can extract the IP's out of the Wordfence tables. More information on how to install that can be found here:
watchmouse / mysql-udf-ipv6 — Bitbucket
I suggest to run the script as an hourly cronjob:
Code:
ln -s /usr/local/sbin/ipset-for-wordfence /etc/cron.hourly/
And watch it fill your firewall with all those nasty perpetrators.
After a reboot all the IP's are lost. For now I will not make any efforts to keep them. I assume most of you have uptimes of more than months and years. Maybe those perpetrators come back another time, but then they will be blocked out soon again.
EDIT:
I made a change to the code, so it now does not rely on an entry already in iptables.
It inserts a row itself in iptables and does this after the line that matches "$REFERENCE_ROW".
This is necessary as you can't use iptables-restore with an ipset line in it if that set has not been created yet. This would be after a reboot.
It also makes it easier to implement this code for those that don't understand iptables.
# cat /usr/local/sbin/ipset-for-wordfence
Code:
#!/bin/bash
# relies on ntop MySQL addition and ipset
#
# apt-get install ipset
#
# ntop in MySQL and can be installed with instructions from here
# https://bitbucket.org/watchmouse/mysql-udf-ipv6
# SELECT inet6_ntop(IP) FROM wp_wfBlocks
# For logging purposes I recommend creating a logging chain using these lines
#
# :WordFenceDrop - [0:0]
# -A WordFenceDrop -j LOG --log-prefix "[WordFence DROP] : " --log-uid --log-tcp-options --log-ip-options
# -A WordFenceDrop -j DROP
#
#
# -A INPUT -m set --match-set WordFence src -j WordFenceDrop
#
HEADLESS=
tty >/dev/null || HEADLESS=true
VHOSTS=/var/www/vhosts/
IPSET_NAME="WordFence"
MYSQL="mysql -uadmin -p`cat /etc/psa/.psa.shadow `"
MAXELEM=65536
MAXELEM=262144
DROP_CHAIN=WordFenceDrop
REFERENCE_ROW=".*0\.0\.0\.0.*INVALID$"
# REFERENCE_ROW="logaccept.*tcp.*0\.0\.0\.0/0.*0\.0\.0\.0/0.*dpt:22"
# Check if ipset is installed
if ! ipset -v >/dev/null 2>&1 ; then
echo "This script relies on ipset, without it this will NOT work!" >&2
echo "Use sudo apt-get install ipset !!!" >&2
exit 1
fi
# Create ipset list if it does not exist.
if ! ipset list ${IPSET_NAME} >/dev/null ; then
echo "Set name \"${IPSET_NAME}\" does not exist, I will create it!" >&2
ipset -N ${IPSET_NAME} iphash maxelem ${MAXELEM}
fi
TMPDIR=`mktemp -t -d ${0//*\/}.XXXXXXXXXX`
ipset list ${IPSET_NAME} >${TMPDIR}/IPSETLIST # Extract ipset list
# Create file with all IPs usable with egrep -f
grep -A99999999 Members ${TMPDIR}/IPSETLIST | tail -n+2 | sed 's/\./\\./g;s/*/.*/g;s/.*/^&$/g' >${TMPDIR}/SET_IPS
LISTELEM=`grep -c '\.' ${TMPDIR}/SET_IPS`
REFERENCES=`head ${TMPDIR}/IPSETLIST | grep -i "^Reference" | awk -F: '{print $2}' | tr -cd '0-9'`
MAXELEM=`head ${TMPDIR}/IPSETLIST | grep -oi maxelem.* | awk '{print $2}' | tr -cd '0-9'`
if [ ${LISTELEM} -ge ${MAXELEM} ] ; then
echo "I've reached the capacity of the ipset list \"${IPSET_NAME}\", I can't add more IP's" >&2
echo "The capacity is ${MAXELEM} and there are ${LISTELEM} in the list already"
exit 1
fi
# Check if you actually added the ipset list to your iptables
if [ ${REFERENCES} ] ; then
if [ "${REFERENCES}" = "0" ] ; then
ROW=`iptables --line-numbers -nL INPUT | grep "${REFERENCE_ROW}" | tail -n1 | awk '{print $1}'`
let ROW+=1
if [ -z "${ROW}" ] ; then
echo "I was unable to find a row in iptables matching \"${REFERENCE_ROW}\"" >&2
echo "I can't add a line in iptables for you, so I quit" >&2
rm -r ${TMPDIR}
exit 1
else
# Create the logging chain
if iptables -N ${DROP_CHAIN} 2>/dev/null ; then
iptables -A ${DROP_CHAIN} -j LOG --log-prefix "[WordFence DROP] : " --log-uid --log-tcp-options --log-ip-options
iptables -A ${DROP_CHAIN} -j DROP
fi
if iptables -I INPUT ${ROW} -m set --match-set ${IPSET_NAME} src -j ${DROP_CHAIN} ; then
echo "I inserted the line before row ${ROW} in iptables" >&2
else
echo "I was unable to add a line to iptables " >&2
echo "Not much use in going on!"
rm -r ${TMPDIR}
exit 1
fi
fi
fi
else
echo "Something's wrong in obtaining the references in ipset, this should never happen!" >&2
rm -r ${TMPDIR}
exit 1
fi
find ${VHOSTS} -maxdepth 4 -type f -name wp-config.php >${TMPDIR}/WPCONFIGS
while read CONFIG ; do
SITEFOLDER=`echo "${CONFIG}" | awk -F/ '{print $5}'`
# Find out databasename and the Wordpress prefix
DBNAME="`grep -i DB_NAME ${CONFIG} | awk -F\' '{print $4}'`"
PREFIX=`grep -i table_prefix ${CONFIG} | awk -F\' '{print $2}'`
# Extract the Wordfence entries
${MYSQL} "${DBNAME}" -e "SELECT inet6_ntop(IP) FROM ${PREFIX}wfBlocks" 2>/dev/null | grep ffff | sed "s/.*/${SITEFOLDER}, &/g" >>${TMPDIR}/WFIPS
done<${TMPDIR}/WPCONFIGS
ERROR=0
if [ -f ${TMPDIR}/WFIPS ] ; then # did we find any IP's in the WordFence tables?
# convert them to clean ipv4 addresses
awk -F, '{print $2}' ${TMPDIR}/WFIPS | awk -F::ffff: '{print $2}' | sort -nu >${TMPDIR}/IPS
[ ${HEADLESS} ] || echo "`grep -c '' ${TMPDIR}/IPS` unique IP's found in total"
NEWIPS=0
while read IP4 ; do
if ! echo "${IP4}" | egrep -qf ${TMPDIR}/SET_IPS ; then # Check if IP is not already in set
let NEWIPS+=1
[ ${HEADLESS} ] || echo "Add IP: ${IP4}"
logger -t $0 "${IP4} found for Wordfence"
ipset -A ${IPSET_NAME} ${IP4} 2>${TMPDIR}/error >/dev/null
if grep -q 'Hash is full' ${TMPDIR}/error ; then
ERROR=1
logger -t $0 "${IP4} can NOT be added as the set \"${IPSET_NAME}\" is FULL"
echo "${IP4} can NOT be added as the set \"${IPSET_NAME}\" is FULL" >&2
elif [ -s ${TMPDIR}/error ] ; then
ERROR=1
logger -t $0 "`cat ${TMPDIR}/error`"
cat ${TMPDIR}/error >&2
else
logger -t $0 "${IP4} added for Wordfence"
echo "${IP4} added for Wordfence"
fi
fi
done<${TMPDIR}/IPS
[ ${HEADLESS} ] || echo "${NEWIPS} new IP's added to list"
if [ ${NEWIPS} -gt 0 ] ; then
logger -t $0 "${NEWIPS} new IP's found for Wordfence"
fi
fi
rm -rf ${TMPDIR}
exit ${ERROR}
Last edited: