Blog

99% des Codes ist nicht von Ihnen

Jesse Houwing

Aktualisiert Oktober 21, 2025
10 Minuten

Ihr eigenes 1% steht unter Versionskontrolle, aber halten Sie alle Bibliotheken, die Sie importieren, jedes Mal fest, wenn Sie eine Dotnet-Wiederherstellung oder eine npm-Installation durchführen?

In den letzten Jahren ist die Zahl der gemeldeten Supply-Chain-Angriffe gestiegen. Dabei handelt es sich um Angriffe, bei denen der Angreifer nicht versucht, sich Zugang zu Ihren Quellcode-Repositories zu verschaffen, sondern zu denen eines der vielen Projekte, von denen Sie abhängig sind. Eine Bitcoin-Wallet wurde kompromittiert und sendete Wallet-Schlüssel über ein nodejs-Paket, das den Besitzer wechselte, an eine fremde Domain. Die Kreditkartendaten von Tausenden von Benutzern wurden über den Chat-Client abgefangen, der in dieselben Seiten eingebettet war, die auch die Transaktionen abwickelten. Und es ist nicht auf Websites und JavaScript-Anwendungen beschränkt. Bei Asus wurden die Update-Tools für Laptops kompromittiert, so dass bestimmte Ziele zusätzliche Pakete im Rahmen von Treiber-Updates herunterladen und installieren konnten.

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Erfahren Sie, wie Sie DevSecOps auf der Grundlage einer exklusiven Erfahrung anstelle von Lehrbuchbeispielen richtig einsetzen und Ihre DevOps-Einführung beschleunigen können.

Broschüre DevOps Bootcamp

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Die gleichen Gefahren lauern auch für .NET-Entwickler. Sie fragen sich vielleicht: "Wie funktioniert das, und was bedeutet das für mich?"

Ein Angriff über die Lieferkette erfolgt, wenn jemand über einen Drittanbieterdienst oder eine Abhängigkeit in Ihre Systeme eindringt, um eine Schwachstelle in einem System auszunutzen. In der Regel versuchen Angreifer, bösartigen Code in offizielle Downloads und Installationsprogramme von vertrauenswürdigen Drittanbietern oder in von Entwicklern verwendete Abhängigkeiten einzufügen. Sobald Unternehmen diese Dienste nutzen, sind sie automatisch auch der eingebetteten Malware ausgesetzt. In der Regel haben es die Angreifer auf den Quellcode oder sensible Daten abgesehen und können sich diesen Zugang verschaffen, indem sie das schwächste Glied in der Software-Lieferkette ausfindig machen, ohne jemals in die Nähe der Server ihres Ziels gelangen zu müssen. Einer der Vorteile für die Angreifer ist, dass sie mit einem Stück bösartigen Codes in einer Abhängigkeit viele Unternehmen auf einmal angreifen können. Darüber hinaus ist es für Unternehmen oft schwierig, diese Angriffe zu erkennen, da sie von vielen Drittanbieterdiensten und Abhängigkeiten abhängen.

Das ist alles sehr interessant, aber das wird Ihnen doch nicht passieren, oder? Nun, wie sich herausstellt, ist es für Hacker vielleicht gar nicht so schwierig, bösartigen Code in Ihr Projekt einzuschleusen, wie Sie denken. Hier ein kleines Szenario: Stellen Sie sich vor, Sie sind ein .NET-Entwickler in einem Unternehmen und Ihr Team ist für eine Anwendung verantwortlich, die mit sensiblen Daten arbeitet. Sie möchten sich auf die Geschäftslogik Ihrer Anwendung konzentrieren, anstatt für jedes Stückchen Code, das Sie benötigen, das Rad neu zu erfinden, also verwenden Sie NuGet als Paketmanager. Es hilft Ihnen, Code von anderen Entwicklern wiederzuverwenden, um einige Ihrer Aufgaben zu lösen, so dass Sie Ihre Zeit auf die spezifische Logik Ihrer Anwendung verwenden können.

Das ist zwar eine gängige Praxis, aber die Verwendung des Codes eines anderen bedeutet, dass Sie einen Weg finden müssen, ihm zu vertrauen. Wissen Sie immer, was in den Paketen enthalten ist, die Sie verwenden? Was wäre, wenn eine der vielen Abhängigkeiten, die Sie in Ihrem Projekt verwenden, mit bösartigem Code infiziert wäre? Was wären die Folgen? Und wie würden Sie dies überhaupt erkennen?

[2] security.ticketmaster.ie/

[3] securelist.com/operation-shadowhammer/89992/

Wie kann das passieren?

