2 poin oleh GN⁺ 2023-10-12 | 1 komentar | Bagikan ke WhatsApp
  • CVE-2023-38545 yang terungkap bersama rilis curl 8.4.0 adalah heap buffer overflow yang terjadi saat pemrosesan proxy SOCKS5, dan termasuk kerentanan dengan tingkat keparahan HIGH yang jarang muncul di antara isu keamanan curl
  • Masalah ini masuk pada 2020 saat kode koneksi SOCKS5 diubah dari blocking call menjadi state machine non-blocking, dan terdampak mulai curl 7.69.0
  • Logika lama yang mengubah mode resolusi nama jarak jauh menjadi resolusi lokal saat nama host melebihi 255 byte, berinteraksi dengan pemanggilan ulang state machine sehingga nama host yang terlalu panjang bisa disalin ke buffer kecil
  • Agar serangan berhasil, klien libcurl harus menggunakan SOCKS5 proxy-resolver-mode dan redirect otomatis, sementara server HTTPS yang dikendalikan penyerang harus mengembalikan HTTP 30x Location: berisi nama host lebih dari 16KB dan hingga 64KB
  • Mulai curl 8.4.0, nama host yang terlalu panjang tidak lagi mengubah resolusi jarak jauh menjadi resolusi lokal, melainkan mengembalikan error, dan pengujian untuk mencegah skenario yang sama juga telah ditambahkan

CVE-2023-38545 dan curl 8.4.0

  • Bersamaan dengan rilis curl 8.4.0, advisory keamanan CVE-2023-38545 dan detailnya dipublikasikan
  • Masalah ini dinilai sebagai isu keamanan paling serius yang muncul di curl dalam waktu lama, dan tingkat keparahannya ditetapkan sebagai HIGH
  • Cacat intinya adalah heap buffer overflow yang terjadi dalam kondisi tertentu saat menangani koneksi proxy SOCKS5

SOCKS5 dan cara resolusi nama

  • curl mendukung SOCKS5 sejak Agustus 2002
  • SOCKS5 adalah protokol proxy yang menyiapkan komunikasi jaringan melalui server perantara
    • Dapat digunakan untuk menyiapkan komunikasi melalui Tor
    • Juga dapat digunakan saat mengakses internet dari dalam organisasi atau perusahaan
  • SOCKS5 memiliki dua cara resolusi nama host
    • Klien meresolusikan secara lokal nama host lalu mengirim alamat hasil resolusi ke proxy
    • Klien menyerahkan seluruh nama host ke proxy dan proxy meresolusikannya secara jarak jauh

Perubahan 2020 yang memasukkan kerentanan

  • Pada awal 2020, fungsi untuk terhubung ke proxy SOCKS5 diubah dari blocking call menjadi state machine non-blocking
  • Perubahan ini masuk ke master pada 14 Februari 2020 dan disertakan dalam curl 7.69.0
  • curl 7.69.0 adalah rilis pertama yang membawa peningkatan ini, dan akibatnya juga menjadi rilis pertama yang rentan terhadap CVE-2023-38545
  • Tujuan perubahan itu adalah memberi peningkatan yang lebih terasa saat banyak transfer paralel semuanya melewati SOCKS5

Mode resolusi yang rusak di state machine

  • Fungsi state machine dipanggil berulang setiap kali lebih banyak data jaringan masuk sampai koneksi terbentuk
  • Variabel lokal socks5_resolve_local di bagian atas fungsi menunjukkan apakah curl akan meresolusikan nama host sendiri atau menyerahkan nama itu ke proxy
  • Variabel ini diatur ulang setiap kali state machine berjalan, pada awal pemanggilan fungsi, berdasarkan mode proxy
  • Kondisi pada status INIT yang menimbulkan masalah
    • Field nama host di SOCKS5 hanya mengizinkan maksimum 255 byte
    • Jika nama host melebihi 255 byte, proxy SOCKS5 tidak bisa meresolusikannya
    • Kode curl lama, saat bertemu nama host yang terlalu panjang dalam mode resolusi jarak jauh, mengubah socks5_resolve_local menjadi TRUE untuk beralih ke mode resolusi lokal
  • Jika pengguna meminta resolusi jarak jauh, curl seharusnya gagal tanpa mengganti mode, tetapi perilaku peralihan yang ditambahkan sejak lama tetap dipertahankan

