Sicherheitsdesign für Auto-Updates - Warum HTTPS allein nicht reicht

· · Windows-Entwicklung, Sicherheit, Updater, Auto-Update, Signatur, MSIX, ClickOnce

Inhaltsverzeichnis

  1. Vorab das Fazit
  2. Warum Auto-Update gefährliches Terrain ist
  3. Anti-Patterns
  4. Best Practices
  5. Minimale sichere Konfiguration
  6. Überlegungen für Windows-Projekte
  7. Mindest-Checkliste
  8. Zusammenfassung
  9. Quellen

1. Vorab das Fazit

Zunächst die praktischen Eckpunkte im Überblick.

  • Wenn die Anforderungen passen, zuerst auf eine bestehende Update-Infrastruktur wie MSIX App Installer oder ClickOnce setzen
  • Wird ein eigener Updater benötigt, sollte zuerst nicht die UI, sondern Signaturverifikation und Wiederherstellung im Fehlerfall implementiert werden
  • Update-Informationen wie latest.json als signierte Metadaten behandeln, nicht als unsignierte Konfigurationsdatei
  • TLS ist notwendig, aber keine hinreichende Bedingung
  • Die Update-Entscheidung nicht auf „der Server sagt es“ stützen, sondern auf „der Client hat verifiziert und als korrekt beurteilt“
  • Signaturschlüssel für Entwicklung und Produktion trennen und durch HSM oder einen Signaturdienst schützen
  • Bei Update-Fehlern Fail-Closed statt Fail-Open verwenden
  • Ein Updater ohne Rollback-Schutz sollte so behandelt werden, als könnte er jederzeit auf eine verwundbare Version zurückgesetzt werden
  • Solange Signaturverifikation noch nicht implementiert ist, ist die manuelle Verteilung signierter Installer sicherer als Auto-Update

Zunächst die Begriffe fixieren; im Folgenden werden sie in diesem Sinn verwendet.

Begriff Bedeutung
Fail-Closed Design, das im Fehlerfall auf der „Stopp“-Seite landet. Schlägt die Signaturverifikation fehl, wird das Update nicht fortgesetzt
Fail-Open Design, das auch im Fehlerfall auf der „Weiter“-Seite landet. Es wird nur eine Warnung ausgegeben, das Update läuft aber weiter
Staging Verfahren, bei dem die neue Version zunächst an einem separaten Ort bereitgestellt und erst nach abgeschlossener Verifikation umgeschaltet wird
Kill Switch Mechanismus, um ein laufendes Update-Rollout serverseitig sofort zu stoppen
Trust Anchor Der Ausgangspunkt, dem der Client von vornherein vertraut — ein Root-Public-Key oder eine fixierte Zertifikatskette

Kurz gesagt: Der Kern von Auto-Update ist nicht „wie wird heruntergeladen“, sondern „wem wird vertraut, wo wird verifiziert und wie wird im Fehlerfall zurückgekehrt“.

2. Warum Auto-Update gefährliches Terrain ist

Gewöhnliche Funktionen bleiben innerhalb der App gekapselt. Ein Updater dagegen vereint auf einen Schlag drei Dinge.

  1. Er holt Dateien von außen
  2. Er vertraut diesen Dateien
  3. Er ersetzt bestehende ausführbare Dateien

Mit anderen Worten: Ein Pfad zur Ausführung beliebigen Codes ist von Anfang an im Produkt eingebaut.

Ein verbreitetes Missverständnis lautet: „Es ist HTTPS, also ist es sicher.“ TLS ist natürlich notwendig. Es schützt jedoch vor allem den Übertragungsweg und die Legitimität der Gegenstelle. Wenn der Update-Server selbst kompromittiert wurde, ein falsches Artefakt auf dem legitimen CDN landet oder ein unsigniertes Manifest ausgetauscht wird, reicht das allein nicht aus.

Betrachtet man allein die von TUF (The Update Framework, eine Spezifikation, die das Vertrauensmodell für Software-Updates definiert) katalogisierten Bedrohungen, stehen Update-Systeme vor all dem hier.

  • Beliebige bösartige Software einschleusen lassen
  • Rollback-Angriffe, die auf eine verwundbare alte Version zurückstufen
  • Freeze-Angriffe, die neue Versionen verbergen
  • Mix-and-Match-Angriffe, die inkonsistente Metadaten und Artefakte kombinieren

Das heißt: Auto-Update ist nicht „Dateiübertragung“, sondern „Verteilung von Vertrauen“. Erst wenn man dies durchdenkt, läuft Auto-Update sicher.

2.1 Zuordnung von Bedrohung und Gegenmaßnahme

Bedrohungen und Gegenmaßnahmen sind in diesem Artikel auf verschiedene Kapitel verteilt. Vorab die Zuordnung in einer Übersicht — das Grundgerüst des Designs ist damit abgedeckt.

