3 poin oleh GN⁺ 2024-04-06 | 1 komentar | Bagikan ke WhatsApp
  • HTTP/2 CONTINUATION Flood adalah keluarga kerentanan implementasi HTTP/2 yang dapat meruntuhkan ketersediaan server dengan terus mengirim frame header tanpa END_HEADERS
  • Karena request serangan tidak pernah selesai, ia tidak tercatat di log akses HTTP, dan analisis penyebabnya mungkin memerlukan pemeriksaan byte traffic mentah
  • Bergantung pada implementasinya, dampaknya bervariasi mulai dari kehabisan CPU, OOM dengan banyak koneksi, OOM dengan satu koneksi, hingga crash akibat bug timing saat koneksi diputus
  • Pada kasus Go, Firefox, dan Node.js, masing-masing terungkap masalah decoding HPACK yang terus berjalan, tidak adanya batas ukuran header respons, serta benturan antara pemutusan koneksi saat memproses CONTINUATION dan pembaruan counter memori
  • Berbeda dari Rapid Reset, pada banyak implementasi server dapat dibuat crash hanya dengan satu koneksi TCP, sehingga layanan internet yang luas yang memakai HTTP/2 bisa terdampak

Cara frame CONTINUATION digunakan di HTTP/2

  • HTTP/2 adalah protokol yang bertukar frame biner, bukan saling mengirim baris teks seperti HTTP/1.1
  • Frame HEADERS mengirim header HTTP untuk request dan respons, dan data header disimpan dalam field block fragment yang dienkode dengan HPACK
  • Frame HEADERS memiliki flag yang menandai akhir header dan stream
    • END_HEADERS: sinyal bahwa frame tersebut berisi semua header yang hendak dikirim
    • END_STREAM: sinyal bahwa tidak ada lagi body request atau respons
  • Frame memiliki ukuran maksimum yang ditentukan saat komunikasi dimulai, dan jika frame yang diterima melebihi ukuran yang diizinkan, koneksi diputus sebagai error protokol
  • Jika semua header tidak muat dalam satu frame HEADERS, frame HEADERS tanpa END_HEADERS akan diikuti oleh frame-frame CONTINUATION
    • Frame HEADERS tanpa END_HEADERS
    • Frame-frame CONTINUATION tambahan tanpa END_HEADERS
    • END_HEADERS disetel pada frame CONTINUATION terakhir
  • Setelah frame header terakhir, bisa datang frame DATA yang memuat data request, atau stream HTTP/2 berakhir

Inti kerentanan: stream header yang tak pernah berakhir

  • Jika klien memulai stream HTTP/2 baru lalu mengirim frame HEADERS dan CONTINUATION tanpa pernah menyetel END_HEADERS, server akan terus mencoba mem-parse dan menyimpan stream header tak terbatas
  • Server HTTP/1.1 umumnya memiliki dua mekanisme untuk mencegah header tak terbatas
    • Batas ukuran header yang memutus koneksi jika daftar header melebihi ukuran yang diizinkan
    • Timeout request/header yang memutus koneksi jika request atau header tidak dikirim tepat waktu
  • Pada berbagai implementasi HTTP/2, proteksi semacam ini tidak ada atau diterapkan secara salah, termasuk Apache httpd, Envoy, serta berbagai paket dan codec HTTP/2
  • Hasilnya terbagi menjadi empat jenis bergantung pada implementasi
    • Kehabisan CPU: penggunaan CPU meningkat saat membaca dan mendecode header tambahan, sehingga respons terhadap request lain melambat atau terblokir
    • OOM berbasis banyak koneksi: header CONTINUATION disimpan di memori, dan meski ada batas ukuran daftar header, tidak ada timeout header sehingga tiap koneksi terus menahan memori
    • OOM berbasis satu koneksi: sebagian implementasi terus membaca header sampai memori penuh dan OS menghentikan prosesnya
    • Crash hanya dengan beberapa frame: server crash akibat bug implementasi saat koneksi terputus di tengah stream CONTINUATION
  • Jika tidak ada END_HEADERS, request tidak pernah ditutup dengan benar, sehingga request dari klien berbahaya tidak tersimpan di log akses

Kasus Go: kehabisan CPU karena decoding HPACK tidak berhenti

  • Go adalah contoh menonjol kehabisan CPU pada CONTINUATION Flood
  • Implementasi Go menggabungkan satu frame HEADERS, nol atau lebih frame CONTINUATION, dan decoder HPACK dalam abstraksi http2MetaHeadersFrame
  • readMetaFrame memanggil SetEmitEnabled(false) ketika mencapai batas ukuran header atau terjadi error, sehingga menghentikan emisi header yang telah didecode
  • Namun, bahkan setelah emisi header dihentikan, decoder HPACK tetap mendecode byte input
  • Loop pemasok frame hanya berhenti ketika HeadersEnded() menjadi true, dan itu terjadi ketika flag END_HEADERS disetel
  • Jika penyerang tidak mengirim END_HEADERS, readMetaFrame tidak akan kembali, dan selama penyerang terus mengirim data, decoder HPACK terus memproses byte baru

