First off I wouldn't suggest iptables to solve all of this, truely an ideal exit Tor node would load balace traffic though a few VPN tunnels to keep ISP's eyes off the packets and true destination and/or utilize caching proxy to keep outbound repeat requests to popular static content to a minimum... while looking into those options here's a band-aid for the abuse complaint problems;
Sources of information used
http://www.ossramblings.com/using_iptables_rate_limiting_to_prevent_portscans
http://blog.nintechnet.com/how-to-block-w00tw00t-at-isc-sans-dfind-and-other-web-vulnerability-scanners/
Combining the two source links into rules that can be used to frustrate bots trying to use your Tor exit node for port scanning. Note this may make hackers using your exit node very un-happy as these rules causes nmap hang-time.
#!/bin/bash ## Network interface used by Tor exit daemon _tor_iface="eth1" ## Ports that Tor exit daemon binds to, maybe comma or space sepperated. _tor_ports="9050,9051" ## Time to ban connections out in secconds, default equates to 10 minutes, same as default Tor cercut. _ban_time="600" ## How long to monitor conections in seconds, default equates to 10 minutes. _outgoing_tcp_update_seconds="600" ## How many new connections can be placed to a server in aloted update time limits. May nead to increes this depending on exit node usage and remote servers usages. _outgoing_tcp_hitcount="8" ## How long to monitor connections for in minuets, default is 15 minutes but could be lessoned. _outgoing_tcp_burst_minute="15" ## Hom many connections to accept untill un-matched _outgoing_tcp_burst_limit="1000" iptables -N out_temp_ban -m comment --comment "Make custom chain for tracking ban time limits" || exit 1 iptables -A out_temp_ban -m recent --set --name temp_tcp_ban -p TCP -j DROP -m comment --comment "Ban any TCP packet coming to this chain" || exit 1 iptables -N out_vuln_scan -m comment --comment "Make custom chain for mitigating port scans originating from ${_tor_iface}" || exit 1 for _tor_port in ${_tor_ports//,/ }; do iptables -A out_vuln_scan -p TCP -o ${_tor_iface} --sport ${_tor_port} -m recent --name temp_tcp_ban --update --seconds ${_ban_time} -j DROP -m comment --comment "Update ban time if IP address is found in temp_tcp_ban list" || exit 1 iptables -A out_vuln_scan -p TCP -o ${_tor_iface} --sport ${_tor_port} -m state --state NEW -m recent --set -m comment --comment "Monitor number of new conncetions to ${_server_iface}" || exit 1 iptables -A out_vuln_scan -p TCP -o ${_tor_iface} --sport ${_tor_port} -m state --state NEW -m recent --update --seconds 30 --hitcout 10 -j out_temp_ban -m comment --comment "Ban address when to many new connections are attempted on ${_tor_iface}" || exit 1 done iptables -A out_vuln_scan -j RETURN -m comment --comment "Return un-matched packets for further processing" || exit 1 ## Add rules to accept/allow outbound packets iptables -N tor_out -m comment --comment "Make custom chain for allowing Tor exit node services" || exit 1 for _tor_port in ${_tor_ports//,/ }; do iptables -A tor_out -p TCP -o ${_tor_iface} --sport ${_tor_port} -m state --state NEW -m recent --set --name limit_${_tor_port} -m comment --comment "Track out-going tcp connections from port ${_tor_port}" || exit 1 iptables -A tor_out -p TCP -o ${_tor_iface} --sport ${_tor_port} -m state --state NEW -m recent --update --seconds ${_outgoing_tcp_update_seconds:-60} --hitcount ${_outgoing_tcp_hitcount:-8} --rttl --name limit_${_tor_port} -j LOG --log-prefix "TCP flooding port ${_tor_port}" -m comment --comment "Log atempts to flood port ${_tor_port} from your server" || exit 1 iptables -A tor_out -p TCP -o ${_tor_iface} --sport ${_tor_port} -m state --state NEW -m recent --update --seconds ${_outgoing_tcp_update_seconds:-60} --hitcount ${_outgoing_tcp_hitcount:-8} --rttl --name limit_${_tor_port} -j DROP -m comment --comment "Drop attempts to flood port ${_tor_port} from your server" || exit 1 iptables -A tor_out -p TCP -o ${_tor_iface} --sport ${_tor_port} -m limit --limit ${_outgoing_tcp_burst_minute:-15}/minute --limit-burst ${_outgoing_tcp_burst_limit:-1000} -j ACCEPT -m comment --comment "Accept with conditions new connections from port ${_tor_port} from your server" || exit 1 done iptables -A tor_out -j RETURN -m comment ---comment "Reurn un-matched packets for further filtering or default polices to take effect." || exit 1 ## Activate jumps from default output chain to new custom filtering chains iptables -A OUTPUT -p TCP -o ${_tor_iface} -j out_vuln_scan -m comment --comment "Jump outbound packets through vulnerability scaning mitigation" || exit 1 iptables -A OUTPUT -p TCP -o ${_tor_iface} -j tor_out -m comment --comment "Jump outbound packets through conditional acceptance" || exit 1
Run above with bash to have magics preformed on variables with , cammas ie;
user@host~# bash iptables_limit_tor.sh
Here's that list of variables again
_tor_iface="eth1" _tor_ports="9050,9051" _ban_time="600" _outgoing_tcp_update_seconds="600" _outgoing_tcp_hitcount="8" _outgoing_tcp_burst_minute="15" _outgoing_tcp_burst_limit="1000"
Note you may also wish to filter new outbound connections for -m state NEW ! --syn kinds of funny buisness used by some bots for finding exploitable servers here's an example chain that you could have prefice the above two for further filtering such malformed chatter
iptables -N out_bad_packets -m comment --comment "Make new chain for filtering malformed packets" || exit 1 iptables -A out_bad_packets -p TCP --fragment -j out_temp_ban -m comment --comment "Drop all fragmented packets" || exit 1 iptables -A out_bad_packets -p TCP -m state --state INVALID -j out_temp_ban -m comment --comment "Drop all invalid packets" || exit 1 iptables -A out_bad_packets -p TCP ! --syn -m state --state NEW -j out_temp_ban -m comment --comment "Drop new non-syn packets" || exit 1 iptables -A out_bad_packets -p TCP --tcp-flags ALL NONE -j out_temp_ban -m comment --comment "Drop NULL scan" || exit 1 iptables -A out_bad_packets -p TCP --tcp-flags ALL ALL -j out_temp_ban -m comment --comment "Drop XMAS scan"|| exit 1 iptables -A out_bad_packets -p TCP --tcp-flags ALL FIN,URG,PSH -j out_temp_ban -m comment --comment "Drop stealth scan 1" || exit 1 iptables -A out_bad_packets -p TCP --tcp-flags ALL SYN,RST,ACK,FIN,URG -j out_temp_ban -m comment --comment "Drop pscan 1"|| exit 1 iptables -A out_bad_packets -p TCP --tcp-flags SYN,FIN SYN,FIN -j out_temp_ban -m comment --comment "Drop pscan 2" || exit 1 iptables -A out_bad_packets -p TCP --tcp-flags FIN,RST FIN,RST -j out_temp_ban -m comment --comment "Drop pscan 3" || exit 1 iptables -A out_bad_packets -p TCP --tcp-flags SYN,RST SYN,RST -j out_temp_ban -m comment --comment "Drop SYN-RST scan" || exit 1 iptables -A out_bad_packets -p TCP --tcp-flags ACK,URG URG -j out_temp_ban -m comment --comment "Drop URG scans" || exit 1 iptables -A out_bad_packets -p TCP --tcp-flags ALL SYN,FIN -j out_temp_ban -m comment --comment "Drop SYNFIN scan" || exit 1 iptables -A out_bad_packets -p TCP --tcp-flags ALL URG,PSH,FIN -j out_temp_ban -m comment --comment "Drop nmap Xmas scan" || exit 1 iptables -A out_bad_packets -p TCP --tcp-flags ALL FIN -j out_temp_ban -m comment --comment "Drop FIN scan" || exit 1 iptables -A out_bad_packets -p TCP --tcp-flags ALL URG,PSH,SYN,FIN -j out_temp_ban -m comment --comment "Drop nmap-id scan" || exit 1 iptables -A out_bad_packets -p TCP --tcp-flags RST RST -o ${_tor_iface} --sport ${_tor_port} -m limit --limit 2/second --limit-burst 3 -j out_temp_ban -m comment --comment "Mitigate Smurf attacks from excesive RST packets" iptables -A out_bad_packets -p TCP --tcp-flags RST RST -o ${_tor_iface} --sport ${_tor_port} -m limit --limit 2/second --limit-burst 2 -j RETURN -m comment --comment "Ban Smurf attacks using excesive RST packets" iptables -A out_bad_packets -j RETURN -m comment --comment "Return un-matched packets for further processing." || exit 1
However, the above chain would be very restrictive as any matched packet will have the IP banned (perhaps change -j out_temp_ban to -j DROP or -j REJECT for testing) for however many seconds chosen in that chain's rules. This set of rules could also cause faulse positives when badly coded apps on the client's end re-connect over a new Tor cercut.
~~~~~
Software to consider for further shapping traffic Check out firejail for Linux, the source is on Github and Source forge and the man pages can be found on the old home page, a wordpress sub domain, and DigitalOcean has a guide for Nginx with PHP and Firejail that with a little modification could give you far more incite as to where network should be throttled back. There are other tools such as KVM too that can be used to keep spiciffic services within operational boundries so shop arround to find the one that works best for your system.
Yet another option would be to run fail2ban in such a way that when a mad sys-admin attepts a http or ssl connection to your IP that a rule is added to drop -m state --state NEW connections to those requesting your exit notice page. This if combined with sane un-ban time limmits could allow the remote server a break while thier sys-admin mutters about log polution ;-) However, that is beyond the scope of this current answer and dependant upon what software you are using to serve exit notice pages; hint both nginx and apache will serve the first vhost or server block in your configurations if now URL was requested. If using something else other than apache or nginx you'll want to consult the man pages but for me it was as simple as setting the first vhost to log to a different file and have fail2ban add any IPs from that log to a temp ban list; this also works great for banning bots on public servers because they usually use an IP address and not providing a domain request results in the server serving up the bot trap, or in this case, exit notice.
I'd lean twords running a restricted Tor exit policy (looks like you've got that handled) and then pushing traffic through VPN tunnels, extra credit points for load balancing between multipule tunnels. Because this would cause less disruption to the Tor network traffic and keep your ISP's eyes clouded to the fact that you're running an exit node... unless they wish admit to sniffing and cracking your VPN traffic. This is because running rules that temp-ban or allow for remote host to self-ban could lead to a breach of privacy to your node's clients where as pushing the traffic out to a VPN (or few) would aid your client's privacy and keep your ISP from being hounded with requests for your network traffic logs by any goverment capible of running whois www.some.domain.
~~~~
Edits/Updates
~~~~
I took a trip into my extencive notes and pulled up the configs for public servers that I use
Here's the fail2ban jail.local stansa
[apache-ipscan] enabled = true port = http,https filter = apache-ipscan logpath = /var/log/apache*/*error_ip* action = iptables-repeater[name=ipscan] maxretry = 1
And here's the filter apache-ipscan.conf file
[DEFAULT] _apache_error_msg = \[[^]]*\] \[\S*:error\] \[pid \d+\] \[client <HOST>(:\d{1,5})?\] [Definition] failregex = \[client <HOST>\] client denied by server .*(?i)/.* #^<HOST>.*GET*.*(?!)/.* # ^%(_apache_error_msg)s (AH0\d+: )?client denied by server configuration: (uri )?.*$ # ^%(_apache_error_msg)s script '\S+' not found or unable to stat(, referer: \S+)?\s*$ ignoreregex = # DEV Notes: # the web server only responds to clients with a valid Host: # header. anyone who tries using IP only will get shunted into # the dummy-error.log and get a client-denied message # # the second regex catches folks with otherwise valid CGI paths but no good Host: header # # Author: Paul Heinlein
And here's the action iptables-repeater.conf file
# Fail2Ban configuration file # # Author: Phil Hagen <[email protected]> # Author: Cyril Jaquier # Modified by Yaroslav Halchenko for multiport banning and Lukas Camenzind for persistent banning # Modified by S0AndS0 to combine features of previous Authors and Modders # [Definition] # Option: actionstart # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # actionstart = iptables -N fail2ban-BADIPS-<name> iptables -A fail2ban-BADIPS-<name> -j RETURN iptables -I INPUT -j fail2ban-BADIPS-<name> ## Comment above line and uncomment bello line to use multiport and protocol in addition to named jails #iptables -I INPUT -p <protocol> -m multiport --dports <port> -j fail2ban-BADIPS-<name> # set up from the static file #cat /etc/fail2ban/ip.blocklist.<name> |grep -v ^\s*#|awk '{print $1}' | while read IP; do iptables -I fail2ban-BADIPS-<name> 1 -s $IP -j DROP; done cat /etc/fail2ban/ip.blocklist.<name> |grep -v ^\s*#|awk '{print $1}' | while read IP; do iptables -I fail2ban-BADIPS-<name> 1 -d $IP -j DROP; done ## Comment above line and uncomment bellow line to check if there are blacklist files to load before attempting to load them # if [ -f /etc/fail2ban/ip.blacklist.<name> ]; then cat /etc/fail2ban/ip.blacklist.<name> | grep -e <name>$ | cut -d "," -s -f 1 | while read IP; do iptables -I fail2ban-BADIPS-<name> 1 -s $IP -j DROP; done; fi # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -j fail2ban-BADIPS-<name> iptables -F fail2ban-BADIPS-<name> iptables -X fail2ban-BADIPS-<name> # Option: actioncheck # Notes.: command executed once before each actionban command # Values: CMD # #actioncheck = iptables -n -L INPUT | grep -q fail2ban-BADIPS-<name> actioncheck = iptables -n -L OUTPUT | grep -q fail2ban-BADIPS-<name> # Option: actionban # Notes.: command executed when banning an IP. Take care that the # command is executed with Fail2Ban user rights. # Tags: <ip> IP address # <failures> number of failures # <time> unix timestamp of the ban time # Values: CMD # #actionban = if ! iptables -C fail2ban-BADIPS-<name> -s <ip> -j DROP; then iptables -I fail2ban-BADIPS-<name> 1 -s <ip> -j DROP; fi actionban = if ! iptables -C fail2ban-BADIPS-<name> -d <ip> -j DROP; then iptables -I fail2ban-BADIPS-<name> 1 -d <ip> -j DROP; fi # Add offenders to local blacklist, if not already there if ! grep -Fxq '<ip>,<name>' /etc/fail2ban/ip.blocklist.<name>; then echo "<ip>,<name> # fail2ban/$( date '+%%Y-%%m-%%d %%T' ): auto-add for BadIP offender" >> /etc/fail2ban/ip.blocklist.<name>; fi # Report offenders to badips.com # wget -q -O /dev/null www.badips.com/add/<name>/<ip> # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the # command is executed with Fail2Ban user rights. # Tags: <ip> IP address # <failures> number of failures # <time> unix timestamp of the ban time # Values: CMD # #actionunban = iptables -D fail2ban-REPEAT-<name> -s <ip> -j DROP actionunban = iptables -D fail2ban-REPEAT-<name> -d <ip> -j DROP # Disabled clearing out entry from ip.blacklist (somehow happens after each stop of fail2ban) #sed --in-place '/<ip>,<name>/d' /etc/fail2ban/ip.blacklist.<name> [Init] # Defaut name of the chain # # Defaut name of the chain name = BADIPS # Option: port # Notes.: specifies port to monitor # Values: [ NUM | STRING ] Default: # #port = ssh # Option: protocol # Notes.: internally used by config reader for interpolations. # Values: [ tcp | udp | icmp | all ] Default: tcp
Note above filter has been edited to block OUTPUT on the start/stop actions but you'll still want to add the -p TCP -m state --state NEW configs to each line to only have new outbound connections banned from the logged IP address.
Last is setting up a Apache vHost config that routs those not requesting a domain to a specifide access and error log and setting the allowed vs denied access such that it always errors, not even the loopback should be able to pull up the page without popping errors. Last but not least is setting the error page for Apache to the default exit notice from Tor so that that is served instead of 503 or 404 bland messages. Or if you've added the state lines to iptables actions for fail2ban you could easily just point to the same log file that is used by your exit notice. The result would be that your server would not be able to make new connections out to the server's IP that checked your IP address but established and related connections would still be permitted, ie they could still browse your other pages but you could not browse through thiers.