Block ftp hacking

From QNAPedia
Jump to navigation Jump to search

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

  1. turn on FTP Server logging
  2. rotate ftp server logfiles and restart server
  3. prepare proftpd config
  4. Watch Failed Logins (and block IPs)
  5. Unblock blocked IPs
  6. stop block script
turn on FTP Server logging

(Ftp EnableLogging)

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`