Hati-hati terhadap kerentanan yang membawa solusi. Bagian 1: FragmentSmack / SegmentSmack



Halo semuanya! Nama saya Dmitry Samsonov, saya bekerja sebagai administrator sistem terkemuka di Odnoklassniki. Kami memiliki lebih dari 7 ribu server fisik, 11 ribu kontainer di cloud kami dan 200 aplikasi, yang dalam konfigurasi berbeda membentuk 700 cluster berbeda. Sebagian besar server menjalankan CentOS 7.
Informasi Kerentanan FragmenKemungkinan Dirilis 14 Agustus 2018
( CVE-2018-5391 ) dan SegmentSmack ( CVE-2018-5390 ). Ini adalah kerentanan dengan vektor serangan jaringan dan peringkat yang agak tinggi (7,5), yang mengancam dengan penolakan layanan (DoS) karena kelelahan sumber daya (CPU). Perbaikan di kernel untuk FragmentSmack tidak diusulkan pada waktu itu, apalagi, itu keluar lebih lambat daripada publikasi informasi tentang kerentanan. Untuk menghilangkan SegmentSmack, diusulkan untuk memperbarui kernel. Paket pembaruan itu sendiri dirilis pada hari yang sama, yang tersisa hanyalah menginstalnya.
Tidak, kami sama sekali tidak menentang pembaruan kernel! Namun, ada nuansa ...

Bagaimana cara kami memperbarui inti pada prod


Secara umum, tidak ada yang rumit:
  1. Paket unduhan
  2. Instal mereka di sejumlah server (termasuk server hosting cloud kami);
  3. Pastikan tidak ada yang rusak;
  4. Pastikan bahwa semua pengaturan kernel standar berlaku tanpa kesalahan;
  5. Tunggu beberapa hari;
  6. Periksa kinerja server;
  7. Alihkan penyebaran server baru ke kernel baru;
  8. Perbarui semua server dengan pusat data (satu pusat data pada satu waktu untuk meminimalkan efek bagi pengguna jika terjadi masalah);
  9. Mulai ulang semua server.

Ulangi untuk semua cabang inti yang kita miliki. Saat ini:

  • Stock CentOS 7 3.10 - untuk sebagian besar server biasa;
  • Vanilla 4.19 adalah untuk cloud kami karena kami membutuhkan BFQ, BBR, dll.
  • Elrepo kernel-ml 5.2 adalah untuk distributor yang sangat banyak memuat , karena 4.19 digunakan untuk berperilaku tidak stabil, dan fitur memerlukan yang sama.

Seperti yang sudah Anda duga, me-reboot ribuan server membutuhkan waktu paling lama. Karena tidak semua kerentanan sangat penting untuk semua server, kami hanya memulai kembali yang dapat diakses langsung dari Internet. Di cloud, agar tidak membatasi fleksibilitas, kami tidak mengikat wadah yang dapat diakses secara eksternal untuk masing-masing server dengan inti baru, tetapi reboot semua host tanpa kecuali. Untungnya, prosedur di sana lebih mudah daripada dengan server biasa. Misalnya, wadah stateless dapat dengan mudah pindah ke server lain selama reboot.

Namun demikian, masih ada banyak pekerjaan, dan itu bisa memakan waktu beberapa minggu, dan jika ada masalah dengan versi baru - hingga beberapa bulan. Penyerang sangat menyadari hal ini, jadi rencana B diperlukan.

FragmentSmack / SegmentSmack. Penanganan masalah


Untungnya, untuk beberapa kerentanan, ada rencana "B", dan itu disebut Workaround. Paling sering, ini adalah perubahan dalam pengaturan kernel / aplikasi, yang dapat meminimalkan efek yang mungkin atau sepenuhnya menghilangkan eksploitasi kerentanan.

Dalam kasus FragmentSmack / SegmentSmack , solusi berikut ini diusulkan:

β€œ Anda dapat mengubah nilai default 4MB dan 3MB di net.ipv4.ipfrag_high_thresh dan net.ipv4.ipfrag_low_thresh (dan analognya untuk ipv6 net.ipv6.ipfrag_high_thresh dan net.ipv6.ipfrag_low_thresh) masing-masing dengan 256 kB, dan 192 kB, masing-masing dengan 192 kB, dan 192 kB. Pengujian menunjukkan sedikit penurunan signifikan dalam penggunaan CPU selama serangan, tergantung pada peralatan, pengaturan, dan kondisi. Namun, mungkin ada beberapa dampak kinerja karena ipfrag_high_thresh = 262144 bytes, karena hanya dua fragmen 64K yang dapat masuk dalam antrean rekondisi sekaligus. Misalnya, ada risiko bahwa aplikasi yang bekerja dengan paket UDP besar akan rusak . "

