Pewnego popołudnia sklep na Magento, którym się opiekuję, przestał odpowiadać. Nie z powodu awarii ani błędu we wdrożeniu - po prostu ktoś zaczął go zasypywać ruchem. W ciągu jednego dnia do serwera dotarło ponad 175 milionów żądań, a zdecydowana większość z nich trafiała na serwer już tak przeciążony, że nie był w stanie odpowiedzieć. Sklep był za Cloudflare. To nie wystarczyło. Poniżej opisuję, jak ten atak wyglądał w logach, dlaczego automatyczna ochrona go nie zdusiła i co realnie zatrzymało go w kilkadziesiąt minut.

Zgłoszenie

O 17:53 dostałem informację, że sklep działa wolno i się zawiesza. Pierwszy rzut oka na serwer nie pozostawiał złudzeń: obciążenie pod sufit, a w logach nginx ruch, jakiego przy normalnej sprzedaży ten sklep nie generuje przez tydzień, a co dopiero w kilka minut. To nie był skok zainteresowania. To był atak - i to trwający już od jakiegoś czasu.

Jak wyglądał atak w liczbach

Gdy wyciąłem z logów cały ten dzień, skala zrobiła się namacalna. Do serwera dotarło ponad 175 milionów żądań. Z tego około 117 milionów - czyli dwie trzecie całości - to były zapytania o jedną rzecz: stronę główną. W kółko, to samo GET /.

Jeszcze wymowniejszy jest rozkład odpowiedzi serwera. Około 117 milionów żądań zakończyło się kodem 502 - czyli serwer, w momencie ich obsługi, był już tak obciążony, że nie potrafił zwrócić strony. Serwer nie tyle był atakowany, co po prostu nie nadążał już z obsługą. Sklep dla realnych klientów w praktyce nie działał.

Cała ta lawina przyszła z około 62 tysięcy adresów IP. To brzmi jak dużo - i jest - ale w przeliczeniu wychodzi średnio ponad 2800 żądań z każdego adresu w ciągu dnia. To nie był ruch „po jednym z wielu miejsc". To była garść adresów waląca w kółko, część z nich przez sieć TOR i otwarte proxy.

Dlaczego blokowanie po IP nie miało sensu

Pierwszy odruch przy takim ruchu to blokować adresy. Problem w tym, że przy 62 tysiącach IP, w dodatku częściowo anonimizowanych przez TOR, to nie miało sensu - na miejsce jednego zablokowanego adresu natychmiast pojawiały się kolejne. Zacząłem od wycinania tego, co widziałem na bieżąco: dało się bezpiecznie odciąć część świata geograficznie - w tamtym momencie sporo ruchu szło z Indonezji, Chin i Wietnamu, gdzie klient nie prowadzi sprzedaży. To ugasiło część pożaru, ale nie źródło. Żeby zatrzymać atak, a nie gonić adresy, musiałem znaleźć w tym ruchu coś wspólnego - cechę, która odróżnia bota od prawdziwego klienta.

Wzorzec

Odpowiedź była w logach. Żądania ataku wyglądały mniej więcej tak:

GET / HTTP/2.0  502  "https://www.facebook.com/"  "-"
GET / HTTP/2.0  502  "https://www.youtube.com/"   "-"
GET / HTTP/2.0  502  "-"                           "-"

Trzy rzeczy rzucały się w oczy. Po pierwsze, wszystkie szły na stronę główną. Po drugie, wiele z nich nie miało user-agenta - puste "-" w miejscu, gdzie każda prawdziwa przeglądarka przedstawia się nazwą i wersją. Po trzecie, referery. Bardzo dużo żądań podawało referer w rodzaju https://www.facebook.com/, https://www.youtube.com/ czy https://www.google.com/ - gołą domenę, bez żadnej ścieżki.

A taki referer w naturze nie istnieje. Kiedy ktoś trafia do sklepu z Facebooka, referer wskazuje konkretny post czy stronę, nigdy samą domenę. To samo z YouTube czy Google. Referer ustawiony na czystą domenę serwisu, który fizycznie nie ma jak do Ciebie linkować z poziomu swojej strony głównej, to jeden z czytelniejszych podpisów zautomatyzowanego ruchu. Botnet miał wpisane na sztywno kilka „wiarygodnie wyglądających" adresów i doklejał je do każdego żądania - nie zdając sobie sprawy, że właśnie w ten sposób sam się oznacza.

Miałem więc sygnaturę: żądanie na stronę główną, z pustym user-agentem albo z refererem, który nie może być prawdziwy.

Rozwiązanie

Tę sygnaturę przełożyłem na reguły w Cloudflare WAF. Zamiast blokować adresy, blokowałem żądania pasujące do wzorca - niezależnie od tego, z jakiego IP przyszły. Ruch zaczął być wycinany, w szczytowym momencie rzędu 250 tysięcy żądań na minutę.

Atakujący nie odpuścili od razu - w pewnym momencie zmienili taktykę i podrobione referery zniknęły. Ale zostawili inny ślad: ruch wciąż w całości kierował się na stronę główną, tyle że teraz masowo z tym samym pustym user-agentem. Wystarczyło dopisać regułę pod nowy wariant i atak znów był wycinany. To był żywy przeciwnik, nie skrypt puszczony i zapomniany - i dlatego samo „ustawienie ochrony i odejście od komputera" by nie zadziałało. Trzeba było przy tym siedzieć i reagować na każdą zmianę.

Około 18:40 ruch wrócił do normy. Sklep przez cały czas pozostał dostępny pod tą samą domeną. Nie musiałem sięgać po tryb „Under Attack", w którym Cloudflare serwuje każdemu odwiedzającemu wyzwanie CAPTCHA - rozwiązanie skuteczne, ale uciążliwe, bo obrywa też prawdziwych klientów i każe im przechodzić weryfikację przy każdym wejściu. Precyzyjne reguły pozwoliły wyciąć sam atak, nie ruszając normalnego ruchu.

Dlaczego sam Cloudflare nie wystarczył

Najważniejszy wniosek z tego dnia dotyczy nie ataku, tylko powszechnego przekonania: „mam Cloudflare, jestem chroniony". Otóż automatyczna ochrona Cloudflare wycięła tego dnia z własnej inicjatywy jakieś 22 miliony żądań. Brzmi dużo - dopóki nie zestawi się tego z całością. Reszta, grubo ponad sto milionów, doszłaby do serwera i go dobiła, gdyby nie reguły, które trzeba było napisać ręcznie, dopasowane do konkretnej sygnatury tego konkretnego ataku.

To jest sedno. CDN z automatycznym WAF-em to potężne narzędzie, ale to wciąż tylko narzędzie. Zatrzymuje to, co pasuje do jego gotowych wzorców - a nie zatrzymuje ataku, który z pozoru wygląda jak zwykłe wejścia na stronę główną. Różnicę robi ktoś, kto potrafi przeczytać ruch, znaleźć w nim sygnaturę i przełożyć ją na reguły, zanim serwer się podda. Tarcza jest warta tyle, ile ręka, która nią steruje.

Masz sklep za Cloudflare i zakładasz, że to załatwia sprawę?

Warto sprawdzić, czy w razie takiego ataku Twoja konfiguracja jest na niego przygotowana - i czy ktoś będzie umiał zareagować, zanim sklep przestanie odpowiadać. Audyt bezpieczeństwa serwera pokazuje takie luki, zanim wykorzysta je ktoś inny. A jeśli atak trwa właśnie teraz - odezwij się.

Podobne wpisy