Kai Ole Hartwig
17 Min. Lesezeit
Von

Go CVE-2026-111943: Warum der Schutz vor den eMudhra-Schlüsseln im Binary steckt und nicht im Trust-Store

Wenn eine Zertifizierungsstelle ihre Schlüssel verliert, entfernen Browser und Distributionen ihre Zertifikate aus dem Trust-Store, und für die meisten Programme ist die Sache damit erledigt. Bei den acht kompromittierten Zwischenzertifikaten von eMudhra reicht das nicht, weil dieselben Schlüssel über mehrere Wege zu vertrauenswürdigen Roots führen und die meisten Programme außerhalb des Browsers weder Sperrlisten noch Certificate Transparency prüfen. Go hat deshalb mit CVE-2026-111943 einen anderen Weg gewählt und die Schlüssel direkt in crypto/x509 gesperrt, unabhängig davon, welchem Root ein Programm vertraut. Der Haken liegt im Bauprinzip von Go. Die Standardbibliothek wird in jedes Binary einkompiliert, und so bleibt jedes Werkzeug, jeder Operator und jeder Controller in deinem Cluster ungeschützt, bis er mit Go 1.27.3 oder 1.26.10 neu gebaut wurde.

TL;DR — 90 Sekunden

Eine Zertifikatskette, drei Wege zum Vertrauen

Was bei eMudhra passiert ist, beschreibt der Beitrag zur eMudhra-CA-Kompromittierung ausführlich. Für Go zählt vor allem eine Eigenschaft des Falls. Die acht Zwischenzertifikate wurden mit ECDSA-Schlüsseln erzeugt, deren privater Anteil kleiner als 256 ist, und lassen sich deshalb aus dem öffentlichen Schlüssel in Millisekunden zurückrechnen. Die Zertifikate sind öffentlich, und damit sind es praktisch auch die Schlüssel. Dazu kommt, dass jedes dieser Zertifikate auf mehr als einem Weg zu einem vertrauten Root führt, einmal über die emSign-Hierarchie unter emSign ECC Root CA - G3 und zusätzlich über Cross-Zertifikate, die Sectigo unter AAA Certificate Services ausgestellt hat.

Warum das für Go-Programme besonders unangenehm ist, zeigt der Blick in den Code, der unter Linux die System-Roots lädt. crypto/x509 sucht nacheinander in festen Pfaden wie /etc/ssl/certs/ca-certificates.crt für Debian oder /etc/ssl/cert.pem für Alpine, nimmt die erste gefundene Datei und liest zusätzlich Verzeichnisse wie /etc/ssl/certs. Die Variablen SSL_CERT_FILE und SSL_CERT_DIR überschreiben diese Pfade. Geladen wird das Ganze genau einmal pro Prozess, beim ersten Gebrauch. Das lese ich aus dem sync.Once in root.go, und es hat eine praktische Folge: Ein Controller, der seit Wochen läuft, sieht ein aktualisiertes CA-Paket im Container erst nach einem Neustart.

Der dritte Punkt betrifft das, was Go bei einer Verbindung nicht tut. crypto/x509 prüft weder Sperrlisten noch OCSP noch Certificate Transparency. Dass eMudhra die Zwischenzertifikate am 18. Oktober und Sectigo die Cross-Zertifikate am 19. Oktober widerrufen hat, bemerkt ein Go-Client also nie. Auf macOS und Windows überlässt Go die Prüfung zwar der Plattform, sofern das Programm den System-Pool verwendet. Auf Linux, und damit in praktisch jedem Container, entscheidet allein die Liste der Roots im Image. Fehlt dort die emSign-Root, aber nicht AAA Certificate Services, bleibt der Weg über die Sectigo-Cross-Zertifikate offen, so lese ich zumindest die Kombination aus Ladecode und Kettenaufbau.

Was der Fix-Commit ändert

Der Fix-Commit 7f86f95 von Roland Shoemaker trägt den Titel „crypto/x509: distrust a handful of intermediates“ und berührt drei Dateien. In verify.go führt er eine Map distrustedSPKIHashes mit acht SHA-256-Hashes ein, jeweils über das Feld SubjectPublicKeyInfo eines Zertifikats, und eine Funktion containsDistrustedSPKIHash, die für jedes Glied einer Kette diesen Hash bildet und nachschlägt. Verify ruft sie im selben Filter auf, der schon Ketten mit ungültigen Policies, unpassendem Verwendungszweck oder verletzten Name Constraints aussortiert. Bleibt danach keine Kette übrig, schlägt die Prüfung fehl.