Alur terjadinya heap overflow

  • Jika server SOCKS5 tidak cukup cepat sehingga state machine belum menerima data jaringan tambahan untuk melanjutkan, fungsi akan mengembalikan kontrol
  • Setelah data tersedia, fungsi state machine yang sama dipanggil lagi
  • Saat dipanggil ulang, socks5_resolve_local kembali diatur di bagian atas fungsi berdasarkan mode proxy
    • Nilai yang pada pemanggilan sebelumnya telah diubah menjadi TRUE karena nama host terlalu panjang tidak dipertahankan
    • Nilainya kembali menjadi keadaan di mana proxy harus meresolusikan nama secara jarak jauh
  • curl membangun frame protokol untuk dikirim ke proxy di dalam buffer memori dan menyalin informasi tujuan ke sana
  • Karena nilai status yang salah, saat mencoba meneruskan nama host yang terlalu panjang apa adanya, data dapat menimpa memori heap yang bersebelahan melewati buffer tujuan yang telah dialokasikan

Ukuran buffer dan batasan nama host

  • Saat membangun frame protokol yang akan dikirim ke proxy, curl menggunakan ulang buffer download umum
  • Syarat ukuran buffer download adalah sebagai berikut
    • Nilai default adalah 16KB
    • Tool curl menetapkan ukuran buffer menjadi 100KB
    • Aplikasi dapat menggunakan ukuran lain sesuai permintaan
    • Ukuran minimum yang diizinkan adalah 1024 byte
  • Jika ukuran buffer lebih kecil dari 65541 byte, overflow ini dimungkinkan
  • Semakin kecil buffer, semakin besar ukuran overflow yang mungkin terjadi
  • Field nama host pada URL pada praktiknya tidak memiliki batas ukuran, tetapi parser URL libcurl menolak nama yang melebihi 65535 byte
  • DNS hanya mengizinkan nama host hingga 253 byte
  • Nama sah yang melebihi 253 byte jarang ada, dan nama nyata di atas 1024 byte pada dasarnya hampir tidak pernah terlihat, sehingga serangan ini memerlukan nama host yang sengaja dibuat sangat panjang
  • Field nama host URL hanya dapat memuat oktet tertentu, dan nilai byte yang tidak valid akan ditolak parser URL
  • Jika libcurl dibangun untuk menggunakan pustaka IDN, pustaka itu juga dapat menolak nama host yang tidak valid

Syarat agar serangan berhasil

  • Penyerang harus mengendalikan server HTTPS yang diakses klien berbasis libcurl melalui proxy SOCKS5 dalam proxy-resolver-mode
  • Server penyerang harus bisa mengembalikan redirect HTTP 30x yang dimanipulasi
  • Header Location: pada redirect dapat berisi nama host yang sangat panjang dalam bentuk seperti berikut
  • Jika pelacakan redirect otomatis aktif pada klien libcurl, dan proxy SOCKS5 “cukup lambat” untuk memicu masalah variabel lokal ini, nama host yang dimanipulasi akan disalin ke buffer yang terlalu kecil
  • Jika syarat ini terpenuhi, penulisan ke memori heap yang bersebelahan akan terjadi dan heap buffer overflow pun terbentuk

Perbaikan dan bug bounty

  • Mulai curl 8.4.0, nama host yang terlalu panjang tidak lagi mengubah mode dari resolusi jarak jauh ke resolusi lokal, melainkan langsung mengembalikan error
  • Kasus uji khusus untuk skenario yang sama juga telah ditambahkan
  • Masalah ini dilaporkan, dianalisis, dan ditambal oleh Jay Satiro
  • Kerentanan ini menerima bug bounty terbesar yang pernah dibayarkan di curl hingga saat ini
    • 4.660 dolar kepada pelapor
    • 1.165 dolar kepada proyek curl sesuai kebijakan IBB

