
Firma Variti opracowuje ochronę przed botami i atakami DDoS, a także przeprowadza testy obciążeniowe i wydajnościowe. Na konferencji HighLoad++ 2018 opowiadaliśmy, jak zabezpieczyć zasoby przed różnego rodzaju atakami. Krótko mówiąc: izoluj części systemu, korzystaj z usług chmurowych i CDN oraz regularnie aktualizuj oprogramowanie. Jednak bez wyspecjalizowanych firm zajmujących się ochroną nie poradzisz sobie 🙂
Przed przeczytaniem tekstu możesz zapoznać się z krótkimi punktami podsumowującymi .
A jeśli nie lubisz czytać lub po prostu chcesz obejrzeć nagranie, znajdziesz poniżej nasze wystąpienie w spoilerze.
Nagranie wideo wystąpienia

Wiele firm potrafi przeprowadzać testy obciążeniowe, ale nie wszystkie wykonują testy wytrzymałościowe. Niektórzy z naszych klientów są przekonani, że ich strona jest odporna na ataki, ponieważ mają system highload, który dobrze chroni. My jednak pokazujemy, że to nie do końca prawda.
Oczywiście przed przeprowadzeniem testów uzyskujemy zgodę od klienta, z podpisem i pieczęcią, a z naszą pomocą nie można przeprowadzić DDoS na nikogo. Testowanie odbywa się w ustalonym przez klienta czasie, gdy ruch na jego zasobach jest minimalny, a problemy z dostępem nie wpłyną na klientów. Dodatkowo, ponieważ w trakcie testowania zawsze mogą wystąpić nieprzewidziane zdarzenia, mamy stały kontakt z klientem. To pozwala nie tylko na informowanie o osiągniętych wynikach, ale także na dokonywanie zmian w trakcie testów. Po zakończeniu testów zawsze sporządzamy raport, w którym wskazujemy na znalezione wady i udzielamy rekomendacji dotyczących usunięcia słabych punktów strony.
Jak pracujemy
Podczas testowania emulujemy botnet. Ponieważ pracujemy z klientami, którzy nie znajdują się w naszych sieciach, aby test nie zakończył się w pierwszej minucie z powodu limitów lub ochrony, obciążenie aplikujemy nie z jednego IP, lecz z własnej podsieci. Dodatkowo, aby stworzyć znaczące obciążenie, mamy własny wystarczająco mocny serwer testowy.
Postulaty
Wiele — nie znaczy dobrze
Im mniej obciążone zasoby doprowadzimy do awarii, tym lepiej. Jeśli uda się tak zorganizować, że strona przestanie działać przy jednym żądaniu na sekundę lub nawet przy jednym żądaniu na minutę, to będzie wspaniale. Ponieważ zgodnie z prawem Murphy'ego użytkownicy lub hakerzy przypadkowo trafią dokładnie w tę lukę.
Częściowa awaria jest lepsza niż całkowita
Zawsze radzimy, aby systemy były heterogeniczne. Powinny być dzielone na poziomie fizycznym, a nie tylko przez konteneryzację. W przypadku fizycznego podziału, nawet jeśli coś zawiedzie na stronie, to istnieje duże prawdopodobieństwo, że nie przestanie ona działać całkowicie, a użytkownicy będą mieli dostęp przynajmniej do części funkcji.
Prawidłowa architektura to podstawa odporności
Odporność zasobu i jego zdolność do wytrzymania ataków i obciążeń powinny być uwzględnione na etapie projektowania, właściwie już na etapie rysowania pierwszych schematów blokowych w notesie. Ponieważ jeśli pojawią się fatalne błędy, można je naprawić później, ale jest to bardzo trudne.
Nie tylko kod, ale i konfiguracja musi być dobra
Wielu myśli, że dobra drużyna programistyczna to gwarancja odporności usługi. Rzeczywiście potrzebna jest dobra drużyna programistyczna, ale równie ważna jest także dobra eksploatacja, dobry DevOps. To znaczy potrzebni są specjaliści, którzy prawidłowo skonfigurują Linux i sieć, poprawnie napiszą konfiguracje w nginx, ustawią limity i inne elementy. W przeciwnym razie zasób będzie działał dobrze tylko na testach, a w środowisku produkcyjnym w pewnym momencie wszystko się zepsuje.
Różnice między testowaniem obciążeniowym a testowaniem stresowym
Testowanie obciążeniowe pozwala na odkrycie granic działania systemu. Testowanie stresowe koncentruje się na znalezieniu słabych miejsc systemu i jest używane do rozbicia tego systemu oraz obserwowania, jak będzie się zachowywał w trakcie awarii różnych elementów. Przy tym charakter obciążenia zwykle pozostaje nieznany dla zamawiającego przed rozpoczęciem testowania stresowego.
Wyróżniające cechy ataków L7
Rodzaje obciążeń dzielimy zazwyczaj na obciążenia na poziomie L7 oraz L3&4. L7 to obciążenie na poziomie aplikacji, najczęściej rozumiane tylko jako HTTP, my jednak rozumiemy przez to każde obciążenie na poziomie protokołu TCP.
Ataki L7 mają pewne charakterystyczne cechy. Po pierwsze, pojawiają się one bezpośrednio w aplikacji, więc przechwycenie ich za pomocą środków sieciowych jest mało prawdopodobne. Takie ataki wykorzystują logikę i dzięki temu bardzo efektywnie, nawet przy niewielkim ruchu, zużywają CPU, pamięć, dysk, bazę danych i inne zasoby.
HTTP Flood
W przypadku jakiegokolwiek ataku łatwiej jest stworzyć obciążenie niż je przetworzyć, co również dotyczy ataków L7. Ruch ataku nie zawsze łatwo odróżnić od ruchu legalnego, a najczęściej udaje się to zrobić na podstawie częstotliwości, jednak jeśli wszystko jest dobrze zaplanowane, analiza logów nie pozwala określić, gdzie jest atak, a gdzie legalne żądania.
Jako pierwszy przykład weźmy atak HTTP Flood. Z wykresu widać, że zwykle takie ataki są bardzo potężne; w poniższym przykładzie szczytowa liczba żądania przekraczała 600 tysięcy na minutę.