Weil dieser Filter nach dem Kettenaufbau sitzt, greift er unabhängig davon, woher die Roots stammen, ob aus dem System, aus einem eigenen CertPool oder aus x509roots/fallback. Für macOS und Windows ergänzt der Commit dieselbe Prüfung in systemVerify von root_darwin.go und root_windows.go, weil die Plattformprüfung den Go-eigenen Filter sonst umgehen würde. Einen Schalter zum Abschalten per GODEBUG sehe ich im Commit nicht.

Am meisten verrät die Wahl des Merkmals. Gesperrt werden Schlüssel statt Zertifikate, so wie es laut Google auch Chrome mit seiner CRLSet-Sperre getan hat. Ein Hash über den öffentlichen Schlüssel trifft die eMudhra-Ausstellung und das Sectigo-Cross-Zertifikat gleichermaßen, denn beide tragen denselben Schlüssel. Das Go-Team sperrt also das, was tatsächlich kompromittiert ist, und nicht einen der Wege dorthin. Der Preis ist, dass diese Liste im Programm selbst steckt und mit ihm ausgeliefert werden muss.

Angriffsweg und tatsächlich Betroffene

Für einen Angriff braucht es zweierlei: ein Zertifikat, das mit einem der acht Schlüssel signiert ist, und eine Position im Netz, aus der sich eine TLS-Verbindung umleiten lässt. Das Zertifikat kann sich jeder ausstellen, der die öffentlichen Zwischenzertifikate kennt, denn die Schlüssel lassen sich laut der Analyse von Scott Helme in Millisekunden berechnen. Die Netzwerkposition ist die eigentliche Hürde. Typische Wege sind ein manipulierter DNS-Resolver, ein übernommener Proxy, ein offenes WLAN oder ein kompromittierter Knoten im selben Netz. Gelingt beides, sieht und verändert der Angreifer alles, was der Client sendet und empfängt, ohne dass der Client einen Fehler meldet.

In einer Kubernetes-Umgebung lohnt der Blick darauf, welche Go-Programme überhaupt über die System-Roots prüfen. Der Verkehr zum API-Server von innen ist kaum betroffen, denn client-go nutzt bei der In-Cluster-Konfiguration das CA-Zertifikat aus dem ServiceAccount-Mount als einzigen Vertrauensanker. Anders sieht es bei allem aus, was nach außen spricht. cert-manager holt Zertifikate bei ACME-Servern, Argo CD und Flux klonen von Git-Hostern, OpenTofu-Provider und external-dns sprechen Cloud-APIs an, Helm lädt Charts aus Registries, und der GitLab Runner meldet sich bei GitLab. All diese Verbindungen vertrauen standardmäßig den Roots im Image. Das leite ich aus der Funktionsweise von crypto/x509 ab. Ob ein einzelnes Werkzeug eigene Roots mitbringt, musst du im Einzelfall prüfen.

Wirklich betroffen bist du, wenn ein solches Programm vor dem Fix gebaut wurde und in einem Image läuft, dessen CA-Paket die betroffenen Roots noch enthält. Zwei Gruppen halten sich dagegen leicht für betroffen, ohne es zu sein. Programme, die ausschließlich mit einer eigenen privaten CA sprechen, vertrauen den eMudhra-Roots gar nicht. Und bei Prüfungen mit dem Verwendungszweck ServerAuth, dem Standard von Verify, spielen die beiden Code-Signing-Zwischenzertifikate keine Rolle. Laut der Liste auf der Disclosure-Seite erlauben sie nur Code Signing, und der Filter verwirft Ketten mit unpassendem Verwendungszweck, noch bevor er die Sperrliste befragt.

CVEBetroffenFixVoraussetzungCVSS
CVE-2026-111943 (GO-2026-6809)Go vor 1.26.10 und 1.27.0 bis 1.27.2, Symbol crypto/x509.Certificate.Verify1.26.10 / 1.27.3Programm vertraut einem Root, über den die acht Schlüssel erreichbar sind; Angreifer in Netzwerkposition6.8 laut Amazon (eigene Einstufung: kritisch)

