Warum alle Dependency-Cooldowns verwenden sollten
(blog.yossarian.net)- Dependency-Cooldowns sind eine einfache und wirksame Sicherheitstechnik, mit der sich die meisten Angriffe auf die Open-Source-Lieferkette abmildern lassen
- Angreifer kapern in der Regel beliebte Open-Source-Projekte und verteilen darüber Schadcode, doch bei den meisten Angriffen ist das Zeitfenster der Exponierung kürzer als eine Woche
- Wenn nach der Veröffentlichung einer neuen Version ein Cooldown mit einer Wartezeit (z. B. 7 Tage) festgelegt wird, lässt sich das Infektionsrisiko durch automatische Updates deutlich senken
- Dependabot, Renovate, pnpm usw. unterstützen Cooldowns bereits standardmäßig, die Einrichtung ist einfach und verursacht keine zusätzlichen Kosten
- Wenn Cooldowns auf Ebene des Paketmanagers standardmäßig bereitgestellt werden, kann das die Sicherheit der Lieferkette stärken und unnötige Warnmeldungen reduzieren
Struktur und Probleme von Lieferkettenangriffen
- Die meisten Lieferkettenangriffe (supply chain attacks) folgen demselben Muster
- Angreifer verschaffen sich Zugang über gestohlene Zugangsdaten eines beliebten Open-Source-Projekts oder über CI/CD-Schwachstellen
- Sie laden bösartige Änderungen in Distributionskanäle wie PyPI oder npm hoch
- Durch automatische Updates oder fehlende Versionsfixierung installieren Nutzer die infizierte Version
- Sicherheitsanbieter erkennen dies, warnen davor, und das Paket-Repository entfernt die betroffene Version
- Der Abstand zwischen den Schritten (1) und (2) ist lang, aber die Schritte (2) bis (5) werden innerhalb von Stunden bis wenigen Tagen abgewickelt, sodass das aktive Zeitfenster der Angreifer kurz ist
- Zeitfenster der Angriffsmöglichkeit (window of opportunity) in wichtigen Fällen der letzten 18 Monate
- xz-utils: etwa 5 Wochen
- Ultralytics: 12 Stunden (Phase 1), 1 Stunde (Phase 2)
- tj-actions: 3 Tage
- chalk: weniger als 12 Stunden
- Nx: 4 Stunden
- rspack: 1 Stunde
- num2words: weniger als 12 Stunden
- Kong Ingress Controller: etwa 10 Tage
- web3.js: 5 Stunden
- 8 dieser Fälle hatten ein Angriffsfenster von weniger als einer Woche und wären größtenteils durch Cooldowns blockiert worden
Konzept und Wirkung von Cooldowns
- Ein Cooldown verzögert die Nutzung neuer Abhängigkeiten für einen bestimmten Zeitraum nach ihrer Veröffentlichung
- In dieser Zeit können Sicherheitsanbieter erkennen, ob eine Version bösartig ist
- Vorteile
- Nachweislich wirksam und in der Lage, die meisten groß angelegten Angriffe zu blockieren
- Sehr einfach umzusetzen und in den meisten Tools kostenlos konfigurierbar
- Dependabot-Beispiel
version: 2 - package-ecosystem: github-actions directory: / schedule: interval: weekly cooldown: default-days: 7 - Fördert positives Verhalten bei Sicherheitsanbietern: Statt übermäßiger Warnungen oder PR konzentrieren sie sich auf schnelle Erkennung
Fazit und Empfehlungen
- Bei 8 von 10 Angriffen lag das Angriffsfenster bei höchstens einer Woche, und mit einem 7-Tage-Cooldown ließe sich der Großteil blockieren
- Mit einem 14-Tage-Cooldown wären alle Fälle außer xz-utils abgedeckt
- Cooldowns sind keine perfekte Lösung, aber eine einfache Methode, das Expositionsrisiko um 80–90 % zu senken
- Neben Dependabot und Renovate sollten auch Paketmanager selbst Cooldowns standardmäßig unterstützen
- Sicherheit in der Lieferkette ist nicht nur ein technisches Problem, sondern auch eine Frage sozialer Vertrauensstrukturen – Cooldowns sind dabei jedoch eine nützliche, realistische Gegenmaßnahme
3 Kommentare
Eigentlich scheint es besser zu sein, gar nicht erst zu aktualisieren, wenn es kein Problem gibt.
Sollte man wirklich unbedingt eine neue Version einspielen, die sich kaum von der vorherigen unterscheidet, und dafür das Risiko in Kauf nehmen?
Hacker-News-Kommentar
Menschen sorgen sich, dass sie ohne sofortige Updates schweren Schwachstellen ausgesetzt sind, aber in der Praxis ist das meist nicht der Fall
Viele Softwareprodukte werden nicht per Continuous Deployment ausgeliefert, sondern Kunden installieren neue Versionen selbst, daher erfolgen Updates oft nur im Abstand von Wochen oder Monaten
Entscheidend sind Dependency-Monitoring und die Prüfung veröffentlichter Schwachstellen. Man bewertet zunächst, ob das Produkt tatsächlich betroffen ist, und aktualisiert diese Dependency dann nur in diesem Fall sofort
Es ist die Vorstellung verbreitet, dass man bei jeder neuen Version heute noch aktualisieren müsse
Änderungen nicht wirklich zu prüfen und stattdessen nach dem Motto „Später wird es noch schwerer, also machen wir es jetzt“ vorzugehen, ist ineffizient
An der vordersten Front der Versionsnummern zu bleiben, kann der Sicherheit sogar abträglich sein
In unserem Unternehmen muss innerhalb von 7 Tagen aktualisiert werden, wenn ein Scanner eine kritische Schwachstelle findet
Wird die Frist überschritten, beginnt wegen eines Compliance-Verstoßes ein komplizierter Prozess, daher aktualisieren die meisten einfach alles sofort
Anwendungen wie Browser mit viel externem Input sollten häufig aktualisiert werden, während etwa eine Wetter-App mit begrenztem Input vergleichsweise sicher ist
Effizienter ist es, regelmäßig zu aktualisieren und gleichzeitig Schutzmaßnahmen gegen Supply-Chain-Angriffe umzusetzen
Ein Modell wie Debian stable, bei dem die Distribution gemeinsame Dependencies verwaltet und alle paar Jahre ein Gesamt-Upgrade durchführt, wirkt zunehmend vernünftig
Manche Ökosysteme bewegen sich zu schnell, oder ihre distributionsspezifischen Paketierungssysteme sind zu schwach
Zum Beispiel ist es immer noch schwierig, Node.js-Bibliotheken per apt zu installieren und im Projekt zu verwenden
Ein Ökosystem, das sich schnell bewegt, ohne grundlegende Veränderungen hervorzubringen, ist nicht gesund
JS hat in den letzten 3 Jahren kaum echten Fortschritt gemacht, aber wenn man ein 3 Jahre altes Projekt erneut bauen will, bedeutet das Leid auf Rewrite-Niveau
In Distributionen wie Arch gibt es sie teils gar nicht
Es gibt die Annahme, dass eine Cooldown-Phase bei Dependency-Updates zur Abwehr von Supply-Chain-Angriffen besser ist, als sofort die neueste Version zu verwenden, um 0-days zu stoppen
Es geht um den Vergleich zwischen der Wahrscheinlichkeit, dass ein Update eine neue Schwachstelle einführt, und der Wahrscheinlichkeit, dass es eine bestehende behebt
Nach SemVer sind Patch-Versionen relativ sicher, daher ist auch ein Ansatz mit kurzer Cooldown-Phase denkbar
Wenn zum Beispiel auf 2.3.4 die Version 2.4.0 erscheint, ist es ohne dringenden Funktionsbedarf womöglich besser, bis 2.4.1 zu warten
Die meisten Schwachstellen entstehen nicht durch absichtliche Angriffe, sondern durch gewöhnliche Bugs
Eine Politik, die Anzahl und Komplexität von Dependencies begrenzt, ist ein stärkerer Ansatz
Statt „Bibliotheken, die alles machen“, sollte man nur kleine Dependencies mit klarem Zweck hinzufügen
Außerdem wäre die Verwaltung einfacher, wenn Bibliotheken LTS-Versionen anbieten würden, die nur Security-Patches enthalten
Es selbst noch einmal zu implementieren, kann Verschwendung sein. Ich würde gern ein konkretes Beispiel für eine problematische „everything library“ sehen
Wenn man demselben Entwickler über mehrere Bibliotheken hinweg vertraut, sind Vertrauensbeziehungen wichtiger als die Zahl der Pakete
Komplexität korreliert zwar mit Schwachstellen, ist aber nicht deren direkte Ursache
Damit werden Entscheidungen möglich, die statt auf kurzfristige Geschwindigkeit stärker auf langfristige Wartbarkeit zielen
In der C++-Welt ist das mitunter sogar ein Wettbewerbsfaktor
Der Druck, immer auf die neueste Version zu aktualisieren, beruht auf dem falschen Glauben, dass Software ständig besser wird
In Wirklichkeit tauscht man damit womöglich bekannte Bugs gegen neue unbekannte Bugs aus
Veröffentlichte Issues zu beobachten und nur bei Bedarf zu patchen, ist vernünftig
Das ist also ein Fall, in dem die ältere Version sogar sicherer war
Vielleicht liegt das auch daran, dass wir bereits ausreichend stabile Software verwenden
Wenn alle sagen „Warten wir ein wenig“, überprüft am Ende wie bei einer Tragik der Allmende niemand mehr zuerst
Je länger man wartet, desto mehr technische Schulden häufen sich an, daher braucht es inkrementelle Updates und Gegenmaßnahmen wie Zero Trust und Monitoring
In dieser Zeit haben Security-Scanner Schwachstellen meist bereits erkannt
Wenn ein Angreifer einen unautorisierten Release hochlädt, wird das manchmal sofort bemerkt
Die Cooldown-Idee ist gut, aber es besteht das Risiko, dass Angreifer sie missbrauchen und künstliche Dringlichkeit erzeugen
Unter dem Vorwand eines „dringenden Security-Patches“ könnten sie zu früher Installation verleiten, obwohl diese Version in Wahrheit bösartig ist
Gegen solche Angriffe mit psychologischem Druck muss man gewappnet sein
Ein weiterer Grund für Cooldown ist, Maintainern Zeit zu geben, einen eigenen Kompromittierungsfall zu bemerken
Angreifer zielen auf Zeitpunkte, an denen Maintainer nicht verfügbar sind, etwa Urlaub, Konferenzen oder Feiertage
Viele Projekte werden von ein oder zwei Personen betreut, daher ist ein solcher Zeitversatz sehr wichtig
Es hängt von den Eigenschaften des Projekts und seiner Angriffsfläche ab
Die Zeit, in der man einfach nur „Best Practices“ folgt, sollte vorbei sein
Security ist wie ein Kontaktsport: Man muss täglich kritisch über Details nachdenken
Es gibt auch Grenzen des Cooldowns als Annahme, dass etwas allein durch Zeit sicherer wird
Wenn niemand den Code anschaut, bleibt er auch nach einer Woche genauso riskant
Stattdessen kann ein Ansatz mit gradual rollout wirksam sein
Jeder Verbraucher setzt dabei einen Verzögerungsfaktor, sodass risikobereitere Seiten zuerst auf Probleme stoßen,
während die übrigen in der Zwischenzeit geschützt sind
Riskante Updates werden aus der Queue entfernt, sodass verzögerte Verbraucher sie gar nicht erst zu sehen bekommen
In letzter Zeit bin ich manchmal unsicher, was besser zu verkraften ist: einfach das Rad neu zu erfinden oder den Aufwand, Abhängigkeits-Tetris zu verwalten.
Wenn bei Ersterem etwas schiefläuft, kann ich einfach meinen eigenen Code korrigieren, aber beim Abhängigkeits-Tetris ist es viel schwerer zu debuggen, wenn plötzlich irgendwo ein Rad eiert.