Few month ago i was interested in how to block incoming traffic from Tor network. Tor network have finitely numbers of exit nodes, so the solution is to block traffic from this nodes. I see two solutions how to block exit nodes. First use TorDNSEL service, in brief you can check that connection comes from Tor exit node or not by querying special domain name. It is useful if you want to do this check in your php script, for example. But i do not know how to protect host completely by this method. Another way is to use exitlist. By this way you can get list of addresses. Both ways have advantages and disadvantages. I prefer last solution because, it allow to protect my hosts completely. Cons of this method – near 700-800 rules into firewall, that can slow down your host.
Anyway, first that i done – simply script that fetch list of IP and print them to standard output. When i started to writing script i wanted to expand features in future. I want to generate ready firewall rules for example, but now this script can only print list of IP’s. I do not want to integrate firewall rules application in this script, because i think this job must be done by another script.
So, now this script only fetch and print IP addresses or exit with error code if can not do it:
#!/usr/bin/perl -w
use strict;
use LWP::Simple;
my $list = get("http://exitlist.torproject.org/exit-addresses");
my $i;
my @ips;
if( ! defined( $list ) ) {
die( "Can not fetch addresses.\n" );
}
foreach $i (split( /\n/, $list )) {
push( @ips, $1 ) if( $i =~ m/((?:\d{1,3}\.){3}\d{1,3})/);
}
if( defined( $ARGV[0] ) && $ARGV[0] eq "-ip" ) {
print( join( "\n", @ips ));
} else {
die( "Usage $0 -ip\n" );
} |
#!/usr/bin/perl -w
use strict;
use LWP::Simple;
my $list = get("http://exitlist.torproject.org/exit-addresses");
my $i;
my @ips;
if( ! defined( $list ) ) {
die( "Can not fetch addresses.\n" );
}
foreach $i (split( /\n/, $list )) {
push( @ips, $1 ) if( $i =~ m/((?:\d{1,3}\.){3}\d{1,3})/);
}
if( defined( $ARGV[0] ) && $ARGV[0] eq "-ip" ) {
print( join( "\n", @ips ));
} else {
die( "Usage $0 -ip\n" );
}
Next that you need – apply rules on firewall. As i say before, when you apply one rule per IP you will get near 700-800 rules, it is reason why you need optimize sequence of your rules. It can be done in next manner:
1. Rule to accept packets flowing thru loopback.
2. Rule to accept packets of RELATED and ESTABLISHED connections.
3. Rule to drop packets from blacklisted sources.
4. Rule to accept packets from whitelisted sources.
5. Rule for fail2ban (optional).
6. Rule for Tor exit nodes.
7. Other rules (for example, accept connection on ssh, http, https ports).
This sequence allow to make decision for most packets before they pass Tor filtration rules.
I organize Tor filtration rules in separate table called TorExitnodes, it allow to update only this table and do not touch other firewall rules. Also i create table TorBlockAndLog that contain LOG and DROP target, but it is optional, you can simply drop packets.
For updating firewall rules for exit nodes i made next bash scrip and place it in cront:
#!/bin/bash
SOLVE="/root/bin/solve.pl"
if /usr/bin/perl $SOLVE
then
/sbin/iptables -F TorExitnodes
/sbin/iptables -I TorExitnodes -j RETURN
for i in `/usr/bin/perl $SOLVE -ip`
do
/sbin/iptables -I TorExitnodes -s $i -j TorBlockAndLog
done
else
echo "Can not fetch Tor exit nodes" 1>&2
exit 1;
fi |
#!/bin/bash
SOLVE="/root/bin/solve.pl"
if /usr/bin/perl $SOLVE
then
/sbin/iptables -F TorExitnodes
/sbin/iptables -I TorExitnodes -j RETURN
for i in `/usr/bin/perl $SOLVE -ip`
do
/sbin/iptables -I TorExitnodes -s $i -j TorBlockAndLog
done
else
echo "Can not fetch Tor exit nodes" 1>&2
exit 1;
fi
Where is variable “SOLVE” store path to previous perl script, as i say before, if you do not need special action for incoming connections from Tor, you can change “TorBlockAndLog” on “DROP”.
Thats all.
PS
It is good idea to place whitelist before TorExitnodes, who can know, may be one fine day you found -s 0.0.0.0/0 -j DROP into this table. =)