Čuvajte se ranjivosti koje dovode do zaokreta. Dio 1: FragmentSmack/SegmentSmack

Čuvajte se ranjivosti koje dovode do zaokreta. Dio 1: FragmentSmack/SegmentSmack

Zdravo svima! Moje ime je Dmitrij Samsonov, radim kao vodeći sistem administrator u Odnoklassniki. Imamo više od 7 hiljada fizičkih servera, 11 hiljada kontejnera u našem oblaku i 200 aplikacija koje u različitim konfiguracijama formiraju 700 različitih klastera. Velika većina servera pokreće CentOS 7.
Dana 14. avgusta 2018. objavljena je informacija o ranjivosti FragmentSmack
(CVE-2018-5391) i SegmentSmack (CVE-2018-5390). To su ranjivosti sa vektorom mrežnog napada i prilično visokim rezultatom (7.5), što prijeti uskraćivanjem usluge (DoS) zbog iscrpljivanja resursa (CPU). Ispravka kernela za FragmentSmack tada nije bila predložena, štoviše, izašla je mnogo kasnije od objave informacija o ranjivosti. Da bi se eliminisao SegmentSmack, predloženo je ažuriranje kernela. Sam paket ažuriranja je objavljen istog dana, ostalo je samo da ga instalirate.
Ne, mi uopšte nismo protiv ažuriranja kernela! Međutim, postoje nijanse...

Kako ažuriramo kernel u proizvodnji

Općenito, ništa komplikovano:

  1. Preuzmite pakete;
  2. Instalirajte ih na brojne servere (uključujući servere koji hostuju naš oblak);
  3. Uvjerite se da ništa nije slomljeno;
  4. Uvjerite se da su sve standardne postavke kernela primijenjene bez grešaka;
  5. Sačekajte nekoliko dana;
  6. Provjerite performanse servera;
  7. Prebacite implementaciju novih servera na novi kernel;
  8. Ažurirajte sve servere po data centru (jedan po jedan data centar kako bi se minimizirao efekat na korisnike u slučaju problema);
  9. Ponovo pokrenite sve servere.

Ponovite za sve grane kernela koje imamo. Trenutno je:

  • Stock CentOS 7 3.10 - za većinu regularnih servera;
  • Vanilija 4.19 - za naše jednooblačni oblaci, jer nam treba BFQ, BBR, itd.;
  • Elrepo kernel-ml 5.2 - for visoko opterećeni distributeri, jer se 4.19 nekada ponašao nestabilno, ali su potrebne iste karakteristike.

Kao što ste mogli pretpostaviti, ponovno pokretanje hiljada servera traje najduže. Pošto nisu sve ranjivosti kritične za sve servere, ponovo pokrećemo samo one kojima se direktno pristupa sa Interneta. U oblaku, da ne bismo ograničili fleksibilnost, ne vezujemo eksterno dostupne kontejnere za pojedinačne servere sa novim kernelom, već restartujemo sve hostove bez izuzetka. Srećom, tamo je procedura jednostavnija nego kod običnih servera. Na primjer, kontejneri bez stanja mogu jednostavno premjestiti na drugi server tokom ponovnog pokretanja.

Međutim, posla ima još puno, a može potrajati i nekoliko sedmica, a ako ima problema s novom verzijom i do nekoliko mjeseci. Napadači to vrlo dobro razumiju, pa im je potreban plan B.

FragmentSmack/SegmentSmack. Zaobilazno rješenje

Srećom, za neke ranjivosti postoji takav plan B, 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 rješenje:

«Možete promijeniti zadane vrijednosti od 4MB i 3MB u net.ipv4.ipfrag_high_thresh i net.ipv4.ipfrag_low_thresh (i njihove parnjake za ipv6 net.ipv6.ipfrag_high_thresh i net.ipv6.ipfrag_low_thresh256 kB u odnosu na 192 i 262144 kB) niže. Testovi pokazuju male do značajne padove u upotrebi CPU-a tokom napada u zavisnosti od hardvera, postavki i uslova. Međutim, može postojati određeni utjecaj na performanse zbog ipfrag_high_thresh=64 bajtova, budući da samo dva XNUMXK fragmenta mogu istovremeno stati u red za ponovno sastavljanje. Na primjer, postoji rizik da se aplikacije koje rade s velikim UDP paketima pokvare".

