2 puan yazan GN⁺ 2023-10-12 | 1 yorum | WhatsApp'ta paylaş
  • curl 8.4.0’ın yayımlanmasıyla ortaya çıkan CVE-2023-38545, SOCKS5 proxy işlemleri sırasında oluşan bir heap buffer overflow’du ve curl güvenlik sorunları arasında nadir görülen HIGH ciddiyetinde bir açıktı
  • Sorun, 2020’de SOCKS5 bağlantı kodunun bloklayan çağrılardan non-blocking state machine yapısına dönüştürülmesi sırasında eklendi ve curl 7.69.0’dan itibaren etkili oldu
  • Uzak ad çözümleme modunda 255 baytı aşan host adlarını yerel çözümlemeye çeviren mevcut mantık, state machine’in yeniden çağrılmasıyla birleşince çok uzun host adının küçük bir buffer’a kopyalanmasına yol açabiliyordu
  • Saldırının mümkün olması için libcurl istemcisinin SOCKS5 proxy-resolver-mode ve otomatik redirect kullanması, saldırganın kontrolündeki HTTPS sunucusunun da 16 KB’den büyük ve 64 KB’den küçük/eşit host adı içeren HTTP 30x Location: değeri döndürmesi gerekiyordu
  • curl 8.4.0’dan itibaren çok uzun host adlarında uzak çözümlemeden yerel çözümlemeye geçmek yerine hata döndürülüyor ve aynı senaryoyu engelleyen bir test eklendi

CVE-2023-38545 ve curl 8.4.0

  • curl 8.4.0 sürümüyle birlikte CVE-2023-38545 güvenlik duyurusu ve ayrıntıları yayımlandı
  • Bu sorun, curl’de uzun süredir görülen en ciddi güvenlik sorunlarından biri olarak değerlendirildi ve ciddiyet seviyesi HIGH olarak belirlendi
  • Temel kusur, SOCKS5 proxy bağlantısı işlenirken belirli koşullarda ortaya çıkan bir heap buffer overflow idi

SOCKS5 ve ad çözümleme yöntemi

  • curl, Ağustos 2002’den beri SOCKS5 destekliyor
  • SOCKS5, ağ iletişimini aradaki bir sunucu üzerinden kuran bir proxy protokolüdür
    • Tor üzerinden iletişim kurmak için kullanılabilir
    • Kurum veya şirket içinden internete erişirken de kullanılabilir
  • SOCKS5’te host adı çözümleme için iki yöntem vardır
    • İstemci host adını yerel olarak çözümler ve çözümlenen adresi proxy’ye iletir
    • İstemci tam host adını proxy’ye gönderir ve proxy bunu uzaktan çözümler

Açığın eklendiği 2020 değişikliği

  • 2020’nin başında SOCKS5 proxy’ye bağlanan fonksiyon, bloklayan çağrıdan non-blocking state machine yapısına dönüştürüldü
  • Bu değişiklik 14 Şubat 2020’de master’a girdi ve curl 7.69.0 içine dahil edildi
  • curl 7.69.0, bu iyileştirmenin yer aldığı ilk sürüm ve sonuç olarak CVE-2023-38545’e karşı açık olan ilk sürüm oldu
  • Değişikliğin amacı, çok sayıda paralel aktarımın hepsi SOCKS5 üzerinden geçtiğinde daha belirgin bir iyileşme sağlamaktı

State machine’de bozulan çözümleme modu

  • State machine fonksiyonu, bağlantı kurulana kadar ağdan daha fazla veri geldikçe tekrar tekrar çağrılır
  • Fonksiyonun başındaki socks5_resolve_local yerel değişkeni, curl’ün host adını kendisinin mi çözümleyeceğini yoksa adı proxy’ye mi ileteceğini gösterir
  • Bu değişken, state machine her çalıştığında fonksiyon çağrısının başında proxy moduna göre yeniden ayarlanır
  • INIT durumundaki koşul sorunu yarattı
    • SOCKS5’in host adı alanı en fazla 255 bayta izin verir
    • Host adı 255 baytı aşarsa SOCKS5 proxy bunu çözümleyemez
    • Mevcut curl kodu, uzak çözümleme modunda çok uzun bir host adıyla karşılaşınca socks5_resolve_local değerini TRUE yaparak yerel çözümleme moduna geçiyordu
  • Kullanıcı uzak çözümleme istemişse curl’ün modu değiştirmek yerine başarısız olması gerekiyordu; ancak uzun zaman önce eklenen bu geçiş davranışı aynen devam etmişti