HTTP Flood to najprostszy sposób na stworzenie obciążenia. Zazwyczaj używa się do tego jakiegoś narzędzia do testów obciążeniowych, na przykład ApacheBench, i określa się żądanie oraz cel. Przy tak prostym podejściu istnieje duże ryzyko napotkania na pamięć podręczną serwera, ale można ją łatwo obejść, na przykład dodając losowe ciągi do żądania, co zmusi serwer do ciągłego zwracania świeżej strony.
Nie warto również zapominać o user-agencie podczas tworzenia obciążenia. Wiele user-agentów popularnych narzędzi testujących jest filtrowanych przez administratorów systemu, co w takim przypadku może sprawić, że obciążenie po prostu nie dotrze do backendu. Znacząco poprawić wyniki można, wstawiając do żądania bardziej lub mniej ważny nagłówek z przeglądarki.
Pomimo swojej prostoty, ataki HTTP Flood mają również swoje wady. Po pierwsze, do stworzenia obciążenia wymagane są duże zasoby. Po drugie, takie ataki są bardzo łatwe do wykrycia, szczególnie jeśli pochodzą z jednego adresu. W rezultacie, żądania natychmiast zaczynają być filtrowane przez administratorów systemu lub nawet na poziomie dostawcy.
Co szukać
Aby zmniejszyć liczbę żądań na sekundę i jednocześnie nie stracić na efektywności, trzeba wykazać się nieco kreatywnością i zbadać stronę. Można obciążać nie tylko łącze lub serwer, ale także poszczególne części aplikacji, takie jak bazy danych czy systemy plików. Można także poszukać miejsc na stronie, które wykonują duże obliczenia: kalkulatory, strony z rekomendacjami produktów i tym podobne. Wreszcie, często zdarza się, że na stronie znajduje się pewien skrypt PHP, który generuje stronę z kilku setek tysięcy linii. Taki skrypt również w znacznym stopniu obciąża serwer i może stać się celem ataku.
Gdzie szukać
Gdy skanujemy zasób przed przeprowadzeniem testów, zwracamy uwagę przede wszystkim na samą stronę. Szukamy wszelkich pól wejściowych, dużych plików — ogólnie wszystkiego, co może stwarzać problemy dla zasobu i spowalniać jego działanie. W tym pomagają podstawowe narzędzia deweloperskie w Google Chrome i Firefox, które pokazują czasy odpowiedzi strony.
Skanujemy także subdomeny. Na przykład, jeśli istnieje pewien sklep internetowy, abc.com, i ma on subdomenę admin.abc.com. Prawdopodobnie jest to panel administracyjny z autoryzacją, ale jeśli na niego nałożyć obciążenie, może to stworzyć problemy dla głównego zasobu.
Strona może mieć subdomenę api.abc.com. Prawdopodobnie jest to zasób dla aplikacji mobilnych. Aplikację można znaleźć w App Store lub Google Play, ustawić specjalny punkt dostępu, zanalizować API i zarejestrować konta testowe. Problem w tym, że często ludzie myślą, że wszystko, co jest chronione autoryzacją, jest odporne na ataki typu denial of service. Rzekomo autoryzacja to najlepsza CAPTCHA, ale tak nie jest. Stworzenie 10-20 kont testowych jest proste, a po ich założeniu uzyskujemy dostęp do skomplikowanej i niezabezpieczonej funkcjonalności.
Oczywiście przyglądamy się historii, plikowi robots.txt i WebArchive, ViewDNS, szukając starych wersji zasobu. Czasami zdarza się, że deweloperzy wprowadzają, powiedzmy, mail2.yandex.net, a stara wersja, mail.yandex.net, pozostaje. Ten mail.yandex.net przestaje być wspierany, nie ma na niego przydzielonych zasobów deweloperskich, ale nadal spożywa bazę danych. W związku z tym, korzystając ze starej wersji, można skutecznie zaangażować zasoby backendu i wszystkiego, co stoi za frontem. Oczywiście nie zawsze tak się dzieje, ale wciąż dość często się z tym spotykamy.
Oczywiście, analizujemy wszystkie parametry zapytania oraz strukturę cookie. Możemy na przykład umieścić w tablicy JSON wewnątrz cookie jakąś wartość, stworzyć dużą zagnieżdżoną strukturę i zmusić zasób do pracy w nierozsądnie długi sposób.
Obciążenie w wyszukiwarce
Pierwsze, co przychodzi do głowy podczas badania strony, to obciążenie bazy danych, ponieważ wyszukiwanie występuje niemal wszędzie i niestety często jest źle zabezpieczone. Dlaczegoś deweloperzy nie poświęcają wystarczającej uwagi wyszukiwaniu. Ale istnieje jedna rekomendacja — nie należy wykonywać jednolitych zapytań, ponieważ można napotkać cache, tak jak w przypadku ataku HTTP flood.
Wykonywanie losowych zapytań do bazy danych nie zawsze jest efektywne. O wiele lepiej jest stworzyć listę słów kluczowych, które odnoszą się do wyszukiwania. Jeśli wrócimy do przykładu sklepu internetowego: powiedzmy, że strona sprzedaje opony samochodowe i pozwala na określenie promienia opon, typu samochodu i innych parametrów. W związku z tym kombinacje odpowiednich słów sprawią, że baza danych będzie pracować w znacznie bardziej złożonych warunkach.
Ponadto warto stosować paginację: wyszukiwaniu znacznie trudniej jest zwrócić przedostatnią stronę wyników niż pierwszą. Dzięki paginacji można nieco urozmaicić obciążenie.
Na poniższym przykładzie pokazujemy obciążenie w wyszukiwarce. Widać, że od pierwszej sekundy testu przy prędkości dziesięciu zapytań na sekundę strona padła i przestała odpowiadać.