Sami parametri u dokumentaciji kernela opisano kako slijedi:

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 na proizvodnim uslugama. Nema fragmentiranog saobraćaja na LAN-u; postoji fragmentiran saobraćaj na WAN-u, ali nije značajan. Nema znakova - možete pokrenuti Workaround!

FragmentSmack/SegmentSmack. Prva krv

Prvi problem na koji smo naišli bio je taj što su cloud kontejneri ponekad primjenjivali nove postavke samo djelimično (samo ipfrag_low_thresh), a ponekad ih uopće nisu primjenjivali - jednostavno su se srušili na početku. Nije bilo moguće stabilno reproducirati problem (sva podešavanja su primijenjena ručno bez ikakvih poteškoća). Razumevanje zašto se kontejner ruši na početku takođe nije tako lako: greške nisu pronađene. Jedno je bilo sigurno: vraćanje postavki unatrag rješava problem s rušenjem kontejnera.

Zašto nije dovoljno primijeniti Sysctl na hostu? Kontejner živi u vlastitoj namjenskoj mreži Namespace, barem tako dio mrežnih Sysctl parametara u kontejneru može se razlikovati od domaćina.

Kako se tačno Sysctl postavke primjenjuju u kontejneru? Budući da su naši kontejneri neprivilegirani, nećete moći promijeniti nijednu Sysctl postavku ulaskom u sam kontejner - jednostavno nemate dovoljno prava. Za pokretanje kontejnera, naš oblak je u to vrijeme koristio Docker (sada podman). Parametri novog kontejnera su proslijeđeni Docker-u preko API-ja, uključujući potrebne Sysctl postavke.
Prilikom pretraživanja verzija, pokazalo se da Docker API nije vratio sve greške (barem u verziji 1.10). Kada smo pokušali pokrenuti kontejner preko “docker run”, konačno 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 važi samo ponekad? Ispostavilo se da Docker ne garantuje redoslijed primjene Sysctl parametara (posljednja testirana verzija je 1.13.1), pa se ponekad ipfrag_high_thresh pokušava 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 greške.

U to vrijeme smo već koristili vlastiti mehanizam za rekonfiguraciju kontejnera nakon pokretanja (zamrzavanje kontejnera nakon grupni zamrzivač i izvršavanje naredbi u imenskom prostoru kontejnera preko ip netns), a ovom dijelu smo također dodali pisanje Sysctl parametara. Problem je riješen.

FragmentSmack/SegmentSmack. Prva krv 2

Prije nego što smo imali vremena da shvatimo korištenje Workaround-a u oblaku, počele su stizati prve rijetke pritužbe korisnika. U to vrijeme je prošlo nekoliko sedmica od početka korištenja Workaround-a na prvim serverima. Prvobitna istraga je pokazala da su pritužbe zaprimljene protiv pojedinih servisa, a ne svih servera ovih servisa. Problem je ponovo postao krajnje neizvjestan.

Prije svega, mi smo, naravno, pokušali da vratimo Sysctl postavke, ali to nije imalo efekta. Nisu pomogle ni razne manipulacije postavkama servera i aplikacije. Ponovno pokretanje je pomoglo. Ponovno pokretanje Linuxa je neprirodno koliko je bilo normalno za Windows u stara vremena. Međutim, to je pomoglo, a mi smo to pripisali „kvaru kernela“ prilikom primjene novih postavki u Sysctl-u. Kako je bilo neozbiljno...

