Č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:

  1. preuzimanje paketa;
  2. Instalirajte ih na više poslužitelja (uključujući poslužitelje koji hostiraju naš oblak);
  3. Uvjerite se da ništa nije slomljeno;
  4. Provjerite jesu li sve standardne postavke jezgre primijenjene bez grešaka;
  5. Pričekajte nekoliko dana;
  6. Provjerite rad poslužitelja;
  7. Prebacite postavljanje novih poslužitelja na novu jezgru;
  8. 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);
  9. 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;
  • Vanilla 4.19 - za naše jednooblačni oblaci, jer trebamo BFQ, BBR itd.;
  • 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".

Sami parametri u dokumentaciji kernela opisano na sljedeći način:

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:

Čuvajte se ranjivosti koje donose runde posla. 1. dio: FragmentSmack/SegmentSmack

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:

Čuvajte se ranjivosti koje donose runde posla. 1. dio: FragmentSmack/SegmentSmack

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:

Čuvajte se ranjivosti koje donose runde posla. 1. dio: FragmentSmack/SegmentSmack

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:

qdisc fq 2c6c: parent 1:2c6c limit 10000p flow_limit 100p buckets 1024 orphan_mask 1023 quantum 3028 initial_quantum 15140 refill_delay 40.0ms
 Sent 454701676345 bytes 491683359 pkt (dropped 464545, overlimits 0 requeues 0)
 backlog 0b 0p requeues 0
  1024 flows (1021 inactive, 0 throttled)
  0 gc, 0 highprio, 0 throttled, 464545 flows_plimit

“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.

Čuvajte se ranjivosti koje donose runde posla. 1. dio: FragmentSmack/SegmentSmack

Dijalog za probleme je bio ovakav (popravljeno na Nginx proxy strani):

  1. Klijent: zahtjev za primanje informacija o preuzimanju datoteke.
  2. Java poslužitelj: odgovor.
  3. Klijent: POST s datotekom.
  4. 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:

Čuvajte se ranjivosti koje donose runde posla. 1. dio: FragmentSmack/SegmentSmack

U ovom slučaju, mreža dolazi do ovog poslužitelja u obliku Vlan-tagged prometa, koji također dodaje svoja polja paketima:

Čuvajte se ranjivosti koje donose runde posla. 1. dio: FragmentSmack/SegmentSmack

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:

Čuvajte se ranjivosti koje donose runde posla. 1. dio: FragmentSmack/SegmentSmack

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.

  1. Paket dolazi do L3 balansera. Za ispravno usmjeravanje unutar podatkovnog centra, paket se enkapsulira u tunel i šalje na mrežnu karticu.
  2. Budući da zaglavlja paketa + tunela ne stanu u MTU, paket se reže na fragmente i šalje na mrežu.
  3. Prekidač nakon L3 balansera, prilikom primanja paketa, dodaje mu Vlan oznaku i šalje ga dalje.
  4. 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.
  5. Linux uzima fragmente pojedinačnih paketa i spaja ih u jedan veliki paket.
  6. Zatim paket dolazi do Vlan sučelja, gdje se s njega uklanja prvi sloj - Vlan enkapsulacija.
  7. 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:

14:02:58.471063 In 00:de:ff:1a:94:11 ethertype IPv4 (0x0800), length 1516: (tos 0x0, ttl 63, id 53652, offset 0, flags [+], proto IPIP (4), length 1500)
    11.11.11.11 > 22.22.22.22: truncated-ip - 20 bytes missing! (tos 0x0, ttl 50, id 57750, offset 0, flags [DF], proto TCP (6), length 1500)
    33.33.33.33.33333 > 44.44.44.44.80: Flags [.], seq 0:1448, ack 1, win 343, options [nop,nop,TS val 11660691 ecr 2998165860], length 1448
        0x0000: 0000 0001 0006 00de fb1a 9441 0000 0800 ...........A....
        0x0010: 4500 05dc d194 2000 3f09 d5fb 0a66 387d E.......?....f8}
        0x0020: 1x67 7899 4500 06xx e198 4000 3206 6xx4 [email protected].
        0x0030: b291 x9xx x345 2541 83b9 0050 9740 0x04 .......A...P.@..
        0x0040: 6444 4939 8010 0257 8c3c 0000 0101 080x dDI9...W.......
        0x0050: 00b1 ed93 b2b4 6964 xxd8 ffe1 006a 4578 ......ad.....jEx
        0x0060: 6966 0000 4x4d 002a 0500 0008 0004 0100 if..MM.*........

14:02:58.471103 In 00:de:ff:1a:94:11 ethertype IPv4 (0x0800), length 62: (tos 0x0, ttl 63, id 53652, offset 1480, flags [none], proto IPIP (4), length 40)
    11.11.11.11 > 22.22.22.22: ip-proto-4
        0x0000: 0000 0001 0006 00de fb1a 9441 0000 0800 ...........A....
        0x0010: 4500 0028 d194 00b9 3f04 faf6 2x76 385x E..(....?....f8}
        0x0020: 1x76 6545 xxxx 1x11 2d2c 0c21 8016 8e43 .faE...D-,.!...C
        0x0030: x978 e91d x9b0 d608 0000 0000 0000 7c31 .x............|Q
        0x0040: 881d c4b6 0000 0000 0000 0000 0000 ..............

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:

netstat -s | grep "packet reassembles failed”

Također je u snmpd pod OID=1.3.6.1.2.1.4.31.1.1.16.1 (ipSystemStatsReasmFails).

"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.

Izvor: www.habr.com

Dodajte komentar