ProHoster > Blog > uprava > Čuvajte se ranjivosti koje donose runde posla. 1. dio: FragmentSmack/SegmentSmack
Čuvajte se ranjivosti koje donose runde posla. 1. dio: FragmentSmack/SegmentSmack
Bok svima! Moje ime je Dmitry Samsonov, radim kao vodeći sistemski administrator u Odnoklassniki. Imamo više od 7 tisuća fizičkih poslužitelja, 11 tisuća spremnika u našem oblaku i 200 aplikacija koje u različitim konfiguracijama tvore 700 različitih klastera. Velika većina poslužitelja pokreće CentOS 7.
Dana 14. kolovoza 2018. objavljene su informacije o ranjivosti FragmentSmack
(CVE-2018-5391) i SegmentSmack (CVE-2018-5390). Riječ je o ranjivostima s vektorom mrežnog napada i prilično visokom ocjenom (7.5), koja prijeti uskraćivanjem usluge (DoS) zbog iscrpljenosti resursa (CPU). Popravak kernela za FragmentSmack u to vrijeme nije predložen; štoviše, pojavio se mnogo kasnije od objave informacija o ranjivosti. Kako bi se uklonio SegmentSmack, predloženo je ažuriranje kernela. Sam paket ažuriranja izašao je isti dan, preostalo ga je samo instalirati.
Ne, mi uopće nismo protiv ažuriranja kernela! Međutim, postoje nijanse...
Kako ažuriramo kernel u proizvodnji
Općenito, ništa komplicirano:
preuzimanje paketa;
Instalirajte ih na više poslužitelja (uključujući poslužitelje koji hostiraju naš oblak);
Uvjerite se da ništa nije slomljeno;
Provjerite jesu li sve standardne postavke jezgre primijenjene bez grešaka;
Pričekajte nekoliko dana;
Provjerite rad poslužitelja;
Prebacite postavljanje novih poslužitelja na novu jezgru;
Ažurirajte sve poslužitelje po podatkovnom centru (jedan po jedan podatkovni centar kako bi se smanjio učinak na korisnike u slučaju problema);
Ponovno pokrenite sve poslužitelje.
Ponovite za sve grane kernela koje imamo. Trenutno je:
Stock CentOS 7 3.10 - za većinu uobičajenih poslužitelja;
Elrepo kernel-ml 5.2 - za visoko opterećeni razdjelnici, jer se 4.19 prije ponašao nestabilno, ali potrebne su iste značajke.
Kao što ste mogli pretpostaviti, ponovno pokretanje tisuća poslužitelja traje najdulje. Budući da nisu sve ranjivosti kritične za sve poslužitelje, ponovno pokrećemo samo one koji su izravno dostupni s interneta. U oblaku, kako ne bismo ograničili fleksibilnost, ne vezujemo eksterno dostupne spremnike na pojedinačne poslužitelje s novom jezgrom, već ponovno pokrećemo sve hostove bez iznimke. Srećom, tamo je procedura jednostavnija nego s običnim poslužiteljima. Na primjer, spremnici bez stanja mogu se jednostavno premjestiti na drugi poslužitelj tijekom ponovnog pokretanja.
Ipak, posla ima još puno, a može potrajati nekoliko tjedana, a ako bude problema s novom verzijom i do nekoliko mjeseci. Napadači to jako dobro razumiju pa im treba plan B.
FragmentSmack/SegmentSmack. Raditi okolo
Srećom, za neke ranjivosti takav plan "B" postoji, a zove se Zaobilazno rješenje. Najčešće se radi o promjeni postavki kernela/aplikacije koja može minimizirati mogući učinak ili potpuno eliminirati iskorištavanje ranjivosti.
U slučaju FragmentSmack/SegmentSmack je predloženo ovo zaobilazno rješenje:
«Možete promijeniti zadane vrijednosti od 4MB i 3MB u net.ipv4.ipfrag_high_thresh i net.ipv4.ipfrag_low_thresh (i njihovim pandanima za ipv6 net.ipv6.ipfrag_high_thresh i net.ipv6.ipfrag_low_thresh) na 256 kB odnosno 192 kB ili niži. Testovi pokazuju male do značajne padove u korištenju CPU-a tijekom napada, ovisno o hardveru, postavkama i uvjetima. Međutim, može doći do određenog utjecaja na performanse zbog ipfrag_high_thresh=262144 bajta, budući da samo dva fragmenta od 64K mogu stati u red čekanja za ponovno sastavljanje odjednom. Na primjer, postoji rizik da će se aplikacije koje rade s velikim UDP paketima pokvariti".
ipfrag_high_thresh - LONG INTEGER
Maximum memory used to reassemble IP fragments.
ipfrag_low_thresh - LONG INTEGER
Maximum memory used to reassemble IP fragments before the kernel
begins to remove incomplete fragment queues to free up resources.
The kernel still accepts new fragments for defragmentation.
Nemamo velike UDP-ove za proizvodne usluge. Nema fragmentiranog prometa na LAN-u; postoji fragmentirani promet na WAN-u, ali nije značajan. Nema znakova - možete pokrenuti Zaobilazno rješenje!
FragmentSmack/SegmentSmack. Prva krv
Prvi problem s kojim smo se susreli je da su cloud kontejneri ponekad samo djelomično primijenili nove postavke (samo ipfrag_low_thresh), a ponekad ih uopće nisu primijenili - jednostavno su se srušili u startu. Nije bilo moguće stabilno reproducirati problem (sve su postavke primijenjene ručno bez ikakvih poteškoća). Također nije lako razumjeti zašto se spremnik ruši na početku: nisu pronađene greške. Jedno je bilo sigurno: vraćanje postavki rješava problem s padom spremnika.
Zašto nije dovoljno primijeniti Sysctl na glavnom računalu? Spremnik živi u vlastitom namjenskom mrežnom prostoru imena, tako da barem dio mrežnih parametara Sysctl u spremniku može se razlikovati od hosta.
Kako se točno Sysctl postavke primjenjuju u spremniku? Budući da su naši spremnici neprivilegirani, nećete moći promijeniti nijednu Sysctl postavku ulaskom u sam spremnik - jednostavno nemate dovoljno prava. Za pokretanje kontejnera, naš oblak je u to vrijeme koristio Docker (sada podman). Parametri novog spremnika proslijeđeni su Dockeru putem API-ja, uključujući potrebne Sysctl postavke.
Prilikom pretraživanja po verzijama pokazalo se da Docker API ne vraća sve pogreške (barem u verziji 1.10). Kada smo pokušali pokrenuti spremnik putem "docker run", napokon smo vidjeli barem nešto:
write /proc/sys/net/ipv4/ipfrag_high_thresh: invalid argument docker: Error response from daemon: Cannot start container <...>: [9] System error: could not synchronise with container process.
Vrijednost parametra nije važeća. Ali zašto? A zašto ne vrijedi samo ponekad? Ispostavilo se da Docker ne jamči redoslijed primjene Sysctl parametara (posljednja testirana verzija je 1.13.1), pa se ponekad ipfrag_high_thresh pokušalo postaviti na 256K kada je ipfrag_low_thresh još uvijek bio 3M, odnosno gornja granica je bila niža od donje granice, što je dovelo do pogreške.
U to vrijeme već smo koristili vlastiti mehanizam za rekonfiguraciju spremnika nakon pokretanja (zamrzavanje spremnika nakon grupni zamrzivač i izvršavanje naredbi u imenskom prostoru spremnika putem ip netns), a dodali smo i pisanje Sysctl parametara u ovaj dio. Problem je riješen.
FragmentSmack/SegmentSmack. Prva krv 2
Prije nego što smo stigli razumjeti korištenje Workarounda u oblaku, počele su stizati prve rijetke pritužbe korisnika. U to vrijeme je prošlo nekoliko tjedana od početka korištenja Workarounda na prvim poslužiteljima. Inicijalna istraga pokazala je da su pritužbe stizale na pojedinačne servise, a ne na sve poslužitelje tih servisa. Problem je opet postao krajnje neizvjestan.
Prije svega, naravno, pokušali smo vratiti postavke Sysctl-a, ali to nije imalo nikakvog učinka. Nisu pomogle ni razne manipulacije s postavkama poslužitelja i aplikacije. Ponovno pokretanje je pomoglo. Ponovno pokretanje Linuxa jednako je neprirodno kao što je bilo normalno za Windows u starim danima. Međutim, pomoglo je, a mi smo to pripisali "propustu kernela" prilikom primjene novih postavki u Sysctlu. Kako je to bilo neozbiljno...
Tri tjedna kasnije problem se ponovio. Konfiguracija ovih poslužitelja bila je prilično jednostavna: Nginx u proxy/balancer modu. Nema puno prometa. Nova uvodna napomena: broj 504 grešaka na klijentima raste svakim danom (Istek vremena). Grafikon prikazuje broj od 504 pogreške dnevno za ovu uslugu:
Sve se pogreške odnose na istu pozadinu – na onu koja je u oblaku. Grafikon potrošnje memorije za fragmente paketa na ovoj pozadini izgledao je ovako:
Ovo je jedna od najočitijih manifestacija problema u grafovima operativnog sustava. U oblaku je u isto vrijeme riješen još jedan mrežni problem s postavkama QoS (Kontrola prometa). Na grafikonu potrošnje memorije za fragmente paketa to je izgledalo potpuno isto:
Pretpostavka je bila jednostavna: ako izgledaju isto na grafikonima, onda imaju isti razlog. Štoviše, problemi s ovom vrstom memorije iznimno su rijetki.
Suština riješenog problema bila je u tome što smo koristili fq planer paketa sa zadanim postavkama u QoS-u. Prema zadanim postavkama, za jednu vezu, omogućuje vam dodavanje 100 paketa u red čekanja, a neke su veze, u situacijama nedostatka kanala, počele začepiti red čekanja do kapaciteta. U ovom slučaju paketi se odbacuju. U tc statistici (tc -s qdisc) može se vidjeti ovako:
“464545 flows_plimit” su paketi odbačeni zbog prekoračenja ograničenja čekanja jedne veze, a “drepped 464545” je zbroj svih odbačenih paketa ovog planera. Nakon povećanja duljine čekanja na 1 tisuću i ponovnog pokretanja spremnika, problem se prestao javljati. Možete se zavaliti i popiti smoothie.
FragmentSmack/SegmentSmack. Posljednja krv
Prvo, nekoliko mjeseci nakon najave ranjivosti u kernelu, konačno se pojavio popravak za FragmentSmack (da vas podsjetim da je uz najavu u kolovozu objavljen i popravak samo za SegmentSmack), što nam je dalo priliku da napustimo Workaround, što nam je zadalo dosta problema. Za to vrijeme već smo uspjeli prebaciti neke poslužitelje na novi kernel, a sada smo morali krenuti ispočetka. Zašto smo ažurirali kernel bez čekanja na FragmentSmack popravak? Činjenica je da se proces zaštite od ovih ranjivosti poklopio (i spojio) s procesom ažuriranja samog CentOS-a (što oduzima još više vremena nego ažuriranje samo kernela). Osim toga, SegmentSmack je opasnija ranjivost, a popravak za nju se pojavio odmah, tako da je svejedno imalo smisla. Međutim, nismo mogli jednostavno ažurirati kernel na CentOS-u jer je ranjivost FragmentSmack, koja se pojavila tijekom CentOS-a 7.5, popravljena samo u verziji 7.6, tako da smo morali zaustaviti ažuriranje na 7.5 i početi ispočetka s ažuriranjem na 7.6. I ovo se također događa.
Drugo, vratile su nam se rijetke pritužbe korisnika na probleme. Sada već pouzdano znamo da su svi povezani s učitavanjem datoteka s klijenata na neki od naših poslužitelja. Štoviše, vrlo mali broj uploada od ukupne mase prošao je kroz ove servere.
Kao što se sjećamo iz gornje priče, vraćanje Sysctla nije pomoglo. Ponovno pokretanje je pomoglo, ali privremeno.
Sumnje vezane uz Sysctl nisu otklonjene, no ovaj put je bilo potrebno prikupiti što više informacija. Postojao je i veliki nedostatak mogućnosti reproduciranja problema s učitavanjem na klijentu kako bi se preciznije proučilo što se događa.
Analiza svih dostupnih statistika i logova nije nas nimalo približila razumijevanju što se događa. Postojao je akutni nedostatak sposobnosti reproduciranja problema kako bi se "osjetila" određena veza. Konačno, programeri su pomoću posebne verzije aplikacije uspjeli postići stabilnu reprodukciju problema na testnom uređaju kada je povezan putem Wi-Fi-ja. Ovo je bio pomak u istrazi. Klijent se povezao s Nginxom, koji je poslao proxy na backend, što je bila naša Java aplikacija.
Dijalog za probleme je bio ovakav (popravljeno na Nginx proxy strani):
Klijent: zahtjev za primanje informacija o preuzimanju datoteke.
Java poslužitelj: odgovor.
Klijent: POST s datotekom.
Java poslužitelj: pogreška.
U isto vrijeme, Java poslužitelj zapisuje u zapisnik da je primljeno 0 bajtova podataka od klijenta, a Nginx proxy piše da je zahtjev trajao više od 30 sekundi (30 sekundi je vremensko ograničenje klijentske aplikacije). Zašto timeout i zašto 0 bajtova? Iz HTTP perspektive, sve radi kako treba, ali POST s datotekom kao da nestaje s mreže. Štoviše, nestaje između klijenta i Nginxa. Vrijeme je da se naoružate Tcpdumpom! Ali prvo morate razumjeti konfiguraciju mreže. Nginx proxy stoji iza L3 balansera NFware. Tuneliranje se koristi za isporuku paketa od L3 balansera do poslužitelja, koji dodaje svoja zaglavlja paketima:
U ovom slučaju, mreža dolazi do ovog poslužitelja u obliku Vlan-tagged prometa, koji također dodaje svoja polja paketima:
I ovaj promet također može biti fragmentiran (onaj isti mali postotak dolaznog fragmentiranog prometa o kojem smo govorili kada smo procjenjivali rizike iz Workarounda), što također mijenja sadržaj zaglavlja:
Još jednom: paketi su enkapsulirani Vlan oznakom, enkapsulirani tunelom, fragmentirani. Da bismo bolje razumjeli kako se to događa, pratimo rutu paketa od klijenta do Nginx proxyja.
Paket dolazi do L3 balansera. Za ispravno usmjeravanje unutar podatkovnog centra, paket se enkapsulira u tunel i šalje na mrežnu karticu.
Budući da zaglavlja paketa + tunela ne stanu u MTU, paket se reže na fragmente i šalje na mrežu.
Prekidač nakon L3 balansera, prilikom primanja paketa, dodaje mu Vlan oznaku i šalje ga dalje.
Prekidač ispred Nginx proxyja vidi (na temelju postavki priključka) da poslužitelj očekuje Vlan-inkapsulirani paket, pa ga šalje kakav jest, bez uklanjanja Vlan oznake.
Linux uzima fragmente pojedinačnih paketa i spaja ih u jedan veliki paket.
Zatim paket dolazi do Vlan sučelja, gdje se s njega uklanja prvi sloj - Vlan enkapsulacija.
Linux ga zatim šalje u Tunnel sučelje, gdje se s njega uklanja još jedan sloj - Tunnel encapsulation.
Poteškoća je proslijediti sve ovo kao parametre u tcpdump.
Krenimo od kraja: postoje li čisti (bez nepotrebnih zaglavlja) IP paketi od klijenata, s uklonjenim vlan i enkapsulacijom tunela?
tcpdump host <ip клиента>
Ne, nije bilo takvih paketa na poslužitelju. Dakle, problem mora postojati ranije. Ima li paketa s uklonjenom samo Vlan enkapsulacijom?
tcpdump ip[32:4]=0xx390x2xx
0xx390x2xx je IP adresa klijenta u hex formatu.
32:4 — adresa i duljina polja u kojem je zapisan SCR IP u paketu tunela.
Adresa polja je morala biti odabrana grubom silom, jer na Internetu pišu o 40, 44, 50, 54, ali tamo nije bilo IP adrese. Također možete pogledati jedan od paketa u hex (parametar -xx ili -XX u tcpdump) i izračunati IP adresu koju znate.
Postoje li fragmenti paketa bez uklonjene enkapsulacije Vlan-a i tunela?
tcpdump ((ip[6:2] > 0) and (not ip[6] = 64))
Ova magija će nam pokazati sve fragmente, uključujući i posljednji. Vjerojatno se ista stvar može filtrirati prema IP-u, ali nisam pokušao, jer nema mnogo takvih paketa, a one koje sam trebao lako sam pronašao u općem toku. Evo ih:
Radi se o dva fragmenta jednog paketa (isti ID 53652) s fotografijom (u prvom paketu vidljiva je riječ Exif). Zbog činjenice da postoje paketi na ovoj razini, ali ne u spojenom obliku u dumpovima, problem je očito u sklopu. Konačno postoji dokumentarni dokaz za to!
Paketni dekoder nije otkrio nikakve probleme koji bi spriječili izgradnju. Probao ovdje: hpd.gasmi.net. U početku, kada pokušate nešto ubaciti tamo, dekoderu se ne sviđa format paketa. Ispostavilo se da postoje dva dodatna okteta između Srcmaca i Ethertypea (nevezano uz informacije o fragmentima). Nakon njihovog uklanjanja, dekoder je počeo raditi. Međutim, nije pokazao nikakve probleme.
Što god se moglo reći, ništa drugo nije pronađeno osim onih Sysctl. Ostalo je samo pronaći način identificiranja problematičnih poslužitelja kako bismo razumjeli razmjere i odlučili o daljnjim akcijama. Traženi brojač je pronađen dovoljno brzo:
"Broj kvarova koje je otkrio algoritam ponovnog sastavljanja IP-a (iz bilo kojeg razloga: isteklo vrijeme, pogreške itd.)."
U skupini poslužitelja na kojima je proučavan problem, na dva je taj brojač rastao brže, na dva sporije, a na još dva se uopće nije povećavao. Usporedba dinamike ovog brojača s dinamikom HTTP pogrešaka na Java poslužitelju otkrila je korelaciju. Odnosno, brojilo se moglo nadzirati.
Imati pouzdan indikator problema vrlo je važno kako biste mogli točno utvrditi pomaže li vraćanje Sysctla na staro, jer iz prethodne priče znamo da se to ne može odmah shvatiti iz aplikacije. Ovaj pokazatelj bi nam omogućio da identificiramo sva problematična područja u proizvodnji prije nego što to korisnici otkriju.
Nakon vraćanja Sysctl-a, greške u praćenju su prestale, čime je dokazan uzrok problema, kao i činjenica da vraćanje pomaže.
Vratili smo postavke fragmentacije na drugim poslužiteljima, gdje je novi nadzor ušao u igru, a negdje smo dodijelili još više memorije za fragmente nego što je prethodno bilo zadano (to je bila UDP statistika, čiji djelomični gubitak nije bio vidljiv na općoj pozadini) .
Najvažnija pitanja
Zašto su paketi fragmentirani na našem L3 balanseru? Većina paketa koji stižu od korisnika do balansera su SYN i ACK. Veličine ovih paketa su male. Ali budući da je udio takvih paketa vrlo velik, na njihovoj pozadini nismo primijetili prisutnost velikih paketa koji su se počeli fragmentirati.
Razlog je bila pokvarena konfiguracijska skripta advmss na poslužiteljima s Vlan sučeljima (u to vrijeme bilo je vrlo malo poslužitelja s označenim prometom u proizvodnji). Advmss nam omogućuje da klijentu prenesemo informaciju da bi paketi u našem smjeru trebali biti manje veličine tako da nakon pričvršćivanja zaglavlja tunela na njih ne moraju biti fragmentirani.
Zašto Sysctl vraćanje nije pomoglo, ali ponovno pokretanje jest? Vraćanje Sysctla promijenilo je količinu memorije dostupne za spajanje paketa. U isto vrijeme, očito je sama činjenica prelivanja memorije za fragmente dovela do usporavanja veza, što je dovelo do dugog kašnjenja fragmenata u redu čekanja. Odnosno, proces je išao u ciklusima.
Ponovno pokretanje je izbrisalo memoriju i sve se vratilo na svoje mjesto.
Je li bilo moguće bez Workarounda? Da, ali postoji veliki rizik da korisnici ostanu bez usluge u slučaju napada. Naravno, korištenje Workarounda rezultiralo je raznim problemima, uključujući i usporavanje jednog od servisa za korisnike, ali unatoč tome vjerujemo da su postupci bili opravdani.
Puno hvala Andreju Timofejevu (atimofejev) za pomoć u provođenju istrage, kao i Alexey Krenev (uređajx) - za titanski rad ažuriranja Centosa i kernela na poslužiteljima. Proces koji se u ovom slučaju nekoliko puta morao pokretati ispočetka, zbog čega se razvukao na više mjeseci.