Es ist nicht schwer, bei der Wiederherstellung von Paketen auf verschiedenen Rechnern ein anderes Paket angezeigt zu bekommen. Dies ist das Standardverhalten der meisten Paketmanager, einschließlich NuGet. Wenn Sie Pakete wiederherstellen, versucht NuGet, die von Ihnen gewünschten Versionen zu finden, und versucht nach besten Kräften, Probleme zu beheben.

## Warnung NU1603: Microsoft.IdentityModel.Clients.ActiveDirectory 3.13.5 ist abhängig von System.Net.Http (>= 4.0.1), aber System.Net.Http 4.0.1 wurde nicht gefunden. Es wurde eine ungefähre beste Übereinstimmung von System.Net.Http 4.1.0 aufgelöst. 

Ein Beispiel aus einem der von uns betreuten Open-Source-Projekte

Es gibt einige Fälle, in denen NuGet nicht in der Lage ist, bei jeder Wiederherstellung auf allen Rechnern denselben Paketgraphen zu erhalten. Die meisten dieser Fälle treten auf, wenn Konsumenten oder Repositories nicht den NuGet Best Practices folgen:

1. nuget.config stimmt nicht überein: Dies kann zu einem inkonsistenten Satz von Paket-Repositorys (oder Quellen) bei Wiederherstellungen führen. Je nachdem, welche Version der Pakete in diesen Repositories verfügbar ist, löst NuGet bei der Wiederherstellung möglicherweise unterschiedliche Versionen der Pakete auf.

2. Zwischenversionen: Eine fehlende Version des Pakets, die den PackageReference-Versionsanforderungen entspricht, wird veröffentlicht:

  • Tag 1: Wenn Sie <PackageReference Include="My.Sample.Lib" Version="4.0.0"/> angegeben haben, die in den NuGet-Repositories verfügbaren Versionen aber 4.1.0, 4.2.0 und 4.3.0 sind, löst NuGet die Version 1.0 auf, da dies die nächstliegende Mindestversion ist.
  • Tag 2: Version 4.0.0 wird veröffentlicht. NuGet stellt nun die Version 0.0wieder her , dasie genau übereinstimmt.

3. Löschung von Paketen: Obwohl nuget.org das Löschen von Paketen nicht zulässt, gilt diese Einschränkung nicht für alle Paket-Repositories. Die Löschung einer Paketversion führt dazu, dass NuGet die beste Übereinstimmung findet, wenn es die gelöschte Version nicht auflösen kann.

4. Fließende Versionen: Wenn Sie Floating-Versionen wie verwenden, erhalten Sie möglicherweise unterschiedliche Versionen, wenn neue Versionen verfügbar sind. Es ist zwar beabsichtigt, bei jeder Wiederherstellung von Paketen auf die neueste Version zu wechseln, aber es gibt Szenarien, in denen Benutzer verlangen, dass der Graph auf eine bestimmte neueste Version fixiert wird und nur auf ausdrückliche Geste hin auf eine spätere Version wechselt, falls diese verfügbar ist.

5. Nicht übereinstimmende Paketinhalte: Wenn dasselbe Paket (ID und Version) in verschiedenen Repositories mit unterschiedlichem Inhalt vorhanden ist, kann NuGet nicht sicherstellen, dass dasselbe Paket (mit demselben Inhalts-Hash) jedes Mal aufgelöst wird. Es gibt in diesen Fällen auch keine Warnung oder Fehlermeldung aus.

6. Cache-Vergiftung: NuGet prüft den lokalen Paket-Cache, bevor es die konfigurierten Paket-Feeds prüft (sofern nicht --no-cache angegeben ist). Diese werden im Falle einer exakten Versionsübereinstimmung verwendet. Wenn Sie einen Proxy-Feed (z.B. Azure Artefacts) verwenden, könnte ein Angreifer mit Zugriff auf den Feed (oder einen Upstream-Feed) eine bestimmte Version in diesem Feed veröffentlichen, die dann anstelle der von Ihnen erwarteten Version verwendet wird.

[4]docs.microsoft.com/de-us/nuget/concepts/dependency-resolution

[5] Wiederholbare-Paket-Wiederherstellung-unter Verwendung einer Sperrdatei aktivieren/

Mehr und mehr Wiederverwendung

Wenn wir nur von einigen wenigen Abhängigkeiten abhängig wären und diese sich nur einmal in sehr langer Zeit ändern würden, wäre es nicht schwer, die Änderungen manuell zu überprüfen. Wenn Sie Zugang zu den Quellen hätten. Und in diesem Fall könnten Sie alle Ihre Abhängigkeiten in einen manuell kuratierten Feed kopieren. Aber wir leben nicht mehr in dieser Welt.

99 Code