Heap overflow’un oluştuğu akış

  • SOCKS5 sunucusu yeterince hızlı değilse ve state machine’in ilerlemesi için daha fazla ağ verisi alınamazsa fonksiyon döner
  • Daha sonra veri oluştuğunda aynı state machine fonksiyonu yeniden çağrılır
  • Yeniden çağrıldığında socks5_resolve_local, fonksiyonun başında proxy moduna göre yeniden ayarlanır
    • Önceki çağrıda çok uzun host adı nedeniyle TRUE olan değer korunmaz
    • Değer yeniden proxy’nin adı uzaktan çözümlemesi gereken duruma döner
  • curl, proxy’ye gönderilecek protokol frame’ini bellek buffer’ında oluşturur ve hedef bilgilerini kopyalar
  • Hatalı durum değeri nedeniyle çok uzun host adı olduğu gibi iletilmeye çalışılırsa, ayrılan hedef buffer’ın ötesine geçip bitişikteki heap belleğin üzerine yazılabilir

Buffer boyutu ve host adı kısıtları

  • curl, proxy’ye gönderilecek protokol frame’ini oluştururken normal indirme buffer’ını yeniden kullanır
  • İndirme buffer’ı boyutu koşulları şöyledir
    • Varsayılan değer 16 KB’dir
    • curl aracı buffer boyutunu 100 KB olarak ayarlar
    • Uygulama isteğine göre farklı boyutlar kullanılabilir
    • İzin verilen minimum boyut 1024 bayttır
  • Buffer boyutu 65541 bayttan küçükse bu overflow mümkündür
  • Buffer ne kadar küçükse mümkün olan overflow boyutu o kadar büyür
  • URL’deki host adı için pratikte bir boyut sınırı yoktur, ancak libcurl URL parser’ı 65535 baytı aşan adları reddeder
  • DNS, en fazla 253 bayta kadar host adlarına izin verir
  • 253 baytı aşan yasal adlar nadirdir; 1024 baytı aşan gerçek adları görmekse fiilen çok zordur, bu nedenle saldırı için kasıtlı olarak çok uzun bir host adı gerekir
  • URL’nin host adı alanına yalnızca bazı oktetler girebilir; geçersiz bayt değerleri URL parser tarafından reddedilir
  • libcurl, IDN kütüphanesi kullanacak şekilde derlenmişse bu kütüphane de geçersiz host adlarını reddedebilir

Saldırının gerçekleşmesi için gereken koşullar

  • Saldırgan, libcurl kullanan istemcinin SOCKS5 proxy’ye proxy-resolver-mode ile eriştiği bir HTTPS sunucusunu kontrol etmelidir
  • Saldırı sunucusu, HTTP 30x yanıtıyla manipüle edilmiş bir redirect döndürebilir
  • Redirect’in Location: header’ında şu biçimde çok uzun bir host adı yer alabilir
  • libcurl istemcisinde otomatik redirect takibi açıksa ve SOCKS5 proxy bu yerel değişken sorununu tetikleyecek kadar “yeterince yavaşsa”, manipüle edilmiş host adı çok küçük bir buffer’a kopyalanır
  • Bu koşullar sağlandığında bitişikteki heap belleğe kadar yazma gerçekleşir ve heap buffer overflow oluşur

Düzeltme ve bug bounty

  • curl 8.4.0’dan itibaren çok uzun host adı nedeniyle uzak çözümlemeden yerel çözümlemeye geçilmiyor; bunun yerine hata döndürülüyor
  • Aynı senaryo için özel bir test case de eklendi
  • Bu sorun Jay Satiro tarafından raporlandı, analiz edildi ve yamalandı
  • Bu açık için curl’de bugüne kadar ödenen en büyük bug bounty verildi
    • Raporlayan kişiye 4.660 dolar
    • IBB policy uyarınca curl projesine 1.165 dolar