Parameter itu sendiri dalam dokumentasi kernel dijelaskan sebagai berikut:

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.

Kami tidak memiliki UDP besar pada layanan produksi. Tidak ada lalu lintas terfragmentasi pada LAN, ada, tetapi tidak signifikan, lalu lintas di WAN. Tidak ada yang menjadi pertanda - Anda dapat memutar solusi!

FragmentSmack / SegmentSmack. Darah pertama


Masalah pertama yang kami temui adalah bahwa wadah cloud terkadang hanya menerapkan sebagian pengaturan baru (hanya ipfrag_low_thresh), dan kadang-kadang mereka tidak menggunakannya sama sekali - mereka hanya macet di awal. Itu tidak mungkin untuk mereproduksi masalah secara stabil (secara manual semua pengaturan diterapkan tanpa kesulitan). Memahami mengapa wadah jatuh di awal juga tidak begitu sederhana: tidak ada kesalahan yang ditemukan. Satu hal yang pasti: memutar kembali pengaturan memecahkan masalah menjatuhkan wadah.

Mengapa tidak cukup menggunakan Sysctl di host? Kontainer hidup di Namespace jaringan khusus, sehingga setidaknya bagian dari parameter Sysctl jaringan dalam wadah mungkin berbeda dari host.

Bagaimana tepatnya pengaturan Sysctl dalam wadah berlaku? Karena kita memiliki wadah yang tidak terjangkau, mengubah pengaturan Sysctl dengan masuk ke wadah itu sendiri akan gagal - tidak akan ada cukup hak. Saat itu, cloud kami menggunakan Docker (sekarang Podman ) untuk meluncurkan kontainer. Buruh pelabuhan melalui API melewati parameter wadah baru, termasuk pengaturan Sysctl yang diperlukan.
Dalam proses penghitungan versi, ternyata Docker API tidak membuang semua kesalahan (setidaknya dalam versi 1.10). Ketika mencoba memulai wadah melalui "docker run", kami akhirnya melihat setidaknya sesuatu:

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.

Nilai parameter tidak valid. Tapi mengapa? Dan mengapa kadang-kadang itu tidak valid? Ternyata Docker tidak menjamin penerapan parameter Sysctl (versi teruji terbaru adalah 1.13.1), jadi kadang-kadang ipfrag_high_thresh mencoba mengatur sendiri ke 256K ketika ipfrag_low_thresh masih 3M, mis. Batas atas lebih rendah daripada yang lebih rendah, yang menyebabkan kesalahan.

Pada saat itu, kami sudah menggunakan mekanisme kami sendiri untuk mengkonfigurasi ulang wadah setelah memulai (membekukan wadah melalui cgroup freezer dan menjalankan perintah di namespace wadah melalui ip netn ), dan kami juga menambahkan parameter Sysctl ke bagian ini. Masalahnya telah diatasi.

FragmentSmack / SegmentSmack. Darah pertama 2


Sebelum kami tahu cara menggunakan Penanganan Masalah di cloud, keluhan langka pertama dari pengguna mulai berdatangan. Pada saat itu, beberapa minggu berlalu sejak dimulainya penyelesaian masalah di server pertama. Penyelidikan awal menunjukkan bahwa keluhan diterima tentang layanan individual, dan tidak semua server layanan ini. Masalahnya telah mendapatkan kembali karakter yang sangat kabur.

Pertama-tama, tentu saja, kami mencoba untuk memutar kembali pengaturan Sysctl, tetapi ini tidak memberikan efek apa pun. Berbagai manipulasi dengan pengaturan server dan aplikasi juga tidak membantu. Reboot membantu. Reboot untuk Linux sama tidak wajarnya dengan kondisi normal untuk bekerja dengan Windows di masa lalu. Namun demikian, itu membantu, dan kami menulis semuanya menjadi "kesalahan dalam kernel" ketika menerapkan pengaturan baru di Sysctl. Betapa remehnya itu ...

Tiga minggu kemudian, masalahnya terulang kembali. Konfigurasi server ini cukup sederhana: Nginx dalam mode proxy / balancer. Lalu lintas sedikit. Pengantar baru: jumlah 504 kesalahan ( Gateway Timeout ) meningkat setiap hari pada klien. Grafik menunjukkan jumlah 504 kesalahan per hari untuk layanan ini:



