Każdego ranka, przez godzinę, może dwie, obciążenie serwerów jednego z klientów rosło o jakieś 250%. W normalnych godzinach ruch obsługiwały cztery do sześciu serwerów - podczas tych porannych skoków autoscaling dokładał kolejne, dochodząc do kilkunastu. Po jakimś czasie wszystko wracało do normy. Sklep działał, klient nie tracił dostępności. Tracił pieniądze - bo każdy z tych dodatkowych serwerów w chmurze AWS to realny koszt, naliczany co godzinę, dzień w dzień. To nie był atak, który mógł położyć serwis. To był atak, który generował rachunek. Poniżej opisujemy, dlaczego zwykłe metody go nie zatrzymały i co ostatecznie pozwoliło go wyciąć - jedną, sprytną regułą.

Dlaczego typowe metody zawiodły

Pierwsze, co się robi przy takim ruchu, to blokowanie źródła. Problem w tym, że atak przychodził jednocześnie z wielu lokalizacji na całym świecie i z wielu różnych sieci. Blokowanie pojedynczych adresów IP nic nie dawało - było ich za dużo i wciąż dochodziły nowe. Blokowanie całych sieci (nawet po numerze AS) też nie wchodziło w grę: część ruchu szła przez tych samych dostawców, przez których łączą się prawdziwi klienci, więc wycięcie całego AS'a, całej sieci odcięłoby realnych użytkowników, a nie tylko bota.

Kiedy nie da się zablokować po tym, skąd ruch przychodzi, zostaje jedno: znaleźć w samych żądaniach coś, co odróżnia bota od człowieka. I tu jest cała trudność. Żaden bot nie przedstawia się w nagłówku jako „jestem botem". Żaden nie wysyła wygodnego żądania w rodzaju GET /?jestem-botem, które dałoby się jednym prostym warunkiem odfiltrować. Dobrze napisany bot stara się wyglądać dokładnie jak przeglądarka. Sztuka polega na znalezieniu miejsca, w którym to przebranie nie jest wystarczająco staranne.

Nieścisłość w nagłówkach

Tym miejscem okazały się nagłówki HTTP. Żądania ataku podszywały się pod nową wersję przeglądarki - podawały user-agent aktualnego Chrome na Windowsie, kompletny i wyglądający wiarygodnie. Ale jednocześnie niosły nagłówki, których ta przeglądarka w tych warunkach nigdy by nie wysłała.

Żądania szły po protokole HTTP/2, a mimo to zawierały nagłówki Connection: keep-alive oraz Keep-Alive: 300. To są nagłówki z HTTP/1.1, służące do zarządzania podtrzymaniem połączenia. W HTTP/2 są one wprost zabronione - protokół zarządza połączeniem w zupełnie inny sposób i te nagłówki są w nim niedozwolone. Prawdziwa przeglądarka komunikująca się po HTTP/2 po prostu ich nie wysyła, bo byłoby to niezgodne ze standardem.

W praktyce takie żądanie wyglądało mniej więcej tak: leciało po HTTP/2, a mimo to niosło w nagłówkach Connection: keep-alive i Keep-Alive: 300 - dokładnie to, czego w HTTP/2 być nie powinno.

Innymi słowy, żądanie twierdziło dwie sprzeczne rzeczy naraz: „jestem najnowszym Chrome rozmawiającym po HTTP/2" i jednocześnie niosło bagaż ze starszej wersji protokołu, którego ten Chrome nigdy by nie dołożył. Taka kombinacja nie występuje w prawdziwym ruchu. Bot był najpewniej zbudowany na prostszej bibliotece sieciowej, która doklejała nagłówki połączeniowe automatycznie, nie zważając na wersję protokołu - i właśnie to go zdradziło.

Rozwiązanie

To była sygnatura, której szukaliśmy - cecha wspólna wszystkich żądań ataku, niezależna od tego, z jakiego adresu czy sieci przychodziły. Przełożyliśmy ją na regułę w AWS WAF, dopasowaną do tej konkretnej niespójności: żądanie na stronę główną, z nagłówkami połączeniowymi typowymi dla HTTP/1.1 przy jednoczesnym podszywaniu się pod nowoczesną przeglądarkę. Reguła była na tyle wąska, że trafiała wyłącznie w ruch bota, nie ruszając prawdziwych klientów.

Efekt był natychmiastowy. Poranne skoki obciążenia zniknęły, autoscaling przestał dokładać serwery pod ruch, który nie pochodził od realnych klientów, a rachunek za infrastrukturę wrócił do normalnego poziomu. Bez blokowania krajów, bez ścigania adresów IP, bez uderzania w prawdziwych użytkowników - jeden precyzyjny warunek, oparty na tym, że atakujący nie dopilnował zgodności z protokołem, pod który się podszywał.

Dlaczego automatyczny WAF tego nie wychwycił

Ten przypadek dobrze pokazuje, na czym w praktyce polega obrona przed dobrze zamaskowanym ruchem. Rzadko chodzi o brutalną siłę czy blokowanie po adresach - bo najbardziej uciążliwe boty są rozproszone po całym świecie i nie mają jednego źródła, które dałoby się odciąć. Klucz to znalezienie wspólnego punktu: jednej cechy, którą wszystkie żądania ataku posiadają i powtarzają, a której prawdziwy ruch nie ma. Czasem jest nią wzorzec w adresie czy w refererze. Czasem, jak tutaj, drobna niezgodność na poziomie samego protokołu - szczegół, który dla bota jest niewidoczny, a dla kogoś, kto wie, jak powinien wyglądać poprawny ruch, jest jak podpis.

Warto zauważyć, że automatyczny WAF tego nie wyłapał - i nie dlatego, że jest zły. Gotowe reguły szukają rzeczy z góry znanych: prób SQL injection, typowych złośliwych ciągów w żądaniach, znanych sygnatur ataków. Ale ten ruch nie zawierał niczego „złośliwego" w tym sensie - to były pozornie zwykłe wejścia na stronę główną. Żeby go wychwycić, trzeba było zauważyć anomalię, której żadna gotowa reguła nie przewiduje: drobną niezgodność z protokołem, widoczną dopiero dla kogoś, kto wie, jak powinno wyglądać poprawne żądanie.

Płacisz za moc, którą zjadają boty?

Szacuje się, że blisko połowa całego ruchu w internecie pochodzi dziś nie od ludzi, tylko od botów, skanerów i innych automatów. Każdy z nich zużywa zasoby, za które płacisz - a w chmurze, gdzie rachunek rośnie wprost z obciążeniem, widać to bezpośrednio na fakturze. Największą różnicę robi tu nie jednorazowa reguła, ale stały monitoring obciążenia i wykorzystania zasobów oraz administrator, który wyłapie takie anomalie, zanim urosną w comiesięczny koszt. To właśnie stała opieka administracyjna - ktoś, kto patrzy na Twoją infrastrukturę na tyle uważnie, żeby zauważyć anomalię, realny wzrost od ruchu, za który niepotrzebnie płacisz.

Podobne wpisy