Dejte si pozor na zranitelnosti, které přinášejí pracovní kola. Část 1: FragmentSmack/SegmentSmack

Dejte si pozor na zranitelnosti, které přinášejí pracovní kola. Část 1: FragmentSmack/SegmentSmack

Ahoj všichni! Jmenuji se Dmitrij Samsonov, pracuji jako přední správce systému ve společnosti Odnoklassniki. Máme více než 7 tisíc fyzických serverů, 11 tisíc kontejnerů v našem cloudu a 200 aplikací, které v různých konfiguracích tvoří 700 různých clusterů. Na velké většině serverů běží CentOS 7.
Dne 14. srpna 2018 byla zveřejněna informace o zranitelnosti FragmentSmack
(CVE-2018-5391) a SegmentSmack (CVE-2018-5390). Jedná se o zranitelnosti s vektorem síťového útoku a poměrně vysokým skóre (7.5), které hrozí odmítnutím služby (DoS) z důvodu vyčerpání zdrojů (CPU). Oprava jádra pro FragmentSmack v té době nebyla navržena, navíc vyšla mnohem později než zveřejnění informací o zranitelnosti. Pro odstranění SegmentSmack bylo navrženo aktualizovat jádro. Samotný aktualizační balíček byl vydán ve stejný den, zbývalo jej pouze nainstalovat.
Ne, vůbec nejsme proti aktualizaci jádra! Existují však nuance...

Jak aktualizujeme jádro při výrobě

Obecně nic složitého:

  1. Stahování balíčků;
  2. Nainstalujte je na řadu serverů (včetně serverů hostujících náš cloud);
  3. Ujistěte se, že nic není rozbité;
  4. Ujistěte se, že všechna standardní nastavení jádra jsou použita bez chyb;
  5. Počkejte několik dní;
  6. Zkontrolujte výkon serveru;
  7. Přepnout nasazení nových serverů na nové jádro;
  8. Aktualizujte všechny servery podle datového centra (jedno datové centrum po druhém, aby se minimalizoval dopad na uživatele v případě problémů);
  9. Restartujte všechny servery.

Opakujte pro všechny větve jader, které máme. V tuto chvíli je to:

  • Stock CentOS 7 3.10 - pro většinu běžných serverů;
  • Vanilka 4.19 - za naše jednooblakové mraky, protože potřebujeme BFQ, BBR atd.;
  • Elrepo kernel-ml 5.2 - pro vysoce zatížené distributory, protože 4.19 se dříve choval nestabilně, ale jsou potřebné stejné funkce.

Jak jste možná uhodli, restartování tisíců serverů trvá nejdelší dobu. Protože ne všechny zranitelnosti jsou kritické pro všechny servery, restartujeme pouze ty, které jsou přímo dostupné z internetu. V cloudu, abychom neomezovali flexibilitu, nepřipojujeme externě přístupné kontejnery k jednotlivým serverům s novým jádrem, ale restartujeme všechny hostitele bez výjimky. Naštěstí je tam postup jednodušší než u běžných serverů. Například kontejnery bez stavu se mohou během restartu jednoduše přesunout na jiný server.

Stále je však hodně práce, která může trvat několik týdnů, v případě problémů s novou verzí až několik měsíců. Útočníci tomu velmi dobře rozumí, takže potřebují plán B.

FragmentSmack/SegmentSmack. Řešení

Naštěstí pro některé zranitelnosti takový plán B existuje a nazývá se Workaround. Nejčastěji se jedná o změnu nastavení jádra/aplikace, která může minimalizovat možný efekt nebo zcela eliminovat zneužití zranitelností.

V případě FragmentSmack/SegmentSmack bylo navrženo Řešení takto:

«Výchozí hodnoty 4 MB a 3 MB v net.ipv4.ipfrag_high_thresh a net.ipv4.ipfrag_low_thresh (a jejich protějšky pro ipv6 net.ipv6.ipfrag_high_thresh a net.ipv6.ipfrag_low_thresh) můžete změnit na 256 nebo 192 kB dolní. Testy ukazují malé až významné poklesy využití CPU během útoku v závislosti na hardwaru, nastavení a podmínkách. Může však dojít k určitému dopadu na výkon kvůli ipfrag_high_thresh=262144 bajtů, protože do fronty opětovného sestavení se vejdou pouze dva fragmenty o velikosti 64 kB najednou. Existuje například riziko, že aplikace, které pracují s velkými UDP pakety, se porouchají".