Missbrauch der Schlüssel gegen echte Ziele ist bisher nicht dokumentiert. Laut Google war vor der Sperrung kein TLS-Zertifikat unter den sechs TLS-Zwischenzertifikaten in Certificate-Transparency-Logs eingetragen, und eMudhra gibt im Mozilla-Bug an, dass keines der acht Zwischenzertifikate Endzertifikate ausgestellt habe. Für Clients ohne CT-Prüfung beweist das allerdings wenig, denn ein Angreifer muss ein gefälschtes Zertifikat nirgends eintragen lassen. Im CISA-KEV-Katalog steht die CVE nicht, und der EPSS-Wert lag am 21. Oktober bei 0,145 Prozent.

Go 1.27.3, 1.26.10 und der Weg in deine Binaries

Die Fix-Versionen sind Go 1.27.3 und Go 1.26.10, beide laut Release-Historie vom 19. Oktober 2026, die Advisory GO-2026-6809 folgte am 21. Oktober. Das Go-Team empfiehlt ein sofortiges Update. Go 1.25 bekommt keinen Fix, denn laut Release-Policy wird jede Version nur unterstützt, bis zwei neuere erschienen sind, und Go 1.27 kam am 19. August. Baust du noch mit 1.25, führt der Weg also über den Wechsel auf eine unterstützte Version.

Für den Betrieb zählt vor allem, wie sich Go hier von Sprachen mit gemeinsamer Laufzeit unterscheidet. Bei Python, Node.js oder Java tauschst du eine Laufzeit oder ein CA-Paket, und alle Programme profitieren davon. Ein Go-Binary bringt seine Standardbibliothek dagegen selbst mit, und die neue Sperrliste ist Teil davon. Für deine eigenen Programme heißt das: Toolchain anheben, also den Build-Container auf golang:1.27.3 oder golang:1.26.10 setzen oder die toolchain-Zeile in go.mod anpassen, neu bauen und ausrollen. Für fremde Werkzeuge wie kubectl, Helm, OpenTofu, Traefik oder deine Operatoren musst du auf Releases warten, die mit der neuen Toolchain gebaut wurden, und das dürfte bei vielen Projekten Tage bis Wochen dauern.

Deshalb gehört das zweite Bein immer dazu: aktuelle CA-Pakete in jedem Image. Unter Linux liest Go die Roots zur Laufzeit aus dem Image, ein erneuertes Paket wirkt also auch für ältere Binaries, sobald der Prozess neu startet. Das deckt allerdings nur den Weg über die entfernten Roots ab. Ob dein CA-Paket auch AAA Certificate Services entfernt, unterscheidet sich je nach Distribution, und laut Red-Hat-Bulletin RHSB-2026-012 gibt es für RHEL bisher nur einen Workaround über die Blockliste. Amazon Linux 2023 hatte zum Redaktionsschluss laut Issue #1147 noch kein aktualisiertes Paket.

Für Images ohne CA-Paket, etwa scratch, bietet Go das Modul golang.org/x/crypto/x509roots/fallback an, das ein Root-Bundle einkompiliert. In der aktuellen Modulversion v0.0.0-20261020153306-9f2b28d4331a steht von eMudhra nur noch emSign Root CA - G1, und AAA Certificate Services fehlt ganz, wie ich im Bundle selbst nachgesehen habe. Nutzt du das Modul, hebst du es also ebenfalls an. Eine Verhaltensänderung bringt der Fix nur für Verbindungen, die über die gesperrten Schlüssel laufen. Sie scheitern künftig mit einem Zertifikatsfehler, und für legitime Dienste sollte das nicht vorkommen, weil eMudhra nach eigener Aussage aus diesen Zwischenzertifikaten keine Endzertifikate ausgestellt hat.

Härtung über den Patch hinaus

Zur Einordnung hilft die Frage, was hier eigentlich angegriffen wird. Es ist kein Fehler im Programm, sondern eine Vertrauensentscheidung: Der Client glaubt einem Zertifikat, weil eine Kette zu einem Root führt, den er für vertrauenswürdig hält. Wirksam ist deshalb alles, was die Menge der vertrauten Aussteller verkleinert, was die Sperrliste schnell in jedes Binary bringt und was einem Angreifer die Netzwerkposition erschwert.

