1 Punkte von GN⁺ 6 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • Das -- von Git ist kein allgemeiner Optionsabschluss, sondern trennt Revisionen und Pathspecs. Um nicht vertrauenswürdige Revisionen sicher zu übergeben, ist daher --end-of-options nötig, das seit Git 2.24.0 unterstützt wird
  • In git log --end-of-options "$rev" -- "$path" trennt die vordere Markierung Optionen und Revisionen, das hintere -- trennt Revisionen und Pfade; beide sind nicht austauschbar
  • Selbst wenn ohne Shell direkt ein argv-Array ausgeführt wird, kann CWE-88 Argument Injection auftreten, wenn Eingaben mit führendem Bindestrich als Optionen wie --upload-pack, core.sshCommand oder ProxyCommand interpretiert werden
  • Von 19 untersuchten Paketmanagern führten 17 das Git-Binary standardmäßig oder ausschließlich aus, aber das einzige Tool mit --end-of-options war Go cmd/go
  • Eine grundlegende Abhilfe bringt Kompatibilitätskosten mit sich, weil je nach Unterbefehl die minimale Git-Version auf 2.24.0, 2.30.0 oder 2.43.1 angehoben werden muss; Git-Bibliotheken beseitigen zwar die Grenze für Argument Injection, müssen dafür aber Upstream-Fixes zur Checkout-Sicherheit direkt nachverfolgen

Der Unterschied zwischen -- und --end-of-options

  • Bei üblichen Unix-Tools markiert -- das Ende der Optionsauswertung, daher behandelt rm -- -f -f als Dateinamen statt als Option zum erzwungenen Löschen
  • Git verwendet -- schon seit Langem als Trennzeichen zwischen Revisionen und Pathspecs
    • git log foo ist mehrdeutig: Es kann einen Branch namens foo oder eine Datei meinen
    • git log main -- README.md meint die Commits von main, die README.md verändert haben
  • Wegen dieses Designs gab es an der Revisionsposition keinen Marker zum Beenden von Optionen, und wenn in git log "$rev" $rev mit einem Bindestrich beginnt, interpretiert Git es als Option
  • Der Commit zur Einführung von --end-of-options erklärt, dass das bestehende -- bereits Revisionen und Pathspecs trennt und deshalb ein eigener Marker nötig ist, um Optionen und Revisionen zu unterscheiden
  • --end-of-options ist in gitcli(7) dokumentiert und wurde im November 2019 mit Git 2.24.0 hinzugefügt

Korrekte Verwendung je nach Befehl

  • git clone -- "$url" folgt bei clone der POSIX-Konvention, daher beendet -- vor der URL die Optionsauswertung
  • Das hintere -- in git checkout "$ref" -- kennzeichnet $ref als Revision statt als Dateinamen, verhindert aber nicht, dass $ref vorher als Option interpretiert wird
  • Um nicht vertrauenswürdige Revisionen und Pfade gemeinsam sicher zu übergeben, müssen beide Marker verwendet werden, wie in git log --end-of-options "$rev" -- "$path"
    • --end-of-options trennt Optionen und Revisionen
    • -- trennt Revisionen und Pfade
  • Wenn man beide Marker als austauschbar behandelt, lassen sich Eingaben mit führendem Bindestrich nicht blockieren

Unterschiedliche Unterstützung je nach Unterbefehl

  • Die Unterstützung für --end-of-options wurde nicht auf einmal auf alle Git-Befehle angewendet, sondern pro Unterbefehl ergänzt
  • git rev-parse verwendet einen eigenen Argument-Parser und unterstützt es erst ein Jahr nach der ursprünglichen Einführung, ab Git 2.30.0
  • git checkout und git reset werten -- selbst aus und lehnten --end-of-options ab, weil die frühe Implementierung es in der Argumentliste stehen ließ
    • Dieses Problem wurde im Februar 2024 mit Git 2.43.1 behoben