C ve bellek güvenli diller

  • curl C yerine bellek güvenli bir dilde yazılmış olsaydı bu tür kusurlar ortaya çıkmazdı
  • Ancak curl’ü başka bir dile port etmek şu anda gündemde değil
  • Gerçekçi yaklaşım iki seçeneğe indirgeniyor
    • Bellek güvenli dillerle yazılmış bağımlılıklara daha fazla izin vermek, onları kullanmak ve desteklemek
    • hyper entegrasyonunda olduğu gibi curl’ün bazı bölümlerini kademeli olarak değiştirmek
  • Bu tür geliştirme şu anda çok yavaş ilerliyor ve ilgili zorluklar da açıkça görülüyor
  • curl yakın gelecekte de C ile yazılmış halde kalacak
  • curl 8.4.0’da bildirilen en son 2 CVE de dahil edildiğinde, bugüne kadar curl’de bulunan güvenlik açıklarının %41’i bellek güvenli bir dil kullanılsaydı muhtemelen ortaya çıkmayabilirdi
  • Ancak C ile ilgili sorunların ilk yaklaşık %80’inin eklendiği dönemde Rust bu amaç için pratik bir seçenek değildi

1315 gün sonra bulunan kusur

  • Bu kusur kodda 1315 gün boyunca kaldı
  • Daha iyi bir test seti olsaydı bulunabilirdi
  • curl birçok statik kod analiz aracını düzenli olarak çalıştırıyor, ancak bu fonksiyondaki sorunu hiçbir analiz aracı bulamadı
  • 20 milyardan fazla kurulum ortamına giren kodda heap overflow dağıtmış olmak tavsiye edilecek bir deneyim değildi
  • Yayın öncesi raporlama ve işleme süreci HackerOne report üzerinden görülebilir