Eigene Vertrauensanker für interne Ziele

Die wirksamste Maßnahme ist, Go-Programmen für interne Verbindungen gar nicht erst die gesamte Web-PKI anzuvertrauen. Ein Dienst, der nur mit deiner Datenbank, deinem Vault oder einem internen API-Gateway spricht, braucht als Vertrauensanker nur deine private CA. In Go setzt du dazu RootCAs in der tls.Config auf einen eigenen CertPool, und viele Werkzeuge bieten dafür eine Option wie --cacert oder einen CA-Pfad in der Konfiguration. Für Verbindungen innerhalb des Clusters erledigen ein Service Mesh mit mTLS oder Zertifikate aus einem cert-manager-Issuer mit eigener CA dasselbe. Der Aufwand liegt im Verteilen der CA und im Rotieren, der Gewinn darin, dass keine öffentliche Zertifizierungsstelle der Welt deinen internen Verkehr mehr beeinflussen kann.

Gepflegte Trust-Bundles mit SSL_CERT_FILE

Für Verbindungen ins Internet bleibt die Web-PKI nötig, aber du kannst bestimmen, welches Bundle gilt. Weil Go unter Linux SSL_CERT_FILE auswertet, kannst du jedem Pod ein zentral gepflegtes Bundle mitgeben, statt dich auf das CA-Paket des jeweiligen Images zu verlassen. Im Cluster erzeugt trust-manager von cert-manager solche Bundles als ConfigMap. Laut Doku muss das Standardpaket mindestens den Stand trust-pkg-debian-trixie:20250419-deb13u1.0 oder trust-pkg-debian-bookworm:20250419-deb12u2.0 haben, ältere Tags vertrauen der kompromittierten CA weiter. Gemountet als Datei und per Umgebungsvariable gesetzt, gilt dieses Bundle für alle Go-Programme im Pod. Zwei Grenzen solltest du kennen. Go-Programme lesen das Bundle nur beim Start, ein aktualisiertes Bundle wirkt also erst nach einem Rollout. Und ein Bundle ersetzt die Sperrliste nicht, solange es AAA Certificate Services enthält.

Ein Inventar der Go-Versionen

Bevor du neu bauen kannst, musst du wissen, was neu zu bauen ist. Go schreibt die verwendete Toolchain in jedes Binary, und go version -m liest sie zusammen mit den Modulversionen aus. govulncheck -mode binary geht einen Schritt weiter und gleicht die Symbole eines Binaries mit der Go-Schwachstellendatenbank ab, wobei es auch Funde in der Standardbibliothek meldet. Beides lässt sich in der Pipeline gegen jedes gebaute Image und regelmäßig gegen die Images im Cluster laufen lassen. Viele Scanner für Container-Images erkennen die Go-Version in Binaries ebenfalls, prüf aber, ob deiner GO-2026-6809 schon kennt. Ohne ein solches Inventar bleibt das Neubauen ein Ratespiel, gerade bei Werkzeugen, die irgendwann einmal in ein Basis-Image kopiert wurden.

Automatischer Rebuild bei neuen Go-Patches

Weil jeder Go-Patch nur durch Neubauen wirkt, sollte ein neuer Patch automatisch einen Build auslösen. Renovate oder Dependabot können das Tag des golang-Build-Images und die Basis-Images deiner Laufzeit-Container aktuell halten, und eine gepinnte Toolchain in go.mod macht sichtbar, mit welcher Version gebaut wird. Bei minimalen Images ohne Shell, etwa auf Wolfi- oder Distroless-Basis, kommt das CA-Paket mit dem Basis-Image, ein neuer Digest bringt also auch neue Roots. Der Betriebsaufwand ist gering, sobald die Pipeline steht, und er zahlt sich bei jedem künftigen Fix in der Standardbibliothek erneut aus.

Egress über bekannte Wege

Der Angreifer braucht eine Position zwischen Client und Ziel. Eine NetworkPolicy, die Pods nur zu bekannten Zielen oder nur über einen kontrollierten Egress-Proxy hinauslässt, verhindert diese Position nicht, macht sie aber seltener, weil weniger Wege existieren, die sich umleiten lassen. Zusammen mit einem vertrauenswürdigen DNS-Resolver im Cluster ist das eine sinnvolle Ergänzung, ersetzt aber keine der anderen Maßnahmen. Wie sich Egress-Regeln für eine TYPO3-Plattform aufbauen lassen, beschreibt die Serie TYPO3 auf Kubernetes unter IT-Grundschutz.

