When dealing with network attacks, identifying Tor exit nodes becomes crucial for security teams. The Tor network routes traffic through multiple relays, with exit nodes being the final point where traffic enters the clearnet. These IPs frequently appear in brute force attacks, credential stuffing, and scraping attempts.
The Tor Project maintains several methods to check exit nodes:
- Official JSON API:
https://check.torproject.org/torbulkexitlist
- DNS-based lists:
exitlist.torproject.org
- Daily updated files:
https://metrics.torproject.org/
Here's a complete script to check multiple IPs against Tor's exit list:
import requests
import ipaddress
def is_tor_exit_node(ip):
try:
# Validate IP address first
ipaddress.ip_address(ip)
# Fetch current exit node list
response = requests.get('https://check.torproject.org/torbulkexitlist')
exit_nodes = response.text.split('\n')
return ip in exit_nodes
except ValueError:
return False
except requests.RequestException:
# Fallback to cached version if API fails
return False
# Example usage
print(is_tor_exit_node('185.220.101.4')) # Returns True for known exit node
print(is_tor_exit_node('8.8.8.8')) # Returns False for Google DNS
For production systems, consider these enhancements:
# Caching implementation
from datetime import datetime, timedelta
import os
TOR_CACHE_FILE = 'tor_exit_nodes.cache'
CACHE_EXPIRY = timedelta(hours=6)
def get_exit_nodes():
# Check cache first
if os.path.exists(TOR_CACHE_FILE):
mod_time = datetime.fromtimestamp(os.path.getmtime(TOR_CACHE_FILE))
if datetime.now() - mod_time < CACHE_EXPIRY:
with open(TOR_CACHE_FILE, 'r') as f:
return set(f.read().splitlines())
# Fetch fresh data
try:
response = requests.get('https://check.torproject.org/torbulkexitlist')
nodes = set(response.text.splitlines())
# Update cache
with open(TOR_CACHE_FILE, 'w') as f:
f.write('\n'.join(nodes))
return nodes
except requests.RequestException:
return set()
Alternative APIs for verification:
- AbuseIPDB: Offers Tor node detection in their premium API
- IPHub: Provides Tor identification along with proxy detection
- Spur: Specialized in detecting malicious exit nodes
When implementing checks:
# Redis-based rate limiting example
import redis
from datetime import timedelta
r = redis.Redis()
def rate_limit_ip(ip, limit=50, window=3600):
key = f"tor_check:{ip}"
current = r.incr(key)
if current == 1:
r.expire(key, window)
return current <= limit
html
When dealing with malicious traffic, identifying Tor exit nodes becomes crucial for implementing security measures. Tor exit nodes are the final relays where traffic exits the anonymity network and enters the public internet. These IPs can be identified through several technical methods.
The most authoritative method is using Tor Project's own DNS-based Blackhole List (DNSBL):
# Python example using dnspython
import dns.resolver
def is_tor_exit(ip):
try:
reversed_ip = '.'.join(reversed(ip.split('.')))
query = f"{reversed_ip}.exitlist.torproject.org"
answers = dns.resolver.resolve(query, 'A')
return answers[0].address == "127.0.0.2"
except:
return False
# Example usage:
print(is_tor_exit("185.220.101.134")) # Known Tor exit node
For developers needing quick checks without DNS setup, these APIs provide Tor exit node data:
# Example using requests
import requests
def check_tor_exit_api(ip):
response = requests.get(f"https://check.torproject.org/torbulkexitlist")
exit_nodes = response.text.split('\n')
return ip in exit_nodes
# Or using Onionoo protocol (recommended by Tor Project)
def onionoo_check(ip):
response = requests.get("https://onionoo.torproject.org/summary?search=exit")
data = response.json()
exit_nodes = [relay['a'][0] for relay in data['relays']]
return ip in exit_nodes
For production systems, consider these implementation patterns:
# Nginx configuration example
geo $tor_traffic {
default 0;
include /path/to/tor_exit_list.conf;
}
server {
location / {
if ($tor_traffic) {
return 403;
# or redirect to captcha/challenge
}
}
}
# Cloudflare Worker example
addEventListener('fetch', event => {
const ip = event.request.headers.get('cf-connecting-ip');
const torExits = await TOR_EXITS.get('exit_nodes');
if (torExits.includes(ip)) {
return new Response('Access denied', {status: 403});
}
event.respondWith(fetch(event.request));
});
Tor exit nodes change frequently. Implement a regular update mechanism:
# Bash script for updating list
#!/bin/bash
wget -q -O - https://check.torproject.org/torbulkexitlist > /etc/nginx/tor_exit_list.conf
systemctl reload nginx
# Python scheduled updater
import schedule
import time
import requests
def update_tor_list():
response = requests.get("https://check.torproject.org/torbulkexitlist")
with open("tor_nodes.txt", "w") as f:
f.write(response.text)
schedule.every(6).hours.do(update_tor_list)
while True:
schedule.run_pending()
time.sleep(1)
While blocking Tor exit nodes can reduce abuse, consider these balanced approaches:
- Implement rate limiting specific to Tor traffic
- Use CAPTCHAs instead of complete blocks
- Log Tor access attempts for threat analysis
- Combine with other threat intelligence feeds