Kasus OOM dan dampak pada klien Firefox

  • OOM terjadi pada implementasi yang tidak membatasi ukuran daftar header yang dibuat oleh frame CONTINUATION
  • Pada implementasi tanpa timeout header, server dapat dibuat crash hanya dengan satu koneksi HTTP/2
  • Bahkan pada implementasi dengan idle timeout, penyerang bisa membuat beberapa koneksi HTTP/2 menempati RAM mendekati batas per koneksi, lalu mempertahankan koneksi dengan mengirim frame CONTINUATION terakhir byte demi byte setiap beberapa detik
  • CONTINUATION Flood dapat terjadi tidak hanya di server, tetapi juga di sisi klien seperti browser
  • Commit perbaikan Mozilla Firefox menambahkan pemeriksaan yang mengembalikan error sesi PROTOCOL_ERROR jika jumlah ukuran header teragregasi dan ukuran frame baru melebihi network_http_max_response_header_size()

Kasus Node.js: crash assertion saat koneksi diputus

  • Node.js menangani stream frame CONTINUATION tak terbatas itu sendiri dengan tepat, tetapi terjadi data race saat koneksi terputus di tengah stream header
  • Saat kode serangan berjalan, Node.js crash di Http2Session::~Http2Session() karena assertion CHECK_EQ(current_nghttp2_memory_, 0) gagal
  • Crash berkaitan dengan timing persis ketika klien HTTP/2 memutus koneksi dari server Node.js, dan assertion berada di dalam destructor Http2Session
  • Node.js menyertakan library nghttp2 untuk menangani koneksi HTTP/2
  • current_nghttp2_memory_ melacak memori yang dialokasikan di dalam nghttp2, dan setelah session_.reset() pada destructor, ia memeriksa apakah semua artefak nghttp2 telah dihapus dari memori
  • Hasil investigasi menunjukkan ada kasus ketika callback nghttp2 dan reset() berjalan bersamaan saat parsing frame CONTINUATION
    • Frame CONTINUATION tiba dalam status NGHTTP2_IB_EXPECT_CONTINUATION
    • Status berubah menjadi NGHTTP2_IB_READ_HEADER_BLOCK
    • Alur session_after_header_block_received, session_call_on_frame_received, on_frame_recv_callback berlanjut
    • OnFrameReceive dan HandleHeadersFrame milik Node.js memperbarui counter memori
  • Jika HandleHeadersFrame dan Http2Session::~Http2Session() berjalan bersamaan, current_session_memory_ diperbarui secara bersamaan, nilai current_nghttp2_memory_ menjadi negatif, dan CHECK_EQ gagal

Perbedaan dengan kerentanan HTTP/2 tahun 2019

  • Kumpulan kerentanan HTTP/2 yang dilaporkan Netflix dan Google pada 2019 dirangkum dalam CERT/CC Vulnerability Note VU#605641
  • CVE-2019-9516, “0-Length Headers Leak”, adalah masalah yang membuat sebagian implementasi mengalokasikan memori untuk nama dan nilai header berpanjang 0 lalu mempertahankannya sampai sesi berakhir
  • CONTINUATION Flood bukan menggunakan header kosong, melainkan mengirim banyak header acak hingga batas ukuran frame yang ditetapkan server
  • CVE-2019-9518, “Empty Frame Flooding”, adalah masalah pengiriman frame DATA, HEADERS, CONTINUATION, PUSH_PROMISE, dan lainnya dengan payload kosong tanpa end-of-stream, sehingga pihak lawan menghabiskan waktu pemrosesan berlebihan dibanding bandwidth serangan
  • CONTINUATION Flood tidak menggunakan frame kosong, melainkan frame sebesar mungkin untuk menempati memori dan mengonsumsi siklus CPU selama proses decoding

Mengapa ini bisa lebih serius daripada Rapid Reset

  • Pada Oktober 2023, detail “Rapid Reset”, zero-day pada protokol HTTP/2, dipublikasikan dan disebut sebagai “serangan DDoS terbesar hingga saat ini”
  • Rapid Reset menggunakan kombinasi frame HEADERS dengan END_STREAM dan END_HEADERS disetel serta frame RST_STREAM
  • Dalam metode ini, mitigasi standar seperti rate limiting dapat mengurangi dampaknya, dan administrator server dapat melihat banyak request masuk di log serta menerima peringatan
  • Pada CONTINUATION Flood, karena tidak ada END_HEADERS, tidak satu pun request selesai, dan administrator tidak dapat melihat request tersebut di log
  • Pada banyak implementasi, CONTINUATION Flood dapat membuat server crash hanya dengan satu koneksi TCP, dan pada sebagian kasus bahkan hanya dengan data yang sangat sedikit
  • Rapid Reset digunakan dalam serangan DDoS, dan dalam kebanyakan kasus serangan yang berhasil membutuhkan botnet

