January 6, 2020

Guide: Defend against DDoS attacks on Tor

Use a Gateway to Worker configuration to prevent DDoS by playing a game of BOT or NOT.

This is going to be LONG. Today we learn how to authenticate bots from real users. Using Gateways and Workers will help limit the effectiveness of DDoS attacks.

We'll use 4 servers - 1 GATEWAY and 3 WORKERS.
This is to balance the potential impact and loads.
Feel free to downscale or upscale to suit.

GATEWAY - These dictates which WORKER to send to your USER.
WORKERS - Serves your application, your user communicates with WORKER.

TIP - Have WORKERS communicate with DATABASES / MIRROR - if one was to drop, using APIs or automated processes WORKERS can spin up with demand automatically. Create a SQL / SQLite  database with all WORKERS and feed the hungry GATEWAYS. Epic!

If your user was to go straight to the WORKER without a pre-set cookie, it would simply return a 444. "Connection closed"

Find an example of 444 error code at this .onion - Give me your best shot?!

Tor creates an interesting scenario; everyone becomes identical, IPs become localhost, browser fingerprinting is minimal, request restriction is nonsensical and rate limiting is useless. Great for the users, equals a tricky time for server administrators (see GIF)

Let's call this situation BOT OR NOT.
Today, we play. A game of BOT OR NOT..  Some snazzy theme tune pls!🥁🎵

GIF Sourced from https://www.youtube.com/watch?v=W2sHIS7_B44

Here's a picture of DDOS without this method (200)

Here's screenshots of DDOS with this method (444)
NOTE - Within the configuration we have turned off logging 444 requests to prevent I/O bottleneck

This demo we're using 'Bionic' Ubuntu 18.04 LTS

OK. Let's start. With the terminals open

Only use the latest releases by updating the Repository

echo -e "deb https://deb.torproject.org/torproject.org bionic main\ndeb-src https://deb.torproject.org/torproject.org bionic main" > /etc/apt/sources.list.d/tor.list

Authenticate the keys

wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --import 
gpg --export A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 | apt-key add 

Update, upgrade the system. Install the packages.
GATEWAY - nginx, Tor, php7.2-fpm php-curl
WORKERS - nginx, Tor

apt update && apt -y upgrade && apt-get -y install tor nginx php7.2-fpm php-curl

Enable nginx, Tor, PHP on boot. Who turns their server off intentionally?

systemctl enable nginx && systemctl enable php7.2-fpm && systemctl enable tor && systemctl start tor.service

Let's tighten things up, we're blocking Clearnet ports apart from port 22

ufw default deny incoming && ufw allow from YOUR_IP to any port 22 && ufw deny 80 && ufw deny 443 && ufw allow 9050 && ufw enable
y

Type Y and enter. Agree to everything, sign nothing.

nano /etc/nginx/nginx.conf 

Within http {

server_names_hash_bucket_size 125;
server_tokens off;

}

Exit and Save:  CTRL + x + y | ENTER
Do test for any errors, reload nginx

nginx -t
nginx -s reload

No errors, no worries. If only life was this simple.

Edit the Hidden Service configuration

nano /etc/tor/torrc

On GATEWAY and WORKERS configure our Hidden Service

HiddenServiceDir /var/lib/tor/nginx/
HiddenServiceEnableIntroDoSDefense 1
HiddenServiceEnableIntroDoSRatePerSec 42
HiddenServiceEnableIntroDoSBurstPerSec 87
HiddenServicePort 80 127.0.0.1:80
HiddenServiceVersion 3

Exit and Save:  CTRL + x + y | ENTER

Here's  further explanation for number crunching


TIP: Here's an example allowing Tor to load balance should one of these fail.
NOTE - Make sure these are on separate servers for increased redundancy.
Lock down your private network should you be using untrustworthy hosting providers, if this isn't default.

HiddenServiceDir /var/lib/tor/nginx/
HiddenServiceEnableIntroDoSDefense 1
HiddenServiceEnableIntroDoSRatePerSec 42
HiddenServiceEnableIntroDoSBurstPerSec 87
HiddenServicePort 80 127.0.0.1:80
HiddenServicePort 80 192.168.0.2:80
HiddenServicePort 80 192.168.0.3:80
HiddenServicePort 80 192.168.0.4:80
HiddenServicePort 80 192.168.0.5:80
HiddenServiceVersion 3