Wenn Sie ein neues Visual Studio 2019 (16.2.2) React.js-Webanwendungsprojekt erstellen, haben Sie am Ende 15214 Nodejs-Pakete (686 mit bekannten Sicherheitslücken) und 284 NuGet-Pakete (18 mit bekannten Sicherheitslücken). Wenn eines dieser Pakete kompromittiert ist, fügen Sie es möglicherweise Ihrem Projekt hinzu, wenn Sie das nächste Mal npm install oder dotnet restore ausführen. Oder schlimmer noch, Ihr lokaler Entwicklungscomputer ist vielleicht in Ordnung, aber der Build-Server holt sich möglicherweise alle aktuellen Versionen. Dies ist vor allem dann der Fall, wenn Sie den Azure Pipelines Hosted Pool verwenden, da bei jedem Build ein frisches Image verwendet wird, bei dem nur sehr wenige Pakete zwischengespeichert sind.

Was wir brauchen, ist eine Möglichkeit, alle unsere abhängigen Pakete auf effiziente Weise in der Versionsverwaltung zu speichern, vorzugsweise ohne den gesamten Inhalt der Pakete in der Versionsverwaltung speichern zu müssen. Das mag wie ein Widerspruch klingen, ist es aber nicht. Anstatt alle Paketinhalte und die aller ihrer Abhängigkeiten zu speichern, verwenden Sie, was npm, NuGet und yarn tun. Diese Tools speichern den Namen, die genaue Version und einen Hash des Paketinhalts für alle Pakete im Abhängigkeitsbaum in einer Datei. Diese Datei wird als Lock-Datei bezeichnet. Indem Sie diese Lock-Datei an Ihr Versionskontroll-Repository übergeben, stellen Sie sicher, dass:

  1. Ihr Build-Server (und Ihre Kollegen) werden genau die Pakete verwenden, die Sie auf Ihrem Entwicklungsrechner eingesetzt haben.
  2. Sie führen ein prüfbares Protokoll aller Änderungen an Ihrem Abhängigkeitsbaum.
  3. Sie können alle Änderungen an den Abhängigkeiten vor dem Commit oder als Teil des Pull-Request-Überprüfungsprozesses überprüfen.

[6] twitter.com/liran_tal

Erzeugen von Lock-Dateien für .NET-Lösungen

Ihre .NET-Projekte werden standardmäßig keine Lock-Dateien erzeugen. Sie müssen Ihr Projekt auch aktualisieren, um das neue <PackageReference>-Format zu verwenden. Dann können Sie den Build-Prozess über einen Befehlszeilenparameter anweisen, die Lock-Datei zu erzeugen:

Erzeugen Sie die Lock-Datei über dotnet:

> dotnet restore --use-lock-file

Erzeugen Sie die Lock-Datei mit msbuild:

> msbuild /t:restore /p: RestorePackagesWithLockFile=true

Sie können auch eine Eigenschaft zu Ihren Projektdateien hinzufügen, um bei jeder Wiederherstellung Sperrdateien zu erzeugen:

<Projekt>
 
 true
 
</Projekt>

Hinweis: Dieses Verhalten unterscheidet sich von npm und yarn, die die Lock-Dateien jedes Mal automatisch erzeugen, wenn Sie Ihre Abhängigkeiten wiederherstellen.

NuGet speichert jetzt neben jedem Projekt eine packages.lock.json. Die Datei enthält alle Abhängigkeiten, ihre genauen Versionen, wie die Abhängigkeit eingeführt wurde und einen Hash des Paketinhalts:

  "Microsoft.AspNetCore.WebSockets": {
  "Typ": "Direkt",
  "angefordert": "[2.2.1, )",
  "aufgelöst": "2.2.1",
  "contentHash": "Ilk4fQ0xdVpJk1a+72thHv2LglUZPWL+vECOG3mw+gOesNx0/p56HNJXZw8k1pj8ff1cVHn8KtfvyRZxdplNQA==",
  "Abhängigkeiten": {
  "Microsoft.AspNetCore.Http.Extensions": "2.2.0",
  "Microsoft.Extensions.Logging.Abstractions": "2.2.0",
  "Microsoft.Extensions.Options": "2.2.0",
  "System.Net.WebSockets.WebSocketProtocol": "4.5.3"
  }
  },

Übertragen Sie diese Dateien in Ihr Versionsverwaltungs-Repository, um die genauen Abhängigkeiten zusammen mit Ihren anderen Quelldateien zu speichern.

Wiederherstellung aus der Lock-Datei in Ihrer CI-Lösung