Argument Injection auch ohne Shell

  • Git, Mercurial und SSH besitzen offiziell unterstützte Optionen, mit denen der Aufrufer bestimmte Befehle ausführen lassen kann
    • git clone --upload-pack=<cmd> legt das serverseitige Binary fest
    • -c core.sshCommand=<cmd> ändert bei allen Git-Aufrufen den Verbindungsbefehl
    • --config=alias.<subcmd>=!<shell> von Mercurial überschreibt den auszuführenden Unterbefehl mit einem beliebigen Shell-Skript
    • -oProxyCommand=<cmd> von SSH gibt einen Proxy-Befehl an
  • Wenn Wrapper-Programme nicht vertrauenswürdige Strings in die Argumentliste aufnehmen, werden diese Funktionen zu Angriffsvektoren
  • Dieser Fehlertyp entspricht CWE-88, also Argument Injection, und unterscheidet sich von Shell Command Injection
    • Er tritt auch dann auf, wenn ein Programm statt system() ein argv-Array und exec verwendet
    • Das Array wird unbeschädigt an Git übergeben, aber Git interpretiert Argumente mit führendem Bindestrich als Optionen
  • docker build CVE-2019-13139 ist ein Beispiel ohne Shell, das Go os/exec und ein argv-Array nutzte
    • Das Fragment #ref:dir einer Git-Kontext-URL wurde an git fetch origin <ref> weitergegeben, worauf <ref> als --upload-pack=<cmd> interpretiert wurde

Wiederkehrende Schwachstellen in mehreren Versionsverwaltungssystemen

  • Am selben Tag im August 2017 wurde dasselbe Muster in vier Versionsverwaltungssystemen veröffentlicht
  • Alle vier Systeme übergaben Hostnamen aus URLs als SSH-Argumente, und Hostnamen, die mit -oProxyCommand= begannen, wurden als SSH-Optionen behandelt
  • Laut der Phabricator-Nachanalyse fügte von den damals aktiv gepflegten drei Tools nur Subversion vor dem Hostnamen ein -- ein
    • Git und Mercurial validierten stattdessen das Hostnamenformat, auch weil nicht jede SSH-Implementierung -- unterstützt
  • Code ohne -- wirkt und funktioniert normal, solange kein Argument mit einem Bindestrich beginnt; dieser Mechanismus ist daher standardmäßig unsicher

Wie Paketmanager exponiert werden

  • Paketmanager übernehmen Git-URLs oder Refs aus Manifesten, Lockfiles und Metadaten transitiver Abhängigkeiten und reichen sie an Unterprozesse weiter
    • gem 'foo', git: '...' in Gemfile
    • github:user/repo#ref in package.json
    • entsprechende Konfigurationen in pyproject.toml, Cargo.toml, mix.exs, Package.swift, pubspec.yaml, conanfile.py und go.mod
  • Zum Stand von HEAD im Juli 2026 führten 17 von 19 untersuchten Paketmanagern das Git-Binary standardmäßig oder ausschließlich aus
  • Die verbleibenden zwei verwenden standardmäßig Bibliotheken
    • Cargo nutzt libgit2 und startet einen Git-Prozess, wenn net.git-fetch-with-cli aktiviert ist
    • Poetry ist ab 1.2.0 auf dulwich umgestiegen und kann per Einstellung system-git-client das System-Git nutzen
  • Nix verwendet beim Lesen lokaler Repositories libgit2, startet aber für Fetches einen Git-Prozess, weil libgit2 keine git-credential-Hilfsprogramme unterstützt
  • Untersucht wurden Bundler, Cargo, CocoaPods, Composer, Conan, Go, Helm, Homebrew, Mix, Nix, npm, pip, pnpm, Poetry, Pub, SwiftPM, uv, vcpkg und Yarn

Bestätigte CVEs in Paketmanagern

