This project's purpose is to demonstrate a DDoS attack and then try to come up with defense techniques to mitigate the effects of the attack.
TodoApi is a CRUD application in which you can add tasks and edit their content and done status. All changes are saved to a PostgreSQL database.
All services run as Docker containers. They are defined using the Docker Compose version 2 file format. Services are seperated into two machines.
First machine (server machine) runs the docker-compose-server.yml
file.
- app: Basic CRUD web API written in .NET 6.0.
- postgres: PostgreSQL database.
- server-1 to server-4: Apache2 (httpd) web servers serving static web pages.
Second machine (reverse proxy machine) runs the docker-compose-reverse-proxy.yml
file.
- proxy-nginx: Nginx reverse proxy server which redirects incoming requests to upstream web servers.
-
ApacheBench load testing tool
sudo apt install apache2-utils
-
Web browser
-
Server Machine
-
Change URL of the API in
./TodoClient/index.js
according to machine's IP.const todoApiUrl = "http://<machine_ip>:5122/api/TodoItems";
-
Compile TodoApi using Dockerfile.
sudo docker build -t "todo-api" TodoApi
-
Copy docker compose file to root.
cp ./docker/docker-compose-server.yml docker-compose.yml
-
Initialize docker compose with the script.
bash initialize_compose.sh
-
Run the application.
sudo docker compose run -d
-
-
Reverse Proxy Machine
-
Copy docker compose file to root.
cp ./docker/docker-compose-reverse-proxy.yml docker-compose.yml
-
Run the application.
sudo docker compose run -d
-
-
You can access the application:
- Go to
http://<reverse_proxy_machine_ip>
to view the webpage. - Go to
http://<server_machine_ip>:5122/api/todoitems
to access the API. - Go to following pages to access each server.
http://<server_machine_ip>:8080 http://<server_machine_ip>:8081 http://<server_machine_ip>:8082 http://<server_machine_ip>:8083
- Use
psql
CLI utility if you want to access the database directlypsql -h <server_machine_ip> -p 5432 -U postgres -d postgres -W # Enter the password when prompted # Password: 0000
- Go to
- Note: Nginx reverse proxy caches responses from upstream servers by default. This can prevent upstream server DDoS attacks because not every request reach the upstream server.
- Core functionality
- HTTP upstream module
worker_processes
: Defines the number of worker processes.worker_connections
: Sets the maximum number of simultaneous connections that can be opened by a worker process.proxy_cache
: Defines a shared memory zone used for caching.limit_conn
: Sets the shared memory zone and the maximum allowed number of connections for a given key value. When this limit is exceeded, the server will return the error in reply to a request.limit_req_zone
: Sets the shared memory zone and the maximum burst size of requests. If the requests rate exceeds the rate configured for a zone, their processing is delayed such that requests are processed at a defined rate. Excessive requests are delayed until their number exceeds the maximum burst size in which case the request is terminated with an error.
-
Note: Nginx round robin load balancing is not as expected
You cannot always see the load balancing action by looking at the server number in the webpage header. Because there are total of 3 files (index.html, index.js, styles.css) in the client and each of them cause GET request. You can only see the server number which serves the GET request of index.html file.
-
Note: If you use Nginx as a load balancer, all traffic goes through Nginx and consumes its bandwidth. This way you cannot exhaust upstream servers by opening more connections. However you can exhaust servers by sending more requests.
Default Nginx configuration is vulnerable to Slowloris attack. Scarce resource is the maximum number of simultaneous
worker connections. This number can be calculated as worker_connections * worker_processes
and equals to 512 in
default nginx configuration. So, it is quite easy to take down unprotected Nginx.
-
Note: You must change ulimit in Linux to open more sockets than the default soft limit of 1021.
ulimit -n 65536
-
Start the Slowloris from attacker machine.
python slowloris.py -p 80 -s 3000 --sleeptime 1 <victim_ip>
-
SlowLoris will flood a server with connections. In our example if SlowLoris sends 3000 connections per second, and Nginx can only handle 2048 connections per second, Nginx cannot respond to legit requests so you cannot access the webpage. Load balancing between web servers does not solve the problem because reverse proxy is down.
-
Now change
proxy-nginx.conf
to mitigate the DDoS attack.- Uncomment
limit_conn two 10;
to limit maximum allowed number of connections for a single IP. - Uncomment
limit_req zone=one burst=100;
to limit allowed request rate per second. - Uncomment
client_body_timeout 1s;
- Uncomment
client_header_timeout 1s;
- Uncomment
-
You can also increase the
worker_connections
to be able to handle more connections.
-
Start by only one server defined in the upstream section of
proxy-nginx.conf
.upstream server_cluster { server <server_machine_ip>:8080 weight=1; # server <server_machine_ip>:8081 weight=1; # server <server_machine_ip>:8082 weight=1; # server <server_machine_ip>:8083 weight=1; keepalive 32; }
-
You can change number of CONNECTIONS and THREADS to use in the attack by modifying
xerxes.c
source code.#define CONNECTIONS 32 #define THREADS 96
-
Start the XerXes from attacker machine.
./xerxes <server_machine_ip> 8080
-
Benchmark performance of server using ApacheBench.
ab -t 30 -c 10 http://<reverse_proxy_machine_ip>/
-
Now activate load balancing in the
proxy-nginx.conf
. Uncomment following lines:server <server_machine_ip>:8080 weight=1; server <server_machine_ip>:8081 weight=1; server <server_machine_ip>:8082 weight=1;
-
Benchmark performance of server using ApacheBench again and compare the results.