C dan bahasa yang memory-safe

  • Jika curl ditulis bukan dalam C melainkan dalam bahasa yang memory-safe, cacat jenis ini tidak akan terjadi
  • Namun mem-porting curl ke bahasa lain saat ini tidak sedang menjadi agenda
  • Pendekatan yang realistis menyempit menjadi dua hal
    • Lebih mengizinkan, menggunakan, dan mendukung dependensi yang ditulis dalam bahasa memory-safe
    • Mengganti sebagian curl secara bertahap, seperti lewat adopsi hyper
  • Pengembangan seperti ini saat ini berjalan sangat lambat, dan kesulitannya juga terlihat jelas
  • curl akan tetap ditulis dalam C dalam waktu dekat
  • Jika memasukkan dua CVE terbaru yang dilaporkan pada curl 8.4.0, maka 41% dari kerentanan keamanan yang pernah ditemukan di curl mungkin tidak akan terjadi andaikata bahasa memory-safe digunakan
  • Namun sekitar 80% dari masalah terkait C pada fase awal saat diperkenalkan muncul pada masa ketika Rust belum menjadi pilihan praktis untuk tujuan ini

Cacat yang ditemukan setelah 1315 hari

  • Cacat ini tetap berada di dalam kode selama 1315 hari
  • Dengan rangkaian pengujian yang lebih baik, masalah ini mungkin bisa ditemukan
  • curl berulang kali menjalankan berbagai penganalisis kode statis, tetapi tak satu pun menemukan masalah di fungsi ini
  • Pengalaman merilis heap overflow dalam kode yang terpasang di lebih dari 20 miliar lingkungan bukanlah sesuatu yang layak direkomendasikan
  • Laporan dan proses penanganan sebelum pengungkapan publik dapat dilihat di HackerOne report