Co jeśli nie ma wyszukiwania?
Jeśli nie ma wyszukiwania, to nie znaczy, że strona nie zawiera innych podatnych pól wejściowych. Takim polem może być autoryzacja. Współcześni deweloperzy lubią tworzyć skomplikowane hashe, aby zabezpieczyć bazę loginów przed atakami przy użyciu tabel tęczowych. To dobrze, ale takie hashe zużywają duże zasoby CPU. Duży natężenie fałszywych autoryzacji prowadzi do przeciążenia procesora, a w efekcie strona przestaje działać.
Obecność na stronie wszelkich formularzy do komentarzy i kontaktu to pretekst do wysyłania tam bardzo dużych tekstów lub tworzenia masowego spamu. Czasami strony akceptują załączone pliki, także w formacie gzip. W takim przypadku pobieramy plik o rozmiarze 1TB, kompresujemy go przy użyciu gzip do kilku bajtów lub kilobajtów i wysyłamy na stronę. Następnie jest on dekompresowany i uzyskujemy bardzo ciekawy efekt.
Rest API
Chciałbym poświęcić chwilę na omówienie popularnych obecnie usług, takich jak Rest API. Ochrona Rest API jest znacznie trudniejsza niż ochrona standardowej strony internetowej. Nawet banalne metody zabezpieczenia przed atakami typu brute force i innymi nieautoryzowanymi działaniami nie działają w przypadku Rest API.
Rest API jest bardzo łatwe do złamania, ponieważ bezpośrednio komunikuje się z bazą danych. Awaria takiej usługi może mieć poważne konsekwencje dla biznesu, ponieważ przez Rest API często są powiązane nie tylko główna strona, ale i aplikacja mobilna oraz inne wewnętrzne zasoby biznesowe. Jeśli wszystko to przestaje działać, skutki są znacznie poważniejsze niż w przypadku awarii prostej strony internetowej.
Obciążenie przy dużym obciążeniu treści
Jeśli proponują nam przetestowanie zwykłej aplikacji jednostronicowej, lądowania lub wizytówki, które nie mają złożonej funkcjonalności, poszukujemy obciążającej treści. Na przykład dużych zdjęć, które serwer dostarcza, plików binarnych, dokumentacji PDF — próbujemy pobierać wszystkie te materiały. Takie testy skutecznie obciążają system plików i zajmują pasmo, co czyni je efektywnymi. Oznacza to, że jeśli nie wyłączysz serwera podczas pobierania dużego pliku przy niskich prędkościach, po prostu zablokujesz pasmo docelowego serwera, co spowoduje odmowę obsługi.
Na przykładzie takiego testu widać, że przy prędkości 30 RPS strona przestała odpowiadać lub zwracała błędy serwera 500.