Samotné parametry v dokumentaci jádra popsané následovně:

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.

Nemáme velké UDP na produkční služby. V LAN není žádný fragmentovaný provoz, na WAN je fragmentovaný provoz, ale není významný. Nejsou žádné známky – můžete zavést řešení!

FragmentSmack/SegmentSmack. První krev

První problém, na který jsme narazili, byl ten, že cloudové kontejnery někdy aplikovaly nová nastavení jen částečně (pouze ipfrag_low_thresh) a někdy je neaplikovaly vůbec – jednoduše se zhroutily na začátku. Nebylo možné problém stabilně reprodukovat (všechna nastavení byla bez problémů použita ručně). Pochopení, proč se kontejner na začátku zhroutí, také není tak snadné: nebyly nalezeny žádné chyby. Jedna věc byla jistá: vrácení nastavení vyřeší problém s pády kontejneru.

Proč nestačí použít Sysctl na hostitele? Kontejner žije ve své vlastní vyhrazené síti Namespace, tedy alespoň součástí sítě parametry Sysctl v kontejneru se může lišit od hostitele.

Jak přesně se v kontejneru použijí nastavení Sysctl? Vzhledem k tomu, že naše kontejnery nejsou privilegované, nebudete moci změnit žádné nastavení Sysctl přechodem do samotného kontejneru – jednoduše nemáte dostatečná práva. Ke spouštění kontejnerů náš cloud v té době používal Docker (nyní Podman). Parametry nového kontejneru byly předány Dockeru přes API, včetně potřebného nastavení Sysctl.
Při prohledávání verzí se ukázalo, že Docker API nevrací všechny chyby (alespoň ve verzi 1.10). Když jsme se pokusili spustit kontejner pomocí „docker run“, konečně jsme viděli alespoň něco:

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.

Hodnota parametru není platná. Ale proč? A proč to neplatí jen někdy? Ukázalo se, že Docker nezaručuje pořadí, ve kterém jsou parametry Sysctl aplikovány (poslední testovaná verze je 1.13.1), takže se někdy ipfrag_high_thresh pokusil nastavit na 256K, když ipfrag_low_thresh byl stále 3M, to znamená, že horní hranice byla nižší než spodní hranice, což vedlo k chybě.

V té době jsme již používali vlastní mechanismus pro rekonfiguraci kontejneru po startu (zmrazení kontejneru po skupinový mrazák a provádění příkazů ve jmenném prostoru kontejneru via IP sítě), a do této části jsme přidali i zápis parametrů Sysctl. Problém byl vyřešen.

FragmentSmack/SegmentSmack. První krev 2

Než jsme stačili pochopit použití Workaround v cloudu, začaly přicházet první vzácné stížnosti uživatelů. V té době uplynulo několik týdnů od začátku používání Workaround na prvních serverech. Počáteční šetření ukázalo, že byly obdrženy stížnosti na jednotlivé služby, nikoli na všechny servery těchto služeb. Problém se opět stal extrémně nejistým.

Nejprve jsme se samozřejmě pokusili vrátit nastavení Sysctl, ale to nemělo žádný účinek. Nepomohly ani různé manipulace s nastavením serveru a aplikací. Pomohl restart. Restartování Linuxu je stejně nepřirozené, jak bylo za starých časů pro Windows normální. Nicméně to pomohlo a při použití nového nastavení v Sysctl jsme to označili za „závadu jádra“. Jak frivolní to bylo...

Po třech týdnech se problém opakoval. Konfigurace těchto serverů byla poměrně jednoduchá: Nginx v režimu proxy/balancer. Žádný velký provoz. Nová úvodní poznámka: počet 504 chyb na klientech se každým dnem zvyšuje (Brána Timeout). Graf ukazuje počet 504 chyb za den pro tuto službu:

Dejte si pozor na zranitelnosti, které přinášejí pracovní kola. Část 1: FragmentSmack/SegmentSmack