Dampak yang bisa terjadi pada layanan internet

  • Menurut Cloudflare Radar, traffic HTTP/2 mencakup sekitar 60% dari traffic HTTP manusia, tidak termasuk bot
  • Cloudflare Radar memperkirakan traffic HTTP adalah lebih dari 70% dari seluruh transmisi internet
  • Mengingat pentingnya proyek yang terdampak dan kemudahan eksploitasi, sebagian besar internet sempat terekspos pada kerentanan ini
  • HTTP digunakan bukan hanya untuk situs web, tetapi juga untuk banyak RESTful API
  • Masalah ketersediaan pada API dan situs web penting milik perusahaan maupun pemerintah dapat menimbulkan kerugian jutaan dolar atau kekacauan
  • Jika dieksploitasi, masalah ini bisa sangat sulit di-debug oleh administrator server tanpa pengetahuan HTTP/2
    • Request HTTP berbahaya tidak ditutup dengan benar
    • Request tidak terlihat di log akses server
    • Sebagian besar server HTTP/2 tidak memiliki kemampuan analisis frame tingkat lanjut
    • Data koneksi mentah harus dianalisis secara manual

Pengungkapan terkoordinasi dan respons CERT/CC

  • Keluarga kerentanan ini memiliki risiko besar terhadap keamanan internet
  • Setelah laporan pada Januari 2024, CERT/CC membuka kasus Vulnerability Coordination untuk melacak masalah ini
  • Sejumlah perusahaan teknologi besar dan proyek open source berpartisipasi dalam proses pengungkapan bertanggung jawab terkait masalah ini
  • Karena sulit bagi satu peneliti untuk memeriksa begitu banyak implementasi, masalah yang memengaruhi banyak vendor memerlukan Vulnerability Coordination
  • CERT/CC memublikasikan Vulnerability Note untuk masalah ini, dan note semacam ini hanya dipublikasikan beberapa kali setiap tahun