Nie można również zapominać o konfiguracji serwerów. Często można spotkać sytuacje, gdy ktoś kupił wirtualną maszynę, zainstalował na niej Apache, skonfigurował wszystko domyślnie, umieścił aplikację PHP, a poniżej można zobaczyć wyniki.

Tutaj obciążenie dotyczyło głównego katalogu i wynosiło zaledwie 10 RPS. Czekaliśmy 5 minut, a serwer upadł. Na koniec wiadomo, dlaczego upadł, ale istnieje podejrzenie, że po prostu przechwycił za dużo pamięci i dlatego przestał odpowiadać.
Wave based
W ciągu ostatnich dwóch lat ataki falowe stały się dość popularne. Wynika to z faktu, że wiele organizacji kupuje różne urządzenia do ochrony przed DDoS, które wymagają określonego czasu na zebranie statystyk przed rozpoczęciem filtracji ataku. To znaczy, że nie filtrują ataku w pierwszych 30-40 sekundach, ponieważ zbierają dane i uczą się. Odpowiednio w te 30-40 sekund na stronę można wysłać tyle żądań, że zasób będzie niedostępny przez długi czas, aż wszystkie żądania zostaną przetworzone.
W przypadku poniższego ataku interwał wynosił 10 minut, po czym nadeszła nowa, zmodyfikowana partia ataku.

