Vær oppmerksom på sårbarheter som bringer arbeidsrunder. Del 1: FragmentSmack/SegmentSmack

Vær oppmerksom på sårbarheter som bringer arbeidsrunder. Del 1: FragmentSmack/SegmentSmack

Hei alle sammen! Mitt navn er Dmitry Samsonov, jeg jobber som en ledende systemadministrator hos Odnoklassniki. Vi har mer enn 7 tusen fysiske servere, 11 tusen containere i skyen vår og 200 applikasjoner, som i ulike konfigurasjoner danner 700 forskjellige klynger. De aller fleste servere kjører CentOS 7.
14. august 2018 ble informasjon om sårbarheten FragmentSmack publisert
(CVE-2018-5391) og SegmentSmack (CVE-2018-5390). Dette er sårbarheter med en nettverksangrepsvektor og en ganske høy score (7.5), som truer denial of service (DoS) på grunn av ressursutmattelse (CPU). En kjernefiks for FragmentSmack ble ikke foreslått på det tidspunktet; dessuten kom den ut mye senere enn publisering av informasjon om sårbarheten. For å eliminere SegmentSmack ble det foreslått å oppdatere kjernen. Selve oppdateringspakken ble utgitt samme dag, det gjensto bare å installere den.
Nei, vi er ikke imot å oppdatere kjernen i det hele tatt! Men det er nyanser...

Hvordan vi oppdaterer kjernen ved produksjon

Generelt er ingenting komplisert:

  1. Last ned pakker;
  2. Installer dem på en rekke servere (inkludert servere som er vert for skyen vår);
  3. Sørg for at ingenting er ødelagt;
  4. Sørg for at alle standard kjerneinnstillinger brukes uten feil;
  5. Vent noen dager;
  6. Sjekk serverytelse;
  7. Bytt distribusjon av nye servere til den nye kjernen;
  8. Oppdater alle servere etter datasenter (ett datasenter om gangen for å minimere effekten på brukere i tilfelle problemer);
  9. Start alle servere på nytt.

Gjenta for alle grener av kjernene vi har. For øyeblikket er det:

  • Stock CentOS 7 3.10 - for de fleste vanlige servere;
  • Vanilje 4.19 - for vår skyer med én sky, fordi vi trenger BFQ, BBR, etc.;
  • Elrepo kjerne-ml 5.2 - for høyt belastede distributører, fordi 4.19 pleide å oppføre seg ustabilt, men de samme funksjonene er nødvendige.

Som du kanskje har gjettet, tar omstart av tusenvis av servere lengst tid. Siden ikke alle sårbarheter er kritiske for alle servere, starter vi bare på nytt de som er direkte tilgjengelige fra Internett. I skyen, for ikke å begrense fleksibiliteten, knytter vi ikke eksternt tilgjengelige containere til individuelle servere med en ny kjerne, men restarter alle verter uten unntak. Heldigvis er prosedyren der enklere enn med vanlige servere. For eksempel kan statsløse beholdere ganske enkelt flytte til en annen server under en omstart.

Det gjenstår imidlertid fortsatt mye arbeid, og det kan ta flere uker, og hvis det er problemer med den nye versjonen, opptil flere måneder. Angripere forstår dette veldig godt, så de trenger en plan B.

FragmentSmack/SegmentSmack. Løsning

Heldigvis eksisterer en slik plan B for noen sårbarheter, og den kalles Workaround. Oftest er dette en endring i kjerne/applikasjonsinnstillinger som kan minimere den mulige effekten eller helt eliminere utnyttelsen av sårbarheter.

Når det gjelder FragmentSmack/SegmentSmack ble foreslått denne løsningen:

«Du kan endre standardverdiene på 4MB og 3MB i net.ipv4.ipfrag_high_thresh og net.ipv4.ipfrag_low_thresh (og deres motparter for ipv6 net.ipv6.ipfrag_high_thresh og net.ipv6.ipfrag_low_thresh) til henholdsvis 256 kB eller 192 kB Nedre. Tester viser små til betydelige fall i CPU-bruk under et angrep avhengig av maskinvare, innstillinger og forhold. Imidlertid kan det være en viss ytelsespåvirkning på grunn av ipfrag_high_thresh=262144 byte, siden bare to 64K-fragmenter kan passe inn i remonteringskøen om gangen. For eksempel er det en risiko for at applikasjoner som fungerer med store UDP-pakker går i stykker'.

