Block ftp hacking
If you own a Qnap that does not have ftp / ip blocking built in, you can use the script/procedure described below to enhance security.
This script will block specific IP addresses that have tried to login to your FTP server, but failed to enter a valid account. The number of failures and the time to block can be configured (default 3 failures and one hour).
| This script can disable the HDD spindown functionality of your QNAP, thus causing premature harddisk failure!!!
To prevent this, you need to mount your disks with the noatime option, as discussed here. |
Skills required
You must be able to login to your qnap using telnet/ssh. Know how to use vi and you need to have logging turned on for your FTP server (see Ftp EnableLogging). You also need to know how to make (persistent) changed to the crontab (see Add items to crontab).
Steps involved
- turn on FTP Server logging
- rotate ftp server logfiles and restart server
- prepare proftpd config
- Watch Failed Logins (and block IPs)
- Unblock blocked IPs
- stop block script
turn on FTP Server logging
Edit the proftpd configuration to include the following config items: (customize the location of the log file to suit your needs)
LogFormat userlog "%u %P %a %h %t \"%r\" %s" ExtendedLog /share/MD0_DATA/.../ftplogs/proftpd.log AUTH,READ,WRITE userlog
rotate ftp server logfiles
Add an entry to the crontab to restart the proftpd ftp server during the night and also rotate the logfile; this will leave you with a freshly restarted ftp server and a log history of 10 days.
Rotating the logfile will ensure the amount of log space you will need will not become too much.
Add the following entry to your crontab (customize script location as desired):
35 4 * * * /share/custom/scripts/proftpd_clean.sh
and create the script (proftpd_clean.sh):
#!/bin/sh # customize the location of the log files below: /bin/mv /share/.../ftplogs/proftpd.log.8 /share/.../ftplogs/proftpd.log.9 /bin/mv /share/.../ftplogs/proftpd.log.7 /share/.../ftplogs/proftpd.log.8 /bin/mv /share/.../ftplogs/proftpd.log.6 /share/.../ftplogs/proftpd.log.7 /bin/mv /share/.../ftplogs/proftpd.log.5 /share/.../ftplogs/proftpd.log.6 /bin/mv /share/.../ftplogs/proftpd.log.4 /share/.../ftplogs/proftpd.log.5 /bin/mv /share/.../ftplogs/proftpd.log.3 /share/.../ftplogs/proftpd.log.4 /bin/mv /share/.../ftplogs/proftpd.log.2 /share/.../ftplogs/proftpd.log.3 /bin/mv /share/.../ftplogs/proftpd.log.1 /share/.../ftplogs/proftpd.log.2 /bin/mv /share/.../ftplogs/proftpd.log /share/.../ftplogs/proftpd.log.1 #restart the ftp server: /etc/init.d/ftp.sh restart #start a script to customize the proftpd.conf (optional) #/share/custom/scripts/proftpd.sh
prepare proftpd config
In order to run the script, the proftpd config file must contain a section for blocking access to the ftp server. You can use this section to manually block IP addresses (if you want) and the script will add IP addresses to this list as well.
The following section must be added to the end of your proftpd.conf file (or to the end a file that you include into the proftpd.conf at the end):
<Limit LOGIN> # Manually added blocked IP addresses: #Deny 1.2.3.4 ####################################### ## IPs below are automatically added ## ####################################### </LIMIT>
Watch Failed Logins
Next, a script must be put in place to monitor the proftpd logfile, detect login failures, store ip addresses that failed to login and (if the maxlogin for a specific IP is exceeded), block an ip address.
Add the following entry to your crontab (customize script location as desired):
37 4 * * * /share/custom/scripts/proftpd_block.sh &
and create the script (proftpd_block.sh):
#!/bin/sh
# script name: proftpd.block script
# location: /share/custom/scripts
# purpose: watch proftpd logfile and detect failed login attempts
# designed for Qnap TS-201
#
# this script asumes the following log config (in proftpd.conf):
# LogFormat userlog "%u %P %a %h %t \"%r\" %s"
# ExtendedLog /share/MD0_DATA/data/website/ftplogs/proftpd.log AUTH,READ,WRITE userlog
datim=$(date +%F-%H%M)
# customize paths of files:
log2file=/share/.../ftplogs/block.log
# proftpd logfile:
logfile=/share/.../ftplogs/proftpd.log
# file that stores the ips that failed to login:
watchfile=/share/custom/scripts/proftpd_watch.ip
# tmp watch file (used when editing the file)
tmpfile=/share/custom/scripts/proftpd_watch.ip.new
# path to your standard/customized proftpd.conf file
# the file must be manually edited to end with the following lines:
# <Limit LOGIN>
# #manually entered blocked ips:
# #1.2.3.4
# #######################################
# ## IPs below are automatically added ##
# #######################################
# </Limit>
cfgfile=/share/custom/customized/proftpd.conf
# intermediate configfile (used when editing config)
tmpcfgfile=/share/custom/customized/proftpd.conf.new
# max number of filed logins per ip:
maxfailedlogin=3
# manage logfiles:
#(customize location of logfiles!)
/bin/mv /share/.../ftplogs/block.log.8 /share/.../ftplogs/block.log.9
/bin/mv /share/.../ftplogs/block.log.7 /share/.../ftplogs/block.log.8
/bin/mv /share/.../ftplogs/block.log.6 /share/.../ftplogs/block.log.7
/bin/mv /share/.../ftplogs/block.log.5 /share/.../ftplogs/block.log.6
/bin/mv /share/.../ftplogs/block.log.4 /share/.../ftplogs/block.log.5
/bin/mv /share/.../ftplogs/block.log.3 /share/.../ftplogs/block.log.4
/bin/mv /share/.../ftplogs/block.log.2 /share/.../ftplogs/block.log.3
/bin/mv /share/.../ftplogs/block.log.1 /share/.../ftplogs/block.log.2
/bin/mv /share/.../ftplogs/block.log /share/.../ftplogs/block.log.1
echo $(date +%F-%H%M) "BCK: Starting Proftpd blocking..." $datim > $log2file
# ensure the logfile exists, so the tail will not fail &exit the script:
touch $logfile
#tail the logfile:
tail -n 0 -f $logfile | while read logline
do
ip=''
result=''
#echo $logline
let ncount=0
for e in $logline
do
let ncount=$ncount+1
#echo $ncount $e
if [ "x${ncount}" = "x3" ]; then
ip=$e;
#echo $ip;
fi
result=$e
done
#echo $ip: $result
if [ "x$result" = "x530" ]; then
# failed login
echo $(date +%F-%H%M) 'BCK: FAILED login from' $ip >> $log2file
# read attempts from watchfile
#echo grep -i $ip $watchfile
block=`grep -i $ip $watchfile`
#echo $(date +%F-%H%M) $block >> $log2file
let count=0
if [ "x${block}" = "x" ]; then
# new ip:
echo $(date +%F-%H%M) 'BCK: new ip:' $ip >> $log2file
let count=1
else
#existing ip
echo $(date +%F-%H%M) 'BCK: existing ip' $ip >> $log2file
# read nr attempts:
let ncount=0
for t in $block
do
let ncount=$ncount+1
if [ "x${ncount}" = "x2" ]; then
# nr of login attempts
count=$t;
#echo $t
fi
done
let count=$count+1
#echo $count
fi
# remove ip from watchlist
grep -vi $ip $watchfile > $tmpfile
mv $tmpfile $watchfile
if expr $count \> $maxfailedlogin ; then
echo $(date +%F-%H%M) 'BCK: Denying' $ip >> $log2file
echo $ip 0 $(date +%s) $(date +%F-%H%M) >> $watchfile
# must modify proftpd.conf
grep -vi $ip $cfgfile | grep -vi /LIMIT > $tmpcfgfile
#echo $(date +%F-%H%M) "BCK: Deny" $ip >> $log2file
echo " Deny" $ip >> $tmpcfgfile
echo "</LIMIT>" >> $tmpcfgfile
mv $tmpcfgfile $cfgfile
# and reread config:
/etc/init.d/ftp.sh reconfig >> $log2file
else
# add to watchfile :
#echo $ip $count $(date +%F-%H%M)
echo $(date +%F-%H%M) 'BCK: Inc Atttempts to' $count 'for' $ip >> $log2file
echo $ip $count $(date +%s) $(date +%F-%H%M) >> $watchfile
fi
elif [ "x$result" = "x230" ]; then
# login OK!
echo $(date +%F-%H%M) 'BCK: login OK from' $ip >> $log2file
blocked=`grep -i $ip $cfgfile`
#echo 'blocked:' $blocked
if [ "x$blocked" = "x" ] ; then
echo $(date +%F-%H%M) 'BCK:' $ip 'not blocked in proftpd.conf' >> $log2file
else
# remove from config
echo $(date +%F-%H%M) 'BCK: removing' $ip 'from proftpd.conf' >> $log2file
grep -vi $ip $cfgfile > $tmpcfgfile
mv $tmpcfgfile $cfgfile
# and reread config:
/etc/init.d/ftp.sh reconfig >> $log2file
fi
# remove ip from watch list
blocked=`grep -i $ip $watchfile`
if [ "x$blocked" = "x" ] ; then
echo $(date +%F-%H%M) 'BCK:' $ip 'nothing to do' >> $log2file
else
echo $(date +%F-%H%M) 'BCK: removing' $ip 'from watchlist' >> $log2file
grep -vi $ip $watchfile > $tmpfile
mv $tmpfile $watchfile
fi
fi
done
Unblock blocked IPs
Blocked IP addresses will be automatically be unblocked after a timeout period (default an hour). Add the following entry to your crontab (customize script location as desired):
00 * * * * /share/custom/scripts/proftpd_block_check.sh
Be aware: this crontab entry will wakeup your qnap every hour!
and create the script (proftpd_block_check.sh):
#!/bin/sh
# script name: proftpd.block.check script
# location: /share/custom/scripts
# purpose: unblock blocked ip addresses from proftpd.conf
# designed for Qnap TS-201
datim=$(date +%F-%H%M)
# customize file locations!
log2file=/share/.../ftplogs/block.log
watchfile=/share/custom/scripts/proftpd_watch.ip
tmpfile=/share/custom/scripts/proftpd_watch.ip.new
# standard or your customized proftpd.conf:
cfgfile=/share/custom/customized/proftpd.conf
# tmp file, used when editing:
tmpcfgfile=/share/custom/customized/proftpd.conf.new
#block duration (in sec):
blocktime=3600
echo $(date +%F-%H%M) 'CHK: checking expired records...' >> $log2file
#ensure tmpfile is empty & exists:
rm $tmpfile 2>&1 >/dev/null
touch $tmpfile
#init global vars:
let found=0
# read through watchfile:
while read watchline
do
echo $(date +%F-%H%M) 'CHK:' $watchline >> $log2file
#init loop vars:
ncount=0
ip=''
dt=''
dt2=''
# determine <date to unblock
dt2=$(date +%s)
dt2=$(($dt2 - $blocktime))
# find blocked time & ip in line:
for w in $watchline
do
let ncount=$ncount+1
if [ "x$ncount" = "x1" ]; then
ip=$w
elif [ "x$ncount" = "x3" ]; then
#datetime: blocked since (in secs)
dt=$w
fi
done
echo $(date +%F-%H%M) 'CHK: check if' $dt '<' $dt2 'for ip' $ip >> $log2file
if expr $dt \< $dt2 ; then
blocked=`grep -i $ip $cfgfile`
#echo 'blocked:' $blocked
if [ "x${blocked}" = "x" ] ; then
echo $(date +%F-%H%M) 'CHK: removed:' $watchline >> $log2file
else
echo $(date +%F-%H%M) 'CHK: removed: also remove' $ip 'from proftpd.conf' >> $log2file
found=1
grep -vi $ip $cfgfile > $tmpcfgfile
mv $tmpcfgfile $cfgfile
chown michiel.everyone $cfgfile
fi
else
echo $watchline >> $tmpfile
fi
done < $watchfile
mv $tmpfile $watchfile
echo $(date +%F-%H%M) 'CHK: found: ' $found >> $log2file
if expr $found \= 1 ; then
# removed ips from watch list & config file; reread config file:
echo $(date +%F-%H%M) 'CHK: Removed expired records' >> $log2file
/etc/init.d/ftp.sh reconfig >> $log2file
fi
echo $(date +%F-%H%M) 'CHK: checking expired records: done' >> $log2file
Stop block script
The block script must be stopped, to stop the tail on the logfile. This stop is required, because the proftpd logfile will be rotated.
34 4 * * * /share/custom/scripts/proftpd_block_stop.sh
and create the script (proftpd_block_stop.sh):
#!/bin/sh # script name: proftpd_block_stop.script # location: /share/custom/scripts # purpose: stop monitoring the proftpd log file and lock out ips # designed for Qnap TS-201 #stop script from running: kill -9 `ps -ef | grep proftpd_block.sh | cut -c 1-5` kill -9 `ps -ef | grep /proftpd.log | cut -c 1-5`