1 poin oleh GN⁺ 6 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • -- di Git bukan terminator opsi umum, melainkan pemisah antara revisi dan spesifikasi path, sehingga untuk meneruskan revisi yang tidak tepercaya dengan aman diperlukan --end-of-options, yang didukung sejak Git 2.24.0
  • Dalam git log --end-of-options "$rev" -- "$path", penanda pertama memisahkan opsi dan revisi, sedangkan -- di belakang memisahkan revisi dan path; keduanya tidak dapat saling menggantikan
  • Meski menjalankan array argv langsung tanpa shell, jika input yang diawali tanda hubung ditafsirkan sebagai opsi seperti --upload-pack, core.sshCommand, atau ProxyCommand, injeksi argumen CWE-88 dapat terjadi
  • Dari 19 package manager yang diteliti, 17 menjalankan binary Git sebagai cara default atau satu-satunya, tetapi alat yang memakai --end-of-options hanyalah cmd/go milik Go
  • Penanganan mendasar memiliki biaya kompatibilitas karena harus menaikkan versi minimum Git menjadi 2.24.0, 2.30.0, atau 2.43.1 tergantung subcommand; sementara library Git menghilangkan batas injeksi argumen, tetapi harus melacak sendiri perbaikan keamanan checkout dari upstream

Perbedaan antara -- dan --end-of-options

  • Pada tool Unix umum, -- menandai akhir pemrosesan opsi, sehingga rm -- -f memperlakukan -f sebagai nama file, bukan opsi hapus paksa
  • Sejak awal, Git menggunakan -- sebagai pemisah antara revisi dan spesifikasi path (pathspec)
    • git log foo ambigu: apakah yang dimaksud branch bernama foo atau file bernama foo
    • git log main -- README.md berarti commit di main yang menyentuh README.md
  • Karena desain ini, tidak ada penanda akhir opsi pada posisi revisi, dan dalam git log "$rev", jika $rev diawali tanda hubung, Git menafsirkannya sebagai opsi
  • Commit yang memperkenalkan --end-of-options menjelaskan bahwa karena -- yang sudah ada dipakai untuk memisahkan revisi dan spesifikasi path, diperlukan penanda terpisah untuk membedakan opsi dan revisi
  • --end-of-options didokumentasikan di gitcli(7), dan ditambahkan pada November 2019 saat Git 2.24.0 dirilis

Cara penggunaan yang benar per perintah

  • git clone -- "$url" mengikuti konvensi POSIX pada clone, sehingga -- sebelum URL mengakhiri pemrosesan opsi
  • -- di belakang pada git checkout "$ref" -- menandai $ref sebagai revisi, bukan nama file, tetapi tidak mencegah $ref ditafsirkan lebih dulu sebagai opsi
  • Untuk meneruskan revisi dan path yang tidak tepercaya secara aman sekaligus, gunakan kedua penanda seperti git log --end-of-options "$rev" -- "$path"
    • --end-of-options memisahkan opsi dan revisi
    • -- memisahkan revisi dan path
  • Jika kedua penanda dianggap bisa saling dipertukarkan, input yang diawali tanda hubung tidak akan terblokir

Waktu dukungan berbeda untuk tiap subcommand

  • Dukungan --end-of-options tidak diterapkan sekaligus ke semua perintah Git, tetapi ditambahkan per subcommand
  • git rev-parse menggunakan parser argumennya sendiri, sehingga mulai mendukungnya di Git 2.30.0, satu tahun setelah pengenalan awal
  • git checkout dan git reset menafsirkan -- sendiri, dan implementasi awal meninggalkan --end-of-options di daftar argumen sehingga keduanya menolaknya
    • Masalah ini diselesaikan pada Februari 2024 saat Git 2.43.1 dirilis

Injeksi argumen juga terjadi tanpa shell

  • Git, Mercurial, dan SSH memiliki opsi resmi untuk menjalankan perintah yang ditentukan pemanggil
    • git clone --upload-pack=<cmd> menentukan binary sisi server
    • -c core.sshCommand=<cmd> pada semua pemanggilan Git mengubah perintah koneksi
    • --config=alias.<subcmd>=!<shell> di Mercurial mendefinisikan ulang subcommand yang akan dijalankan sebagai skrip shell arbitrer
    • -oProxyCommand=<cmd> di SSH menentukan perintah proxy
  • Jika program wrapper memasukkan string yang tidak tepercaya ke daftar argumen, fitur-fitur ini dapat berubah menjadi sarana serangan
  • Jenis kegagalan ini termasuk CWE-88 injeksi argumen (argument injection) dan berbeda dari injeksi perintah shell
    • Ini tetap terjadi meski program menggunakan array argv dan exec, bukan system()
    • Array diteruskan ke Git tanpa rusak, tetapi Git menafsirkan argumen yang diawali tanda hubung sebagai opsi
  • docker build CVE-2019-13139 adalah contoh yang memakai os/exec dan array argv milik Go tanpa melalui shell
    • Fragmen #ref:dir pada URL konteks Git diteruskan ke git fetch origin <ref>, lalu <ref> ditafsirkan sebagai --upload-pack=<cmd>