Selve parameterne i kjernedokumentasjonen beskrevet som følger:

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.

Vi har ikke store UDP-er på produksjonstjenester. Det er ingen fragmentert trafikk på LAN, det er fragmentert trafikk på WAN, men ikke signifikant. Det er ingen tegn – du kan rulle ut Workaround!

FragmentSmack/SegmentSmack. Første blod

Det første problemet vi møtte var at skybeholdere noen ganger brukte de nye innstillingene bare delvis (bare ipfrag_low_thresh), og noen ganger ikke brukte dem i det hele tatt - de krasjet rett og slett i starten. Det var ikke mulig å reprodusere problemet stabilt (alle innstillinger ble brukt manuelt uten problemer). Å forstå hvorfor containeren krasjer ved starten er heller ikke så lett: ingen feil ble funnet. En ting var sikkert: Å rulle tilbake innstillingene løser problemet med containerkrasj.

Hvorfor er det ikke nok å bruke Sysctl på verten? Containeren bor i sitt eget dedikerte nettverk Namespace, så i det minste del av nettverkets Sysctl-parametere i beholderen kan avvike fra verten.

Hvordan brukes Sysctl-innstillingene i beholderen? Siden våre containere er uprivilegerte, vil du ikke kunne endre noen Sysctl-innstilling ved å gå inn i selve containeren - du har rett og slett ikke nok rettigheter. For å kjøre containere brukte skyen vår på den tiden Docker (nå Podman). Parametrene til den nye beholderen ble sendt til Docker via API, inkludert de nødvendige Sysctl-innstillingene.
Mens du søkte gjennom versjonene, viste det seg at Docker API ikke returnerte alle feil (i hvert fall i versjon 1.10). Da vi prøvde å starte containeren via "docker run", så vi til slutt i det minste noe:

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.

Parameterverdien er ikke gyldig. Men hvorfor? Og hvorfor er det ikke gyldig bare noen ganger? Det viste seg at Docker ikke garanterer rekkefølgen som Sysctl-parametere brukes i (den siste testede versjonen er 1.13.1), så noen ganger prøvde ipfrag_high_thresh å settes til 256K når ipfrag_low_thresh fortsatt var 3M, det vil si at den øvre grensen var lavere enn den nedre grensen, noe som førte til feilen.

På det tidspunktet brukte vi allerede vår egen mekanisme for å rekonfigurere beholderen etter start (frysing av beholderen etter gruppefryser og utføre kommandoer i navnerommet til beholderen via ip netns), og vi la også til å skrive Sysctl-parametere til denne delen. Problemet ble løst.

FragmentSmack/SegmentSmack. Første blod 2

Før vi rakk å forstå bruken av Workaround i skyen, begynte de første sjeldne klagene fra brukere å komme. På det tidspunktet hadde det gått flere uker siden man begynte å bruke Workaround på de første serverne. Den første undersøkelsen viste at det ble mottatt klager mot individuelle tjenester, og ikke alle serverne til disse tjenestene. Problemstillingen er igjen blitt ekstremt usikker.

Først av alt prøvde vi selvfølgelig å rulle tilbake Sysctl-innstillingene, men dette hadde ingen effekt. Ulike manipulasjoner med server- og applikasjonsinnstillinger hjalp heller ikke. Omstart hjalp. Å starte Linux på nytt er like unaturlig som det var normalt for Windows i gamle dager. Det hjalp imidlertid, og vi kalkulerte det opp til en "kjernefeil" når vi brukte de nye innstillingene i Sysctl. Hvor useriøst det var...

Tre uker senere gjentok problemet seg. Konfigurasjonen av disse serverne var ganske enkel: Nginx i proxy/balanseringsmodus. Ikke mye trafikk. Ny introduksjonsnotat: antall 504 feil på klienter øker hver dag (Gateway Timeout). Grafen viser antall 504 feil per dag for denne tjenesten:

Vær oppmerksom på sårbarheter som bringer arbeidsrunder. Del 1: FragmentSmack/SegmentSmack

Alle feilene er omtrent samme backend - om den som er i skyen. Minneforbruksgrafen for pakkefragmenter på denne bakenden så slik ut:

