HTTP/2 CONTINUATION Flood’un Teknik Ayrıntıları
(nowotarski.info)- HTTP/2 CONTINUATION Flood,
END_HEADERSolmadan header frame’leri göndermeye devam ederek sunucu erişilebilirliğini çökertebilen bir HTTP/2 uygulama zafiyetleri ailesidir - Saldırı isteği tamamlanmadığı için HTTP erişim loglarında görünmez; nedeni anlamak için ham trafik baytlarının analiz edilmesi gerekebilir
- Uygulamaya bağlı olarak etki kapsamı CPU tükenmesi, çoklu bağlantı OOM, tek bağlantı OOM ve bağlantı koparma zamanlaması hatası nedeniyle crash’e kadar değişir
- Go, Firefox ve Node.js örneklerinde sırasıyla HPACK decoding’in sürmesi, yanıt header boyutu sınırının eksikliği,
CONTINUATIONişlenirken bağlantı kopması ile bellek sayacı güncellemesinin çakışması ortaya çıktı - Rapid Reset’ten farklı olarak birçok uygulamada tek bir TCP bağlantısıyla bile sunucuyu crash ettirmek mümkün olduğundan, HTTP/2 kullanan çok geniş internet servisleri etkilenebilirdi
HTTP/2’de CONTINUATION frame’inin kullanım şekli
- HTTP/2, HTTP/1.1 gibi metin satırları gönderip alan değil, ikili frame’ler değiş tokuş eden bir protokoldür
HEADERSframe’i istek ve yanıtların HTTP header’larını gönderir; header verisiHPACKile encode edilmiş field block fragment içinde tutulurHEADERSframe’inde header’ların ve stream’in sonunu bildiren flag’ler bulunurEND_HEADERS: Bu frame’in gönderilmek istenen tüm header’ları içerdiğini belirten sinyalEND_STREAM: İstek veya yanıt body’sinin devamının olmadığını belirten sinyal
- Frame’lerin iletişim başlangıcında belirlenen bir maksimum boyutu vardır; alınan frame izin verilen boyutu aşarsa bağlantı protokol hatasıyla kesilir
- Tüm header’lar tek bir
HEADERSframe’ine sığmazsa,END_HEADERSolmayan birHEADERSframe’inin ardındanCONTINUATIONframe’leri gelirEND_HEADERSolmayanHEADERSframe’iEND_HEADERSolmayan ekCONTINUATIONframe’leri- Son
CONTINUATIONframe’indeEND_HEADERSayarlanır
- Son header frame’inden sonra istek verisini içeren
DATAframe’i gelir veya HTTP/2 stream’i sona erer
Zafiyetin özü: bitmeyen header stream’i
- Bir istemci yeni bir HTTP/2 stream’i başlattıktan sonra
HEADERSveCONTINUATIONframe’leri gönderirkenEND_HEADERS’ı asla ayarlamazsa, sunucu sonsuz header stream’ini parse etmeye ve saklamaya devam etmeye çalışır - HTTP/1.1 sunucularında sonsuz header’ları engelleyen genellikle iki mekanizma bulunur
- Header listesinin izin verilen boyutu aşması halinde bağlantıyı kesen header boyutu sınırı
- İstek veya header zamanında iletilmezse bağlantıyı kesen istek/header timeout’u
- Birçok HTTP/2 uygulamasında bu korumalar yoktu veya yanlış uygulanmıştı; buna Apache httpd, Envoy ve çeşitli HTTP/2 paketleri ile codec’ler de dahildi
- Uygulamaya göre sonuçlar dört gruba ayrılır
- CPU tükenmesi: Ek header’lar okunup decode edilirken CPU kullanımı artar; diğer isteklere verilen yanıtlar yavaşlar veya engellenir
- Çoklu bağlantı tabanlı OOM:
CONTINUATIONheader’ları bellekte saklanır; header listesi boyutu sınırı vardır ama header timeout’u olmadığı için her bağlantı belleği işgal etmeye devam eder - Tek bağlantı tabanlı OOM: Bazı uygulamalar bellek dolana kadar header okumaya devam eder ve işletim sistemi süreci sonlandırır
- Birkaç frame ile crash:
CONTINUATIONstream’inin ortasında bağlantı koptuğunda uygulama hatası nedeniyle sunucu crash olur
END_HEADERSolmadığı için istek düzgün kapanmaz; bu nedenle kötü niyetli istemcinin isteği erişim loglarına kaydedilmez
Go örneği: HPACK decoding’in durmamasıyla CPU tükenmesi
- Go,
CONTINUATIONFlood’da CPU tükenmesinin öne çıktığı bir örnektir - Go uygulaması
http2MetaHeadersFramesoyutlamasıyla tek birHEADERSframe’ini, sıfır veya daha fazlaCONTINUATIONframe’ini ve HPACK decoder’ını bir araya getirir readMetaFrame, header boyutu sınırına ulaşıldığında veya hata oluştuğundaSetEmitEnabled(false)çağırarak decode edilmiş header’ların dışarı verilmesini durdurur- Ancak header yayımı durdurulduktan sonra bile HPACK decoder giriş baytlarını decode etmeye devam eder
- Frame besleme döngüsü yalnızca
HeadersEnded()değeritrueolduğunda durur; bu daEND_HEADERSflag’i ayarlandığında gerçekleşir - Saldırgan
END_HEADERSgöndermezsereadMetaFramegeri dönmez ve saldırgan veri gönderdiği sürece HPACK decoder yeni baytları işlemeyi sürdürür
OOM örnekleri ve Firefox istemci etkisi
- OOM,
CONTINUATIONframe’leriyle oluşturulan header listesi boyutunu sınırlamayan uygulamalarda ortaya çıkar - Header timeout’u olmayan uygulamalarda tek bir HTTP/2 bağlantısıyla sunucuyu crash ettirmek mümkün olabiliyordu
- idle timeout’u olan uygulamalarda bile birden çok HTTP/2 bağlantısının bağlantı başına sınıra yakın RAM işgal etmesini sağladıktan sonra, son
CONTINUATIONframe’ini birkaç saniyede bir bayt bayt göndererek bağlantıyı canlı tutma yöntemi mümkündü CONTINUATIONFlood yalnızca sunucularda değil, tarayıcılar gibi istemci tarafında da gerçekleşebilir- Mozilla Firefox düzeltme commit’inde, birikmiş header boyutu ile yeni frame boyutunun toplamı
network_http_max_response_header_size()değerini aşarsaPROTOCOL_ERRORile oturum hatası döndüren bir kontrol eklendi
Node.js örneği: bağlantı koparken assertion crash’i
- Node.js sonsuz
CONTINUATIONframe stream’inin kendisini uygun şekilde işliyordu, ancak header stream’i sırasında bağlantı koptuğunda data race oluşuyordu - Saldırı kodu çalışırken Node.js,
Http2Session::~Http2Session()içindeCHECK_EQ(current_nghttp2_memory_, 0)assertion başarısızlığıyla crash oluyordu - Crash, HTTP/2 istemcisinin Node.js sunucusundan bağlantıyı kestiği kesin zamanlamayla ilişkiliydi ve assertion
Http2Sessiondestructor’ı içindeydi - Node.js, HTTP/2 bağlantılarını işlemek için nghttp2 kütüphanesini yerleşik olarak kullanır
current_nghttp2_memory_, nghttp2 içinde ayrılan belleği izler ve destructor’dakisession_.reset()sonrasında tüm nghttp2 artefact’lerinin bellekten kaldırıldığını doğrular- İnceleme sonucunda,
CONTINUATIONframe’i parse edilirken nghttp2 callback’leri ilereset()’in birlikte çalıştığı durumlar olduğu görüldüNGHTTP2_IB_EXPECT_CONTINUATIONdurumundaCONTINUATIONframe’i gelir- Durum
NGHTTP2_IB_READ_HEADER_BLOCKolarak değişir session_after_header_block_received,session_call_on_frame_received,on_frame_recv_callbackakışı devam eder- Node.js’in
OnFrameReceiveveHandleHeadersFrameiçinde bellek sayaçları güncellenir
HandleHeadersFrameileHttp2Session::~Http2Session()aynı anda çalışırsacurrent_session_memory_eşzamanlı olarak güncellenir,current_nghttp2_memory_değeri negatife düşer veCHECK_EQbaşarısız olur
2019 HTTP/2 zafiyetlerinden farkı
- Netflix ve Google’ın 2019’da bildirdiği HTTP/2 zafiyetleri kümesi, CERT/CC Vulnerability Note VU#605641 içinde özetlenmiştir
- CVE-2019-9516, “0-Length Headers Leak”, uzunluğu 0 olan header adı ve değeriyle bazı uygulamaların bellek ayırıp bunu oturum bitene kadar tutmasına neden olan bir sorundur
CONTINUATIONFlood, boş header’lar değil; sunucunun belirlediği frame boyutu sınırına kadar çok sayıda rastgele header gönderme yöntemidir- CVE-2019-9518, “Empty Frame Flooding”, boş payload’lı
DATA,HEADERS,CONTINUATION,PUSH_PROMISEframe’leri vb. end-of-stream olmadan göndererek karşı tarafın saldırı bant genişliğine kıyasla aşırı işlem süresi harcamasına neden olan bir sorundur CONTINUATIONFlood boş frame’ler değil, mümkün olduğunca büyük frame’ler kullanarak belleği işgal eder ve decoding sürecinde CPU cycle’ları tüketir
Rapid Reset’ten daha ciddi olabilmesinin nedeni
- Ekim 2023’te HTTP/2 protokolündeki zero-day “Rapid Reset”in ayrıntıları açıklandı ve “bugüne kadarki en büyük DDoS saldırısı” olarak adlandırıldı
- Rapid Reset,
END_STREAMveEND_HEADERSayarlanmışHEADERSframe’i ileRST_STREAMframe’i kombinasyonunu kullanır - Bu yöntemde rate limiting gibi standart hafifletmeler zararı azaltabilir ve sunucu yöneticileri loglarda çok sayıda inbound isteği görüp uyarı alabilir
CONTINUATIONFlood’daEND_HEADERSolmadığı için tek bir istek bile tamamlanmaz ve yöneticiler loglarda isteği göremez- Birçok uygulamada
CONTINUATIONFlood, tek bir TCP bağlantısıyla sunucuyu crash ettirebiliyordu; bazı örneklerde çok az veriyle bile mümkündü - Rapid Reset DDoS saldırılarında kullanıldı ve çoğu durumda başarılı bir saldırı için botnet gerekiyordu
İnternet servisleri üzerindeki olası etkisi
- Cloudflare Radar’a göre HTTP/2 trafiği, botlar hariç insan HTTP trafiğinin yaklaşık %60’ını oluşturur
- Cloudflare Radar, HTTP trafiğinin toplam internet aktarımının %70’inden fazlası olduğunu tahmin eder
- Etkilenen projelerin önemi ve kolay sömürülebilirliği düşünüldüğünde internetin büyük bir bölümü bu zafiyete açık durumdaydı
- HTTP yalnızca web sitelerinde değil, birçok RESTful API’de de kullanılır
- Kritik şirket ve kamu API’leri ile web sitelerinde erişilebilirlik sorunları milyonlarca dolarlık kayıp veya kargaşa yaratabilir
- Sömürülmüş olsaydı, HTTP/2 bilgisi olmayan sunucu yöneticileri için debug etmesi çok zor olabilirdi
- Kötü niyetli HTTP isteği düzgün kapanmaz
- Sunucu erişim loglarında istek görünmez
- Çoğu HTTP/2 sunucusunda gelişmiş frame analiz özellikleri yoktur
- Ham bağlantı verilerinin elle analiz edilmesi gerekir
Koordineli açıklama ve CERT/CC’nin müdahalesi
- Bu zafiyet ailesi internet güvenliği açısından önemli bir risk taşıyordu
- Ocak 2024’teki bildirimden sonra CERT/CC bu sorunu izlemek için bir Vulnerability Coordination vakası açtı
- Birçok büyük teknoloji şirketi ve açık kaynak projesi ilgili sorumlu açıklama sürecine katıldı
- Tek bir araştırmacının çok sayıda uygulamanın tamamını denetlemesi zor olduğundan, birden çok tedarikçiyi etkileyen sorunlarda Vulnerability Coordination gerekliydi
- CERT/CC bu sorun hakkında bir Vulnerability Note yayımladı; bu tür notlardan yılda yalnızca birkaç tane yayımlanır
1 yorum
Hacker News yorumları
Geçen ay Bandit’te tam da bu sorunu hafifletmiştik
https://github.com/mtrudel/bandit/blob/main/lib/bandit/http2...
Uygulayıcı açısından bakınca, dürüst olmak gerekirse engellenmesi son derece bariz bir nokta. Uzun zamandır buna dikkat ediyordum ve diğer uygulamaların da doğal olarak buna karşı savunma yaptığını varsayıyordum
Son birkaç ayda onlarca uygulamayı kontrol ettim; garip biçimde, büyük HTTP/2 sunucularının bile bu korumaya sahip olmadığını ya da yanlış uyguladığını gördüm
Özünde bunun, her şeyi otomatik olarak dinamik biçimde büyütmeye alışmış ve boyutun ne kadar büyüyebileceğini umursamayan geliştirici kültürünün bir sonucu olduğunu düşünüyorum
Bu tür sorunlar yalnızca HTTP/2’ye özgü değil, ama HTTP/2’nin aşırı karmaşıklığının bunda payı olması çok muhtemel. HTTP/1.x döneminde C gibi dillere alışkın daha çok geliştirici vardı; buffer uzunluğu yönetimini sürekli düşünürlerdi ve tüm istek boyunca header tahsisi en fazla birkaç KB ile yetebilecekken bunu sınırsız büyüyecek şekilde yapmazlardı
slowloris, sorgu parametresi hash çakışmaları gibi pek çok hizmet reddi saldırısı, sınırlı kaynak kullanımının ancak sonradan dikkate alınmasıyla gerçeğe dönüştü
> Etkilenmeyenler: Nginx, Jetty, HAProxy, NetScaler, Varnish. [0]0: https://nowotarski.info/http2-continuation-flood/
En azından dolu olmayan HEADERS frame’lerinden sonra yasaklama önerisi kabul edilseydi daha sağlam olurdu; ama bunun encoding işini daha da zorlaştırabileceği düşünülmüştü. Sıkıştırıcının byte sınırları gibi meseleler yüzünden
Her 10 yılda bir aynı şeylerin “yeniden keşfedilmesi” komik. Yakın zamanda iyi bilinen RESET_STREAM flood vardı, bu kez CONTINUATION var; yakında sıfır uzunluklu DATA frame’leri, 1 byte’lık WINDOW_UPDATE, CPU’yu çok yoran INITIAL_WINDOW SETTINGS gibi şeyler de gelir herhalde. Bilinen bir soruna bir ad ve mümkünse bir logo iliştirilebildiği sürece dünya bu güvenlik sirkinin etrafında dönmeye devam ediyor
Aynı yazarın etkilenen web sunucularını/reverse proxy’leri derlediği önceki yazı
https://nowotarski.info/http2-continuation-flood/
Bu yazı bütün gün en üstte kaldı
Merak ediyorum, trafiği düşük bir web sitesi için yalnızca HTTP/1.1 ile çalışmak daha güvenli olabilir mi?
HTTP/2 ve HTTP/3 özellikler açısından oldukça farklı. Multiplexing, windowing, HPACK gibi şeyler eklenince HTTP/1.1’in neredeyse durum bilgisi tutmayan bağlantısı durum bilgisi tutan bir bağlantıya dönüşüyor. Durum bilgisi tutan bir bağlantıyı sürdürmek için state ve ayarlar gibi verileri saklamak gerekiyor; bu sorunlar da buradan çıkıyor
HTTP/2’de multiplexing eklendiği için savunma özellikleri de değişiyor. Örneğin bağlantı CDN origin isteğinden geliyorsa az sayıda bağlantıya izin verip her bağlantıda büyük bir multiplexing kanal havuzu bulundurabilirsiniz; ama doğrudan kullanıcı erişimi söz konusuysa daha çok bağlantıya izin verip her bağlantıdaki multiplexing kanal sayısını azaltmak isteyebilirsiniz. HTTP/1’de neredeyse herkes benzer göründüğü için savunma çok daha basitti
HTTP/1’de göze pek çarpmayan sınır koşulları ve eski istisna davranışları çoktur. Metin formatı, yalnızca geçerli header’lara baktığınızda sandığınızdan çok daha esnektir; çok satırlı header’lar, eski MIME özellikleri, 100-continue yarış koşulları, özel hop-by-hop header’lar, GET body’si gibi muğlak özellikler de vardır
Neyse ki yeni HTTP RFC’leri bu tuzakların çoğunu belgeliyor. Yalnızca RFC 2616’ya bakarak uygulama yaparsanız güvenli bir uygulama elde etmezsiniz
Bir isteğin ya da yanıtın gerçek boyutu aynı anda birden çok yolla belirtilebilir ve değerler çakışabilir. Ayrıca çeşitli özellik kombinasyonlarına ve geriye dönük uyumluluk için tuhaf parsing kuralları gerektiren header değerlerine bağlı olarak, “basit” bir HTTP uygulaması request smuggling karşısında kandırılabilir
Her iki durumda da sağlam, iyi test edilmiş ve olgun bir uygulama gerekir
Tipik statik asset sunumunda HTTP/1’in tek avantajı, domain başına bağlantı sayısı sınırına takıldığı için daha fazla asset’i paralel yükleyebilmesidir. Farklı domain’lerde CDN kullanırsanız bu sorun da genellikle aşılabilir
Teoride HTTP/2 ile bundle edilmemiş JavaScript asset’leri sunabilirsiniz, ama gerçek üretim ortamlarında buna rastlamadım. Çoğu durumda hâlâ bir compile aşaması gerektiği için olabilir
Bunu yavaş yavaş yaparsanız buna slowloris v2 diyebiliriz :(
HTTP/2, ya da taşıma katmanı “yükseltmesini” uygulama katmanı protokolüne zorla sokuşturma sanatı