To znaczy, że ochrona się nauczyła, uruchomiła filtrację, ale pojawiła się nowa, zupełnie inna partia ataku, i ochrona znów rozpoczęła naukę. Faktycznie filtracja przestaje działać, ochrona staje się nieskuteczna, a strona jest niedostępna.
Ataki falowe charakteryzują się bardzo wysokimi wartościami szczytowymi, mogą osiągać sto tysięcy lub milion zapytań na sekundę w przypadku L7. Jeśli mówimy o L3 i L4, może to sięgać setek gigabitów ruchu, lub odpowiednio setek mpps, jeśli liczyć w pakietach.
Problem takich ataków leży w synchronizacji. Ataki pochodzą z botnetu, a aby stworzyć bardzo duży jednorazowy szczyt, wymagana jest wysoka synchronizacja. I ta koordynacja nie zawsze się udaje: czasami na wyjściu otrzymuje się jakiś paraboliczny szczyt, który wygląda dość żałośnie.
Nie samym HTTP żyjemy
Oprócz HTTP na poziomie L7, lubimy także wykorzystywać inne protokoły. Zazwyczaj w przypadku zwykłej strony internetowej, tym bardziej w ramach standardowego hostingu, na zewnątrz wystają protokoły pocztowe i MySQL. Protokoły pocztowe są mniej narażone na obciążenia niż bazy danych, ale można je również obciążyć wystarczająco efektywnie, co skutkuje przeciążonym CPU na serwerze.
Z powodzeniem wykorzystaliśmy lukę w SSH z 2016 roku. Obecnie ta luka została prawie wszędzie naprawiona, ale to nie znaczy, że nie można obciążać SSH. Można. Po prostu wysyłana jest ogromna liczba prób autoryzacji, SSH zużywa prawie całe CPU na serwerze, a strona internetowa przestaje działać już przy jednym lub dwóch żądaniach na sekundę. Odpowiednio te jedno-dwa żądania nie mogą być w logach odróżnione od legalnego obciążenia.
Wiele połączeń, które otwieramy na serwerach, wciąż pozostaje aktualnych. Kiedyś problem ten dotyczył Apache, a obecnie w dużej mierze dotyczy nginx, ponieważ często jest on konfigurowany domyślnie. Liczba połączeń, które nginx może utrzymać otwartych, jest ograniczona; w związku z tym, po otwarciu tej liczby połączeń, nowe połączenie nginx już nie akceptuje, co skutkuje tym, że strona nie działa.
Nasz klaster testowy dysponuje wystarczającą mocą CPU, aby przeprowadzić atak na SSL handshake. Z praktyki wynika, że botnety czasami również to robią. Z jednej strony, oczywiste jest, że nie można obejść się bez SSL, ponieważ wpływa on na wyniki Google, pozycjonowanie i bezpieczeństwo. Z drugiej strony, SSL niestety ma problem z CPU.
L3&4
Kiedy mówimy o atakach na poziomach L3&4, zazwyczaj odnosimy się do ataku na poziomie kanału. Taki ruch prawie zawsze można odróżnić od normalnego, chyba że jest to atak SYN-flood. Problemy związane z atakami SYN-flood dla środków ochrony wynikają z dużych objętości. Maksymalna wielkość L3&4 wynosiła 1,5-2 Tb/s. Taki ruch jest bardzo trudny do przetworzenia, nawet dla dużych firm, takich jak Oracle i Google.
SYN i SYN-ACK to pakiety używane przy ustanawianiu połączenia. Dlatego SYN-flood jest trudny do odróżnienia od normalnego obciążenia: nie wiadomo, czy to SYN, który przybył w celu nawiązania połączenia, czy część floodu.
UDP-flood
Zazwyczaj przestępcy nie dysponują takimi mocami, jakie mamy my, dlatego do organizowania ataków może być wykorzystywana amplifikacja. Oznacza to, że przestępca skanuje internet i znajduje albo podatne, albo niewłaściwie skonfigurowane serwery, które na przykład w odpowiedzi na jeden pakiet SYN odpowiadają trzema SYN-ACK. Fałszując adres źródłowy z adresu serwera docelowego, można za pomocą jednego pakietu zwiększyć moc, powiedzmy, trzykrotnie, i przekierować ruch na ofiarę.

Problem amplifikacji polega na ich trudnym wykrywaniu. Z ostatnich przykładów można przytoczyć głośny przypadek z podatnością memcached. Co więcej, obecnie pojawiło się wiele urządzeń IoT, kamer IP, które również zazwyczaj są skonfigurowane domyślnie i są źle skonfigurowane, dlatego za pomocą takich urządzeń przestępcy najczęściej przeprowadzają ataki.