Semua kesalahan adalah tentang backend yang sama - tentang kesalahan yang ada di cloud. Grafik konsumsi memori untuk fragmen paket di backend ini adalah sebagai berikut:



Ini adalah salah satu manifestasi paling mencolok dari masalah pada grafik sistem operasi. Di cloud, pada saat yang sama, masalah jaringan lain telah diperbaiki dengan pengaturan QoS (Traffic Control). Pada grafik konsumsi memori untuk fragmen paket, tampilannya persis sama:



Asumsinya sederhana: jika mereka terlihat sama pada grafik, maka mereka memiliki alasan yang sama. Terlebih lagi, masalah dengan memori jenis ini sangat jarang.

Inti dari masalah yang diperbaiki adalah bahwa kami menggunakan fq paket sheduler dengan pengaturan default di QoS. Secara default, untuk satu koneksi, ini memungkinkan Anda untuk menambahkan 100 paket ke antrian, dan beberapa koneksi dalam situasi kekurangan saluran mulai menyumbat antrian. Dalam hal ini, paket turun. Dalam statistik tc (tc -s qdisc), ini dapat dilihat sebagai berikut:

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 flow_plimit" - ini adalah paket yang dibatalkan karena melebihi batas antrian satu koneksi, dan "turun 464545" adalah jumlah dari semua paket yang dijatuhkan dari sheduler ini. Setelah menambah panjang antrian menjadi 1.000 dan memulai kembali wadah, masalah tidak lagi muncul. Anda dapat duduk di kursi dan menikmati smoothie.

FragmentSmack / SegmentSmack. Darah terakhir


Pertama, beberapa bulan setelah pengumuman kerentanan di kernel, akhirnya perbaikan untuk FragmentSmack muncul (saya ingat bahwa dengan pengumuman pada bulan Agustus perbaikan dirilis hanya untuk SegmentSmack), yang memberi kami kesempatan untuk meninggalkan Workaround, yang menyebabkan kami banyak masalah. Beberapa server selama ini kami sudah berhasil mentransfer ke kernel baru, dan sekarang kami harus mulai dari awal. Mengapa kami memperbarui kernel tanpa menunggu perbaikan FragmentSmack? Faktanya adalah bahwa proses melindungi terhadap kerentanan ini bertepatan (dan digabung) dengan proses memperbarui CentOS itu sendiri (yang bahkan membutuhkan waktu lebih lama daripada hanya memperbarui kernel). Selain itu, SegmentSmack adalah kerentanan yang lebih berbahaya, dan perbaikan untuk itu muncul segera, jadi intinya adalah dalam hal apa pun. Namun, kami tidak bisa begitu saja memperbarui kernel pada CentOS, karena kerentanan FragmentSmack, yang muncul selama CentOS 7.5, diperbaiki hanya dalam versi 7.6, jadi kami harus menghentikan upgrade ke 7.5 dan mulai dari awal lagi dengan upgrade ke 7.6. Begitulah.

Kedua, keluhan pengguna yang jarang tentang masalah telah kembali kepada kami. Sekarang kita sudah tahu pasti bahwa semuanya terhubung dengan unduhan file dari klien ke beberapa server kami. Dan melalui server ini ada sejumlah kecil unggahan dari total massa.

Seperti yang kita ingat dari kisah di atas, kemunduran Sysctl tidak membantu. Reboot membantu, tetapi sementara.
Kecurigaan dengan Sysctl tidak dicabut, tetapi kali ini diminta untuk mengumpulkan informasi sebanyak mungkin. Juga, ada sangat kekurangan kemampuan untuk mereproduksi masalah dengan unggahan klien untuk memeriksa dengan lebih akurat apa yang terjadi.

Analisis dari semua statistik dan log yang tersedia tidak membawa kita lebih dekat untuk memahami apa yang terjadi. Ada kekurangan akut untuk mereproduksi masalah untuk "merasakan" koneksi tertentu. Akhirnya, pengembang pada versi khusus aplikasi berhasil mencapai reproduksi masalah yang stabil pada perangkat uji saat terhubung melalui Wi-Fi. Ini adalah terobosan dalam investigasi. Klien terhubung ke Nginx, itu diproksi ke backend, yang merupakan aplikasi Java kami.



Dialog dengan masalah adalah sebagai berikut (diperbaiki pada sisi proxy Nginx):

  1. Klien: meminta informasi tentang mengunduh file.
  2. Server Java: jawab.
  3. Klien: POST dengan file.
  4. Server Java: kesalahan.