Bedrohung Was passiert Reicht TLS allein? Wesentliche Gegenmaßnahme Ausführliches Kapitel
Verteilung eines bösartigen Artefakts Eine vom Angreifer vorbereitete Datei wird als reguläres Update installiert Nein (wirkungslos bei Origin-Kompromittierung, Fehlverteilung) Signierte Metadaten und clientseitige Verifikation von Hash / Signatur des Artefakts 4.2 / 4.3 / 4.4
Rollback Die Signatur ist korrekt, aber es wird auf eine ältere Version mit bekannter Schwachstelle zurückgestuft Nein (Signatur und TLS sind beide korrekt) Höchste bekannte Release-Version behalten, Älteres zurückweisen 4.8
Freeze Eine neue Version ist verfügbar, aber es werden weiterhin alte Metadaten geliefert, sodass kein Update erfolgt Nein expires_at in den Metadaten, zu Altes zurückweisen 4.3 / 4.8
Mix-and-Match Eine inkonsistente Kombination aus Metadaten und Artefakt wird ausgeliefert Nein Hash / Größe / Version des Ziel-Artefakts im Manifest fixieren 4.3 / 4.8
Kompromittierung des Signaturschlüssels Ein regulär signiertes, aber bösartiges Update wird verteilt Nein Trennung von Entwicklungs- und Produktionsschlüssel, HSM oder Signaturdienst, Freigabeprozess und Audit-Log, Trennung von Root- und Metadatenschlüssel 4.5
Fehler / Abbruch beim Update Der Austausch bricht mittendrin ab, die App startet nicht mehr Nicht anwendbar Staging + atomare Aktivierung + Rollback 4.6
Umgehung der Verifikation Ein Hintertürchen wie skipVerify bleibt in der Produktion Nicht anwendbar Fail-Closed als feste Vorgabe, kein Umgehungs-Flag 3.7 / 4.6
Kein Stoppen im Ernstfall möglich Eine problematische Version wird weiter verteilt Nicht anwendbar Blocklist, Minimum Allowed Version, Kill Switch 4.8 / 7

Dass die Spalte „Reicht TLS allein?“ durchgehend „Nein“ oder „Nicht anwendbar“ ergibt, ist die Kernaussage dieses Artikels. TLS schützt den Übertragungsweg, aber den Inhalt dessen, was verteilt wird, und die Berechtigung zur Verteilung schützt ein anderer Mechanismus.

3. Anti-Patterns

Zunächst eine Zusammenfassung gefährlicher Muster, die in der Praxis häufig vorkommen.

Anti-Pattern Warum gefährlich Mindestmaßnahme
version.json per HTTPS abrufen und das zip / exe der URL unverändert ausführen Anfällig für Origin-Kompromittierung, Konfigurationsmanipulation, Fehlverteilung Auf clientseitige Verifikation signierter Metadaten und Artefakte umstellen
Nur die Binärdatei ist signiert, das Manifest unsigniert URL, Version, Channel und Pflicht-Update-Flag können manipuliert werden Signiertes Manifest mit Version / Hash / Größe / Channel / Ablaufdatum verwenden
Signaturschlüssel liegt auf einem Entwickler-PC oder in CI-Dateien Bei Kompromittierung kann regulär signierte Malware verteilt werden HSM / Signaturdienst + Freigabeprozess + Audit-Log
Bei Update-Fehlern „Verifikationsfehler ignorieren und fortsetzen“ Genau im Ernstfall öffnet sich der schwächste Pfad Fail-Closed
Überschreibendes Update ohne Erhalt der alten Version Stromausfall, Speicherplatzmangel oder Abbruch führen zu einer nicht mehr startenden App Staging + atomare Aktivierung + Rollback
Alte Versionen allein per Versionsvergleich zulassen Rollback auf eine verwundbare Version wird durchgelassen Monoton steigende Release-Version und Speichern der höchsten bekannten Version
Gesamten Updater mit Administratorrechten ausführen Großer Schadensradius bei Kompromittierung Download/Verifikation mit niedrigen Rechten, nur den Austausch in einen minimalen Helper auslagern
Mit Delta-Updates beginnen Implementierung ist komplex, Verifikationslücken häufen sich Zuerst mit vollständigen Paket-Updates beginnen

Im Folgenden etwas ausführlicher.

3.1 Es bleibt bei „HTTPS, also ist es in Ordnung“

Das ist der häufigste Fall.

  • Beim Start latest.json lesen
  • downloadUrl extrahieren
  • zip / exe herunterladen
  • Entpacken und ersetzen
  • Fertig

Das sieht plausibel aus, aber die Vertrauensbasis stützt sich zu stark auf die Server-Antwort. Ist der Update-Server oder die Verteilungskonfiguration kompromittiert, kann ein bösartiges Update über korrektes HTTPS verteilt werden.

TLS ist notwendig. Aber TLS allein macht das Design eines Updaters nicht vollständig.

3.2 Es wird signiert, aber der Client verifiziert nicht

Selbst wenn Dateien beim Release signiert werden, bringt das nichts, wenn der Client die Signatur nie prüft.

Ein häufiges Muster:

  • CI signiert
  • Der Updater prüft aber nur einen Hash
  • Und dieser Hash selbst stammt aus einem unsignierten Manifest

In diesem Fall wird beim Austausch des Manifests der Hash gleich mit ausgetauscht. „Wir prüfen den Hash, also ist es sicher“ gilt nur, wenn auch die Herkunft des Hashes geschützt ist.

3.3 Das Manifest ist unsigniert

Was ein Update-System wirklich schützen muss, ist nicht nur die ausführbare Datei selbst. Mindestens die folgenden Informationen sind bei Manipulation gefährlich.

  • Version / Release-ID
  • URL oder Dateiname des Download-Ziels
  • Hash / Größe
  • Channel (stable / beta usw.)
  • ob das Update verpflichtend ist
  • anwendbares OS / Architektur
  • Ablaufdatum der Metadaten
  • minimal benötigte Updater-Version

Das heißt: Alles, was für die Update-Entscheidung verwendet wird, gehört in signierte Metadaten — diese Faustregel passt gut.

3.4 Nachlässiger Umgang mit dem Signaturschlüssel