Wir möchten, dass NuGet genau die Pakete herunterlädt, die wir auf unserem Entwicklungssystem verwendet haben. Es reicht nicht aus, Ihre Abhängigkeiten in der Versionsverwaltung zu speichern. Einer der ersten Schritte Ihres CI-Prozesses ist wahrscheinlich die Dotnet-Wiederherstellung, und wenn wir nichts dagegen unternehmen, wird dies einfach einen neuen Satz von Abhängigkeiten herunterladen und die Lock-Datei dann überschreiben.

Stattdessen sollten wir NuGet anweisen, genau die Pakete wiederherzustellen, die in der Lock-Datei angegeben sind. Und auch dies kann über einen Befehlszeilenparameter oder eine msbuild-Eigenschaft erfolgen.

Zur Wiederherstellung im gesperrten Modus mit dotnet:

> dotnet restore --locked-mode

Wiederherstellung im gesperrten Modus mit msbuild:

> msbuild /t:wiederherstellen /p:RestoreLockedMode=true

Um sicherzustellen, dass der Continuous Integration Server standardmäßig den gesperrten Modus verwendet, können Sie diese Eigenschaft auch in der Projektdatei festlegen:

<Projekt>
 
 true
 
  condition="'$(RestoreLockedMode)' == ''
  && ('$(TF_BUILD)' == 'wahr'
  || '$(CONTINUOUS_INTEGRATION)' == 'true')"
  >
  true
 
 
</Projekt>

Ihre .NET-Projekte werden nun bei jedem Build auf einen vorhersehbaren Satz von Abhängigkeiten zurückgesetzt, oder der Build wird fehlschlagen.

Jedes Mal, wenn Sie lokal wiederherstellen, sehen Sie genau, welche Pakete aktualisiert wurden, und Sie können deren Inhalt auf Ihrem Entwicklungsrechner überprüfen:

99 Code

Die Wiederherstellung mit einer anderen .NET Core-Version kann zu unterschiedlichen Paketinhalten bei gleicher Version führen. Dies wird erkannt und Ihr Build schlägt fehl.

Auswirkungen auf die Bauzeiten

Sie werden sich vielleicht fragen, wie sich die Aktivierung dieser Funktion auf die Wiederherstellungszeiten auswirken wird. Auf dem Entwicklungsrechner dauert die Wiederherstellung länger, da die Sperrdatei erstellt und der Hash für den Paketinhalt berechnet werden muss.

Auf dem Build-Server ist die Sache weniger eindeutig. Die Zeit für die Auflösung von Paketversionen und die Berechnung des Abhängigkeitsbaums wird auf die Zeit reduziert, die für das Laden der Lock-Datei benötigt wird. Das kann eine Menge Zeit sparen. Andererseits wird die Überprüfung des Paketinhalts etwas Zeit in Anspruch nehmen. In unseren Tests waren die durchschnittlichen Zeiten für die Ausführung des Builds auf Azure Pipelines schneller, wenn der Lock-Modus aktiviert war.

Praktisch: Testen Sie die Herausforderung des Global DevOps Bootcamp 2019

Das Global DevOps Bootcamp 2019 beinhaltete eine Supply Chain Attack Challenge, bei der Sie die Auswirkungen eines Supply Chain Angriffs erleben können. Im Rahmen der praktischen Übungen können Sie npm- und NuGet-Paket-Sperrdateien erzeugen, den Build-Prozess anpassen, um gesperrte Wiederherstellungen durchzuführen, und einen Scanner zu Ihrem Build-Prozess hinzufügen, um bekannte Sicherheitslücken in Ihren Abhängigkeiten zu erkennen. Durch die Anwendung dieser Techniken werden Sie in der Lage sein, die Kontrolle darüber zu übernehmen, was Sie Ihren Kunden jedes Mal zur Verfügung stellen, wenn Sie Ihre neuesten Änderungen bereitstellen.

Jesse Houwing

Sofie Wisse

[7] gdbc-challenges.com/Herausforderungen/ChallengeDetails/VULNPACKAGE

Erfahren Sie, wie Sie DevOps auf der Grundlage einer exklusiven Erfahrung statt auf der Grundlage von Lehrbuchbeispielen richtig einsetzen und Ihre DevOps-Einführung beschleunigen können.

Broschüre DevOps Bootcamp

Verfasst von

Jesse Houwing

Jesse is a passionate trainer and coach, helping teams improve their productivity and quality all while trying to keep work fun. He is a Professional Scrum Trainer (PST) through Scrum.org, Microsoft Certified Trainer and GitHub Accredited Trainer. Jesse regularly blogs and you'll find him on StackOverflow, he has received the Microsoft Community Contributor Award three years in a row and has been awarded the Microsoft Most Valuable Professional award since 2015. He loves espresso and dark chocolate, travels a lot and takes photos everywhere he goes.

Contact

Let’s discuss how we can support your journey.