1 komentar

 
GN⁺ 2024-04-06
Pendapat Hacker News
  • Bulan lalu kami sudah memitigasi masalah persis ini di Bandit
    https://github.com/mtrudel/bandit/blob/main/lib/bandit/http2...
    Dari sudut pandang implementor, sejujurnya ini bagian yang sudah sangat jelas harus dicegah. Saya sudah lama memperhatikannya, dan selama ini mengira implementasi lain tentu juga punya pertahanan

    • Kamu tahu kan apa yang terjadi kalau kita berasumsi. Itu bisa membuat kamu dan saya jadi headline halaman depan
  • Selama beberapa bulan terakhir saya memeriksa puluhan implementasi, dan anehnya bahkan server HTTP/2 utama pun tidak punya perlindungan seperti ini, atau menerapkannya dengan salah
    Pada dasarnya, menurut saya ini akibat budaya pengembangan yang terbiasa membuat semuanya otomatis mengembang secara dinamis dan tidak peduli seberapa besar ukurannya bisa menjadi
    Masalah jenis ini tidak terbatas pada HTTP/2 saja, tetapi kompleksitas HTTP/2 yang parah kemungkinan besar turut berperan. Pada era HTTP/1.x, lebih banyak developer yang terbiasa dengan bahasa seperti C sehingga terus memperhatikan pengelolaan panjang buffer, dan meski alokasi header untuk seluruh request paling-paling hanya butuh beberapa KB, mereka tidak akan membiarkannya tumbuh tanpa batas

    • Orang-orang terus berfokus dan mengoptimalkan happy path, tetapi tidak berhenti untuk memikirkan apa yang terjadi jika penyerang sengaja memicu kondisi terburuk berulang-ulang
      Banyak serangan denial-of-service seperti slowloris dan tabrakan hash parameter kueri menjadi nyata karena penggunaan sumber daya terbatas baru dipikirkan belakangan
  • > Tidak terdampak: Nginx, Jetty, HAProxy, NetScaler, Varnish. [0]
    0: https://nowotarski.info/http2-continuation-flood/

    • Dengan kata lain, ini adalah implementasi yang sejak 10 tahun lalu menentang penggunaan CONTINUATION karena risiko denial-of-service. Kalau membaca thread-thread panjangnya, inti pembahasannya selalu bagaimana menghindari CONTINUATION yang merepotkan: https://lists.w3.org/Archives/Public/ietf-http-wg/2014JulSep...
      Setidaknya kalau usulan untuk melarangnya setelah frame HEADERS yang belum penuh diterima, hasilnya akan lebih kokoh, tetapi itu dianggap bisa membuat pekerjaan encoding itu sendiri lebih sulit. Penyebabnya hal-hal seperti batas byte pada kompresor
      Lucu melihat hal-hal yang sama “ditemukan ulang” setiap 10 tahun. Baru-baru ini ada RESET_STREAM flood yang sudah dikenal, kali ini CONTINUATION, dan sebentar lagi mungkin frame DATA panjang 0, WINDOW_UPDATE 1 byte, serta SETTINGS INITIAL_WINDOW yang membuat CPU bekerja berat. Selama masalah yang sudah diketahui bisa diberi nama dan kalau bisa logo, dunia akan terus memutar sirkus keamanan ini
    • Bagaimana dengan Caddy? Proyek bagus, layak mendapat satu baris tersendiri ;)
  • Artikel sebelumnya dari penulis yang sama yang merangkum web server/reverse proxy yang terdampak
    https://nowotarski.info/http2-continuation-flood/

  • Artikel ini ada di posisi teratas sepanjang hari
    Saya penasaran, untuk situs web dengan trafik kecil, apakah mungkin lebih aman menjalankannya dengan HTTP/1.1 saja?

    • HTTP/1.1 jauh lebih mudah diimplementasikan, jadi masuk akal untuk menganggap bug-nya akan lebih sedikit
      HTTP/2 dan HTTP/3 sangat berbeda dari sisi fitur. Dengan ditambahkannya multiplexing, windowing, HPACK, dan lainnya, koneksi HTTP/1.1 yang hampir stateless berubah menjadi koneksi stateful. Untuk mempertahankan koneksi stateful, data seperti state dan pengaturan harus disimpan, dan dari situlah masalah seperti ini muncul
      Di HTTP/2, multiplexing ditambahkan sehingga karakteristik pertahanannya juga berubah. Misalnya jika koneksi berasal dari request origin CDN, Anda bisa mengizinkan jumlah koneksi yang sedikit dan menyediakan pool kanal multiplexing besar di tiap koneksi, tetapi untuk akses pengguna langsung, Anda mungkin ingin mengizinkan banyak koneksi namun mengurangi jumlah kanal multiplexing per koneksi. Di HTTP/1, hampir semuanya terlihat mirip, sehingga pertahanannya jauh lebih sederhana
    • Upgrade hanya demi upgrade bukan praktik engineering yang baik. Jika upgrade tidak memberikan manfaat tambahan, itu sulit dibenarkan
    • Belum tentu. Orang yang bilang HTTP/1.1 itu sederhana mungkin belum pernah mengimplementasikan parser lengkap yang kompatibel dengan lingkungan nyata
      HTTP/1 punya banyak kondisi batas yang tidak terlalu terlihat dan perilaku pengecualian lama. Format teksnya jauh lebih fleksibel daripada sekadar header yang valid, dan ada juga fitur ambigu seperti header multi-baris, fitur MIME lama, race condition 100-continue, header hop-by-hop khusus, serta body pada GET
      Untungnya RFC HTTP yang baru mendokumentasikan banyak jebakan. Mengimplementasikannya hanya dengan melihat RFC 2616 tidak akan menghasilkan implementasi yang aman
      Ukuran aktual request atau response bisa ditentukan secara bersamaan dengan beberapa cara, dan nilainya bisa saling bertentangan. Selain itu, ukurannya bergantung pada berbagai kombinasi fitur serta nilai header yang membutuhkan aturan parsing aneh demi kompatibilitas mundur, sehingga implementasi HTTP yang “sederhana” bisa tertipu oleh request smuggling
      Apa pun pilihannya, Anda memerlukan implementasi matang yang kokoh dan teruji dengan baik
    • Saya juga penasaran soal itu. Karena lebih matang dan tidak terlalu kompleks, sepertinya mungkin lebih aman
    • Mungkin memang begitu. HTTP/2 bagus untuk streaming, dan itu pun sedang digantikan oleh protokol yang lebih baru
      Untuk penyajian aset statis umum, keunggulannya hanya karena HTTP/1 dibatasi jumlah koneksi per domain sehingga lebih banyak aset bisa dimuat secara paralel. Jika memakai CDN dari domain berbeda, biasanya masalah ini juga bisa dihindari
      Secara teori, HTTP/2 bisa menyajikan aset JavaScript yang tidak di-bundle, tetapi saya belum pernah melihatnya di lingkungan produksi nyata. Kemungkinan karena dalam kebanyakan kasus masih dibutuhkan tahap kompilasi
  • Kalau ini dilakukan pelan-pelan, bisa disebut slowloris v2 ya :(

  • HTTP/2, atau cara menjejalkan “upgrade” lapisan transport ke dalam protokol lapisan aplikasi secara paksa