Die Sicherheit einer Update-Funktion ist zu einem großen Teil die Sicherheit der Schlüsselverwaltung.

Liegt der Produktions-Signaturschlüssel in einer der folgenden Formen vor, ist das ziemlich gefährlich.

  • Er verbleibt im Zertifikatspeicher eines Entwickler-PCs
  • Eine .pfx wird als CI-Secret hochgeladen
  • Derselbe private Schlüssel wird lokal an mehrere Personen verteilt
  • Entwicklungs- und Produktionssignatur teilen sich dieselbe Vertrauenskette

In diesem Fall lässt sich ein „regulär signiertes, aber bösartiges Update“ nicht verhindern, selbst wenn der Updater selbst korrekt arbeitet.

3.5 Überschreibendes Update ohne Erhalt der alten Version

Bei Updates ist das Design für den Fehlerfall wichtiger als für den Erfolgsfall.

  • Der Download wurde mittendrin unterbrochen
  • Das Entpacken schlug fehl
  • Während des Austauschs kam es zu einem Stromausfall
  • Die neue Version startete, stürzte aber bei der ersten Migration ab

Ist die alte Version zu diesem Zeitpunkt bereits gelöscht, wird die Wiederherstellung aufwendig. In der Praxis ist „die App startet vor Ort nicht mehr“ ein größeres Problem als die Tatsache, dass „das Update fehlgeschlagen ist“.

3.6 Rollback wird nicht bedacht

Auch eine signierte, reguläre Version kann für einen Angreifer nützlich sein, wenn es sich um eine alte, verwundbare Version handelt.

Zum Beispiel:

  • Version 1.8 hat eine bekannte Schwachstelle
  • Im Feld läuft bereits Version 2.3
  • Ein Angreifer verteilt 1.8 erneut

Geht das durch, ist es gefährlich, obwohl die Signatur selbst korrekt ist.

Es reicht nicht zu prüfen, „ist es signiert“; man muss auch prüfen, „darf diese Version jetzt installiert werden“.

3.7 Fail-Open

Das ist das Schlimmste, was man in Produktion tun kann.

  • Bei fehlgeschlagener Signaturverifikation nur eine Warnung anzeigen und fortfahren
  • Ein verstecktes Flag, mit dem sich Zertifikatsablauf-Fehler ignorieren lassen
  • Ein Debug-Flag skipVerify=true, das auch in Produktion erhalten bleibt

Gerade bei Störungen oder Angriffen werden solche Hintertüren zum bevorzugten Pfad.

4. Best Practices

4.1 Zuerst auf eine bestehende Update-Infrastruktur setzen

Es ist sicherer, zuerst zu hinterfragen, ob wirklich ein eigener Updater nötig ist.

Unter Windows lassen sich, sofern die Anforderungen passen, folgende Optionen zuerst prüfen.

  • MSIX + App Installer
  • ClickOnce
  • Store / MDM / interne Verteilungsinfrastruktur
  • MSI + unternehmensseitiges Verteilungsmanagement

Der Grund ist einfach: Ein beträchtlicher Teil der Verantwortung für das Update lässt sich auf die Plattform verlagern. Man verliert zwar an Flexibilität, aber Update-UI, Verteilungsmanifest, Paketsignatur und Betriebsabstimmung werden dadurch leichter konsistent zu halten.

Ein eigener Updater wird zum Beispiel in folgenden Fällen nötig.

  • Mehrere Channels (stable / beta / preview) sollen streng kontrolliert werden
  • Gestaffelte Verteilung oder Rollout-Quoten werden benötigt
  • Aus geschäftlichen Gründen ist eine feingranulare Steuerung des Update-Zeitpunkts nötig
  • Es gibt Konfigurationen, die nicht zu MSIX / ClickOnce passen

Auch in diesem Fall bleibt man klarer im Denken, wenn man es nicht als „wir wollen Flexibilität“, sondern als „wir übernehmen die Verantwortung für Updates selbst“ versteht.

4.2 Den Vertrauensursprung auf die Clientseite legen

Ein sicherer Updater vertraut der Server-Antwort nicht blind. Die Clientseite benötigt mindestens diese beiden Dinge.

  1. Einen vertrauenswürdigen Public Key oder eine Zertifikatskette
  2. Einen Mechanismus, um mit diesem Schlüssel signierte Metadaten zu verifizieren

Anders gesagt: Es muss ein Zustand geschaffen werden, in dem der Client nicht bestätigt „der Server sagt, das sei die neueste Version“, sondern „diese Metadaten sind die neueste Version, ausgegeben von einem Signierenden, dem wir vertrauen“.

Wie das Vertrauen von der Root bis zur Datei durchgereicht wird, zeigt das folgende Diagramm.

NOKOKNOKOKNOKOKRoot-Schlüsselim Client eingebetteter Trust AnchorMetadaten-Signaturschlüsselvom Root delegiert, häufig erneuertSignierte Update-MetadatenVersion / URL / Hash / Größe / AblaufdatumSignatur, Ablaufdatum, Version prüfenUpdate abbrechenFail-ClosedArtefakt in den Staging-Bereich herunterladenGröße / Hash / Paketsignatur prüfenAktivieren, alte Version bleibt erhaltenHealth-Check beim ersten StartRollback zur alten VersionUpdate abgeschlossen

Zwei Punkte sind hier wichtig: Von oben nach unten läuft eine einzige, ununterbrochene Vertrauenskette. Und egal, an welcher Stelle diese Kette reißt, das Ergebnis ist immer Update abbrechen oder Rollback — niemals „einfach weitermachen“.

