Detail teknis HTTP/2 CONTINUATION Flood
(nowotarski.info)- 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
CONTINUATIONdan 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
HEADERSmengirim header HTTP untuk request dan respons, dan data header disimpan dalam field block fragment yang dienkode denganHPACK - Frame
HEADERSmemiliki flag yang menandai akhir header dan streamEND_HEADERS: sinyal bahwa frame tersebut berisi semua header yang hendak dikirimEND_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, frameHEADERStanpaEND_HEADERSakan diikuti oleh frame-frameCONTINUATION- Frame
HEADERStanpaEND_HEADERS - Frame-frame
CONTINUATIONtambahan tanpaEND_HEADERS END_HEADERSdisetel pada frameCONTINUATIONterakhir
- Frame
- Setelah frame header terakhir, bisa datang frame
DATAyang 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
HEADERSdanCONTINUATIONtanpa pernah menyetelEND_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
CONTINUATIONdisimpan 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
CONTINUATIONFlood - Implementasi Go menggabungkan satu frame
HEADERS, nol atau lebih frameCONTINUATION, dan decoder HPACK dalam abstraksihttp2MetaHeadersFrame readMetaFramememanggilSetEmitEnabled(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()menjaditrue, dan itu terjadi ketika flagEND_HEADERSdisetel - Jika penyerang tidak mengirim
END_HEADERS,readMetaFrametidak 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
CONTINUATIONterakhir byte demi byte setiap beberapa detik CONTINUATIONFlood dapat terjadi tidak hanya di server, tetapi juga di sisi klien seperti browser- Commit perbaikan Mozilla Firefox menambahkan pemeriksaan yang mengembalikan error sesi
PROTOCOL_ERRORjika jumlah ukuran header teragregasi dan ukuran frame baru melebihinetwork_http_max_response_header_size()
Kasus Node.js: crash assertion saat koneksi diputus
- Node.js menangani stream frame
CONTINUATIONtak 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 assertionCHECK_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 setelahsession_.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 frameCONTINUATION- Frame
CONTINUATIONtiba dalam statusNGHTTP2_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_callbackberlanjut OnFrameReceivedanHandleHeadersFramemilik Node.js memperbarui counter memori
- Frame
- Jika
HandleHeadersFramedanHttp2Session::~Http2Session()berjalan bersamaan,current_session_memory_diperbarui secara bersamaan, nilaicurrent_nghttp2_memory_menjadi negatif, danCHECK_EQgagal
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
CONTINUATIONFlood 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 CONTINUATIONFlood 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
HEADERSdenganEND_STREAMdanEND_HEADERSdisetel serta frameRST_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
CONTINUATIONFlood, karena tidak adaEND_HEADERS, tidak satu pun request selesai, dan administrator tidak dapat melihat request tersebut di log - Pada banyak implementasi,
CONTINUATIONFlood 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
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
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
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/
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
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/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
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
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