Všechny chyby jsou o stejném backendu – o tom, který je v cloudu. Graf spotřeby paměti pro fragmenty balíčků na tomto backendu vypadal takto:

Dejte si pozor na zranitelnosti, které přinášejí pracovní kola. Část 1: FragmentSmack/SegmentSmack

Toto je jeden z nejzřetelnějších projevů problému v grafech operačního systému. V cloudu byl ve stejnou dobu opraven další problém se sítí s nastavením QoS (Traffic Control). Na grafu spotřeby paměti pro fragmenty paketů to vypadalo úplně stejně:

Dejte si pozor na zranitelnosti, které přinášejí pracovní kola. Část 1: FragmentSmack/SegmentSmack

Předpoklad byl jednoduchý: pokud vypadají stejně na grafech, pak mají stejný důvod. Kromě toho jsou jakékoli problémy s tímto typem paměti extrémně vzácné.

Podstatou opraveného problému bylo, že jsme použili plánovač paketů fq s výchozím nastavením v QoS. Ve výchozím nastavení umožňuje pro jedno připojení přidat do fronty 100 paketů a některá připojení v situacích nedostatku kanálů začala frontu zahlcovat. V tomto případě jsou pakety zahazovány. Ve statistice tc (tc -s qdisc) to lze vidět takto:

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“ jsou pakety zahozené kvůli překročení limitu fronty jednoho připojení a „zahozené 464545“ je součet všech zahozených paketů tohoto plánovače. Po zvýšení délky fronty na 1 tisíc a restartu kontejnerů se problém přestal vyskytovat. Můžete se posadit a pít smoothie.

FragmentSmack/SegmentSmack. Poslední krev

Za prvé, několik měsíců po oznámení zranitelností v jádře se konečně objevila oprava pro FragmentSmack (připomenu, že spolu s oznámením v srpnu byla vydána oprava pouze pro SegmentSmack), což nám dalo šanci opustit Workaround, což nám způsobilo docela velké potíže. Během této doby se nám již podařilo přenést některé servery do nového jádra a nyní jsme museli začít od začátku. Proč jsme aktualizovali jádro, aniž bychom čekali na opravu FragmentSmack? Faktem je, že proces ochrany proti těmto zranitelnostem se shodoval (a sloučil) s procesem aktualizace samotného CentOS (která zabere ještě více času než aktualizace pouze jádra). SegmentSmack je navíc nebezpečnější zranitelnost a oprava na ni se objevila okamžitě, takže to každopádně dávalo smysl. Nemohli jsme však jednoduše aktualizovat jádro na CentOS, protože zranitelnost FragmentSmack, která se objevila během CentOS 7.5, byla opravena až ve verzi 7.6, takže jsme museli zastavit aktualizaci na 7.5 a začít znovu s aktualizací na 7.6. A to se také stává.

Zadruhé se nám vrátily vzácné stížnosti uživatelů na problémy. Nyní již s jistotou víme, že všechny souvisí s nahráváním souborů z klientů na některé z našich serverů. Navíc přes tyto servery prošlo velmi malé množství uploadů z celkového množství.

Jak si pamatujeme z příběhu výše, vrácení Sysctl nepomohlo. Restart pomohl, ale dočasně.
Podezření ohledně Sysctl nebyla odstraněna, ale tentokrát bylo potřeba nasbírat co nejvíce informací. Existoval také obrovský nedostatek schopnosti reprodukovat problém s nahráváním na klientovi, aby bylo možné přesněji studovat, co se děje.

Analýza všech dostupných statistik a logů nás nepřiblížila k pochopení toho, co se děje. Existoval akutní nedostatek schopnosti reprodukovat problém, aby bylo možné „cítit“ konkrétní spojení. Nakonec se vývojářům pomocí speciální verze aplikace podařilo dosáhnout stabilní reprodukce problémů na testovacím zařízení při připojení přes Wi-Fi. To byl průlom ve vyšetřování. Klient se připojil k Nginx, který se připojil k backendu, což byla naše Java aplikace.

Dejte si pozor na zranitelnosti, které přinášejí pracovní kola. Část 1: FragmentSmack/SegmentSmack

Dialog pro problémy byl takto (opraveno na straně proxy Nginx):

  1. Klient: žádost o obdržení informací o stahování souboru.
  2. Java server: odpověď.
  3. Klient: POST se souborem.
  4. Java server: chyba.