4.3 Design rund um signierte Metadaten

Mindestens sollten folgende Punkte in die Update-Metadaten aufgenommen und mitsigniert werden.

Feld Grund für die Aufnahme
Release-Version / Release-ID Rollback-Schutz, Audit
Artefaktname, URL, Package-Type Legt fest, welche Datei geholt wird
Hash, Größe Manipulationserkennung, Erkennung defekter Zustellung
Channel Verhindert, dass Beta in Stable gemischt wird
Ziel-OS / -Architektur Verhindert Fehlverteilung
Minimum Updater Version Stoppt alte Updater bei Protokolländerungen
expires_at Freeze-Schutz
published_at Audit, Eingrenzung
mandatory / optional Macht auch die UX-Verzweigung des Updates manipulationssicher

Entscheidend ist hier, alle Entscheidungsgrundlagen für das Update in signierten Metadaten zu bündeln. Die Logik liegt beim Client, die Echtheit der Information wird durch die Signatur geschützt — dieses Muster verringert Fehler.

Ohne eine konkrete Form lässt sich das schlecht umsetzen, deshalb hier ein minimales Beispiel. Zunächst der Inhalt, der signiert wird.

{
  "schema_version": 1,
  "channel": "stable",
  "release_version": "2.4.1",
  "published_at": "2026-04-09T01:00:00Z",
  "expires_at": "2026-04-16T01:00:00Z",
  "minimum_updater_version": "2.0.0",
  "minimum_allowed_version": "2.2.0",
  "mandatory": false,
  "artifacts": [
    {
      "os": "windows",
      "arch": "x64",
      "package_type": "msi",
      "file_name": "MyApp-2.4.1-x64.msi",
      "url": "https://updates.example.com/stable/MyApp-2.4.1-x64.msi",
      "size": 48234496,
      "sha256": "5f2c...64-stellig hexadezimal..."
    }
  ]
}

Das wird zusammen mit der Signatur verpackt.

{
  "signed": {
    "schema_version": 1,
    "channel": "stable",
    "release_version": "2.4.1",
    "_comment": "das obige Objekt unverändert übernehmen"
  },
  "signatures": [
    {
      "keyid": "3f9a...",
      "sig": "MEUCIQ..."
    }
  ]
}

Diese Form wird gewählt, weil alle für die Update-Entscheidung verwendeten Werte innerhalb von signed liegen. URL, Version, Channel und Pflicht-Update-Flag liegen alle innen; wird nur der äußere Teil ausgetauscht, schlägt die Verifikation fehl. Die Verarbeitungsreihenfolge auf der Clientseite wird auf „zuerst signed verifizieren → erst danach die darin enthaltenen Werte verwenden“ festgelegt. Eine Implementierung, die schon vor der Verifikation die url liest und den Download startet, macht diesen Sinn zunichte.

Ein Hinweis zur Implementierung: JSON kann je nach Reihenfolge der Schlüssel und Leerzeichen eine andere Bytefolge ergeben. Die Signaturverifikation erfolgt über die Bytefolge, deshalb muss vorab festgelegt werden, ob das signierte Objekt eine „normalisierte Darstellung“ oder die „empfangene Bytefolge selbst“ ist. Legt man das nicht fest und signiert die Serverseite das erzeugte Objekt, während die Clientseite die neu serialisierte Fassung als Signaturziel verwendet, schlägt die Verifikation trotz eines korrekten Updates fehl. Umgekehrt: Beginnt man, diese Diskrepanz mit „das lassen wir eben durchgehen“ zu umgehen, ist das der erste Schritt in Richtung Fail-Open.

4.4 Auch das Artefakt selbst verifizieren

Nach der Verifikation der Metadaten wird auch beim heruntergeladenen Artefakt Folgendes geprüft.

  • Größe
  • Hash
  • Paketsignatur / Codesignatur
  • Herausgeber oder erwarteter Identifikator

Wer Windows PE / MSI / MSIX behandelt, sollte davon ausgehen, dass die Authenticode- oder Paketsignaturverifikation clientseitig erfolgt. Unter macOS ist es konsistenter, Developer ID und Notarisierung auch für den Update-Pfad vorauszusetzen.

4.5 Schlüssel werden durch Betrieb geschützt, nicht durch Funktionen

Die Schlüsselverwaltung zeigt Unterschiede eher im Betrieb als in der Implementierung.

Mindestens sollten folgende Schlüssel getrennt werden.

  • Entwicklungs-Signaturschlüssel
  • Staging-Signaturschlüssel
  • Produktions-Signaturschlüssel

Für den Produktionsschlüssel sollte darüber hinaus Folgendes durchdacht werden:

  • HSM
  • Cloud-Signaturdienst
  • Signiersystem mit Freigabeprozess
  • Audit-Log
  • Key-Rotation-Verfahren
  • Zeitgestempelte Signaturen

„CI signiert automatisch, sobald der Produktions-Build durchläuft“ ist bequem, vergrößert aber den Schadensradius im Kompromittierungsfall. Zumindest sollte nachvollziehbar sein, wer wann was signiert hat.

Ist der Betrieb ausgereift, ist es zusätzlich sicherer, den selten geänderten Root-Trust von den häufig neu signierten Update-Metadaten-Schlüsseln zu trennen. Wird der Root eher offline gehalten und für die Update-Metadaten ein separater Schlüssel verwendet, lässt sich der Schadensradius einer Schlüsselkompromittierung leichter reduzieren.

4.6 Fail-Closed und gestaffeltes Update