Tri sedmice kasnije problem se ponovio. Konfiguracija ovih servera je bila prilično jednostavna: Nginx u proxy/balancer modu. Nema puno prometa. Nova uvodna napomena: svakim danom se povećava broj 504 greške na klijentima (Gateway Timeout). Grafikon prikazuje broj od 504 greške dnevno za ovu uslugu:

Čuvajte se ranjivosti koje dovode do zaokreta. Dio 1: FragmentSmack/SegmentSmack

Sve greške su o istom backendu - o onom koji je u oblaku. Grafikon potrošnje memorije za fragmente paketa na ovoj pozadini izgledao je ovako:

Čuvajte se ranjivosti koje dovode do zaokreta. Dio 1: FragmentSmack/SegmentSmack

Ovo je jedna od najočiglednijih manifestacija problema u grafovima operativnog sistema. U oblaku je u isto vrijeme riješen još jedan mrežni problem sa postavkama QoS (Kontrola prometa). Na grafu potrošnje memorije za fragmente paketa izgledalo je potpuno isto:

Čuvajte se ranjivosti koje dovode do zaokreta. Dio 1: FragmentSmack/SegmentSmack

Pretpostavka je bila jednostavna: ako izgledaju isto na grafovima, onda imaju isti razlog. Štaviše, bilo kakvi problemi s ovom vrstom memorije su izuzetno rijetki.

Suština riješenog problema je bila da smo koristili fq planer paketa sa zadanim postavkama u QoS-u. Podrazumevano, za jednu vezu, omogućava vam da dodate 100 paketa u red čekanja, a neke veze su, u situacijama nedostatka kanala, počele da zatvaraju red do kraja. U ovom slučaju, paketi se odbacuju. U tc statistici (tc -s qdisc) to se može 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 “dropped 464545” je zbir svih ispuštenih paketa ovog planera. Nakon povećanja dužine reda na 1 hiljadu i ponovnog pokretanja kontejnera, problem je prestao da se javlja. Možete se zavaliti i popiti smoothie.

FragmentSmack/SegmentSmack. Last Blood

Prvo, nekoliko mjeseci nakon objave ranjivosti u kernelu, konačno se pojavio popravak za FragmentSmack (da podsjetim da je uz najavu u avgustu izašao i popravak samo za SegmentSmack), što nam je dalo priliku da napustimo Workaround, što nam je izazvalo dosta problema. Za to vrijeme već smo uspjeli prebaciti neke od servera na novi kernel, a sada smo morali početi ispočetka. Zašto smo ažurirali kernel bez čekanja na popravku FragmentSmack? Činjenica je da se proces zaštite od ovih ranjivosti poklopio (i spojio) sa procesom ažuriranja samog CentOS-a (koji traje čak i više vremena od ažuriranja samo kernela). Osim toga, SegmentSmack je opasnija ranjivost i rješenje za nju se pojavilo 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 tokom CentOS-a 7.5, ispravljena samo u verziji 7.6, tako da smo morali zaustaviti ažuriranje na 7.5 i početi ispočetka sa ažuriranjem na 7.6. I ovo se takođe dešava.

Drugo, vratile su nam se rijetke pritužbe korisnika na probleme. Sada već pouzdano znamo da su svi povezani sa uploadom fajlova sa klijenata na neke od naših servera. Štaviše, vrlo mali broj uploada od ukupne mase prošao je preko ovih servera.

Kao što se sjećamo iz gornje priče, vraćanje Sysctl-a nije pomoglo. Ponovno pokretanje je pomoglo, ali privremeno.
Sumnje u vezi sa Sysctlom nisu otklonjene, ali je ovoga puta bilo potrebno prikupiti što više informacija. Takođe je postojao veliki nedostatak mogućnosti da se problem upload-a reprodukuje na klijentu kako bi se preciznije proučilo šta se dešava.