Zároveň Java server zapíše do logu, že od klienta bylo přijato 0 bajtů dat, a Nginx proxy zapíše, že požadavek trval déle než 30 sekund (30 sekund je časový limit klientské aplikace). Proč časový limit a proč 0 bajtů? Z pohledu HTTP vše funguje jak má, ale POST se souborem jakoby ze sítě mizí. Navíc mizí mezi klientem a Nginx. Je čas vyzbrojit se Tcpdump! Nejprve však musíte porozumět konfiguraci sítě. Nginx proxy stojí za L3 balancerem NFware. Tunelování se používá k doručování paketů z balancéru L3 na server, který k paketům přidává své hlavičky:

Dejte si pozor na zranitelnosti, které přinášejí pracovní kola. Část 1: FragmentSmack/SegmentSmack

V tomto případě síť přichází na tento server ve formě provozu označeného Vlan, který také přidává svá vlastní pole do paketů:

Dejte si pozor na zranitelnosti, které přinášejí pracovní kola. Část 1: FragmentSmack/SegmentSmack

A tento provoz může být také fragmentován (to stejné malé procento příchozího fragmentovaného provozu, o kterém jsme mluvili při posuzování rizik z Workaround), což také mění obsah záhlaví:

Dejte si pozor na zranitelnosti, které přinášejí pracovní kola. Část 1: FragmentSmack/SegmentSmack

Ještě jednou: pakety jsou zapouzdřeny Vlan tagem, zapouzdřeny tunelem, fragmentovány. Abychom lépe porozuměli tomu, jak se to děje, pojďme sledovat cestu paketů od klienta k proxy Nginx.

  1. Paket dosáhne vyvažovače L3. Pro správné směrování v datovém centru je paket zapouzdřen v tunelu a odeslán na síťovou kartu.
  2. Protože paket + hlavičky tunelu nezapadají do MTU, je paket rozřezán na fragmenty a odeslán do sítě.
  3. Přepínač za balancérem L3 k němu při příjmu paketu přidá Vlan tag a odešle ho dál.
  4. Přepínač před proxy serverem Nginx vidí (na základě nastavení portu), že server očekává paket zapouzdřený Vlan, takže jej odešle tak, jak je, bez odstranění značky Vlan.
  5. Linux bere fragmenty jednotlivých balíčků a spojuje je do jednoho velkého balíčku.
  6. Dále se paket dostane na rozhraní Vlan, kde je z něj odstraněna první vrstva – Vlan encapsulation.
  7. Linux jej následně odešle do rozhraní Tunnel, kde se z něj odstraní další vrstva – Tunnel encapsulation.

Obtížné je předat toto vše jako parametry tcpdump.
Začněme od konce: existují čisté (bez zbytečných hlaviček) IP pakety od klientů, s odstraněným zapouzdřením vlan a tunelu?

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

Ne, žádné takové balíčky na serveru nebyly. Problém tedy musí nastat dříve. Existují nějaké pakety s odstraněným pouze zapouzdřením Vlan?

tcpdump ip[32:4]=0xx390x2xx

0xx390x2xx je adresa IP klienta v hexadecimálním formátu.
32:4 — adresa a délka pole, do kterého je zapsána SCR IP v Tunnel packetu.

Adresa pole musela být vybrána hrubou silou, jelikož na internetu píšou o 40, 44, 50, 54, ale IP adresa tam nebyla. Můžete se také podívat na jeden z paketů v hex (parametr -xx nebo -XX v tcpdump) a vypočítat IP adresu, kterou znáte.

Existují fragmenty paketů bez zapouzdření Vlan a Tunnel?

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

Toto kouzlo nám ukáže všechny fragmenty, včetně toho posledního. Pravděpodobně lze totéž filtrovat podle IP, ale nezkoušel jsem to, protože takových paketů není mnoho a ty, které jsem potřeboval, byly snadno nalezeny v obecném toku. Zde jsou:

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

Jedná se o dva fragmenty jednoho balení (stejné ID 53652) s fotografií (v prvním balení je vidět slovo Exif). Vzhledem k tomu, že na této úrovni jsou balíčky, ale ne ve sloučené podobě ve výpisech, problém je jednoznačně v montáži. Konečně o tom existují listinné důkazy!