Kerentanan yang berulang di berbagai sistem version control

  • Pada hari yang sama pada Agustus 2017, pola yang sama diungkap di empat sistem version control
  • Keempat sistem meneruskan hostname dari URL sebagai argumen SSH, dan hostname yang diawali -oProxyCommand= diproses sebagai opsi SSH
  • Menurut postmortem Phabricator, dari tiga tool yang saat itu masih aktif dipelihara, hanya Subversion yang menambahkan -- sebelum hostname
    • Git dan Mercurial memvalidasi format hostname, antara lain karena tidak semua implementasi SSH mendukung --
  • Kode yang tidak memiliki -- juga tampak dan bekerja normal sampai argumennya diawali tanda hubung, sehingga mekanisme ini pada dasarnya tidak aman

Jalur paparan package manager

  • Package manager menerima URL Git atau ref dari manifest, lock file, dan metadata dependensi transitif, lalu meneruskannya ke subprocess
    • gem 'foo', git: '...' di Gemfile
    • github:user/repo#ref di package.json
    • Konfigurasi setara di pyproject.toml, Cargo.toml, mix.exs, Package.swift, pubspec.yaml, conanfile.py, dan go.mod
  • Per HEAD Juli 2026, 17 dari 19 package manager yang diteliti menjalankan binary Git sebagai jalur default atau satu-satunya
  • Dua sisanya memakai library secara default
    • Cargo memakai libgit2 dan menjalankan proses Git jika net.git-fetch-with-cli diaktifkan
    • Poetry beralih ke dulwich sejak 1.2.0 dan dapat memakai Git sistem melalui pengaturan system-git-client
  • Nix memakai libgit2 saat membaca repository lokal, tetapi menjalankan proses Git untuk fetch karena libgit2 tidak mendukung helper git-credential
  • Target penelitian adalah Bundler, Cargo, CocoaPods, Composer, Conan, Go, Helm, Homebrew, Mix, Nix, npm, pip, pnpm, Poetry, Pub, SwiftPM, uv, vcpkg, dan Yarn

CVE yang dikonfirmasi pada package manager

Kondisi pertahanan nyata dan perbaikan Go

  • Dari 17 package manager yang menjalankan proses Git, tool yang menggunakan --end-of-options hanyalah cmd/go milik Go
  • Pada Juni 2019, Go menambahkan -- sebelum URL repository sebagai penguatan pertahanan umum
  • Pada Januari 2026, terungkap bahwa -- saja tidak cukup, sehingga sebagai perbaikan CVE-2025-68119, Go menambahkan --end-of-options secara luas
  • Perbaikan yang sama juga mencakup HGPLAIN=+strictflags
    • Pengaturan ini membatasi pemrosesan opsi awal Mercurial sejak 2017, saat Mercurial 4.4.2 dirilis
  • Commit perbaikan Go menyebut bahwa perubahan yang lebih struktural mungkin diperlukan agar masalah yang sama sulit muncul kembali, tetapi untuk saat ini perbaikan tersebut menyelesaikan masalah yang ada

Sebagian besar pertahanan ditambahkan setelah kerentanan dipublikasikan

  • Package manager lain, meski melindungi daftar argumen, umumnya memakai -- atau menolak tanda hubung di awal input
  • -- sebelum URL git clone di Bundler ditambahkan lewat patch CVE-2021-43809
  • Penolakan tanda hubung di awal pada cocoapods-downloader diterapkan dalam tiga commit selama sepuluh hari pada Maret 2022, bertepatan dengan waktu publikasi CVE-2022-21223
  • Pertahanan Poetry ditambahkan pada September 2021, CVE diberikan satu tahun kemudian, dan enam bulan setelah itu Poetry beralih ke dulwich
  • vcpkg adalah pengecualian: sejak hari pertama dukungan registry Git ditulis, vcpkg sudah memakai --

Batasan kompatibilitas dari versi minimum Git

  • Advisory CVE-2022-24828 Composer menyebut --end-of-options sebagai perbaikan yang benar, tetapi karena harus mendukung Git yang lebih lama, Composer memilih menolak nama branch yang diawali tanda hubung
  • Integrasi Git vcpkg menetapkan versi minimum Git 2.7.4, sedangkan HOMEBREW_MINIMUM_GIT_VERSION untuk Linux di Homebrew adalah 2.7.0 yang ditetapkan pada 2018
  • Dengan berakhirnya masa pakai Amazon Linux 2, yang menyediakan Git 2.14.3, pada Juni 2026, distribusi yang diikuti oleh batas bawah seperti ini baru sekarang keluar dari cakupan dukungan
  • Status dukungan jangka panjang Ubuntu juga mempersulit migrasi serentak
    • Ubuntu 18.04 menyediakan Git 2.17.0 dan mendapat extended support hingga 2028
    • Ubuntu 20.04 menyediakan Git 2.25.1 dan mendapat extended support hingga 2030
    • Git 2.25.1 menerima --end-of-options pada git fetch, tetapi menolaknya pada git rev-parse
  • Untuk bergantung pada --end-of-options, sebagian besar subcommand harus mensyaratkan versi minimum Git 2.24.0, rev-parse 2.30.0, serta checkout dan reset 2.43.1
  • Menaikkan versi minimum membuat pengguna yang memakai Git lama bawaan distribusi tidak lagi dapat didukung