Was hier nicht hilft

Ein Update der CA-Pakete auf dem Host ändert nichts an Containern, denn jedes Image bringt sein eigenes Paket mit. Ein Neustart ohne neues Image lädt nur dieselben alten Roots erneut. Sperrlisten und OCSP helfen nicht, weil Go sie nicht prüft, und selbst Clients, die das tun, scheitern oft daran, dass die Sperrinformation nicht erreichbar ist. CAA-Einträge im DNS schützen ebenfalls nicht. Laut RFC 8659 prüft sie die Zertifizierungsstelle vor der Ausstellung, Clients dürfen sie bei der Validierung ausdrücklich nicht verwenden, und ein Angreifer mit eigenem Schlüssel fragt ohnehin nicht nach.

Auch Certificate Transparency hilft Go-Clients direkt nicht, weil crypto/x509 keine SCTs verlangt. CT-Monitoring für deine eigenen Domains bleibt trotzdem sinnvoll, es meldet aber nur Zertifikate, die jemand freiwillig in ein Log eingetragen hat. Container-Härtung mit seccomp, entzogenen Capabilities, Pod Security oder User Namespaces ist gute Praxis, berührt diesen Mechanismus aber nicht, weil der angegriffene Prozess nichts Ungewöhnliches tut.

Erkennung und Prüfung

Ob du betroffen bist, klärst du in zwei Schritten. Zuerst die Binaries: Mit go version -m oder govulncheck -mode binary prüfst du, mit welcher Toolchain ein Programm gebaut wurde, und alles vor 1.26.10 und zwischen 1.27.0 und 1.27.2 hat die Sperrliste nicht. Danach die Roots: Prüfe im jeweiligen Image, ob das CA-Bundle noch die Root emSign ECC Root CA - G3 oder AAA Certificate Services enthält. Eine Suche nach dem Wort „emSign“ genügt dabei nicht, weil emSign Root CA - G1 weiterhin vertraut bleibt, vergleiche deshalb gezielt Namen oder Fingerprints, wie sie die Disclosure-Seite in roots.json veröffentlicht.

Einen praktischen Gegentest erlauben die Testhosts, die die Disclosure-Seite unter we-have-a-problem.com auflistet, etwa dv-g3b-emudhra.we-have-a-problem.com für die Kette über emSign und dv-g3b-aaa-emudhra.we-have-a-problem.com für die Kette über AAA. Wichtig ist dabei, mit welchem Programm du testest. Ein Test mit curl prüft nur curl, aussagekräftig ist ein Aufruf aus dem betroffenen Go-Programm oder einem Go-Werkzeug im selben Image. Scheitert die Verbindung mit einem Zertifikatsfehler, ist dieses Programm geschützt.

Ob jemand die Schlüssel gegen dich eingesetzt hat, lässt sich nur nachweisen, wenn du die Zertifikatsketten deiner ausgehenden Verbindungen protokollierst. Wo ein Proxy oder ein Mesh das tut, kannst du nach Ketten suchen, die einen der acht SPKI-Hashes aus dem Fix-Commit enthalten. Veröffentlichte Indikatoren für echte Angriffe gibt es nicht. Bei einem Verdacht behandelst du alle Zugangsdaten als kompromittiert, die über die betroffenen Verbindungen gingen, also API-Tokens, Git-Zugangsdaten und Cloud-Credentials, und rotierst sie nach dem Rebuild.

Betreiberempfehlung

Akut handeln solltest du bei Go-Programmen, die Zugangsdaten an öffentliche Endpunkte schicken, also Git-Zugriffe von Argo CD oder Flux, Cloud-APIs aus OpenTofu oder external-dns, ACME-Anfragen von cert-manager und Runner, die sich bei deiner GitLab-Instanz anmelden. Bau deine eigenen Programme heute mit Go 1.27.3 oder 1.26.10 neu, erneuere die CA-Pakete in allen Images, starte die Pods neu und prüfe für fremde Werkzeuge, ob es schon Releases mit der neuen Toolchain gibt. Wo das noch dauert, setze über SSL_CERT_FILE ein gepflegtes Bundle ohne die betroffenen Roots.