Vær oppmerksom på sårbarheter som bringer arbeidsrunder. Del 1: FragmentSmack/SegmentSmack

Dette er en av de mest åpenbare manifestasjonene av problemet i operativsystemgrafer. I skyen, akkurat på samme tid, ble et annet nettverksproblem med QoS (Traffic Control)-innstillinger løst. På grafen over minneforbruk for pakkefragmenter så det nøyaktig likt ut:

Vær oppmerksom på sårbarheter som bringer arbeidsrunder. Del 1: FragmentSmack/SegmentSmack

Antakelsen var enkel: Hvis de ser likt ut på grafene, så har de samme grunn. Dessuten er eventuelle problemer med denne typen minne ekstremt sjeldne.

Essensen av det fikse problemet var at vi brukte fq-pakkeplanleggeren med standardinnstillinger i QoS. Som standard, for én tilkobling, lar den deg legge til 100 pakker i køen, og noen tilkoblinger, i situasjoner med kanalmangel, begynte å tette køen til kapasitet. I dette tilfellet blir pakker droppet. I tc-statistikk (tc -s qdisc) kan det sees slik:

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" er pakkene som ble droppet på grunn av overskridelse av køgrensen for én tilkobling, og "droppet 464545" er summen av alle droppede pakker i denne planleggeren. Etter å ha økt kølengden til 1 tusen og startet beholderne på nytt, sluttet problemet å oppstå. Du kan lene deg tilbake og drikke en smoothie.

FragmentSmack/SegmentSmack. Siste blod

For det første, flere måneder etter kunngjøringen av sårbarheter i kjernen, dukket det endelig opp en rettelse for FragmentSmack (la meg minne deg på at sammen med kunngjøringen i august ble en rettelse bare for SegmentSmack utgitt), som ga oss en sjanse til å forlate Workaround, som forårsaket oss ganske mye trøbbel. I løpet av denne tiden hadde vi allerede klart å overføre noen av serverne til den nye kjernen, og nå måtte vi starte fra begynnelsen. Hvorfor oppdaterte vi kjernen uten å vente på FragmentSmack-fixen? Faktum er at prosessen med å beskytte mot disse sårbarhetene falt sammen (og fusjonerte) med prosessen med å oppdatere selve CentOS (som tar enda mer tid enn å oppdatere bare kjernen). I tillegg er SegmentSmack en farligere sårbarhet, og en løsning for det dukket opp umiddelbart, så det ga mening uansett. Vi kunne imidlertid ikke bare oppdatere kjernen på CentOS fordi FragmentSmack-sårbarheten, som dukket opp under CentOS 7.5, bare ble fikset i versjon 7.6, så vi måtte stoppe oppdateringen til 7.5 og starte på nytt med oppdateringen til 7.6. Og dette skjer også.

For det andre har sjeldne brukerklager om problemer returnert til oss. Nå vet vi allerede med sikkerhet at de alle er relatert til opplasting av filer fra klienter til noen av våre servere. Dessuten gikk et svært lite antall opplastinger fra den totale massen gjennom disse serverne.

Som vi husker fra historien ovenfor, hjalp det ikke å rulle tilbake Sysctl. Omstart hjalp, men midlertidig.
Mistanker angående Sysctl ble ikke fjernet, men denne gangen var det nødvendig å samle inn så mye informasjon som mulig. Det var også en enorm mangel på evne til å reprodusere opplastingsproblemet på klienten for å studere mer nøyaktig hva som skjedde.

Analyse av all tilgjengelig statistikk og logger førte oss ikke nærmere til å forstå hva som skjedde. Det var en akutt mangel på evne til å reprodusere problemet for å "føle" en spesifikk sammenheng. Til slutt klarte utviklerne, ved hjelp av en spesiell versjon av applikasjonen, å oppnå stabil reproduksjon av problemer på en testenhet når de er koblet til via Wi-Fi. Dette var et gjennombrudd i etterforskningen. Klienten koblet til Nginx, som ga proxy til backend, som var vår Java-applikasjon.

Vær oppmerksom på sårbarheter som bringer arbeidsrunder. Del 1: FragmentSmack/SegmentSmack