Der Update-Ablauf folgt grundsätzlich dieser Reihenfolge.

  1. Metadaten abrufen
  2. Signatur, Ablaufdatum und Version verifizieren
  3. Artefakt in den Staging-Bereich herunterladen
  4. Hash / Größe / Signatur verifizieren
  5. Aktivierung vorbereiten, alte Version bleibt erhalten
  6. Beim Neustart oder über einen dedizierten Helper umschalten
  7. Health-Check beim ersten Start
  8. Bei Problemen zurückrollen

Wichtig sind hier zwei Dinge: Nicht ersetzen, bevor die Verifikation abgeschlossen ist. Bei einem Fehlschlag nicht fortfahren.

4.7 Die Rechte des Updaters einschränken

Es sollte vermieden werden, den gesamten Updater mit Administratorrechten laufen zu lassen.

Ideal ist folgende Trennung:

  • Download und Verifikation: niedrige Rechte
  • Nur der eigentliche Dateiaustausch: ein Helper mit minimalen Rechten
  • Der Helper tut nichts über „ein verifiziertes Paket am vorgesehenen Ort ablegen“ hinaus

Je mehr ein Design eine Rechteerhöhung erfordert, desto gefährlicher wird es, wenn nicht klar getrennt ist, was vor der Erhöhung bereits verifiziert wurde.

4.8 Rollback / Freeze / Mix-and-Match von Anfang an ausschließen

Das lässt sich nur schwer nachträglich einbauen, deshalb sollte es von Anfang an vorhanden sein.

  • Rollback-Schutz Der Client behält „die höchste bisher gesehene Metadaten-Version / Release-Version“ und weist Älteres zurück

  • Freeze-Schutz Metadaten mit einem Ablaufdatum versehen und zu alte Metadaten zurückweisen

  • Mix-and-Match-Schutz Konsistenz zwischen den Metadaten sicherstellen. Mindestens Hash / Größe / Version des Ziel-Artefakts im Manifest selbst fixieren

Lässt sich zusätzlich eine Blocklist bestimmter Builds oder eine Minimum Allowed Version über signierte Metadaten verteilen, wird die Eindämmung im Ernstfall deutlich schneller.

Auch ohne TUF vollständig zu übernehmen, sind diese drei Eigenschaften sehr wichtig.

4.9 Zuerst mit vollständigen Updates beginnen

Delta-Updates sparen Bandbreite, sind aber als erste Implementierung komplex.

  • Von welcher alten zu welcher neuen Version das Delta angewendet wird
  • Der vorausgesetzte Hash vor Anwendung des Deltas
  • Der finale Hash nach Anwendung des Deltas
  • Wiederherstellung bei einem Fehlschlag mittendrin
  • Bereinigung teilweise angewendeter oder veralteter Deltas

All das häuft sich auf einmal an. Für die Erstversion reicht es, ein signiertes Vollpaket sicher auszutauschen.

5. Minimale sichere Konfiguration

Auch ohne die volle TUF-Umsetzung sieht die minimale sichere Konfiguration für einen eigenen Updater ungefähr so aus.

5.1 Was der Client vorhält

Der Client hält das Material vor, um die Server-Antwort infrage zu stellen. Fehlt das, wird allein anhand der Aussage des Servers entschieden, ob aktualisiert werden darf.

  • Vertrauenswürdiger Root-Public-Key oder eine fixierte Zertifikatskette
  • Die aktuell laufende Version
  • Die höchste in der Vergangenheit gesehene Metadaten-Version / Release-Version
  • Der zulässige Channel
  • Die unmittelbar vorherige Version für Rollback

5.2 Was der Server liefert

Der Server liefert nicht die Entscheidung selbst, sondern das Material für die Entscheidung. Keines davon wird für sich genommen vertraut — Bedeutung erhält es erst nach erfolgreicher Verifikation über den in 5.1 genannten Trust Anchor.

  • Signierte Update-Metadaten
  • Signierte oder plattform-signierte Artefakte
  • Bei Bedarf Blocklist- / Minimum-Allowed-Version-Informationen

5.3 Typischer Ablauf

Metadaten abrufen
  ↓
Signatur, Ablaufdatum, Version, Channel verifizieren
  ↓
Artefakt in Staging herunterladen
  ↓
Größe / Hash / Paketsignatur verifizieren
  ↓
Aktivieren, alte Version bleibt erhalten
  ↓
Bei fehlgeschlagenem ersten Start zurückrollen

Wichtig ist hier: Allein durch die Antwort des Update-Servers wird nichts zustande gebracht. Zustande gebracht wird es durch den Trust Anchor, den der Client hält, und die Verifikationslogik.

6. Überlegungen für Windows-Projekte

Bei Windows-Apps lässt sich am besten rückwärts von der Verteilungsart her denken.

  • Wenn die Anforderungen passen, MSIX App Installer
  • Für interne .NET-Apps, bei denen Per-User passt, ClickOnce
  • Werden Dienste, Treiber, Shell-Erweiterungen oder eine eigene Channel-Steuerung benötigt, ist MSI + eigener Updater ebenfalls einen Vergleich wert

Wählt man jedoch einen eigenen Updater, wird der Aufwand nicht kleiner. Eher wächst er.

  • Verifikation von Authenticode / Paketsignatur
  • Signiertes Manifest
  • Rollback-Schutz
  • Rechtetrennung für den Update-Helper
  • Eine Update-Strategie für den Updater selbst

6.1 Wie Authenticode-Signaturen clientseitig verifiziert werden