Analiza svih dostupnih statistika i logova nije nas približila razumijevanju onoga što se događa. Postojao je akutni nedostatak sposobnosti da se reproducira problem kako bi se "osjetila" određena veza. Konačno, programeri su, koristeći posebnu verziju aplikacije, uspjeli postići stabilnu reprodukciju problema na testnom uređaju kada su povezani putem Wi-Fi mreže. Ovo je bio iskorak u istrazi. Klijent se povezao na Nginx, koji je proxy serveru, što je bila naša Java aplikacija.

Čuvajte se ranjivosti koje dovode do zaokreta. Dio 1: FragmentSmack/SegmentSmack

Dijalog za probleme je bio ovakav (popravljen na strani Nginx proxyja):

  1. Klijent: zahtjev za primanje informacija o preuzimanju datoteke.
  2. Java server: odgovor.
  3. Klijent: POST sa fajlom.
  4. Java server: greška.

Istovremeno, Java server upisuje u dnevnik da je od klijenta primljeno 0 bajtova podataka, a Nginx proxy piše da je zahtjev trajao više od 30 sekundi (30 sekundi je vrijeme isteka klijentske aplikacije). Zašto je isteklo vrijeme i zašto 0 bajtova? Iz HTTP perspektive, sve radi kako treba, ali POST sa datotekom kao da nestaje s mreže. Štaviše, nestaje između klijenta i Nginxa. Vrijeme je da se naoružate Tcpdumpom! Ali prvo morate razumjeti mrežnu konfiguraciju. Nginx proxy je iza L3 balansera NFware. Tuneliranje se koristi za isporuku paketa iz L3 balansera na server, koji dodaje svoja zaglavlja paketima:

Čuvajte se ranjivosti koje dovode do zaokreta. Dio 1: FragmentSmack/SegmentSmack

U ovom slučaju, mreža dolazi do ovog servera u obliku Vlan-tagovanog saobraćaja, koji takođe dodaje svoja polja u pakete:

Čuvajte se ranjivosti koje dovode do zaokreta. Dio 1: 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 od Workaround-a), što također mijenja sadržaj zaglavlja:

Čuvajte se ranjivosti koje dovode do zaokreta. Dio 1: FragmentSmack/SegmentSmack

Još jednom: paketi su inkapsulirani sa Vlan tagom, enkapsulirani tunelom, fragmentirani. Da bismo bolje razumjeli kako se to događa, pratimo rutu paketa od klijenta do Nginx proxyja.

  1. Paket stiže do L3 balansera. Za ispravno rutiranje unutar podatkovnog centra, paket se inkapsulira u tunel i šalje na mrežnu karticu.
  2. Pošto se zaglavlja paketa + tunela ne uklapaju u MTU, paket se reže na fragmente i šalje u mrežu.
  3. Prekidač nakon L3 balansera, kada primi paket, dodaje mu Vlan tag i šalje ga dalje.
  4. Prekidač ispred Nginx proxy-a vidi (na osnovu postavki porta) da server očekuje Vlan-enkapsuliran paket, pa ga šalje takvog kakav jeste, bez uklanjanja Vlan oznake.
  5. Linux uzima fragmente pojedinačnih paketa i spaja ih u jedan veliki paket.
  6. Zatim paket stiže do Vlan interfejsa, gde se sa njega uklanja prvi sloj - Vlan inkapsulacija.
  7. Linux ga zatim šalje na Tunnel interfejs, gdje se drugi sloj uklanja iz njega - Tunnel encapsulation.

Poteškoća je da se sve ovo prosledi kao parametri u tcpdump.
Počnimo od kraja: da li postoje čisti (bez nepotrebnih zaglavlja) IP paketi od klijenata, sa uklonjenom enkapsulacijom vlan-a i tunela?

tcpdump host <ip клиента>

Ne, nije bilo takvih paketa na serveru. Dakle, problem mora postojati ranije. Postoje li paketi sa uklonjenom samo Vlan inkapsulacijom?

tcpdump ip[32:4]=0xx390x2xx