Dialogen for problemer var slik (fiksert på Nginx proxy-siden):

  1. Klient: forespørsel om å motta informasjon om nedlasting av en fil.
  2. Java-server: svar.
  3. Klient: POST med fil.
  4. Java-server: feil.

Samtidig skriver Java-serveren til loggen at 0 byte med data ble mottatt fra klienten, og Nginx-proxyen skriver at forespørselen tok mer enn 30 sekunder (30 sekunder er tidsavbruddet for klientapplikasjonen). Hvorfor tidsavbrudd og hvorfor 0 byte? Fra et HTTP-perspektiv fungerer alt som det skal, men POST-en med filen ser ut til å forsvinne fra nettverket. Dessuten forsvinner den mellom klienten og Nginx. Det er på tide å bevæpne deg med Tcpdump! Men først må du forstå nettverkskonfigurasjonen. Nginx-proxy står bak L3-balanseren NFware. Tunnelering brukes til å levere pakker fra L3-balanseren til serveren, som legger til overskriftene til pakkene:

Vær oppmerksom på sårbarheter som bringer arbeidsrunder. Del 1: FragmentSmack/SegmentSmack

I dette tilfellet kommer nettverket til denne serveren i form av Vlan-merket trafikk, som også legger til egne felt til pakkene:

Vær oppmerksom på sårbarheter som bringer arbeidsrunder. Del 1: FragmentSmack/SegmentSmack

Og denne trafikken kan også være fragmentert (den samme lille prosentandelen av innkommende fragmentert trafikk som vi snakket om da vi vurderte risikoene fra Workaround), som også endrer innholdet i overskriftene:

Vær oppmerksom på sårbarheter som bringer arbeidsrunder. Del 1: FragmentSmack/SegmentSmack

Nok en gang: pakker er innkapslet med en Vlan-tag, innkapslet med en tunnel, fragmentert. For bedre å forstå hvordan dette skjer, la oss spore pakkeruten fra klienten til Nginx-proxyen.

  1. Pakken når L3-balanseren. For korrekt ruting i datasenteret, blir pakken kapslet inn i en tunnel og sendt til nettverkskortet.
  2. Siden pakke + tunnelhodene ikke passer inn i MTU, blir pakken kuttet i fragmenter og sendt til nettverket.
  3. Bryteren etter L3-balansereren, når den mottar en pakke, legger til en Vlan-tag til den og sender den videre.
  4. Switchen foran Nginx-proxyen ser (basert på portinnstillingene) at serveren forventer en Vlan-innkapslet pakke, så den sender den som den er, uten å fjerne Vlan-taggen.
  5. Linux tar fragmenter av individuelle pakker og slår dem sammen til en stor pakke.
  6. Deretter når pakken Vlan-grensesnittet, hvor det første laget fjernes fra det - Vlan-innkapsling.
  7. Linux sender den deretter til Tunnel-grensesnittet, hvor et annet lag fjernes fra det - Tunnel-innkapsling.

Vanskeligheten er å sende alt dette som parametere til tcpdump.
La oss starte fra slutten: finnes det rene (uten unødvendige overskrifter) IP-pakker fra klienter, med vlan og tunnelinnkapsling fjernet?

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

Nei, det var ingen slike pakker på serveren. Så problemet må være der tidligere. Er det noen pakker med kun Vlan-innkapsling fjernet?

tcpdump ip[32:4]=0xx390x2xx

0xx390x2xx er klientens IP-adresse i hex-format.
32:4 — adresse og lengde på feltet der SCR IP er skrevet i tunnelpakken.

Feltadressen måtte velges med brute force, siden de på Internett skriver om 40, 44, 50, 54, men det var ingen IP-adresse der. Du kan også se på en av pakkene i hex (parameteren -xx eller -XX i tcpdump) og beregne IP-adressen du kjenner.

Er det pakkefragmenter uten Vlan- og Tunnel-innkapsling fjernet?

tcpdump ((ip[6:2] > 0) and (not ip[6] = 64))

Denne magien vil vise oss alle fragmentene, inkludert den siste. Sannsynligvis kan det samme filtreres etter IP, men jeg prøvde ikke, fordi det ikke er veldig mange slike pakker, og de jeg trengte ble lett funnet i den generelle flyten. Her er de:

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