Nur zu schreiben „Authenticode verifizieren“ lässt sich nicht direkt in eine Implementierung übersetzen, deshalb hier die Einstiegspunkte unter Windows.

Ziel Mittel
Verifikation im Code des Updaters Die WinVerifyTrust-API (wintrust.dll). Mit WINTRUST_ACTION_GENERIC_VERIFY_V2 wird die Authenticode-Verifikationsrichtlinie angewendet
Prüfung aus Betriebsabläufen oder CI Get-AuthenticodeSignature in PowerShell
Signieren und Prüfen im Release-Prozess signtool sign / signtool verify aus dem Windows SDK

In PowerShell reicht für die minimale Prüfung Folgendes.

$path = ".\MyApp-2.4.1-x64.msi"
$sig = Get-AuthenticodeSignature -FilePath $path

if ($sig.Status -ne 'Valid') {
    throw "signature check failed: $($sig.Status) / $($sig.StatusMessage)"
}

# "Signatur ist gültig" allein reicht nicht. Es muss auch feststehen, wessen Signatur es ist.
# Der Subject (Distinguished Name) ist jedoch nicht eindeutig. Zertifikate mit demselben
# CN/O/C koennen von einer anderen CA ausgestellt werden, und wenn der Client dieser CA
# vertraut, wird Status Valid und auch der Subject-Vergleich schlaegt an.
# Fixiert werden sollte die "Herausgeberkette" oder der "Public Key"; Subject dient nur
# zur zusaetzlichen Eingrenzung.
$expectedIssuers = @(                      # Fingerabdrücke der Herausgeber (Zwischen-CA/Root)
    '9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B'   # <- durch den tatsaechlichen Wert ersetzen
)
$expectedSubject = 'CN=Example Software Inc., O=Example Software Inc., C=JP'

# Die hier neu aufgebaute Kette dient nur dazu, den Herausgeber zurückzuverfolgen;
# die Vertrauensentscheidung ist bereits mit Status = Valid oben getroffen.
# VerificationTime von X509ChainPolicy ist standardmaessig der Zeitpunkt des
# Konstruktoraufrufs (= jetzt). Baut man ohne Anpassung, schlaegt Build mit
# NotTimeValid fehl, sobald das Signaturzertifikat ablaeuft. Eine zeitgestempelte
# Signatur bleibt jedoch auch nach Ablauf Valid, sodass ein Updater in dieser
# Standardeinstellung "eine korrekt verifizierte alte Version genau in dem Moment
# verwirft, in dem das Zertifikat erneuert wird".
# Ist der Signaturzeitpunkt bekannt, in VerificationTime eintragen. Ist er nicht
# bekannt, wird die Ablaufpruefung bereits oben erledigt, sodass dieser Build sie
# nicht erneut vornehmen muss
$chain = [System.Security.Cryptography.X509Certificates.X509Chain]::new()
$chain.ChainPolicy.VerificationFlags = 'IgnoreNotTimeValid'
$chain.ChainPolicy.RevocationMode    = 'NoCheck'
try {
    $built = $chain.Build($sig.SignerCertificate)

    # Irgendwo zwischen dem Signierenden und der Root muss der erwartete Herausgeber liegen.
    # Schlaegt Build fehl, ist die Kette nur teilweise aufgebaut, sodass auch dieser
    # Fall hier durchfällt (Fail-Closed)
    $chainThumbprints = @($chain.ChainElements | ForEach-Object { $_.Certificate.Thumbprint })
    if (-not ($expectedIssuers | Where-Object { $chainThumbprints -contains $_ })) {
        throw "unexpected issuing chain (built=$built): $($chainThumbprints -join ' / ')"
    }
}
finally {
    $chain.Dispose()
}

if ($sig.SignerCertificate.Subject -ne $expectedSubject) {
    throw "unexpected signer: $($sig.SignerCertificate.Subject)"
}