Start Tor, to generate your .onion address.

service tor start

You've grown an extra long onion

cat /var/lib/tor/nginx/hostname

Write all the addresses somewhere safe.
GATEWAY -
WORKER 1 -
WORKER 2 -
WORKER 3 -

TIP - Repeat this process for Gateway and Workers

First, we edit our GATEWAY default nginx configuration
PATH: /etc/nginx/sites-available/default

nano /etc/nginx/sites-available/default

Remove the old configuration (cut, by CTRL + k)
Paste this

server {
   listen 80 default_server;
   server_name _;
   root /var/www/html/protection;
   set $valid 0;
   if ($cookie_validclient) {
   set $valid 1;
}

   location ~ \.php$ {
   try_files $uri =404;
   include fastcgi.conf;
   include fastcgi_params;
   fastcgi_pass unix:/run/php/php7.2-fpm.sock;
}

   location / {
   add_header Set-Cookie validclient=1;
   return 302 /cookies;
}

   location = /cookies {
   if ($valid = 1) {
   return 301 /mirror.php;
}
   if ($valid = 0) {
   access_log off;
   return 444;
    }
  }
}

Exit and Save:  CTRL + x + y | ENTER

Test the nginx configuration before reloading

nginx -t

Reload nginx

nginx -s reload

Still within the terminal on GATEWAY  - let's create a home for the PHP

mkdir /var/www/html/protection

Create /var/www/html/protection/mirror.php with your favourite editor. I much prefer nano.
Use this pre-built PHP script to determine what WORKER to pass to your USER

<?php

if(empty($_COOKIE['validclient'])) {
header('HTTP/1.0 444');
die;
}

$servers =  array("http://wrk1.onion/set-ckie","http://wrk2.onion/set-ckie","http://wrk3.onion/set-ckie");
$availables = array();

foreach ($servers as $value){
$url = $value;
$ch = curl_init($url);
$proxy = '127.0.0.1:9050';
curl_setopt($ch, CURLOPT_PROXY, $proxy);
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_TIMEOUT,10);
$output = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpcode == 302){
 $availables[] = $url;
}
}
if(!empty($availables)) {
header("Location: " . $availables[array_rand($availables)]);
} else {
echo "We are closed, please call again";
}
die();
?>

Great work!
TIP - Remember to change ownership of this .php file if you created as root.
It never ends well exposing files as root.

chown www-data:www-data -R /var/www/html/

Now move across to the WORKER
Edit /etc/nginx/sites-available/default using your favourite editor.
Remove the old configuration (cut, by CTRL + k)
Paste this

server {
   listen 80 default_server;
   listen [::]:80 default_server;
   set $valid 0;

  location @blackhole {
  return 444;
}
 
  if ($cookie_validclient) {
  set $valid 1;
}
   error_page 301 400 403 404 500 502 503 504 =444 @blackhole;
   root /var/www/html;
   index index.nginx-debian.html index.htm index.html;
   server_name _;

   location  /set-ckie {
   add_header Set-Cookie validclient=1;
   return 302 /;
}

   location / {
   if ($valid = 0) {
   access_log off;
   return 444;
}
   try_files $uri $uri/ =404;
  }
}

Exit and Save:  CTRL + x + y | ENTER

Test the nginx configuration before reloading

nginx -t

Reload nginx

nginx -s reload

Good work!

Please go to your WORKERS first - this should NOT load.
Now proceed to your GATEWAY - this'll choose a WORKER at random.

FUN FACT - We sent 300+ GET requests per millisecond.
Our server load did not go past 0.03

Perfect!

"We are closed, please call again"
This shows if GATEWAY fails to redirect to a WORKER.

TIPS:
– Modify /set-ckie to a long unguessable string, keep these consistent
– Have your GATEWAYS read a database list of WORKERS
– Backend application to monitor response times of WORKERS
– Include a form of cache through nginx to suit your requirements
– Enable compression to suit your circumstances
– Automate spin of WORKERS on load demand via API

Remember you can use - Guide: Generate and rotate .onion addresses to mitigate a heavy DDoS and get you out of hot water. Don't forget to keep back ups.

You can also hire me..

Related articles
Simple guide: Tor Middle Relay
Host your own .onion site using nginx and Tor
Create a vanity .onion web address - learn how to import and export
Decrypt: Ross Ulbricht and Silk Road

Tags
Guide | Insight | Life | Linux | Tor