Dette er to fragmenter av én pakke (samme ID 53652) med et fotografi (ordet Exif er synlig i den første pakken). På grunn av det faktum at det er pakker på dette nivået, men ikke i sammenslått form i dumpene, er problemet helt klart med monteringen. Endelig er det dokumentariske bevis på dette!

Pakkedekoderen avslørte ingen problemer som ville forhindre byggingen. Prøvde det her: hpd.gasmi.net. Til å begynne med, når du prøver å stappe noe der, liker ikke dekoderen pakkeformatet. Det viste seg at det var noen ekstra to oktetter mellom Srcmac og Ethertype (ikke relatert til fragmentinformasjon). Etter å ha fjernet dem begynte dekoderen å fungere. Det viste imidlertid ingen problemer.
Uansett hva man kan si, ble det ikke funnet noe annet enn de Sysctl. Alt som gjensto var å finne en måte å identifisere problemservere for å forstå omfanget og bestemme videre handlinger. Den nødvendige telleren ble funnet raskt nok:

netstat -s | grep "packet reassembles failed”

Den er også i snmpd under OID=1.3.6.1.2.1.4.31.1.1.16.1 (ipSystemStatsReasmFails).

"Antall feil oppdaget av IP-remonteringsalgoritmen (uansett grunn: tidsavbrudd, feil osv.)."

Blant gruppen av servere som problemet ble studert på, økte denne telleren på to raskere, på to langsommere, og på to til økte den ikke i det hele tatt. Sammenligning av dynamikken til denne telleren med dynamikken til HTTP-feil på Java-serveren avslørte en korrelasjon. Det vil si at måleren kunne overvåkes.

Å ha en pålitelig indikator på problemer er veldig viktig, slik at du nøyaktig kan avgjøre om tilbakerulling av Sysctl hjelper, siden vi fra forrige historie vet at dette ikke umiddelbart kan forstås fra applikasjonen. Denne indikatoren vil tillate oss å identifisere alle problemområder i produksjonen før brukere oppdager det.
Etter tilbakerulling av Sysctl stoppet overvåkingsfeilene, dermed ble årsaken til problemene bevist, samt at tilbakerullingen hjelper.

Vi rullet tilbake fragmenteringsinnstillingene på andre servere, der ny overvåking kom inn, og et sted tildelte vi enda mer minne for fragmenter enn det som tidligere var standard (dette var UDP-statistikk, det delvise tapet var ikke merkbart mot den generelle bakgrunnen) .

De viktigste spørsmålene

Hvorfor er pakker fragmentert på L3-balanseren vår? De fleste pakkene som kommer fra brukere til balansere er SYN og ACK. Størrelsene på disse pakkene er små. Men siden andelen av slike pakker er veldig stor, la vi ikke mot deres bakgrunn merke til tilstedeværelsen av store pakker som begynte å fragmentere.

Årsaken var et ødelagt konfigurasjonsskript advmss på servere med Vlan-grensesnitt (det var svært få servere med tagget trafikk i produksjon på den tiden). Advmss lar oss formidle til klienten informasjonen om at pakker i vår retning bør være mindre i størrelse, slik at de ikke trenger å bli fragmentert etter å ha festet tunnelhoder til dem.

Hvorfor hjalp ikke tilbakerulling av Sysctl, men omstart gjorde det? Tilbakestilling av Sysctl endret mengden tilgjengelig minne for å slå sammen pakker. Samtidig førte tilsynelatende selve minnet overflyt for fragmenter til nedgang i forbindelsene, noe som førte til at fragmenter ble forsinket i lang tid i køen. Det vil si at prosessen gikk i sykluser.
Omstarten tømte minnet og alt kom tilbake til orden.

Var det mulig å klare seg uten Workaround? Ja, men det er stor risiko for å forlate brukere uten tjeneste i tilfelle et angrep. Selvfølgelig resulterte bruken av Workaround i ulike problemer, inkludert nedbremsing av en av tjenestene for brukerne, men vi mener likevel at handlingene var berettiget.

Mange takk til Andrey Timofeev (atimofejev) for hjelp til å gjennomføre etterforskningen, samt Alexey Krenev (devicex) - for det titaniske arbeidet med å oppdatere Centos og kjerner på servere. En prosess som i dette tilfellet måtte startes fra starten flere ganger, derfor trakk det ut i mange måneder.

Kilde: www.habr.com

Legg til en kommentar