diff --git a/README.md b/README.md index 62fdce7..b9678a2 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ fixes, improvements and new features. **Maintainer:** Jefferson González +**Contributor (BSD support):** Marc S. Brooks + ## About (D)DoS Deflate is a lightweight bash shell script designed to assist in @@ -14,11 +16,11 @@ command below to create a list of IP addresses connected to the server, along with their total number of connections. It is one of the simplest and easiest to install solutions at the software level. -netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -n +netstat -an | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -n IP addresses with over a pre-configured number of connections are -automatically blocked in the server's firewall, which can be direct -iptables or Advanced Policy Firewall (APF). (We highly recommend that +automatically blocked in the server's firewall, which can be direct +ipfw, iptables, or Advanced Policy Firewall (APF). (We highly recommend that you use APF on your server in general, but deflate will work without it.) ### Notable Features @@ -32,7 +34,7 @@ you use APF on your server in general, but deflate will work without it.) * You can receive email alerts when IP addresses are blocked. * Control blocking by connection state (see man netstat). * Auto-detection of firewall. -* Support for APF, CSF and iptables. +* Support for APF, CSF, ipfw, and iptables. * Logs events to /var/log/ddos.log * Uses tcpkill to reduce the amount of processes opened by attackers. diff --git a/config/ddos.conf b/config/ddos.conf index a30b56b..ac8eb0c 100644 --- a/config/ddos.conf +++ b/config/ddos.conf @@ -8,6 +8,7 @@ CRON="/etc/cron.d/ddos" # Make sure your APF version is atleast 0.96 APF="/usr/sbin/apf" CSF="/usr/sbin/csf" +IPF="/sbin/ipfw" IPT="/sbin/iptables" # frequency in minutes for running the script as a cron job @@ -22,7 +23,7 @@ DAEMON_FREQ=5 NO_OF_CONNECTIONS=150 # The firewall to use for blocking/unblocking, valid values are: -# auto, apf, csf and iptables +# auto, apf, csf, ipfw, and iptables FIREWALL="auto" # An email is sent to the following address when an IP is banned. diff --git a/config/dependencies.list b/config/dependencies.list index 7da2226..da28a5f 100644 --- a/config/dependencies.list +++ b/config/dependencies.list @@ -1,9 +1,9 @@ -/*dependencies apt-get yum*/ -nslookup dnsutils bind-utils -netstat|ifconfig net-tools net-tools -iptables iptables-persistent iptables-services -tcpkill dsniff dsniff -timeout coreutils coreutils -grep grep grep -awk awk awk -sed sed sed +/*dependencies apt-get yum pkg*/ +nslookup dnsutils bind-utils bind-tools +netstat|ifconfig net-tools net-tools net-tools +iptables iptables-persistent iptables-services ipfw +tcpkill dsniff dsniff dsniff +timeout coreutils coreutils timeout +grep grep grep grep +awk awk awk awk +sed sed sed sed diff --git a/install.sh b/install.sh index 6e242f4..771be45 100755 --- a/install.sh +++ b/install.sh @@ -1,15 +1,28 @@ -#!/bin/bash +#!/bin/sh # Check for required dependencies if [ -f "$DESTDIR/usr/bin/apt-get" ]; then - install_type='2'; install_command="apt-get" + install_type='2'; + install_command="apt-get" elif [ -f "$DESTDIR/usr/bin/yum" ]; then - install_type='3'; install_command="yum" + install_type='3'; + install_command="yum" +elif [ -f "$DESTDIR/usr/sbin/pkg" ]; then + install_type='4'; + install_command="pkg" else install_type='0' fi -for dependency in nslookup netstat iptables ifconfig tcpkill timeout awk sed grep; do +packages='nslookup netstat ifconfig tcpkill timeout awk sed grep' + +if [ "$install_type" = '4' ]; then + packages="$packages ipfw" +else + packages="$packages iptables" +fi + +for dependency in $packages; do is_installed=`which $dependency` if [ "$is_installed" = "" ]; then echo "error: Required dependency '$dependency' is missing." @@ -17,12 +30,12 @@ for dependency in nslookup netstat iptables ifconfig tcpkill timeout awk sed gre exit 1 else echo -n "Autoinstall dependencies by '$install_command'? (n to exit) " - fi + fi read install_sign if [ "$install_sign" = 'N' -o "$install_sign" = 'n' ]; then exit 1 fi - eval "$install_command install -y $(grep $dependency config/dependencies.list | awk '{print $'$install_type'}')" + eval "$install_command install -y $(grep $dependency config/dependencies.list | awk '{print $'$install_type'}')" fi done @@ -74,7 +87,7 @@ echo " (done)" echo -n 'Creating ddos script: /usr/local/sbin/ddos...' mkdir -p "$DESTDIR/usr/local/sbin/" -echo "#!/bin/bash" > "$DESTDIR/usr/local/sbin/ddos" +echo "#!/bin/sh" > "$DESTDIR/usr/local/sbin/ddos" echo "/usr/local/ddos/ddos.sh \$@" >> "$DESTDIR/usr/local/sbin/ddos" chmod 0755 "$DESTDIR/usr/local/sbin/ddos" echo " (done)" @@ -95,6 +108,16 @@ fi echo; +if [ -d /etc/newsyslog.conf.d ]; then + echo -n 'Adding newsyslog configuration...' + mkdir -p "$DESTDIR/etc/newsyslog.conf.d" + cp src/ddos.newsyslog "$DESTDIR/etc/newsyslog.conf.d/ddos" > /dev/null 2>&1 + chmod 0644 "$DESTDIR/etc/newsyslog.conf.d/ddos" + echo " (done)" +fi + +echo; + if [ -d /etc/init.d ]; then echo -n 'Setting up init script...' mkdir -p "$DESTDIR/etc/init.d/" @@ -112,6 +135,18 @@ if [ -d /etc/init.d ]; then else echo "ddos service needs to be manually started... (warning)" fi +elif [ -d /etc/rc.d ]; then + echo -n 'Setting up rc script...' + mkdir -p "$DESTDIR/etc/rc.d/" + cp src/ddos.rcd "$DESTDIR/etc/rc.d/ddos" > /dev/null 2>&1 + chmod 0755 "$DESTDIR/etc/rc.d/ddos" > /dev/null 2>&1 + echo " (done)" + + # Activate the service + echo -n "Activating ddos service..." + echo 'ddos_enable="YES"' >> /etc/rc.conf + service ddos start > /dev/null 2>&1 + echo " (done)" elif [ -d /usr/lib/systemd/system ]; then echo -n 'Setting up systemd service...' mkdir -p "$DESTDIR/usr/lib/systemd/system/" @@ -129,9 +164,8 @@ elif [ -d /usr/lib/systemd/system ]; then else echo "ddos service needs to be manually started... (warning)" fi -elif [ -d /etc/cron.d ] && [ "$DESTDIR" = "" ]; then +elif [ -d /etc/cron.d ] || [ -f /etc/crontab ]; then echo -n 'Creating cron to run script every minute...' - mkdir -p "$DESTDIR/etc/cron.d/" /usr/local/ddos/ddos.sh --cron > /dev/null 2>&1 echo " (done)" fi diff --git a/man/ddos.1 b/man/ddos.1 index 7c105e8..1cb94ad 100644 --- a/man/ddos.1 +++ b/man/ddos.1 @@ -34,7 +34,7 @@ and easiest to install solutions at the software level. .PP IP addresses with over a pre-configured number of connections are automatically blocked in the server's firewall, which can be direct -iptables or Advanced Policy Firewall (APF). (We highly recommend that +ipfw, iptables, or Advanced Policy Firewall (APF). (We highly recommend that you use APF on your server in general, but deflate will work without it.) .SH OPTIONS @@ -135,3 +135,4 @@ Zaf (Copyright (C) 2005) .SH CONTRIBUTORS Jefferson González (Fixes and improvements) +Marc S. Brooks (BSD support) diff --git a/src/ddos.newsyslog b/src/ddos.newsyslog new file mode 100644 index 0000000..4d1bee4 --- /dev/null +++ b/src/ddos.newsyslog @@ -0,0 +1 @@ +/var/log/ddos.log 640 4 * $W6D0 JN diff --git a/src/ddos.rcd b/src/ddos.rcd new file mode 100644 index 0000000..26d5211 --- /dev/null +++ b/src/ddos.rcd @@ -0,0 +1,46 @@ +#!/bin/sh +# PROVIDE: ddos +# REQUIRE: DAEMON netif +# KEYWORD: nojail + +# Init script to control ddos daemon +# +# Marc S. Brooks + +. /etc/rc.subr + +name="ddos" +rcvar="${name}_enable" +start_cmd="${name}_start" +stop_cmd="${name}_stop" +restart_cmd="${name}_restart" +status_cmd="${name}_status" +extra_commands="restart status" +ddos_program="/usr/local/sbin/ddos" +# ddos_file is set by rc.conf + +test -x $DAEMON || exit 0 + +ddos_start() +{ + ${ddos_program} --start +} + +ddos_stop() +{ + ${ddos_program} --stop +} + +ddos_status() +{ + ${ddos_program} --status +} + +ddos_restart() +{ + ${ddos_program} --stop + ${ddos_program} --start +} + +load_rc_config $name +run_rc_command "$1" diff --git a/src/ddos.sh b/src/ddos.sh old mode 100644 new mode 100755 index 0743def..882c36e --- a/src/ddos.sh +++ b/src/ddos.sh @@ -1,9 +1,9 @@ -#!/bin/bash +#!/bin/sh ############################################################################## # DDoS-Deflate version 0.8 Author: Zaf # ############################################################################## # Contributors: # -# Jefferson González # +# Jefferson González # ############################################################################## # This program is distributed under the "Artistic License" Agreement # # # @@ -22,7 +22,7 @@ load_conf() { CONF="${CONF_PATH}ddos.conf" if [ -f "$CONF" ] && [ ! "$CONF" == "" ]; then - source $CONF + . $CONF else head echo "\$CONF not found." @@ -127,6 +127,9 @@ unban_ip_list() $APF -u "$ip" elif [ "$FIREWALL" = "csf" ]; then $CSF -dr "$ip" + elif [ "$FIREWALL" = "ipfw" ]; then + rule_number=`$IPF list | awk "/$ip/{print $1}"` + $IPF -q delete $rule_number elif [ "$FIREWALL" = "iptables" ]; then $IPT -D INPUT -s "$ip" -j DROP fi @@ -145,33 +148,48 @@ add_to_cron() { su_required - rm -f $CRON if [ $FREQ -le 2 ]; then - echo "0-59/$FREQ * * * * root $SBINDIR/ddos -k >/dev/null 2>&1" > $CRON + cron_task="0-59/$FREQ * * * * root $SBINDIR/ddos -k >/dev/null 2>&1" + + if [ "$FIREWALL" = "ipfw" ]; then + cron_file=/etc/crontab + sed -i '' '/ddos/d' $cron_file + echo $cron_task >> $cron_file + else + rm -f $CRON + echo $cron_task > $CRON + chmod 644 $CRON + fi else let "START_MINUTE = $RANDOM % ($FREQ - 1)" let "START_MINUTE = $START_MINUTE + 1" let "END_MINUTE = 60 - $FREQ + $START_MINUTE" - echo "$START_MINUTE-$END_MINUTE/$FREQ * * * * root $SBINDIR/ddos -k >/dev/null 2>&1" > $CRON - fi - chmod 644 $CRON + cron_task="$START_MINUTE-$END_MINUTE/$FREQ * * * * root $SBINDIR/ddos -k >/dev/null 2>&1" + + if [ "$FIREWALL" = "ipfw" ]; then + echo $cron_task >> /etc/crontab + else + echo $cron_task > $CRON + chmod 644 $CRON + fi + fi log_msg "added cron job" } ban_incoming_and_outgoing() { - # Improved command - netstat -ntu | \ - # Strip netstat heading - tail -n +3 | \ + # Find all connections + netstat -an | \ # Match only the given connection states grep -E "$CONN_STATES" | \ # Extract only the fifth column awk '{print $5}' | \ + # Strip port without affecting ipv4 addresses + sed -r 's/^([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3})(:|\.)[0-9+]*$/\1.\2.\3.\4/' | \ # Strip port without affecting ipv6 addresses (experimental) - sed "s/:[0-9+]*$//g" | \ + sed 's/:[0-9+]*$//g' | \ # Ignore Server IP sed -r "/($SERVER_IP_LIST)/Id" | \ # Sort addresses for uniq to work correctly @@ -189,41 +207,39 @@ ban_incoming_and_outgoing() ban_only_incoming() { - ALL_LISTENING=$(mktemp $TMP_PREFIX.XXXXXXXX) - ALL_CONNS=$(mktemp $TMP_PREFIX.XXXXXXXX) - - # Find all connections - netstat -ntu | \ - # Strip netstat heading - tail -n +3 | \ - # Match only the given connection states - grep -E "$CONN_STATES" | \ - # Extract both local and foreign address:port - awk '{print $4" "$5;}'> \ - $ALL_CONNS - - # Find all listening sockets - netstat -ntpl | \ - # Strip netstat heading - tail -n +3 | \ - # Only keep local address:port - awk '{print $4}' | \ - # Also include specific server address when address is 0.0.0.0 (only ipv4) - awk -v host_ip=$HOST_IP \ - '{ ip_pos = index($0, "0.0.0.0"); - if(ip_pos != 0) { - port_pos = index($0, ":"); - print $0; - print host_ip substr($0, port_pos); - } else { - print $0; - } - }' > \ - $ALL_LISTENING - - # Only keep connections which are connected to local listening address:port but print foreign address:port - # ipv6 is always included - awk 'NR==FNR{a[$1];next} $1 in a {print $2}' $ALL_LISTENING $ALL_CONNS | \ + ALL_LISTENING=$(mktemp $TMP_PREFIX.XXXXXXXX) + ALL_CONNS=$(mktemp $TMP_PREFIX.XXXXXXXX) + + # Find all connections + netstat -an | \ + # Match only the given connection states + grep -E "$CONN_STATES" | \ + # Extract both local and foreign address:port + awk '{print $4" "$5;}'> \ + $ALL_CONNS + + # Find all connections + netstat -an | \ + # Only keep local address:port + awk '{print $4}' | \ + # Also include specific server address when address is 0.0.0.0 (only ipv4) + awk -v host_ip=$HOST_IP \ + '{ ip_pos = index($0, "0.0.0.0"); + if (ip_pos != 0) { + port_pos = index($0, ":"); + print $0; + print host_ip substr($0, port_pos); + } else { + print $0; + } + }' > \ + $ALL_LISTENING + + # Only keep connections which are connected to local listening address:port but print foreign address:port + # ipv6 is always included + awk 'NR==FNR{a[$1];next} $1 in a {print $2}' $ALL_LISTENING $ALL_CONNS | \ + # Strip port without affecting ipv4 addresses + sed -r 's/^([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3})(:|\.)[0-9+]*$/\1.\2.\3.\4/' | \ # Strip port without affecting ipv6 addresses (experimental) sed "s/:[0-9+]*$//g" | \ # Ignore Server IP @@ -240,11 +256,10 @@ ban_only_incoming() awk "{ if (\$1 >= $NO_OF_CONNECTIONS) print; }" > \ $1 - rm $ALL_LISTENING - rm $ALL_CONNS + rm $ALL_LISTENING + rm $ALL_CONNS } - # Check active connections and ban if neccessary. check_connections() { @@ -315,6 +330,10 @@ check_connections() $APF -d $CURR_LINE_IP elif [ "$FIREWALL" = "csf" ]; then $CSF -d $CURR_LINE_IP + elif [ "$FIREWALL" = "ipfw" ]; then + rule_number=`ipfw list | tail -1 | awk '/deny/{print $1}'` + next_number=$((rule_number + 1)) + $IPF -q add $next_number deny all from $CURR_LINE_IP to any elif [ "$FIREWALL" = "iptables" ]; then $IPT -I INPUT -s $CURR_LINE_IP -j DROP fi @@ -325,7 +344,8 @@ check_connections() if [ $IP_BAN_NOW -eq 1 ]; then if [ -n "$EMAIL_TO" ]; then dt=`date` - cat $BANNED_IP_MAIL | mail -s "[$HOSTNAME] IP addresses banned on $dt" $EMAIL_TO + hn=`hostname` + cat $BANNED_IP_MAIL | mail -s "[$hn] IP addresses banned on $dt" $EMAIL_TO fi if [ $KILL -eq 1 ]; then @@ -342,13 +362,14 @@ check_connections() # Active connections to server. view_connections() { - netstat -ntu | \ - # Strip netstat heading - tail -n +3 | \ + # Find all connections + netstat -an | \ # Match only the given connection states grep -E "$CONN_STATES" | \ # Extract only the fifth column awk '{print $5}' | \ + # Strip port without affecting ipv4 addresses + sed -r 's/^([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3})(:|\.)[0-9+]*$/\1.\2.\3.\4/' | \ # Strip port without affecting ipv6 addresses (experimental) sed "s/:[0-9+]*$//g" | \ # Ignore Server IP @@ -499,12 +520,15 @@ detect_firewall() if [ "$FIREWALL" = "auto" ] || [ "$FIREWALL" = "" ]; then apf_where=`whereis apf`; csf_where=`whereis csf`; + ipf_where=`whereis ipfw`; ipt_where=`whereis iptables`; if [ -e "$APF" ]; then FIREWALL="apf" elif [ -e "$CSF" ]; then FIREWALL="csf" + elif [ -e "$IPF" ]; then + FIREWALL="ipfw" elif [ -e "$IPT" ]; then FIREWALL="iptables" elif [ "$apf_where" != "apf:" ]; then @@ -513,6 +537,9 @@ detect_firewall() elif [ "$csf_where" != "csf:" ]; then FIREWALL="csf" CSF="csf" + elif [ "$ipf_where" != "ipfw:" ]; then + FIREWALL="ipfw" + IPF="ipfw" elif [ "$ipt_where" != "iptables:" ]; then FIREWALL="iptables" IPT="iptables" @@ -533,6 +560,7 @@ IGNORE_HOST_LIST="ignore.host.list" CRON="/etc/cron.d/ddos" APF="/usr/sbin/apf" CSF="/usr/sbin/csf" +IPF="/sbin/ipfw" IPT="/sbin/iptables" FREQ=1 DAEMON_FREQ=5 diff --git a/uninstall.sh b/uninstall.sh index 5b14927..174e193 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh clear @@ -16,6 +16,15 @@ if [ -e '/etc/init.d/ddos' ]; then echo " (done)" fi +if [ -e '/etc/rc.d/ddos' ]; then + echo; echo -n "Deleting rc service..." + service ddos stop > /dev/null 2>&1 + rm -f /etc/rc.d/ddos + sed -i '' '/ddos_enable/d' /etc/rc.conf + echo -n ".." + echo " (done)" +fi + if [ -e '/usr/lib/systemd/system/ddos.service' ]; then echo; echo -n "Deleting systemd service..." SYSTEMCTL_PATH=`whereis update-rc.d` @@ -55,6 +64,17 @@ if [ -e '/etc/cron.d/ddos' ]; then echo -n "Deleting cron job..." rm -f /etc/cron.d/ddos echo -n ".." +fi +if [ -e '/etc/crontab' ]; then + echo -n "Deleting cron job..." + sed -i '' '/ddos/d' /etc/crontab + echo -n ".." +fi +echo " (done)" +if [ -e '/etc/newsyslog.d/ddos' ]; then + echo -n "Deleting newsyslog job..." + rm -f /etc/newsyslog.d/ddos + echo -n ".." echo " (done)" fi