Beobachten genügt, wenn deine Go-Programme ausschließlich mit internen Zielen über eine eigene CA sprechen. Dann vertrauen sie den eMudhra-Roots ohnehin nicht, und der Rebuild kann mit dem nächsten regulären Update kommen.

Dieser Fall zeigt besonders deutlich, warum ich Patches und Updates automatisiert haben will. Bei Go genügt es nicht, ein Paket zu aktualisieren. Jedes Programm muss neu gebaut werden, und das gelingt nur verlässlich, wenn Renovate oder Dependabot das neue golang-Image und die neuen Basis-Images selbstständig als Merge Request vorschlagen und die Pipeline baut, testet und gegen Staging ausrollt. Dann ist Go 1.27.3 in Stunden in allen deinen Images. Eine Karenzzeit für Routine-Updates schützt vor kompromittierten Releases, Sicherheitsupdates wie dieses nimmst du davon aus. Und sollte ein Dienst doch über eines der gesperrten Zwischenzertifikate laufen, fällt der Zertifikatsfehler im Test auf und nicht in Produktion. Beim Bauen von Hand bleibt erfahrungsgemäß das eine Werkzeug liegen, das vor zwei Jahren in ein Basis-Image kopiert wurde, und genau dieses spricht dann monatelang mit der alten Standardbibliothek.

Häufige Fragen zu CVE-2026-111943

Reicht es, ca-certificates zu aktualisieren?+

Es hilft, aber es schließt nicht jeden Weg. Die Schlüssel sind auch über Sectigo-Cross-Zertifikate unter AAA Certificate Services erreichbar, und ob dein Paket diese Root entfernt, hängt von der Distribution ab. Vollständig schützt erst die Sperrliste im neu gebauten Binary.

Reicht ein Neustart meiner Pods?+

Nur, wenn das Image vorher ein aktualisiertes CA-Paket bekommen hat. Die Sperrliste selbst steckt im Binary und kommt erst mit einem Rebuild auf Go 1.27.3 oder 1.26.10 dazu.

Sind meine Verbindungen zum Kubernetes-API-Server betroffen?+

Kaum. In-Cluster-Clients auf Basis von client-go vertrauen nur der Cluster-CA aus dem ServiceAccount-Mount. Betroffen sind Go-Programme, die öffentliche Endpunkte über die System-Roots prüfen.

Ich nutze nur Go 1.25. Was nun?+

Go 1.25 wird seit dem Erscheinen von Go 1.27 nicht mehr unterstützt und bekommt keinen Fix. Steig auf Go 1.26.10 oder 1.27.3 um und halte bis dahin die CA-Pakete in deinen Images aktuell.

Fazit

Der Go-Fix für den eMudhra-Vorfall ist technisch klein, 42 Zeilen in drei Dateien, und für den Betrieb trotzdem groß, weil er nur durch Neubauen wirkt. Er setzt bei den Schlüsseln an und damit an der einen Stelle, die alle Wege zu den kompromittierten Zwischenzertifikaten gemeinsam haben. Die übertragbare Lehre hat zwei Seiten. Vertrauen in die Web-PKI ist für interne Verbindungen fast immer mehr, als ein Dienst braucht, und eine eigene CA nimmt solchen Vorfällen die Wirkung. Und bei einer Sprache, die ihre Standardbibliothek in jedes Programm kompiliert, ist das Inventar der Toolchain-Versionen genauso wichtig wie das der Pakete. Hast du beides, braucht ein Fall wie dieser einen Build und keinen Krisenstab.

Quellen

Ich finde die Go-Binaries in deinem Cluster, baue sie mit der aktuellen Toolchain neu und richte eigene Vertrauensanker und Trust-Bundles ein, damit der nächste CA-Vorfall deinen Betrieb nicht berührt.

Inventar und Rebuild von Go-Werkzeugen und Operatoren, trust-manager-Bundles und private CAs für interne Verbindungen, Renovate-Pipelines für Build- und Basis-Images.

Plattform-Betrieb statt Beratung auf Papier: Ich prüfe, patche und härte deine Infrastruktur laufend.

Über den Autor