Dekodér paketů neodhalil žádné problémy, které by bránily sestavení. Vyzkoušeno zde: hpd.gasmi.net. Nejprve, když se tam pokusíte něco nacpat, dekodéru se nelíbí formát paketu. Ukázalo se, že mezi Srcmac a Ethertype byly nějaké dva oktety navíc (nesouvisející s informacemi o fragmentech). Po jejich odstranění začal dekodér fungovat. Nevykazoval však žádné problémy.
Ať se dá říct cokoli, nic jiného kromě těch Sysctl nebylo nalezeno. Zbývalo jen najít způsob, jak identifikovat problémové servery, aby bylo možné porozumět rozsahu a rozhodnout o dalším postupu. Požadované počítadlo bylo nalezeno dostatečně rychle:

netstat -s | grep "packet reassembles failed”

Je také v snmpd pod OID=1.3.6.1.2.1.4.31.1.1.16.1 (ipSystemStatsReasmFails).

„Počet selhání zjištěných algoritmem opětovného sestavení IP (z jakéhokoli důvodu: vypršel časový limit, chyby atd.)

Ve skupině serverů, na kterých byl problém studován, se na dvou tento čítač zvyšoval rychleji, na dvou pomaleji a na dvou dalších se nezvyšoval vůbec. Porovnání dynamiky tohoto čítače s dynamikou chyb HTTP na serveru Java odhalilo korelaci. To znamená, že měřič by mohl být monitorován.

Mít spolehlivý indikátor problémů je velmi důležité, abyste mohli přesně určit, zda vrácení Sysctl pomáhá, protože z předchozího příběhu víme, že to nelze z aplikace okamžitě pochopit. Tento indikátor by nám umožnil identifikovat všechny problémové oblasti ve výrobě, než to uživatelé objeví.
Po vrácení Sysctl se chyby sledování zastavily, tím se prokázala příčina problémů a také to, že rollback pomáhá.

Vrátili jsme zpět nastavení fragmentace na jiných serverech, kde do hry vstoupilo nové monitorování, a někde jsme fragmentům přidělili ještě více paměti, než bylo dříve výchozí (toto byla statistika UDP, jejíž částečná ztráta nebyla na obecném pozadí patrná) .

Nejdůležitější otázky

Proč jsou pakety fragmentovány na našem balanceru L3? Většina paketů, které přicházejí od uživatelů k balancérům, jsou SYN a ACK. Velikosti těchto balíčků jsou malé. Ale protože podíl takových paketů je velmi velký, na jejich pozadí jsme nezaznamenali přítomnost velkých paketů, které se začaly fragmentovat.

Důvodem byl nefunkční konfigurační skript advmss na serverech s rozhraním Vlan (v té době bylo ve výrobě velmi málo serverů s označeným provozem). Advmss nám umožňuje předat klientovi informaci, že pakety v našem směru by měly mít menší velikost, aby po připojení hlaviček tunelu k nim nemusely být fragmentovány.

Proč vrácení Sysctl nepomohlo, ale restart ano? Odvolání Sysctl změnilo množství paměti dostupné pro slučování balíčků. Zároveň zřejmě samotný fakt přetečení paměti pro fragmenty vedl ke zpomalení připojení, což vedlo k dlouhému zdržení fragmentů ve frontě. To znamená, že proces probíhal v cyklech.
Restart vyčistil paměť a vše se vrátilo do pořádku.

Bylo možné obejít se bez řešení? Ano, ale existuje vysoké riziko, že uživatelé zůstanou bez služby v případě útoku. Použití Workaround samozřejmě vedlo k různým problémům, včetně zpomalení jedné ze služeb pro uživatele, ale přesto věříme, že akce byly oprávněné.

Mnohokrát děkuji Andrey Timofeevovi (atimofejev) za pomoc při vedení vyšetřování, stejně jako Alexey Krenev (zařízeníx) - za titánskou práci aktualizace Centos a jader na serverech. Proces, který v tomto případě musel být zahájen od začátku několikrát, a proto se vlekl řadu měsíců.

Zdroj: www.habr.com

Přidat komentář