Dabei sind fünf Einschränkungen zu beachten.

  • Auch wenn Status Valid ist, bedeutet das nur, dass die Signatur formal korrekt ist. Wer signiert hat, wird separat geprüft
  • Eine Übereinstimmung des Subject beweist nicht die Identität. Der Distinguished Name ist kein eindeutiger Identifikator. Sowohl eine unternehmensinterne als auch eine öffentliche CA können ein Codesignaturzertifikat mit demselben Distinguished Name CN=Example Software Inc., O=Example Software Inc., C=JP ausstellen. Vertraut der Client dieser CA, wird eine mit anderem Schlüssel und anderem Herausgeber signierte, ausgetauschte Version mit Status = Valid und übereinstimmendem Subject durchgelassen.Fixiert werden sollte die Herausgeberkette (dass der Sumprint der erwarteten Zwischen-CA / Root im Pfad vom Signierenden bis zur Root enthalten ist) oder der Public Key; Subject dient nur zur zusätzlichen Eingrenzung darauf
  • Ist einmal fixiert, muss vorab ein Umstellungsverfahren feststehen. Sonst kommt es an dem Tag, an dem Zertifikat oder CA gewechselt werden, auf allen Geräten zum Update-Stillstand. Der erwartete Wert sollte immer als „Array“ gehalten werden, sodass alt und neu parallel akzeptiert werden können, bevor umgestellt wird (dreistufig: zuerst einen Updater ausliefern, der den neuen Herausgeber in der Zulassungsliste zusätzlich erlaubt → dann die Signatur umstellen → danach den alten Wert entfernen). Dieses „vorab ausliefern“ funktioniert nur, wenn sich der Updater selbst aktualisieren lässt — deshalb muss dies zusammen mit der zu Beginn von Kapitel 6 genannten „Update-Strategie für den Updater selbst“ konzipiert werden
  • Eine Signatur ohne Zeitstempel wird ab dem Ablauf des Zertifikats nicht mehr verifiziert. Beim Release sollte immer ein Zeitstempel gesetzt werden. Genau deshalb wird in 4.5 die zeitgestempelte Signatur genannt
  • Der X509Chain zur Rückverfolgung des Herausgebers darf die Gültigkeitsdauer nicht anhand der aktuellen Zeit neu beurteilen. X509ChainPolicy.VerificationTime ist standardmäßig der Zeitpunkt des Konstruktoraufrufs, also jetzt. Auch die Dokumentation hält ausdrücklich fest, dass diese Eigenschaft wichtig ist, weil eine signierte Nachricht zum Zeitpunkt der Signatur, nicht zum Zeitpunkt der Verifikation, gültig sein muss. Bleibt es bei der Standardeinstellung, wird ein Paket, das dank Zeitstempel Status = Valid erhalten hat, unmittelbar danach als „Zertifikat abgelaufen“ zurückgewiesen. Der Sinn des Zeitstempels geht damit verloren, und mit der Erneuerung des Zertifikats fallen sämtliche vergangenen Releases auf einmal durch. Ist der Signaturzeitpunkt bekannt, sollte er in VerificationTime eingetragen werden; ist er nicht bekannt, wird IgnoreNotTimeValid gesetzt. Das ist keine Abschwächung. Die Beurteilung von Gültigkeitsdauer und Vertrauen ist bereits einen Schritt weiter oben mit Status = Valid erledigt, und dieser Build-Aufruf dient nur dazu, „wer ausgestellt hat“ zu ermitteln. Aus demselben Grund wird hier auch keine Sperrprüfung vorgenommen (die CRL eines abgelaufenen Zertifikats wird nicht zwangsläufig weiter veröffentlicht). Das Mittel, um bei einem Schlüsselleck zu stoppen, ist nicht die CRL, sondern die Blocklist und die Minimum Allowed Version (4.8)

Zu beachten ist außerdem, dass das Verifikationsergebnis vom Zertifikatspeicher und den Vertrauenseinstellungen des jeweiligen Geräts abhängt.In einer Umgebung mit locker konfigurierten clientseitigen Vertrauenseinstellungen wird auch die Bedeutung von Status = Valid entsprechend locker. Hält der Updater selbst den erwarteten Herausgeber wie oben gezeigt fest, bleibt er davon unberührt, selbst wenn die Vertrauenseinstellungen des Geräts weiter gefasst sind.

Ein unter Windows verbreitetes gefährliches Muster ist die direkte Kette DownloadFile -> unzip -> kill process -> overwrite -> restart. Das kann funktionieren, ist aber sowohl bei der Sicherheit als auch bei der Wiederherstellbarkeit schwach.

SmartScreen- oder UAC-Warnungen über „Weitere Informationen → Trotzdem ausführen“ wegklicken zu lassen, ist kein Update-Design, sondern Warnungsgewöhnung. Wer einen sauberen Update-Pfad baut, sollte nicht die Nutzer an Warnungen gewöhnen, sondern auf eine Verteilungs- und Verifikationsstruktur hinarbeiten, bei der Warnungen selten auftreten.

Den Vergleich der Verteilungsarten selbst haben wir auch in diesem Artikel zusammengestellt: Wie man die Verteilungsart für Windows-Apps wählt - Entscheidungstabelle für MSI/MSIX/ClickOnce/xcopy/eigenen Updater

7. Mindest-Checkliste

Vor der Auslieferung eines eigenen Updaters sollte mindestens Folgendes geprüft werden.

  • Die Update-Metadaten sind signiert
  • Die Metadaten enthalten Version / Hash / Größe / Channel / Ablaufdatum
  • Der Client verifiziert Signatur und Version
  • Die Fixierung des Signierenden erfolgt über die Herausgeberkette oder den Public Key, nicht über den Distinguished Name (Subject)
  • Es gibt ein Umstellungsverfahren für fixierte Werte (Übergangszeit mit paralleler Akzeptanz von alt und neu)
  • Hash und Plattformsignatur des Artefakts werden verifiziert
  • Der Produktions-Signaturschlüssel ist von der Entwicklungsumgebung getrennt
  • Nutzungsprotokoll und Freigabevermerke des Schlüssels bleiben erhalten
  • Es werden zeitgestempelte Signaturen verwendet
  • Beim gestaffelten Update bleibt die alte Version beim Umschalten erhalten
  • Es gibt Bedingungen und ein Verfahren für Rollback
  • Bei fehlgeschlagener Verifikation stoppt das System Fail-Closed
  • Es gibt eine Update-Richtlinie für den Updater selbst
  • Eine Blocklist / Minimum Allowed Version kann verteilt werden
  • Es gibt einen Kill Switch, um die gestaffelte Verteilung zu stoppen
  • Fehlerrate, Rollback-Rate und Signaturverifikationsfehler sind beobachtbar

Sind viele dieser Punkte noch offen, bringt es mehr, zuerst das Vertrauensmodell der Verteilung zu klären, statt zuerst die UI des Updaters zu bauen.

8. Zusammenfassung

Bei der Sicherheit einer Auto-Update-Funktion läuft am Ende alles auf Folgendes hinaus.

Nicht der Komfort des Updates zählt, sondern die Entscheidung, wem vertraut wird und wie der Client dieses Vertrauen verifiziert.