0xx390x2xx je IP adresa klijenta u hex formatu.
32:4 — adresa i dužina polja u koje je upisan 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. Vjerovatno se ista stvar može filtrirati po IP-u, ali nisam pokušao, jer takvih paketa nema puno, a oni koji su mi trebali lako su se našli 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 ..............

Ovo su dva fragmenta jednog paketa (isti ID 53652) sa fotografijom (na prvom paketu se vidi riječ Exif). Zbog činjenice da postoje paketi na ovom nivou, ali ne u spojenom obliku u dumpima, problem je očito u montaži. Konačno postoje dokumentarni dokazi o tome!

Dekoder paketa 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 (nije vezano za informacije o fragmentima). Nakon uklanjanja, dekoder je počeo da radi. Međutim, nije pokazivalo nikakve probleme.
Šta god da se kaže, ništa drugo nije pronađeno osim onih Sysctl. Ostalo je samo da se pronađe način da se identifikuju problemi serveri kako bi se shvatile razmere i odlučile o daljim akcijama. Potreban brojač je pronađen dovoljno brzo:

netstat -s | grep "packet reassembles failed”

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

"Broj grešaka koje je otkrio algoritam ponovnog sastavljanja IP-a (iz bilo kojeg razloga: isteklo vrijeme, greške, itd.)"

Među grupom servera na kojoj je problem proučavan, na dva se ovaj brojač povećavao brže, na dva sporije, a na još dva se nije povećavao uopće. Poređenjem dinamike ovog brojača sa dinamikom HTTP grešaka na Java serveru otkrivena je korelacija. Odnosno, brojilo bi se moglo nadzirati.

Vrlo je važno imati pouzdan indikator problema kako biste mogli precizno utvrditi da li vraćanje Sysctl-a pomaže, jer iz prethodne priče znamo da se to ne može odmah shvatiti iz aplikacije. Ovaj indikator bi nam omogućio da identifikujemo sva problematična područja u proizvodnji prije nego što ih 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 druge servere, gdje je došlo do novog praćenja, a negdje smo dodijelili čak i više memorije za fragmente nego što je prethodno bilo zadano (ovo je bila UDP statistika, čiji se djelomični gubitak nije primijetio na općoj pozadini) .

Najvažnija pitanja

Zašto su paketi fragmentirani na našem L3 balanseru? Većina paketa koji dolaze od korisnika do balansera su SYN i ACK. Veličine ovih paketa su male. Ali kako je udio takvih paketa vrlo velik, na njihovoj pozadini nismo primijetili prisustvo velikih paketa koji su počeli da se fragmentiraju.

Razlog je bila pokvarena konfiguracijska skripta advmss na serverima sa Vlan interfejsima (u to vreme bilo je vrlo malo servera sa označenim saobraćajem u proizvodnji). Advmss nam omogućava da klijentu prenesemo informaciju da paketi u našem smjeru trebaju 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 jeste? Vraćanje Sysctl-a je promijenilo količinu memorije dostupne za spajanje paketa. Istovremeno, očigledno je sama činjenica prelivanja memorije za fragmente dovela do usporavanja veza, što je dovelo do toga da su fragmenti dugo odlagani u redu čekanja. To jest, proces je išao u ciklusima.
Restart je obrisao memoriju i sve se vratilo u red.

Je li bilo moguće bez zaobilaznog rješenja? Da, ali postoji veliki rizik da korisnici ostanu bez usluge u slučaju napada. Naravno, korištenje Workaround-a rezultiralo je raznim problemima, uključujući i usporavanje jednog od servisa za korisnike, ali ipak smatramo da su radnje bile opravdane.

Veliko hvala Andreyu Timofeevu (atimofeyev) za pomoć u provođenju istrage, kao i Aleksej Krenev (devicex) - za titanski rad na ažuriranju Centosa i kernela na serverima. Proces koji je u ovom slučaju nekoliko puta morao biti pokrenut ispočetka, zbog čega se razvukao na više mjeseci.

izvor: www.habr.com

Dodajte komentar