Trudny SYN-flood
SYN-flood to prawdopodobnie najbardziej interesujący rodzaj ataku z perspektywy programisty. Problem w tym, że często administratorzy systemów stosują do ochrony blokadę IP. Niestety, przez blokadę IP cierpią nie tylko administratorzy, którzy działają według skryptów, ale także, co gorsza, niektóre systemy zabezpieczeń, które są kupowane za duże pieniądze.
Taki sposób może doprowadzić do katastrofy, ponieważ jeśli przestępcy podmienią adresy IP, firma zablokuje swoją własną podsieć. Kiedy zapora ogniowa zablokuje własny klaster, zewnętrzne interakcje będą się psuć, a zasób przestanie działać.
Osiągnięcie blokady własnej sieci nie jest trudne. Jeśli w biurze klienta istnieje sieć Wi-Fi, lub jeśli wydajność zasobów jest mierzona za pomocą różnych monitorników, to bierzemy adres IP tego systemu monitorującego lub biurowego klienta Wi-Fi i używamy go jako źródła. W rezultacie zasób wydaje się dostępny, ale docelowe adresy IP są zablokowane. Tak więc może być zablokowana sieć Wi-Fi konferencji HighLoad, na której prezentowany jest nowy produkt firmy, co pociąga za sobą określone koszty biznesowe i ekonomiczne.
Podczas testowania nie możemy korzystać z amplifikacji przez memcached zewnętrznymi zasobami, ponieważ istnieją umowy dotyczące przesyłania ruchu tylko do dozwolonych adresów IP. W związku z tym używamy amplifikacji przez SYN i SYN-ACK, kiedy na wysłanie jednego SYN system odpowiada dwoma lub trzema SYN-ACK, a w rezultacie atak zwiększa się dwa lub trzy razy.
Narzędzia
Jednym z podstawowych narzędzi, które wykorzystujemy do obciążenia na poziomie L7, jest Yandex-tank. W szczególności jako broń używany jest Phantom, plus istnieje kilka skryptów do generowania pocisków i analizy wyników.
Do analizy ruchu sieciowego używamy Tcpdump, do analizy serwera — Nmap. Do generowania obciążenia na poziomie L3&4 używamy OpenSSL i trochę własnej magii z biblioteką DPDK. DPDK to biblioteka od Intela, która pozwala na pracę z interfejsem sieciowym, omijając stos Linux, co zwiększa efektywność. Oczywiście, DPDK stosujemy nie tylko na poziomie L3&4, ale także na poziomie L7, ponieważ umożliwia ona generowanie bardzo dużego przepływu obciążenia, do kilku milionów zapytań na sekundę z jednego komputera.
Używamy również określonych generatorów ruchu i specjalnych narzędzi, które piszemy pod specyficzne testy. Jeśli przypomnimy sobie o podatności pod SSH, to podany powyżej zestaw nie może być wyeksploatowany. Jeśli atakujemy protokół pocztowy, korzystamy z narzędzi pocztowych lub piszemy na nie skrypty.
Wnioski
Na koniec chcielibyśmy powiedzieć:
- Oprócz klasycznego testowania obciążeniowego należy koniecznie przeprowadzać także testy obciążeniowe. Mamy rzeczywisty przykład, kiedy podwykonawca partnera przeprowadził tylko testowanie obciążeniowe. Pokazało to, że zasób wytrzymuje normalne obciążenie. Ale potem pojawiło się nietypowe obciążenie, odwiedzający stronę zaczęli nieco inaczej korzystać z zasobu — i na wyjściu podwykonawca padł. W ten sposób warto szukać podatności, nawet jeśli już jesteście ochronieni przed atakami DDoS.
- Należy izolować jedne części systemu od innych. Jeśli macie wyszukiwarkę, trzeba ją przenieść na oddzielne maszyny, więc nawet nie w Dockerze. Ponieważ jeśli zawiedzie wyszukiwanie lub autoryzacja, przynajmniej coś będzie nadal działać. W przypadku sklepu internetowego użytkownicy będą nadal znajdować produkty w katalogu, przechodzić z agregatorów, kupować, jeśli już są zautoryzowani lub logować się przez OAuth2.
- Nie należy lekceważyć wszelkich serwisów chmurowych.
- Używaj CDN nie tylko do optymalizacji opóźnień w sieci, ale również jako środek ochrony przed atakami na wyczerpanie kanału i po prostu zalewania statycznych zasobów.
- Należy używać wyspecjalizowanych serwisów ochrony. Od ataków L3&4 na poziomie kanału sami się nie obronicie, ponieważ prawdopodobnie nie macie wystarczającego kanału. Od ataków L7 też raczej nie odbijecie, ponieważ są one bardzo duże. Ponadto, wykrywanie małych ataków jest wciąż prerogatywą specjalnych serwisów, specjalnych algorytmów.
- Regularnie aktualizujcie się. Dotyczy to nie tylko jądra, ale i demona SSH, zwłaszcza jeśli są otwarte na zewnątrz. Ogólnie należy aktualizować wszystko, ponieważ monitorowanie różnych podatności samodzielnie raczej wam się nie uda.
Źródło: habr.com