1 yorum

 
GN⁺ 2023-10-12
Hacker News yorumları
  • Bu kadar çok cihazın fiilen büyük kısmı tek bir kişi tarafından yazılmış bir kütüphaneye bağımlı olması hâlâ şaşırtıcı. Üzerindeki yük muazzam olmalı; aşağıdaki cümlede de bu görülüyor
    “Şimdi kodu okuyunca hatayı görmemek imkânsız. Bu hatayı fark etmemiş olmamı ve kusurun 1315 gün boyunca kodun içinde keşfedilmeden kalmış olmasını kabullenmek gerçekten acı veriyor. Özür dilerim. Ben sadece insanım.”
    Daniel bunu görürse, bence yoğun emeği için teşekkürler; özür dilemesine hiç gerek yok. Sonuçta kaynak kod herkesin okuyup inceleyebilmesi için açıktı

    • Eskiden OpenSSL’in de bir süre Steve adlı iki kişi tarafından geliştirilip bakımı yapıldığını hatırlatıyor. Şimdi yine de yaklaşık 7 kişilik bir ekibe çıkmış gibi görünüyor
      https://www.buzzfeed.com/chrisstokelwalker/the-internet-is-b...
    • Kesinlikle doğru. HackerOne raporundaki şu cümlede de net görülüyor
      “Bu rapor tamamen doğru görünüyor ve içimi derinden acıtıyor.”
      Blogdaki “2 milyardan fazla kurulum ortamına giren koda heap overflow dağıtma deneyimini tavsiye etmek istemem” ifadesi de öyle; bu kadar büyük bir sorumluluğu bu kadar az karşılıkla taşımak acımasızca
    • Alıntının ilk cümlesinin, insanın öğrenme ve dikkatinin nasıl işlediğinin bir kısmını iyi özetlediğini düşünüyorum
      Kişisel olarak, ne kadar odaklanmaya çalışsam da ne kadar çok şeyi kaçırdığımı sık sık düşünmüşümdür. Kod incelemesi gibi etkinlikler bu yönü, yani dikkat eğitimi ya da bütünsel/bağlamsal farkındalığı geliştirmek için iyi bir yöntem gibi görünüyor. Örneğin yıllardır sıkça yanından geçtiğim bir yerde birinin iyi baktığı bir çiçek tarhı vardı; ilk fark ettikten sonra yaklaşık bir yıl boyunca durup bakardım, ama birkaç ay önce hemen birkaç fit yanında eş gibi duran başka bir çiçek tarhı olduğunu ilk kez fark ettim. O tarhın muhtemelen başından beri orada olma ihtimali yüksek, ama varlığından haberdar olmadığım için kesin olarak bilemiyorum
      Bunu insanlığın toplam bilgisine genişleterek düşününce, bazen tek bir kişinin bir şeyi fark ettiği ve o gözlemin davranışı değiştirerek hepimize yayılmaya başladığı hissine kapılıyorum. Bazen birçok deneme ve uzun zaman gerekiyor. Henüz kimsenin fark etmediği alçakta asılı meyvelerin ne kadar çok olabileceğini de merak ediyorum; ayrıca hâlihazırda dikkatini vermiş insanların ortaya koyduğu basit ve temel iyi uygulamaları bütünleştirmenin bile herkesin taban seviyesini yükseltebileceğini düşünüyorum
      Julia Evans’ın https://jvns.ca/blog/2023/10/06/new-talk--making-hard-things... yazısı ve Dan Luu’nun https://danluu.com/p95-skill/ yazısı da bununla bağlantılı. Sonuç olarak Stenberg’e, curl’e ve her gün doğal kabul ederek kullandığımız internet altyapısının birçok parçasını inşa eden insanlara büyük minnettarlık duyuyorum
    • https://xkcd.com/2347/ yayımlanmadan önce de yıllardır doğruydu, yayımlandıktan sonra da yıllarca doğru olmaya devam ediyor
      Bazen “bozuk değilse tamir etme” ilkesi “bozulana kadar destekleme” diye yorumlanıyor ve oldukça tatsız sürprizlere yol açıyor
    • Tek geliştiricili projelerde katkıda bulunan deneyimi her zaman harikaydı; şirket ya da tedarikçi projeleri ise çoğu zaman oldukça kötüydü. Projeyi iyileştirmek için birlikte çalışma çabasına bakınca tutku açıkça görülüyor
      Sürdürülebilirlik sorunu olabilir, ama bu tür projeler gelecekte de var olmaya devam edecek gibi görünüyor; Daniel gibi yalnızca kendisi için değil topluluk için çalışan herkesi saygıyla selamlıyorum
  • Bellek güvenliği ve bellek güvenli diller hakkındaki sonuç son derece makul. Özeti şu
    curl C yerine bellek güvenli bir dille yazılmış olsaydı bu sınıftaki kusurlar mümkün olmazdı
    Bu yönde uygulanabilir ve makul görünen yaklaşım, bellek güvenli dillerle yazılmış bağımlılıklara daha fazla izin vermek/kullanmak/desteklemek ve hyper’ın devreye alınması gibi curl’ün bazı kısımlarını kademeli olarak parça parça değiştirmek
    Ancak bu geliştirme şu anda neredeyse buzul gibi yavaş ilerliyor ve ilgili zorlukları acı verecek kadar net gösteriyor. curl öngörülebilir gelecekte de C olarak kalacak. Bundan hoşlanmayanlar kolları sıvayıp bizzat çalışabilir
    curl 8.4.0’da bildirilen en yeni iki CVE de dahil edildiğinde, bugüne kadar curl’de bulunan güvenlik açıklarının kümülatif toplamının %41’i, bellek güvenli bir dil kullanılsaydı muhtemelen ortaya çıkmazdı. Ancak Rust, C ile ilgili sorunların ilk yaklaşık %80’inin eklendiği dönemde bu kullanım için pratik bir seçenek değildi
    Çeşitli statik kod analiz araçlarını tekrar tekrar çalıştırdılar, ancak bu fonksiyonda hiçbir sorun bulamadılar

    • Geçen hafta yeni bir fuzzer ve testlerin de eklendiği görülüyor. Bu noktada, güvenlik açısından kritik alanlarda olmayan C projelerinin böyle bir pipeline’ı örnek alması iyi olur
    • curl’ün bazı bölümlerini Rust ile değiştirmek muhtemelen imkânsız olacaktır. Çoğu ortam kaynaktan derlemek ister ve araç zincirine yeni bir derleyici almak istemez. Üstelik Rust, olası C hedef platformlarının yalnızca çok küçük bir kısmını destekliyor
  • Harika bir analiz yazısı. Ancak CVE’yi okuduktan sonra bile hangi durumlarda etkilendiği hâlâ belirsiz. Anladığım kadarıyla aşağıdaki koşullar sağlandığında etkileniliyor:
    SOCKS5 proxy kullanmak, ana makine adını bu proxy üzerinden çözümlemek, tampon boyutunun varsayılan 100 KB’den 65541 baytın altına değiştirilmiş olması ve SOCKS5 proxy’nin isteği hemen işleyemeyecek kadar yavaş olması.
    Düzeltme: Tamponla ilgili açıklama doğru değil. libcurl indirme tamponunu yeniden kullanıyor ve varsayılan değer 16 KB; ancak curl’ün kendisi, --limit-rate kullanılmadığı sürece bunu elle 100 KB’ye ayarlıyormuş. Ayrıca gecikme koşulu da yanlış. CVE’ye göre sıradan bir sunucu gecikmesi bile bu hatayı tetiklemeye yetecek kadar “yavaş” olabilir; saldırganın hizmet engelleme yapması ya da SOCKS sunucusunu kontrol etmesi gerekmezmiş.
    Bu yüzden saldırı yolu çok dar görünüyor; kaçırdığım bir şey var mı merak ediyorum.

    • Son koşul olan “SOCKS5 proxy’nin isteği hemen işleyemeyecek kadar yavaş olması”nı anlamaya çalıştım; ancak istek, herhangi bir gecikme ya da yönlendirme olmadan hemen segmentation fault veriyor.
      root@1aac5e228e16:/build/curl-7.74.0# curl -vvv -x socks5h://host.docker.internal:9050 $(python3 -c "print(('A'10000), end='')")
      Trying 192.168.65.254:9050...
      * SOCKS5: server resolving disabled for hostnames of length > 255 [actual len=10000]
      * SOCKS5 connect to AAAAA...
      * Send failure: Bad file descriptor
      * Failed to send SOCKS5 connect request.
      Segmentation fault
      https://gist.github.com/xen0bit/0dccb11605abbeb6021963e2b1a8...
    • Düzeltilmiş hâline göre doğru gibi görünüyor. Paylaşımlı altyapıda çalışan, saldırganın sağladığı URL’ye istek gönderen ve SOCKS5 proxy kullanan yazılımlarla daha çok ilgili gibi. Örneğin webhook’lar gibi.
    • docker-proxy adında bir shell script paketi yaptım; openconnect VPN çalıştıran bir Docker konteyneri üzerinden SOCKS5 tüneli oluşturuyor. Birden fazla müşterinin farklı VPN’leriyle uğraşırken, her VPN makinedeki tüm trafiği kendi tarafına göndermeyi dayattığında işe yarıyor.
      https://github.com/carlosonunez/docker-proxy
      DNS çözümlemesi uzakta gerçekleşecek şekilde tasarlandığı için bu CVE burada doğrudan geçerli.
    • Bu, Tor gibi darknet’lerde gezinirken felaketi davet eden bir kombinasyon gibi görünüyor.
  • Şimdiye kadar okuduğum CVE analiz yazıları arasında en iyisi denebilir. Çağın temel yazılımlarından birini yapan kişi olmasına rağmen yazı boyunca alçakgönüllü bir tutum sergilemesini de takdir etmemek elde değil. Gerçekten saygı uyandırıcı.

  • if(!socks5_resolve_local && hostname_len > 255) {
    socks5_resolve_local = TRUE;
    }
    Bu gerçekten kötü bir fikir. Sansür aşma araçlarıyla gizliliğini koruyan kişiler için DNS üzerinden kimlikleri sızabilir.

    • “Kullanıcı uzak çözümleme istediyse curl bunu uygulamalı ya da başarısız olmalı; bu şekilde modu değiştirmek bence açıkça yanlış. Normal koşullarda bile sadece geçiş yapınca düzgün çalışma olasılığı düşük.”
      Yazar da aynı görüşte.
  • https://hackerone.com/reports/2187833

  • “curl C yerine bellek güvenli bir dille yazılsaydı bu tür kusurlar imkânsız olurdu” deniyor; ama en fazla imkânsız olmuş olabilirdi denebilir. Dilin sanal makine bariyerinin dışına çıkmanın bir yolu olup olmadığını her zaman bilemeyiz. Ancak yazar, RFC1123’te belirtilen DNS ana makine adı sınırını açıkça göz ardı etmiş ve bu sınır Java kütüphanelerinde de sabit kodlanmış durumda.
    https://www.rfc-editor.org/rfc/rfc1123

    • “Ana makine adı uzunluğu” bölümünde ele alınıyor.
      “URL’deki ana makine adı için pratikte gerçek bir boyut sınırı yoktur; ancak libcurl’ün URL ayrıştırıcısı 65535 bayttan uzun adları reddeder. DNS en fazla 253 baytlık ana makine adlarına izin verir. Bu nedenle 253 bayttan uzun yasal adlar nadirdir. 1024 bayttan uzun gerçek adlar ise fiilen duyulmuş şeyler değildir.”
      DNS bugün vakaların %99,9’unda kullanılıyor olabilir, ancak ana makine adını adrese çözümleyen tek mekanizma değil.
    • Daha somut söyleyebilir misin, merak ediyorum. Gördüğüm tek şey “host yazılımı 63 karaktere kadar olan ana makine adlarını mutlaka işlemeli, 255 karaktere kadar olan ana makine adlarını işlemesi önerilir” ifadesiydi.
      RFC’nin ana makine adı boyutu için üst sınırı nerede belirlediğini bulamadım.
    • Bellek güvenli dil mutlaka sanal makine anlamına gelmez.
      Derleyicinin belleğe sınırların dışından erişilmediğini, açık sahiplik olmadan erişilmediğini ve yalnızca temel nesnenin ömrü içinde erişildiğini kanıtlamaya çalışması anlamına da gelebilir. Örneğin Rust bunu yapar.
      Elbette derleyici kritik noktalara iç hata kontrolleri ve korumalar da ekleyebilir. Rust bunu yapmıyor, ama register içindeki bit flip gibi şeylerden endişe etmeniz gereken sistemlerde olması iyi olabilir. Örneğin X-ray tarayıcıları gibi, ECC belleğin yakalayamadığı yüksek radyasyonlu ortamlarda.
    • RFC metnine göre 255 bayttan uzun ana makine adlarını kabul etmek serbest; ancak böyle adlar oluşturmak serbest değil.
  • Sömürülebilmesi için çok spesifik koşullar gerektiren bir meseleye göre ortada epey abartı ve dram var

    • curl, CVE ile ilgili sorunları birçok kez yaşadı; bu yüzden gerçek güvenlik konularını çok ciddiye aldığını göstermek istiyor gibi. Örneğin bkz. https://daniel.haxx.se/blog/2023/08/26/cve-2020-19909-is-eve...
    • Ayrıca bu, milyarlarca cihazda kurulu. Bu ölçekte dağıtıldığında, çok spesifik koşulların orada burada hızla ortaya çıkabileceğini düşünüyorum
  • Dün yayımlanan Windows 10 güvenlik güncellemesi yeni curl sürümünü içermiyordu. Windows’ta yer alan curl hâlâ 8.0.1

  • Uzun ve ilginç bir yazı ama “Sonuç olarak sistem kütüphaneleri güvenli dillerle yazılmalı ya da doğruluğu kanıtlanmış olmalı” diye özetlenebilir
    Çağımızın en iyi, en nazik ve en şeffaf C programcılarından biri böyle bir yazı yazıyorsa buna dikkat etmek gerekir

    • Eğer o programcı “bu tür geliştirme şu anda neredeyse buzul hızıyla ilerliyor ve ilgili zorlukları acı verecek kadar net gösteriyor. curl öngörülebilir gelecekte de C olarak kalacak” diye yazdıysa, bundan hangi sonuca varmamız gerektiğini merak ediyorum