Pada saat yang sama, server Java menulis ke log bahwa 0 byte data diterima dari klien, dan proksi Nginx yang dibutuhkan oleh permintaan lebih dari 30 detik (30 detik adalah batas waktu aplikasi klien). Mengapa batas waktu dan mengapa 0 byte? Dari sudut pandang HTTP, semuanya berfungsi sebagaimana mestinya, tetapi POST dengan file tersebut tampaknya hilang dari jaringan. Dan itu menghilang antara klien dan Nginx. Saatnya mempersenjatai diri dengan Tcpdump! Tetapi pertama-tama Anda harus memahami konfigurasi jaringan. Proxy nginx ada di belakang penyeimbang N3ware L3. Tunneling digunakan untuk mengirimkan paket dari penyeimbang L3 ke server, yang menambahkan header ke paket:



Pada saat yang sama, jaringan datang ke server ini dalam bentuk lalu lintas yang ditandai Vlan, yang juga menambahkan bidangnya ke paket:



Dan lalu lintas ini dapat terfragmentasi (persentase sangat kecil dari lalu lintas terpecah yang masuk yang kita bicarakan ketika menilai risiko dari Penanganan Masalah), yang juga mengubah isi tajuk:



Sekali lagi: paket dienkapsulasi oleh tag Vlan, dienkapsulasi oleh sebuah terowongan, terfragmentasi. Untuk lebih memahami bagaimana ini terjadi, mari kita lacak rute paket dari klien ke proksi Nginx.

  1. Paket sampai ke L3-balancer. Untuk perutean yang benar di dalam pusat data, paket dienkapsulasi dalam terowongan dan dikirim ke kartu jaringan.
  2. Karena header paket + terowongan tidak cocok dengan MTU, paket dipotong menjadi beberapa bagian dan dikirim ke jaringan.
  3. Switch setelah L3-balancer ketika menerima paket menambahkan tag Vlan dan mengirimkannya lebih lanjut.
  4. Switch sebelum proksi Nginx melihat (sesuai dengan pengaturan port) bahwa server mengharapkan paket enkapsulasi Vlan, sehingga ia mengirimkannya apa adanya, tanpa menghapus tag Vlan.
  5. Linux menerima fragmen paket individual dan menempelkannya ke dalam satu paket besar.
  6. Kemudian paket tersebut sampai ke antarmuka-Vlan, di mana lapisan pertama dihapus dari itu - Vlan-enkapsulasi.
  7. Linux kemudian mengirimkannya ke antarmuka Tunnel, tempat lapisan lain dihapus darinya - Enkapsulasi terowongan.

Kesulitannya adalah meneruskan semua ini sebagai parameter ke tcpdump.
Mari kita mulai dari akhir: apakah ada paket IP yang bersih (tanpa header tambahan) dari klien dengan enkapsulasi vlan dan tunnel dihapus?

tcpdump host <ip >

Tidak, tidak ada paket seperti itu di server. Karena itu, masalahnya harus lebih awal. Apakah ada paket dengan enkapsulasi Vlan saja yang dihapus?

tcpdump ip[32:4]=0xx390x2xx

0xx390x2xx adalah alamat IP klien dalam format hex.
32: 4 - alamat dan panjang bidang di mana SCR IP ditulis dalam paket Tunnel.

Alamat lapangan harus dipilih dengan kekerasan, karena Internet menulis sekitar 40, 44, 50, 54, tetapi tidak ada alamat IP. Anda juga dapat melihat salah satu paket dalam hex (parameter -xx atau -XX di tcpdump) dan menghitung alamat IP yang diketahui.

Apakah ada fragmen paket tanpa enkapsulasi Vlan dan Tunnel dihapus?

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

Sihir ini akan menunjukkan kepada kita semua fragmen, termasuk yang terakhir. Mungkin, hal yang sama dapat disaring oleh IP, tetapi saya tidak mencoba, karena tidak ada paket yang sangat banyak, dan yang saya butuhkan mudah ditemukan di aliran umum. Inilah mereka:

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 .faEE.....@.2.m.
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.....j Ex
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 ..............


Ini adalah dua fragmen dari satu paket (ID yang sama 53652) dengan foto (kata Exif terlihat dalam paket pertama). Karena kenyataan bahwa ada paket di tingkat ini, tetapi tidak terpaku pada dump, masalahnya jelas dengan perakitan. Akhirnya, ada bukti dokumenter tentang ini!

