3 puan yazan GN⁺ 2024-04-06 | 1 yorum | WhatsApp'ta paylaş
  • HTTP/2 CONTINUATION Flood, END_HEADERS olmadan 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, CONTINUATION iş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
  • HEADERS frame’i istek ve yanıtların HTTP header’larını gönderir; header verisi HPACK ile encode edilmiş field block fragment içinde tutulur
  • HEADERS frame’inde header’ların ve stream’in sonunu bildiren flag’ler bulunur
    • END_HEADERS: Bu frame’in gönderilmek istenen tüm header’ları içerdiğini belirten sinyal
    • END_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 HEADERS frame’ine sığmazsa, END_HEADERS olmayan bir HEADERS frame’inin ardından CONTINUATION frame’leri gelir
    • END_HEADERS olmayan HEADERS frame’i
    • END_HEADERS olmayan ek CONTINUATION frame’leri
    • Son CONTINUATION frame’inde END_HEADERS ayarlanır
  • Son header frame’inden sonra istek verisini içeren DATA frame’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 HEADERS ve CONTINUATION frame’leri gönderirken END_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: CONTINUATION header’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: CONTINUATION stream’inin ortasında bağlantı koptuğunda uygulama hatası nedeniyle sunucu crash olur
  • END_HEADERS olmadığı 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, CONTINUATION Flood’da CPU tükenmesinin öne çıktığı bir örnektir
  • Go uygulaması http2MetaHeadersFrame soyutlamasıyla tek bir HEADERS frame’ini, sıfır veya daha fazla CONTINUATION frame’ini ve HPACK decoder’ını bir araya getirir
  • readMetaFrame, header boyutu sınırına ulaşıldığında veya hata oluştuğunda SetEmitEnabled(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ğeri true olduğunda durur; bu da END_HEADERS flag’i ayarlandığında gerçekleşir
  • Saldırgan END_HEADERS göndermezse readMetaFrame geri 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, CONTINUATION frame’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 CONTINUATION frame’ini birkaç saniyede bir bayt bayt göndererek bağlantıyı canlı tutma yöntemi mümkündü
  • CONTINUATION Flood 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şarsa PROTOCOL_ERROR ile oturum hatası döndüren bir kontrol eklendi

Node.js örneği: bağlantı koparken assertion crash’i

  • Node.js sonsuz CONTINUATION frame 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çinde CHECK_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 Http2Session destructor’ı 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’daki session_.reset() sonrasında tüm nghttp2 artefact’lerinin bellekten kaldırıldığını doğrular
  • İnceleme sonucunda, CONTINUATION frame’i parse edilirken nghttp2 callback’leri ile reset()’in birlikte çalıştığı durumlar olduğu görüldü
    • NGHTTP2_IB_EXPECT_CONTINUATION durumunda CONTINUATION frame’i gelir
    • Durum NGHTTP2_IB_READ_HEADER_BLOCK olarak değişir
    • session_after_header_block_received, session_call_on_frame_received, on_frame_recv_callback akışı devam eder
    • Node.js’in OnFrameReceive ve HandleHeadersFrame içinde bellek sayaçları güncellenir
  • HandleHeadersFrame ile Http2Session::~Http2Session() aynı anda çalışırsa current_session_memory_ eşzamanlı olarak güncellenir, current_nghttp2_memory_ değeri negatife düşer ve CHECK_EQ baş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
  • CONTINUATION Flood, 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_PROMISE frame’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
  • CONTINUATION Flood 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_STREAM ve END_HEADERS ayarlanmış HEADERS frame’i ile RST_STREAM frame’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
  • CONTINUATION Flood’da END_HEADERS olmadığı için tek bir istek bile tamamlanmaz ve yöneticiler loglarda isteği göremez
  • Birçok uygulamada CONTINUATION Flood, 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

 
GN⁺ 2024-04-06
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

    • Varsayım yapınca ne olduğunu biliyorsun. Seni ve beni ön sayfa manşeti yapar
  • 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ı

    • İnsanlar sürekli normal akışa odaklanıp onu optimize ediyor, ama bir saldırgan en kötü durumu kasıtlı olarak tekrar tekrar tetiklerse ne olur diye durup düşünmüyor
      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/

    • Başka bir deyişle, 10 yıldır hizmet reddi riski nedeniyle CONTINUATION kullanımına karşı çıkan uygulamalar bunlar. Uzun tartışma dizilerini okursanız, meselenin her zaman baş belası CONTINUATION’dan nasıl kaçınılacağı olduğunu görürsünüz: https://lists.w3.org/Archives/Public/ietf-http-wg/2014JulSep...
      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
    • Caddy nasıl? Harika bir proje, ayrı bir satırı hak 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/1.1’i uygulamak çok daha kolay, dolayısıyla daha az bug olacağını düşünmek makul
      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
    • Sırf yükseltmiş olmak için yükseltmek iyi bir mühendislik pratiği değildir. Yükseltme ek bir fayda sağlamıyorsa gerekçelendirilmesi zordur
    • O kadar da değil. HTTP/1.1’in basit olduğunu söyleyenler, gerçek dünyayla uyumlu eksiksiz bir parser hiç uygulamamış demektir
      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
    • Ben de bunu merak etmiştim. Daha olgun ve daha az karmaşık olduğu için daha güvenli olma ihtimali var gibi görünüyor
    • Muhtemelen öyledir. HTTP/2 streaming için iyidir; o bile daha yeni protokollerle değiştiriliyor
      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ı