Darauf aufbauend, grob zusammengefasst die praktischen Entscheidungen:

  • Reicht eine bestehende Infrastruktur aus, sollte zuerst darauf gesetzt werden
  • Wird ein eigener Updater gebaut, kommen signierte Metadaten und Schlüsselverwaltung vor HTTPS
  • Ein Updater ohne durchdachte Wiederherstellung und Rollback wird in Produktion schmerzhaft
  • Der Updater ist keine Verteilungsfunktion, sondern die Sicherheitsgrenze des Produkts selbst

Ist die aktuelle Konfiguration nahe an latest.json + zip-Austausch, sollte zuerst nicht die Download-Logik, sondern wo das Vertrauen verankert ist, korrigiert werden. Allein diese Korrektur verändert den Risikograd erheblich.

9. Quellen

Verwandte Themen

Themenseiten, die diesem Thema nahestehen. Ausgehend von diesem Artikel geht es weiter zu verwandten Services und anderen Artikeln.

Windows-Technikthemen

Ein Einstiegspunkt, der technische Themen zu Windows-Entwicklung, Fehleruntersuchung und Weiternutzung bestehender Assets bündelt.

Services, die zu diesem Thema passen

Windows-Anwendungsentwicklung

Auto-Update ist nicht nur eine Frage der UI, sondern ein Design, das Verteilungsart, Rechte, Wiederherstellung und Betrieb umfasst. Bei der Neuentwicklung von Windows-Apps oder der Überarbeitung bestehender Software kann bereits bei der Klärung der Update-Methode unterstützt werden.

Technische Beratung & Design-Review

Beratung ist bereits in der frühen Klärungsphase möglich: „Brauchen wir wirklich einen eigenen Updater?“, „Reicht MSIX / ClickOnce aus?“, „Wo ist unser aktuelles Update-Design riskant?“

Autorenprofil

Go Komura

Geschäftsführer, KomuraSoft LLC

Schwerpunkte sind Windows-Softwareentwicklung, technische Beratung und Fehleruntersuchung, mit besonderer Stärke bei Projekten mit bestehenden Altbeständen und bei der Untersuchung von Störungen, deren Ursache schwer erkennbar ist.

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Windows-App-Entwicklung

Verteilung, Update, Signatur, Rollback und die Wahl zwischen MSIX und ClickOnce für Windows-Apps erfordern, nicht nur die Implementierung, sondern auch die Verteilungsart und das Betriebsdesign mitzudenken.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Ist ein Auto-Update sicher, solange die Kommunikation über HTTPS läuft?
TLS ist notwendig, aber keine hinreichende Bedingung. TLS schützt vor allem den Übertragungsweg und die Legitimität der Gegenstelle. Es hilft nicht, wenn der Update-Server selbst kompromittiert wurde, ein falsches Artefakt auf dem legitimen CDN landet oder ein unsigniertes Manifest ausgetauscht wird. Die Update-Entscheidung darf nicht darauf beruhen, dass „der Server es so sagt“, sondern muss darauf beruhen, dass „der Client signierte Metadaten verifiziert und als korrekt beurteilt hat“.
Was sollte in die Update-Metadaten aufgenommen und signiert werden?
Grundsätzlich sollte alles, was für die Update-Entscheidung relevant ist, in signierten Metadaten gebündelt werden. Konkret: die Release-Version, URL und Dateiname des Artefakts, Hash und Größe, der Channel (stable / beta usw.), Ziel-OS und -Architektur, die minimal erforderliche Updater-Version, das Ablaufdatum der Metadaten (expires_at) sowie ein Flag, ob das Update verpflichtend ist. Wird nur die Binärdatei signiert und das Manifest bleibt unsigniert, können URL, Version oder das Pflicht-Update-Flag manipuliert werden.
Was ist ein Rollback-Angriff und wie schützt man sich davor?
Dabei verteilt ein Angreifer erneut eine ältere, korrekt signierte Version mit einer bekannten Schwachstelle, um Clients auf diese zurückzustufen. Da die Signatur selbst korrekt ist, reicht die reine Signaturprüfung nicht aus. Als Schutz behält der Client die höchste bisher gesehene Release-Version und weist alles Ältere zurück. Zusätzlich sollten die Metadaten ein Ablaufdatum tragen, um Freeze-Angriffe zu verhindern, die kein neueres Update anzeigen, und das Manifest sollte Hash, Größe und Version des Artefakts fixieren, um Mix-and-Match-Angriffe auszuschließen.
Sollte man einen eigenen Updater bauen oder auf vorhandene Mechanismen setzen?
Wenn die Anforderungen passen, ist es sicherer, zunächst auf eine bestehende Update-Infrastruktur wie MSIX mit App Installer oder ClickOnce zu setzen, da sich damit ein großer Teil der Verantwortung für Updates auf die Plattform verlagern lässt. Ein eigener Updater wird nötig, wenn eine strikte Kontrolle mehrerer Channels, eine gestaffelte Verteilung oder andere Anforderungen bestehen, die sich nicht in die bestehenden Mechanismen einfügen. Auch dann sollte zuerst nicht die Benutzeroberfläche, sondern die Signaturverifikation und die Wiederherstellung im Fehlerfall gebaut werden, mit Fail-Closed-Verhalten bei fehlgeschlagener Verifikation und der Möglichkeit, bei Bedarf zur alten Version zurückzukehren, die dafür erhalten bleibt.

Autorenprofil

Profilseite des Artikelautors.

Go Komura

Geschäftsführer von KomuraSoft LLC

Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.

Zurück zum Blog