1 komentar

 
GN⁺ 2023-10-12
Pendapat Hacker News
  • Masih mengejutkan bahwa begitu banyak perangkat pada dasarnya bergantung pada library yang sebagian besar ditulis oleh satu orang. Bebannya pasti luar biasa besar, dan itu juga terlihat dari kalimat di bawah ini.
    “Membaca kode itu sekarang, rasanya mustahil untuk tidak melihat bug-nya. Sungguh menyakitkan menerima kenyataan bahwa saya tidak menyadari kesalahan ini, dan bahwa cacat itu tidak ditemukan di dalam kode selama 1315 hari. Saya minta maaf. Saya hanya manusia.”
    Kalau Daniel melihat ini, saya rasa ia patut diberi terima kasih atas kerja kerasnya, dan sama sekali tidak perlu meminta maaf. Bagaimanapun, source-nya terbuka untuk dibaca dan ditinjau siapa saja.

    • Saya jadi teringat bahwa dulu OpenSSL juga sempat dikembangkan dan dipelihara oleh dua orang bernama Steve untuk waktu yang cukup lama. Sekarang sepertinya setidaknya sudah bertambah menjadi tim sekitar 7 orang.
      https://www.buzzfeed.com/chrisstokelwalker/the-internet-is-b...
    • Benar sekali. Ini juga terlihat jelas dari kalimat di laporan HackerOne:
      “Laporan ini tampaknya sepenuhnya benar, dan itu terasa sangat menyakitkan.”
      Begitu juga dengan kalimat di blog, “Saya tidak akan merekomendasikan pengalaman merilis heap overflow ke kode yang masuk ke lebih dari 2 miliar instalasi.” Harus memikul tanggung jawab sebesar itu dengan imbalan yang begitu kecil memang kejam.
    • Saya merasa kalimat pertama dari kutipan itu merangkum dengan baik sebagian cara kerja pembelajaran dan perhatian manusia.
      Secara pribadi, saya sering memikirkan betapa banyak hal yang luput dari perhatian, sekeras apa pun saya berusaha fokus. Aktivitas seperti code review tampaknya merupakan cara yang baik untuk melatih hal semacam ini, yaitu melatih perhatian atau membangun kesadaran yang menyeluruh dan kontekstual. Misalnya, selama bertahun-tahun ada sebuah hamparan bunga yang terawat baik di tempat yang sering saya lewati, dan setelah pertama kali menyadarinya, selama kira-kira setahun saya sering berhenti untuk melihatnya. Namun beberapa bulan lalu, saya baru pertama kali menyadari bahwa tepat beberapa kaki di sebelahnya ada hamparan bunga lain yang seperti pasangannya. Kemungkinan besar hamparan bunga itu sudah ada sejak awal, tetapi karena saya bahkan tidak tahu keberadaannya, saya tidak bisa memastikannya.
      Jika ini diperluas ke pengetahuan umat manusia secara keseluruhan, kadang tampaknya hanya satu orang yang menyadari sesuatu, lalu pengamatan itu mengubah tindakan dan mulai menyebar ke kita semua. Kadang dibutuhkan banyak percobaan dan waktu yang panjang. Saya juga penasaran berapa banyak buah yang menggantung rendah yang belum disadari siapa pun, dan saya rasa hanya dengan mengintegrasikan praktik-praktik teladan yang sederhana dan mendasar yang telah dibangun oleh orang-orang yang selama ini memperhatikan, batas minimum untuk semua orang bisa ikut naik.
      Ini juga sejalan dengan tulisan Julia Evans di https://jvns.ca/blog/2023/10/06/new-talk--making-hard-things... dan Dan Luu di https://danluu.com/p95-skill/. Pada akhirnya, saya sangat berterima kasih kepada Stenberg dan curl, serta kepada orang-orang yang membuat berbagai bagian infrastruktur internet yang kita gunakan setiap hari seolah-olah itu hal wajar.
    • https://xkcd.com/2347/ sudah benar selama bertahun-tahun sebelum dipublikasikan, dan tetap benar selama bertahun-tahun setelahnya.
      Kadang prinsip “kalau belum rusak, jangan diperbaiki” ditafsirkan menjadi “jangan didukung sampai rusak”, dan itu menghasilkan kejutan yang cukup tidak menyenangkan.
    • Proyek pengembang tunggal selalu punya pengalaman kontributor yang luar biasa, sementara proyek perusahaan atau vendor sering kali cukup buruk. Ketika melihat upaya untuk bekerja bersama demi memperbaiki proyek, semangatnya terlihat jelas.
      Mungkin ada masalah keberlanjutan, tetapi proyek seperti ini tampaknya akan terus ada, dan saya menghormati semua orang yang, seperti Daniel, bekerja bukan hanya untuk dirinya sendiri tetapi juga untuk komunitas.
  • Kesimpulan tentang keamanan memori dan bahasa yang aman memori sangat masuk akal. Intinya seperti ini:
    Jika curl ditulis dalam bahasa yang aman memori, bukan C, cacat dari kategori seperti ini tidak akan mungkin terjadi.
    Pendekatan yang dianggap layak dan masuk akal ke arah itu adalah lebih mengizinkan, menggunakan, dan mendukung dependensi yang ditulis dalam bahasa aman memori, serta mengganti bagian-bagian curl secara bertahap dan sepotong demi sepotong, seperti adopsi hyper.
    Namun pengembangan semacam ini saat ini berjalan nyaris selambat gletser, dan memperlihatkan dengan sangat jelas berbagai kesulitan terkait. curl akan tetap menggunakan C untuk masa depan yang dapat diperkirakan. Orang yang tidak suka bisa turun tangan sendiri dan mengerjakannya.
    Jika memasukkan dua CVE terbaru yang dilaporkan pada curl 8.4.0, maka dari total kumulatif kerentanan keamanan yang ditemukan di curl sejauh ini, 41% mungkin tidak akan terjadi jika menggunakan bahasa yang aman memori. Namun Rust belum menjadi pilihan yang praktis untuk penggunaan ini pada masa ketika sekitar 80% pertama masalah terkait C mulai diperkenalkan.
    Berbagai static code analyzer sudah dijalankan berulang kali, tetapi tidak menemukan masalah apa pun pada fungsi ini.

    • Tampaknya fuzzer dan pengujian baru juga ditambahkan minggu lalu. Pada titik ini, untuk proyek C yang bukan di bidang safety-critical, sebaiknya pipeline seperti ini ditiru.
    • Mengganti sebagian curl dengan Rust kemungkinan tidak akan memungkinkan. Sebagian besar lingkungan ingin membangun dari source, dan tidak ingin memasukkan compiler baru ke toolchain. Selain itu, Rust hanya mendukung sebagian sangat kecil dari platform target C yang memungkinkan.
  • Ini tulisan analisis yang bagus. Namun, bahkan setelah membaca CVE-nya, masih belum jelas dalam situasi apa dampaknya terjadi. Sejauh yang saya pahami, dampaknya muncul ketika kondisi berikut terpenuhi:
    Menggunakan proxy SOCKS5, melakukan resolusi nama host melalui proxy tersebut, ukuran buffer diubah dari nilai default 100 KB menjadi kurang dari 65.541 byte, dan proxy SOCKS5 terlalu lambat untuk langsung memproses permintaan.
    Koreksi: penjelasan terkait buffer tidak akurat. libcurl memakai ulang buffer unduhan dan nilai default-nya 16 KB, tetapi curl sendiri katanya mengaturnya secara manual menjadi 100 KB kecuali jika memakai --limit-rate. Kondisi keterlambatan juga keliru. Menurut CVE, keterlambatan server biasa saja kemungkinan besar sudah cukup “lambat” untuk memicu bug ini, dan penyerang tidak perlu memengaruhinya lewat denial of service atau kendali atas server SOCKS.
    Jadi jalur serangannya terlihat sangat sempit, dan saya penasaran apakah ada yang terlewat.

    • Saya mencoba memahami kondisi terakhir, yaitu “proxy SOCKS5 terlalu lambat untuk langsung memproses permintaan”, tetapi permintaannya langsung menghasilkan segmentation fault tanpa delay atau redirect.
      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...
    • Berdasarkan koreksi itu, sepertinya benar. Ini kemungkinan terutama relevan untuk software yang berjalan di infrastruktur bersama, mengirim permintaan ke URL yang diberikan penyerang, dan menggunakan proxy SOCKS5. Contohnya kasus seperti webhook.
    • Saya membuat sekumpulan skrip shell bernama docker-proxy yang membuat tunnel SOCKS5 ke container Docker yang menjalankan VPN openconnect. Ini berguna saat menangani berbagai VPN dari pelanggan berbeda, ketika tiap VPN menuntut agar semua traffic mesin diarahkan ke sisi mereka.
      https://github.com/carlosonunez/docker-proxy
      Karena desainnya membuat resolusi DNS terjadi secara remote, CVE ini langsung berlaku di sini.
    • Ini terlihat seperti kombinasi yang mengundang bencana saat menjelajahi darknet seperti Tor.
  • Bisa dibilang ini tulisan analisis CVE terbaik yang pernah saya baca sejauh ini. Meski penulisnya adalah orang yang membuat salah satu software paling penting di era ini, sikap rendah hati yang tampak di seluruh tulisan juga patut dipuji. Benar-benar mengagumkan.

  • if(!socks5_resolve_local && hostname_len > 255) {
    socks5_resolve_local = TRUE;
    }
    Ini benar-benar ide yang buruk. Bagi orang yang melindungi privasi dengan alat anti-sensor, ini bisa membocorkan identitas lewat DNS.

    • “Kalau pengguna meminta resolusi remote, curl seharusnya mematuhinya atau gagal; menurut saya mengubah mode seperti ini jelas salah. Bahkan dalam kondisi normal pun kecil kemungkinan peralihan begitu saja akan bekerja dengan baik.”
      Penulisnya juga berpandangan sama.
  • https://hackerone.com/reports/2187833

  • Ada yang mengatakan, “Jika curl ditulis dalam bahasa yang aman memori alih-alih C, cacat sejenis ini tidak mungkin terjadi,” tetapi paling jauh itu hanya berarti mungkin saja tidak terjadi. Kita tidak selalu tahu apakah ada cara untuk keluar dari batas virtual machine bahasa tersebut. Namun, penulis jelas mengabaikan batas nama host DNS yang ditentukan dalam RFC1123, dan batas ini juga di-hardcode di library Java.
    https://www.rfc-editor.org/rfc/rfc1123

    • Ini dibahas di bagian “panjang nama host”.
      “Tidak ada batas ukuran nyata untuk nama host dalam URL, tetapi parser URL libcurl menolak nama yang lebih panjang dari 65.535 byte. DNS hanya mengizinkan nama host hingga 253 byte. Karena itu, nama legal yang lebih panjang dari 253 byte jarang ada. Nama nyata yang lebih panjang dari 1.024 byte praktis belum pernah terdengar.”
      DNS memang digunakan dalam 99,9% kasus saat ini, tetapi itu bukan satu-satunya mekanisme untuk menerjemahkan nama host menjadi alamat.
    • Saya penasaran apakah bisa dijelaskan lebih spesifik. Yang saya lihat hanya “software host wajib menangani nama host hingga 63 karakter, dan sebaiknya menangani nama host hingga 255 karakter”.
      Saya tidak bisa menemukan di mana RFC menetapkan batas atas ukuran nama host.
    • Bahasa yang aman memori tidak harus berarti virtual machine.
      Itu juga bisa berarti compiler mencoba membuktikan bahwa memori tidak diakses di luar batas, tidak diakses tanpa kepemilikan yang jelas, dan hanya diakses selama masa hidup objek dasarnya. Rust, misalnya, melakukan itu.
      Tentu saja compiler juga bisa menambahkan pemeriksaan kegagalan internal dan pengaman di titik-titik penting. Rust tidak melakukan itu, tetapi pada sistem yang harus mengkhawatirkan bit flip di dalam register, fitur seperti itu bisa berguna. Misalnya di lingkungan radiasi tinggi seperti pemindai X-ray, ketika hal semacam itu tidak tertangkap oleh memori ECC.
    • Dari redaksi RFC, menerima nama host yang lebih panjang dari 255 byte diperbolehkan, tetapi membuat nama seperti itu tidak diperbolehkan.
  • Untuk sesuatu yang membutuhkan kondisi yang sangat spesifik agar bisa dieksploitasi, ada cukup banyak hiperbola dan drama

    • curl sudah beberapa kali mengalami masalah terkait CVE, jadi sepertinya mereka ingin menunjukkan bahwa isu keamanan nyata ditangani dengan sangat serius. Misalnya, lihat https://daniel.haxx.se/blog/2023/08/26/cve-2020-19909-is-eve...
    • Dan ini terpasang di miliaran perangkat. Kalau didistribusikan pada skala sebesar itu, kondisi yang sangat spesifik bisa cepat muncul di sana-sini
  • Pembaruan keamanan Windows 10 yang dirilis kemarin tidak menyertakan versi curl baru. curl yang ada di Windows masih 8.0.1

  • Ini tulisan yang panjang dan menarik, tetapi bisa diringkas menjadi “jadi library sistem harus ditulis dalam bahasa yang aman atau dibuktikan kebenarannya”
    Jika salah satu programmer C terbaik, paling ramah, dan paling transparan di zaman kita menulis hal seperti ini, kita harus memperhatikannya

    • Jika programmer itu menulis, “pengembangan seperti ini saat ini berjalan hampir selambat gletser dan memperlihatkan berbagai masalah sulit terkait dengan sangat jelas sampai terasa menyakitkan. curl akan tetap menggunakan C pada masa depan yang dapat diperkirakan,” saya penasaran kesimpulan apa yang seharusnya kita ambil