Tatsächlicher Stand der Abwehr und der Go-Fix

  • Von den 17 Paketmanagern, die einen Git-Prozess starten, verwendete nur Go cmd/go --end-of-options
  • Go fügte im Juni 2019 im Rahmen einer allgemeinen Härtung vor Repository-URLs ein -- ein
  • Im Januar 2026 stellte sich heraus, dass -- allein nicht ausreicht; der Fix für CVE-2025-68119 fügte daraufhin breit --end-of-options hinzu
  • Derselbe Fix enthält auch HGPLAIN=+strictflags
    • Diese Einstellung schränkt seit Mercurial 4.4.2 von 2017 die frühe Optionsauswertung von Mercurial ein
  • Der Go-Fix-Commit merkt an, dass möglicherweise strukturellere Änderungen nötig sind, damit dasselbe Problem nicht wieder eingeführt wird, behebt aber zunächst das aktuelle Problem

Die meisten Schutzmaßnahmen kamen erst nach Offenlegung

  • Die übrigen Paketmanager schützen ihre Argumentlisten, falls überhaupt, meist mit -- oder durch Ablehnung führender Bindestriche in Eingaben
  • Das -- vor der git clone-URL in Bundler wurde mit dem Patch für CVE-2021-43809 hinzugefügt
  • Die Ablehnung führender Bindestriche in cocoapods-downloader wurde innerhalb von zehn Tagen im März 2022 in drei Commits umgesetzt, zeitlich passend zur Offenlegung von CVE-2022-21223
  • Die Schutzmaßnahme von Poetry wurde im September 2021 hinzugefügt, ein Jahr später erhielt sie eine CVE, und sechs Monate danach erfolgte der Wechsel zu dulwich
  • vcpkg ist eine Ausnahme und nutzte vom ersten Tag der Git-Registry-Unterstützung an --

Kompatibilitätsgrenzen durch minimale Git-Versionen

  • Die Warnmeldung zu Composer CVE-2022-24828 nennt --end-of-options als korrekten Fix, lehnt aber Branch-Namen mit führendem Bindestrich ab, weil auch ältere Git-Versionen unterstützt werden müssen
  • Die Git-Integration von vcpkg nennt Git 2.7.4 als Mindestversion, und HOMEBREW_MINIMUM_GIT_VERSION für Linux in Homebrew ist seit 2018 auf 2.7.0 gesetzt
  • Da Amazon Linux 2 mit Git 2.14.3 im Juni 2026 das Lebensende erreichte, fallen Distributionen, an deren Versionen sich diese Untergrenzen orientierten, erst jetzt aus dem Supportbereich heraus
  • Auch der LTS-Status von Ubuntu erschwert einen einheitlichen Wechsel
    • Ubuntu 18.04 liefert Git 2.17.0 und erhält erweiterten Support bis 2028
    • Ubuntu 20.04 liefert Git 2.25.1 und erhält erweiterten Support bis 2030
    • Git 2.25.1 akzeptiert --end-of-options bei git fetch, lehnt es aber bei git rev-parse ab
  • Wer sich auf --end-of-options stützen will, muss für die meisten Unterbefehle Git 2.24.0, für rev-parse 2.30.0 und für checkout sowie reset 2.43.1 als Mindestversion verlangen
  • Höhere Mindestversionen bedeuten, dass Nutzer mit älteren, von Distributionen mitgelieferten Git-Versionen nicht mehr unterstützt werden können

Git-Bibliotheken statt Prozessausführung

  • libgit2, gitoxide, go-git, JGit und dulwich implementieren die für Clone und Fetch nötigen Git-Transportprotokolle innerhalb des Prozesses
  • Es gibt keine separate argv-Grenze, also kein Ziel mehr, in das eine Argumentliste injiziert werden könnte
  • Jujutsu verwendet für die Git-Integration gitoxide und hat keine veröffentlichten CVEs dieses Typs
    • Die bislang zwei Advisories betreffen Path Traversal und eine aus der Bibliothek übernommene fehlende Prüfung auf SHA-1-Kollisionen
  • CVE-2025-21613 in go-git ist auf den file://-Transport beschränkt
    • Das ist dort der einzige Codepfad, auf dem go-git überhaupt das Git-Binary ausführt
  • Wer eine eigene Git-Implementierung mitliefert, muss sämtliche Upstream-Fixes zur Checkout-Sicherheit nachverfolgen; sowohl libgit2 als auch JGit mussten dazu wiederholt nachbessern
  • Diese Kosten sind real, aber das Problem verschiebt sich von dauerhaft nötiger Argumentprüfung an jedem Aufrufpunkt hin zur Anwendung konkreter Upstream-Patch-Flüsse

