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`