Paket decoder tidak mengungkapkan masalah apa pun yang mencegah perakitan. Mencoba di sini: hpd.gasmi.net . Pada awalnya, ketika mencoba menjejalkan sesuatu di sana, decoder tidak suka format paket. Ternyata ada dua oktet tambahan antara Srcmac dan Ethertype (tidak terkait dengan informasi fragmen). Setelah menghapusnya, dekoder bekerja. Namun, dia tidak menunjukkan masalah.
Katakan apa yang Anda suka, kecuali untuk Sysctl itu tidak ada lagi yang ditemukan. Tetap menemukan cara untuk mengidentifikasi server bermasalah untuk memahami skala dan memutuskan tindakan lebih lanjut. Cukup cepat saya menemukan penghitung yang tepat:

netstat -s | grep "packet reassembles failed”

Itu dalam snmpd di bawah OID = 1.3.6.1.2.1.4.31.1.1.16.1 ( ipSystemStatsReasmFails ).

"Jumlah kegagalan yang terdeteksi oleh algoritma perakitan ulang IP (untuk alasan apa pun: batas waktu, kesalahan, dll.)."

Di antara kelompok server di mana masalah dipelajari, pada dua penghitung ini meningkat lebih cepat, pada dua - lebih lambat, dan pada dua tidak meningkat sama sekali. Perbandingan dinamika penghitung ini dengan dinamika kesalahan HTTP pada server Java mengungkapkan korelasi. Artinya, penghitung bisa diatur untuk pemantauan.

Memiliki indikator masalah yang dapat diandalkan sangat penting sehingga Anda dapat menentukan secara akurat apakah Sysctl rollback membantu, karena kami tahu dari cerita sebelumnya bahwa ini tidak segera jelas dari aplikasi. Indikator ini akan memungkinkan untuk mengidentifikasi semua area masalah dalam produksi sebelum pengguna menemukannya.
Setelah rollback Sysctl, kesalahan pemantauan berhenti, sehingga penyebab masalah terbukti, serta fakta bahwa rollback membantu.

Kami memutar kembali pengaturan fragmentasi pada server lain, di mana pemantauan baru terbakar, dan di suatu tempat kami mengalokasikan lebih banyak memori untuk fragmen daripada sebelumnya secara default (ini adalah udp-statistik, kehilangan sebagian yang tidak terlihat dengan latar belakang umum).

Pertanyaan paling penting


Mengapa paket fragmen pada penyeimbang L3 kami? Sebagian besar paket yang datang dari pengguna ke penyeimbang adalah SYN dan ACK. Ukuran tas ini kecil. Tetapi karena pangsa paket tersebut sangat besar, dengan latar belakang mereka, kami tidak melihat adanya paket besar yang mulai terfragmentasi.

Alasannya adalah skrip konfigurasi advmss rusak pada server dengan antarmuka Vlan (ada sangat sedikit server dengan lalu lintas yang ditandai dalam produksi pada waktu itu). Advmss memungkinkan Anda untuk menyampaikan informasi klien bahwa paket-paket dalam arah kami harus lebih kecil sehingga setelah menempelkan header terowongan kepada mereka, mereka tidak harus terfragmentasi.

Mengapa Sysctl rollback tidak membantu, tetapi reboot membantu? Sysctl rollback mengubah jumlah memori yang tersedia untuk menempelkan paket. Pada saat yang sama, rupanya, fakta bahwa memory overflow untuk fragmen menyebabkan penghambatan koneksi, yang menyebabkan fakta bahwa fragmen tertunda dalam antrian untuk waktu yang lama. Artinya, prosesnya adalah perulangan.
Rebut membatalkan memori dan semuanya beres.

Bisakah Anda melakukannya tanpa Penanganan Masalah? Ya, tetapi ada risiko besar meninggalkan pengguna tanpa layanan jika terjadi serangan. Tentu saja, penggunaan Penanganan Masalah sebagai akibatnya menyebabkan berbagai masalah, termasuk penghambatan salah satu layanan oleh pengguna, namun demikian, kami percaya bahwa tindakan tersebut dapat dibenarkan.

Terima kasih banyak kepada Andrei Timofeev ( atimofeyev ) yang telah membantu penyelidikan, dan kepada Alexei Krenev ( devicex ) atas karya raksasa memperbarui Centos dan kernel di server. Proses, yang dalam hal ini harus dimulai beberapa kali dari awal, karena itu berlangsung selama berbulan-bulan.

Source: https://habr.com/ru/post/id475540/


All Articles