Vorgeschlagener Änderungsumfang für Homebrew

  • Der Homebrew-PR hebt die minimale Git-Version auf 2.30.0 an und fügt --end-of-options an folgenden Stellen hinzu
    • vor URLs bei clone, remote set-url und ls-remote
    • vor Refs bei rev-parse
  • Aufrufe von checkout und reset werden nicht geändert
    • Um auch diese beiden Befehle zu schützen, wäre Git 2.43.1 nötig, veröffentlicht im Februar 2024
    • Diese Version ist neuer als das Git, das mehrere aktuell unterstützte Distributionen bereitstellen

1 Kommentare

 
GN⁺ 6 시간 전
Meinungen auf Lobste.rs
  • Ich verfalle in letzter Zeit immer mehr jj. Im Vergleich zu diesem Git-Wirrwarr ist es regelrecht beruhigend; ich lerne es zwar noch, aber es spiegelt die Absicht gut wider, und auch die gewünschte Arbeitsweise lässt sich leicht nachvollziehen.
    Besonders lässt sich Wissen, das man bei einem jj-Befehl gelernt hat, ganz natürlich auf andere Befehle übertragen. Die git log-Dokumentation ist voll von wuchernden Flags je Ausgabeoption, vermischt mit der Commit-Range-Syntax der „speziellen Notationen“ und zusätzlichen Filter-Flags; außerdem überträgt sich nur wenig davon auf andere Git-Befehle.
    Die jj log-Dokumentation ist dagegen so knapp, dass selbst auf einer Seite noch Platz bleibt. Die zusammengewürfelte Komplexität von Git wird durch drei Dinge ersetzt: Revision Sets, File Sets und eine Output-Template-DSL. Sie werden in jj durchgängig konsistent verwendet und sind dadurch viel einfacher, kombinierbarer und intuitiver. Man muss berücksichtigen, dass Git über 20 Jahre Ballast angesammelt hat, aber jj scheint deutlich besser aufgestellt, um das zu vermeiden.
  • Dass man in Befehlen vor vom Nutzer gelieferten Argumenten normalerweise -- braucht, ist mir klar; wenn dafür aber auch noch eigene Transformationsregeln verlangt werden, wird es zu einer viel zu gefährlichen Falle.
  • Das ist die ehrliche Konsequenz daraus, ein Tool übermäßig komplex gemacht zu haben. Git wirkt wie das neu aufgetauchte Vasa-Syndrom.
  • Ich frage mich, warum ursprünglich -- gewählt wurde, um die Mehrdeutigkeit von Dateiargumenten aufzulösen. Gab es einen nicht offensichtlichen Design-Trade-off, der dafür sprach, statt eines einfacheren Ansatzes mit expliziten Dateiargumenten?
    • Eine bereits etablierte UNIX-Konvention fürs Optionsparsing für einen völlig anderen Zweck zu verwenden, ist erwartungsgemäß töricht und eine ausgesprochen Git-typische Entscheidung.
  • Die UNIX-Philosophie Alles ist Text verursacht wieder einmal Probleme. Die Kommandozeile ist eigentlich ein perfektes Beispiel für strukturierte Daten, aber die Kosten für gemeinsame Koordination, um aus dieser Grube herauszukommen, scheinen zu hoch zu sein.
    Blickt man auf die Debatten zwischen Tcl und Scheme in den 1990ern zurück, könnte in diesem Fall „schlechter ist besser“ tatsächlich gestimmt haben. Während sich die Familie der S-Ausdrücke, einschließlich JSON und XML und Ähnlichem, im UNIX-Ökosystem kaum durchgesetzt hat, hatte Tcl einen vergleichsweise prinzipientreuen Ansatz, Untersprachen innerhalb von Strings zu schichten.