Menggunakan library Git alih-alih menjalankan proses

  • libgit2, gitoxide, go-git, JGit, dan dulwich mengimplementasikan protokol transport Git yang diperlukan untuk clone dan fetch di dalam proses
  • Karena tidak ada batas argv terpisah, tidak ada target yang bisa diinjeksi ke daftar argumen
  • Jujutsu memakai gitoxide untuk integrasi Git dan tidak memiliki CVE publik bertipe injeksi argumen
    • Dua advisory hingga saat ini adalah path traversal dan ketiadaan pemeriksaan tabrakan SHA-1 yang diwarisi dari library
  • CVE-2025-21613 pada go-git terbatas pada transport file://
    • Jalur ini adalah satu-satunya jalur kode di go-git yang menjalankan binary Git
  • Jika menyertakan implementasi Git sendiri, semua perbaikan keamanan checkout yang dirilis upstream Git harus dilacak, dan baik libgit2 maupun JGit pernah beberapa kali membutuhkan perbaikan terkait hal ini
  • Biaya ini memang nyata, tetapi masalahnya berubah menjadi menerapkan alur patch upstream yang konkret, alih-alih harus terus mengingat validasi argumen secara permanen di setiap titik pemanggilan

Cakupan perubahan yang diusulkan untuk Homebrew

  • PR Homebrew menaikkan versi minimum Git menjadi 2.30.0 dan menambahkan --end-of-options di lokasi berikut
    • Sebelum URL pada clone, remote set-url, dan ls-remote
    • Sebelum ref pada rev-parse
  • Pemanggilan checkout dan reset tidak diubah
    • Untuk melindungi kedua perintah tersebut, diperlukan Git 2.43.1 yang dirilis pada Februari 2024
    • Versi ini lebih baru daripada Git yang disediakan banyak distribusi yang saat ini masih didukung

1 komentar

 
GN⁺ 6 jam lalu
Komentar di Lobste.rs
  • Belakangan ini saya makin tertarik pada jj. Dibandingkan dengan kekacauan Git seperti ini, rasanya sampai menenangkan; meski masih dalam proses belajar, ia mencerminkan maksud dengan baik dan cara kerja yang diinginkan juga mudah dipahami
    Terutama, pengetahuan yang dipelajari dari satu perintah jj secara alami berlaku juga pada perintah lain. Dokumentasi git log mengalami ledakan flag untuk tiap opsi keluaran, bercampur dengan “notasi khusus” untuk sintaks rentang commit serta flag filter tambahan, dan hanya sedikit pengetahuan yang bisa ditransfer ke perintah Git lain
    Sebaliknya, dokumentasi jj log begitu ringkas hingga masih menyisakan ruang kosong dalam satu halaman. Kompleksitas Git yang serba campur aduk digantikan oleh tiga hal: himpunan revisi, himpunan file, dan DSL templat keluaran; semuanya digunakan secara konsisten di seluruh jj, sehingga jauh lebih sederhana, dapat dikomposisikan, dan intuitif. Memang perlu diingat bahwa Git telah menumpuk banyak embel-embel selama 20 tahun, tetapi jj tampaknya jauh lebih berpeluang menghindarinya
  • Saya tahu bahwa dalam perintah biasanya diperlukan -- sebelum argumen yang diberikan pengguna, tetapi jika sampai menuntut aturan transformasi tersendiri seperti ini, itu menjadi jebakan yang terlalu berbahaya
  • Ini adalah konsekuensi jujur dari membuat alat menjadi terlalu rumit. Git tampak seperti Bazaar yang baru muncul
  • Saya penasaran mengapa awalnya -- dipilih untuk menghilangkan ambiguitas argumen file. Alih-alih cara sederhana yang menerima file sebagai argumen eksplisit, apakah ada kompromi desain yang tidak tampak dari luar sehingga pilihan ini diambil?
    • Menggunakan konvensi parsing opsi UNIX yang sudah mapan untuk tujuan yang sama sekali berbeda memang bodoh seperti yang bisa diduga, dan sangat khas Git
  • Filosofi UNIX bahwa semuanya adalah teks kembali menimbulkan masalah. Baris perintah adalah contoh sempurna data terstruktur, tetapi biaya koordinasi bersama untuk keluar dari lubang ini tampaknya terlalu besar
    Jika melihat kembali perdebatan Tcl dan Scheme pada 1990-an, mungkin dalam kasus ini “yang lebih buruk itu lebih baik” memang benar. Sementara keluarga S-expression, termasuk JSON dan XML, hampir tidak mengakar di ekosistem UNIX, Tcl memiliki pendekatan yang relatif berprinsip untuk melapiskan subbahasa di dalam string