From 68e8b88afab35cb831dd02b22fb62f8d88daba75 Mon Sep 17 00:00:00 2001 From: Username Date: Thu, 25 Dec 2025 19:18:25 +0100 Subject: [PATCH] tor: use random credentials for circuit isolation --- fetch.py | 12 ++++++++++-- proxywatchd.py | 19 ++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/fetch.py b/fetch.py index aa30a0e..9f2674c 100644 --- a/fetch.py +++ b/fetch.py @@ -1,4 +1,4 @@ -import re, random, time +import re, random, time, string import threading import rocksock import network_stats @@ -7,6 +7,14 @@ from soup_parser import soupify from misc import _log config = None + + +def tor_proxy_url(torhost): + """Generate Tor SOCKS5 proxy URL with random credentials for circuit isolation.""" + chars = string.ascii_lowercase + string.digits + user = ''.join(random.choice(chars) for _ in range(8)) + passwd = ''.join(random.choice(chars) for _ in range(8)) + return 'socks5://%s:%s@%s' % (user, passwd, torhost) _last_fail_log = 0 _fail_log_interval = 60 @@ -58,7 +66,7 @@ def _fetch_contents(url, head = False, proxy=None): http = None try: while True: - proxies = [rocksock.RocksockProxyFromURL('socks4://%s' % random.choice( config.torhosts ))] + proxies = [rocksock.RocksockProxyFromURL(tor_proxy_url(random.choice(config.torhosts)))] if proxy: proxies.append( rocksock.RocksockProxyFromURL(proxy)) http = RsHttp(host,ssl=ssl,port=port, keep_alive=True, timeout=config.ppf.timeout, max_tries=config.ppf.http_retries, follow_redirects=True, auto_set_cookies=True, proxies=proxies, user_agent='Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0', log_errors=False) diff --git a/proxywatchd.py b/proxywatchd.py index 1981752..b3977fd 100644 --- a/proxywatchd.py +++ b/proxywatchd.py @@ -667,6 +667,19 @@ def try_div(a, b): return 0 +def tor_proxy_url(torhost): + """Generate Tor SOCKS5 proxy URL with random credentials for circuit isolation. + + Tor treats different username:password as separate streams, using different + circuits. This ensures each connection gets a fresh circuit. + """ + # 8 random alphanumeric chars for user and pass + chars = string.ascii_lowercase + string.digits + user = ''.join(random.choice(chars) for _ in range(8)) + passwd = ''.join(random.choice(chars) for _ in range(8)) + return 'socks5://%s:%s@%s' % (user, passwd, torhost) + + class MITMCertStats(object): """Track MITM certificate statistics.""" @@ -834,7 +847,7 @@ def get_mitm_certificate(proxy_ip, proxy_port, proto, torhost, target_host, targ """ try: proxies = [ - rocksock.RocksockProxyFromURL('socks5://%s' % torhost), + rocksock.RocksockProxyFromURL(tor_proxy_url(torhost)), rocksock.RocksockProxyFromURL('%s://%s:%s' % (proto, proxy_ip, proxy_port)), ] @@ -1375,7 +1388,7 @@ class TargetTestJob(): duration = time.time() proxies = [ - rocksock.RocksockProxyFromURL('socks5://%s' % torhost), + rocksock.RocksockProxyFromURL(tor_proxy_url(torhost)), rocksock.RocksockProxyFromURL('%s://%s:%s' % (proto, ps.ip, ps.port)), ] @@ -1473,7 +1486,7 @@ class TargetTestJob(): try: http_port = 80 http_proxies = [ - rocksock.RocksockProxyFromURL('socks5://%s' % torhost), + rocksock.RocksockProxyFromURL(tor_proxy_url(torhost)), rocksock.RocksockProxyFromURL('%s://%s:%s' % (proto, ps.ip, ps.port)), ] http_sock = rocksock.Rocksock(host=connect_host, port=http_port, ssl=0,