- EEF CNA melaporkan bahwa 35,8% CVE yang diungkapkannya adalah konsumsi sumber daya tak terkendali, dan dalam ekosistem BEAM, masalah kehabisan atom yang berulang menyumbang porsi besar
- Kehabisan atom adalah kerentanan denial-of-service; atom tidak di-garbage-collect, menumpuk di tabel global, dan saat tabel penuh VM akan crash
- Membuat atom dari data seperti input pengguna, yang kumpulan nilai mungkinnya tidak dijamin terbatas, menimbulkan risiko DoS, dan URI scheme juga bukan pengecualian
- Risikonya ada bukan hanya pada pemanggilan eksplisit seperti
binary_to_atom/1 dan String.to_atom/1, tetapi juga pada dekode kunci JSON menjadi atom serta pembuatan dinamis berbasis interpolasi string
- Penanganan yang aman adalah menghindari pembuatan atom baru saat runtime, membatasi nilai yang diketahui dengan tabel lookup eksplisit atau keluarga
to_existing_atom, dan memeriksanya dengan linter
Kerentanan denial-of-service akibat kehabisan atom
- Dari CVE yang diungkapkan oleh EEF CNA, 35,8% adalah konsumsi sumber daya tak terkendali, dan dalam ekosistem BEAM, masalah kehabisan atom yang berulang menyumbang porsi besar {p:36}
- Distribusi saat ini dapat dilihat di halaman Common Weaknesses milik EEF CNA
- Kehabisan atom adalah kerentanan denial-of-service (DoS)
- Atom tidak di-garbage-collect
- Disimpan di tabel atom global
- Saat tabel penuh, VM akan crash
- Membuat atom dari nilai yang tidak terbatas, khususnya input pengguna, berpotensi menyebabkan DoS
- Risiko ini tidak terbatas pada pemanggilan yang jelas
- Erlang:
binary_to_atom/1, list_to_atom/1
- Elixir:
String.to_atom/1, List.to_atom/1
- Ada juga pola risiko yang kurang mencolok
- Pembuatan atom dinamis melalui interpolasi di Erlang:
% Erlang: 보간을 통한 동적 atom 생성
list_to_atom("field_" ++ UserInput)
- Mendekode kunci JSON sebagai atom di Elixir:
# Elixir: JSON을 atom 키로 디코딩
Jason.decode(json, keys: :atoms)
- Pembuatan atom dinamis melalui interpolasi di Elixir:
# Elixir: 보간을 통한 동적 atom 생성
:"field_#{user_input}"
Cara penanganan yang aman dan apa yang perlu diperiksa
- Kerentanan kehabisan atom bukan sekadar kecerobohan sederhana, melainkan sering muncul pada kode yang mengasumsikan input terkontrol atau terbatas
- URI scheme adalah contoh yang representatif
- Mungkin terasa hanya ada beberapa scheme yang perlu ditangani
- Jika nilainya berasal dari input eksternal, kumpulan kemungkinan nilainya tidak lagi bisa dijamin terbatas
- Kode yang membuat atom dari input tidak aman kecuali kumpulan nilai yang mungkin terbatas, diketahui, dan dipaksakan
- Pendekatan paling aman adalah tidak membuat atom baru saat runtime
- Jika nilai yang diizinkan sudah diketahui, lebih aman menggunakan tabel lookup eksplisit
% Erlang
case Scheme of
<<"http">> -> http;
<<"https">> -> https;
_ -> error
end
- Saat tabel lookup tidak praktis, gunakan varian yang hanya memakai atom yang sudah ada tanpa membuat atom baru
- Fungsi-fungsi ini tidak membuat atom baru dan akan memunculkan error
% Erlang
binary_to_existing_atom(Value)
list_to_existing_atom(Value)
# Elixir
String.to_existing_atom(value)
List.to_existing_atom(value)
- Linter membantu menangkap pola berisiko sebelum berkembang menjadi kerentanan
- Untuk proyek Elixir, Anda dapat mempertimbangkan mengaktifkan Credo.Check.Warning.UnsafeToAtom milik Credo
- Pemeriksaan ini menandai pemanggilan tidak aman pada
String.to_atom/1, List.to_atom/1, Module.concat/1,2, dan Jason.decode/2 yang memakai keys: :atoms
- Pemeriksaan tersebut dinonaktifkan secara default
- Maintainer proyek Erlang atau Elixir perlu mencari kode yang membuat atom dari biner, string, kunci JSON, komponen URI, header, dan nilai konfigurasi
- Kategori kerentanan ini termasuk salah satu yang paling mudah diperbaiki sebelum menjadi CVE
- Panduan yang lebih rinci dirangkum dalam panduan pencegahan kehabisan atom dari EEF Security Working Group
1 komentar
Komentar Lobste.rs
Terdengar mirip dengan situasi sebelum
Symboldi Ruby menjadi objek yang bisa di-garbage-collectSaya tidak paham judulnya. Ini jelas terlihat seperti footgun
Kalau berpikir, “Bukankah Ruby juga punya symbol seperti atom di Erlang?”, itu benar, tetapi Ruby melakukan garbage collection pada symbol
Selain itu, tabel lookup tempat atom Erlang disimpan secara default hanya mengizinkan maksimal 1.048.576 entri
Jika atom dibuat secara dinamis dari input pengguna seperti form, itu sangat berbahaya dan membuat perangkat lunak rentan terhadap serangan denial-of-service
Meski begitu, menurut pengalaman saya, istilah “footgun” sendiri cukup luas, jadi bagaimanapun frasa judulnya terasa janggal
Saya kaget karena ini terdengar seperti desain atau implementasi dari bagian yang mendasar itu buruk. Lebih mengejutkan lagi karena ini bahasa yang terus dipuji di internet
Menambahkan reference count ke tabel itu mahal, dan akan mengubah karakteristik skalabilitas kode yang sudah ada selama puluhan tahun
Jumlah maksimum atom secara default adalah 1 juta, dan ditentukan saat VM dijalankan
Ini memang jebakan, tetapi tidak terlalu sulit dihindari. Rekomendasi sejak lama adalah “jangan membuat atom dari input pengguna”
Misalnya, saat mem-parsing JSON, biasanya kunci tidak diubah menjadi atom, atau hanya diubah jika atomnya sudah ada. Dengan begitu Anda tetap bisa melakukan pattern matching dengan kunci atom, atom-atom itu sudah dibuat lewat pemuatan kode, dan klausa fallback bisa menerima string alih-alih atom
Elixir jauh lebih populer, jadi pengembang Erlang kemungkinan besar tahu hal ini, sedangkan pengembang Elixir mungkin tidak
Secara pribadi, memakai atom seperti itu terasa aneh. Saya memahami atom di Erlang kurang lebih seperti tipe enum di C
Saya menganggapnya sebagai fitur kenyamanan di mana jika Anda menulis kata dengan cara tertentu, secara internal ia menjadi enum
Tulisan itu memang menyebut input pengguna, tetapi sejak awal saya tidak mengerti mengapa ada use case yang menginginkan pembuatan enum baru dari input pengguna. Kegunaannya tampak sangat sempit
Komentar-komentar di sebelah membahas parsing, tetapi idealnya bukankah yang diparsing adalah struktur data yang sudah diketahui sebelumnya? Rasanya saya melewatkan sesuatu
Dalam bahasa itu, ada setidaknya dua keuntungan menjadikan symbol sebagai tipe terpisah, bukan sekadar untuk perbandingan kesetaraan yang cepat. Symbol adalah atom, yakni unit atomik, bukan urutan berbentuk list karakter, sehingga banyak operator memperlakukannya berbeda, dan symbol juga tervektorisasi sehingga bisa disimpan rapat dalam list bertipe tunggal
Di K dan Q, sangat diinginkan agar kolom dalam tabel database direpresentasikan sebagai tipe yang tervektorisasi. Ini memberi locality yang baik, penggunaan memori yang lebih efisien, dan banyak jalur cepat di berbagai operator. Tetapi karena keterbatasan tabel symbol, Anda harus berhati-hati memakai symbol untuk kolom dengan kardinalitas tinggi
Jika mem-parsing JSON dengan skema yang sudah diketahui, symbol sangat bagus untuk key dictionary, dan di k2/k3 pada praktiknya hampir wajib. Tetapi jika JSON itu tidak diketahui bentuknya, maka itu tidak boleh berasal dari input pengguna
Beberapa dialek K membatasi panjang symbol agar bisa dikemas dan dipindahkan sebagai nilai 64-bit. Dengan mengorbankan generalitas, kebutuhan akan tabel symbol itu sendiri jadi hilang
Pembedaan antara “input terkendali” dan “input tak terkendali” dalam keamanan terasa seperti urusan nilai
nullSaat melihat entri seperti
webpack-plugin-less-cssmenyebabkan denial-of-service jika menerima file CSS yang tidak tepercaya, saya cukup merasakan kelelahan CVEMeski begitu, akan bagus jika ada penandaan batas yang lebih baik di sini. Misalnya, akan berguna jika aturan komposisi seperti properti keamanan apa yang tetap terjaga setelah konkatenasi string juga bisa dibahas dengan baik
Dan jika Anda menandai banyak hal yang diterima dari HTTP POST sebagai
SafeString, maka itu sampai taraf tertentu memang tanggung jawab Anda sendiri