Someone left the master key under a doormat made of the number 4, and every Go service in your fleet would have opened the door
CVE-2026-111943 tracks Go's crypto/x509 package failing to distrust eight eMudhra intermediate CA certificates whose private keys were generated with catastrophically low entropy during an April 2025 key ceremony. The private key values were single-byte integers — literally values like 4, 56, and 237 — on a P-384 curve that should produce 384-bit random numbers (~10¹¹⁵ possibilities). Anyone who examined the public certificate could recover the private key in milliseconds on commodity hardware. Six intermediates issued TLS certificates (DV, OV, EV) and two issued code-signing certificates, all cross-signed by Sectigo's AAA Certificate Services root. Go versions prior to 1.26.10 and 1.27.0 through 1.27.2 are affected. The fix adds a hard-coded SPKI denylist to crypto/x509.Verify(), returning x509: no valid chains built when a blocked intermediate appears in the chain. Crucially, installing a newer Go compiler does not protect running binaries — every Go application must be rebuilt with a patched toolchain.
No vendor CVSS was published by the Go team or NVD. Red Hat assigned CVSS:3.1 6.8 (Important) with AC:H/UI:R, which models browser-centric MitM. That underweights the real exposure: Go powers Kubernetes, Vault, Docker, Terraform, etcd, Prometheus, and thousands of headless API services that make TLS connections with zero user interaction. For those workloads, UI:N is correct, pushing the base to 7.4. Factor in the 17-month exposure window, the trivial key recovery, and two compromised code-signing intermediates, and the realistic severity for enterprise Go estates is HIGH at 7.5. Browsers are already protected — your headless Go services and CI/CD pipelines are the gap.
5 steps from start to impact.
Recover eMudhra intermediate private keys
d × G for integers 1 through 256 on the P-384 curve. When the result matches the certificate's public key, they have the private key. This takes under one second on any modern laptop. No specialized hardware, no brute-force cluster — a Python one-liner with cryptography suffices.- Access to the public certificate (freely available via CT logs or CCADB)
- Basic cryptographic tooling (OpenSSL, Python cryptography library)
- The keys have been publicly documented since October 17, 2026 — the 'secret' is out. This step has zero friction for any attacker.
Forge a TLS certificate for target domain
api.stripe.com, registry.npmjs.org, kube-apiserver.internal). They sign it with the compromised intermediate. The resulting chain is forged leaf → compromised eMudhra intermediate → Sectigo AAA root, which chains to a universally trusted root CA. Scott Helme confirmed this works against non-browser clients with default settings on Linux, macOS, and Windows.- Recovered private key from Step 1
- OpenSSL or equivalent for certificate generation
- Browsers with updated CRLSets/OneCRL will reject the chain immediately. This step only succeeds against non-browser TLS clients.
Establish man-in-the-middle position
- Network proximity or control of routing/DNS infrastructure
- Victim Go service connects to an external HTTPS endpoint over a path the attacker can intercept
- MitM positioning is non-trivial — requires network-level access or control of routing infrastructure
- Enterprise networks with proper segmentation, DNSSEC, and monitoring make this harder
- Cloud-native workloads communicating within a VPC over private endpoints are harder to intercept
Present forged certificate to Go service
crypto/x509.Verify() will accept the chain because the compromised intermediate is not in the denylist. The TLS session completes successfully. The attacker now has full read/write access to the encrypted channel.- Victim application built with Go < 1.26.10 or 1.27.0-1.27.2
- Victim's OS trust store has not independently removed the eMudhra root/intermediate
- No certificate pinning or CT enforcement in the application
- OS-level trust store updates (ca-certificates, certctl) may block the chain independently of Go
- Applications using certificate pinning are immune
- Go applications rebuilt with patched toolchain are immune
Intercept or modify sensitive traffic
- Successful MitM from Steps 3-4
- Application-layer integrity checks (e.g., digest verification for container images, checksum validation for packages) catch modification
- mTLS with client certificates not chaining to eMudhra prevents impersonation in both directions
ca-certificates packages across all Linux hosts and containers (yum update ca-certificates on RHEL, apt-get install --only-upgrade ca-certificates on Debian/Ubuntu, apk upgrade ca-certificates-bundle on Alpine). On macOS, apply the latest security update. On Windows, the October 20 CTL update already blocks the intermediates. This breaks the chain at the trust anchor level regardless of the Go binary version. Per the noisgate mitigation SLA for HIGH, deploy within 30 days — but given the trivial key recovery and 17-month window, treat this as urgent and push within 72 hours.scratch and distroless). Audit your Go binary inventory: find / -type f -exec file {} + | grep 'Go BuildID' or check container SBOMs. Rebuild and redeploy. Per the noisgate remediation SLA for HIGH, complete within 180 days. Prioritize binaries in Kubernetes control plane, CI/CD pipelines, and secret-management integrations.crypto/tls with a custom VerifyPeerCertificate callback that checks Certificate Transparency SCTs. This eliminates the forged-certificate vector entirely for pinned services, independent of trust store state.EM DV TLS, EM OV TLS, EM EV TLS, EM OV CS under emSign Root). If you see one, you have active exploitation. Integrate with SIEM for correlation.- WAF / NGFW at the perimeter — this is a trust-store issue in the TLS client, not a network payload that a firewall can inspect or block. The forged certificate is structurally valid; only the trust anchor is wrong.
- OCSP stapling alone — eMudhra revoked the intermediates, but Go's
crypto/x509does not check OCSP or CRL by default. Most Go applications have no revocation checking. The OS CRL/OCSP infrastructure is also inconsistently enforced on non-browser clients. - Rotating application-layer API keys — key rotation protects against credential reuse after interception, but does not prevent the MitM itself. The attacker can intercept the new keys too if the TLS channel remains compromised.
- Network segmentation alone — while segmentation reduces MitM opportunities, it does not eliminate them. An attacker inside a network segment (post-compromise) can still ARP-spoof and intercept Go service traffic within that segment.
The supporting signals.
| In-the-Wild Exploitation | No confirmed exploitation. eMudhra states it is "not aware of any evidence that the weak keys were recovered or exploited by anyone before the issue was disclosed." No TLS certificates chaining to the six affected website intermediates appeared in Certificate Transparency logs before Chrome blocked the keys. However, the keys were trivially recoverable for 17 months (April 2025 – October 2026) and CT logging is not mandatory. |
|---|---|
| Proof of Concept | Cryptographic proof demonstrated, not traditional exploit code. Security researcher Wayne submitted valid signed attestations proving private-key control for all eight certificates. Mozilla independently recovered the keys. Scott Helme published a detailed walkthrough at scotthelme.co.uk confirming forged certificates for api.stripe.com verified successfully on unpatched Linux, macOS, and Windows. No traditional exploit repo on GitHub or pocindex.io — the 'PoC' is trivial key recovery math, not a weaponized tool. |
| EPSS | 0.00145 (low percentile). EPSS models traditional software exploits poorly for CA trust issues — the exploitation pattern is fundamentally different from a CVE with a network-reachable code path. |
| KEV Status | Not listed on CISA KEV as of 2026-10-22. Given no confirmed exploitation, KEV listing is unlikely unless intelligence surfaces. |
| CVSS Vector (Red Hat) | CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N = 6.8. Red Hat's UI:R models browser usage; for headless Go services UI:N is more accurate → 7.4. noisgate assesses 7.5 accounting for code-signing and supply-chain blast radius. |
| Affected Versions | Go stdlib crypto/x509: all versions before 1.26.10 and 1.27.0 through 1.27.2. Also affects: Python certifi < 2026.10.18, Node.js < 22.23.4 / 24.22.1 / 26.12.1, Mozilla NSS < 3.131.1, Rust webpki-roots < 1.0.10, and OS ca-certificates packages across RHEL, Ubuntu, Alpine, macOS. |
| Fixed Versions | Go 1.26.10, Go 1.27.3. Applications must be rebuilt — upgrading the Go toolchain alone is insufficient. OS trust stores: RHEL ca-certificates via RHSB-2026-012, Ubuntu/Debian via update-ca-certificates, Alpine via apk upgrade ca-certificates-bundle. |
| Compromised Certificates | Eight eMudhra intermediates under emSign Root TLS CA - G3 and emSign Root CS CA - G3: EM DV TLS CA G3B/G3C, EM OV TLS CA G3B/G3C, EM EV TLS CA G3B/G3C (TLS), EM OV CS CA G3B/G3C (code signing). Private key values: 4, 56, 66, 115, 155, 156, 230, 237 on P-384. |
| Disclosure Timeline | April 30, 2025: Weak keys generated. May 2025: Sectigo cross-signs. October 17, 2026 20:06 UTC: Wayne reports to Mozilla. Same day: Chrome CRLSet block deployed. October 18: eMudhra revokes as cACompromise. October 19: Apple blocks. October 20: Microsoft blocks, Go releases 1.26.10/1.27.3. October 21: eMudhra public statement, Red Hat RHSB-2026-012. |
| Discovery | Security researcher Wayne discovered the flaw on October 17, 2026, while running checks against the Common CA Database (CCADB). Mozilla independently reproduced key recovery. Scott Helme published detailed technical analysis and confirmed forged certificate acceptance. |
Sources.
- Red Hat CVE Page & CVSS Vector
- Red Hat Security Bulletin RHSB-2026-012
- Scott Helme — Eight Bits of Trust: eMudhra's CA Keys Cracked
- Threat Landscape — Go 1.27.3 Blocks Eight eMudhra Intermediate CAs
- SOCPrime — CVE-2026-111943 Analysis
- Cloudflare — eMudhra Certificate Compromise
- Help Net Security — eMudhra Private Keys Compromised
- Cross-Ecosystem Impact: Go, Python certifi, Node.js, Java
Why this verdict
- MitM prerequisite is real friction but not disqualifying. The attack requires network positioning (AC:H), which eliminates drive-by exploitation. However, nation-state actors, compromised cloud DNS, BGP hijacks, and rogue internal actors can all achieve MitM — and the 17-month window means any of them could have already done so undetectably.
- Trivial key recovery eliminates attacker skill as a barrier. Unlike most CA compromises requiring HSM theft or sophisticated attacks, these keys are literally single-byte integers. A script kiddie with Python can forge a trusted certificate for any domain in seconds. The attacker cost is functionally zero once positioned.
- Role multiplier: Go crypto/x509 is canonical to high-value infrastructure. Kubernetes (apiserver, kubelet, controller-manager — all Go), HashiCorp Vault, Docker/containerd, Terraform, etcd, Prometheus, and Argo CD all use
crypto/x509for TLS verification. A forged kube-apiserver certificate in a MitM position yields cluster takeover → fleet compromise. A forged Vault certificate yields secret exfiltration → domain/cloud credential theft. These are not edge cases — they represent the *primary* deployment pattern for Go. ≥50% of Go binary deployments occupy high-value roles by definition. The blast radius floor is HIGH. - Code-signing intermediates extend impact beyond TLS. Two of eight compromised intermediates (EM OV CS CA G3B/G3C) issue code-signing certificates. Forged code signatures could make malware appear legitimately signed, bypassing Windows SmartScreen, macOS Gatekeeper (if eMudhra roots are trusted), and enterprise allowlisting policies. This is a supply-chain-scale outcome.
- Binary rebuild requirement amplifies remediation lag. Unlike browser or OS trust store updates that propagate automatically, every Go application binary must be individually rebuilt with Go ≥1.26.10 or ≥1.27.3. Container images with frozen Go binaries, embedded/IoT devices, and legacy deployments will remain vulnerable far longer than the OS trust store update cycle. Some container images have certificates expiring in July 2039.
Why not higher?
CRITICAL would require either confirmed active exploitation or a remotely exploitable code path without the MitM positioning requirement. The MitM prerequisite (AC:H) meaningfully reduces the reachable attack surface — most Go services communicate within VPCs over private networks where MitM is substantially harder. No certificates appeared in CT logs during the 17-month window, and no exploitation has been confirmed. Browser traffic — the largest surface — is already protected.
Why not lower?
MEDIUM would underweight the blast radius. Go crypto/x509 is not an optional library in a niche product — it is THE trust verification layer for the entire Go ecosystem, which dominates cloud-native infrastructure. Kubernetes alone represents millions of clusters. The key recovery is trivial (zero attacker cost), the exposure window was 17 months, and the code-signing compromise adds a supply-chain dimension. The role multiplier for Kubernetes, Vault, and container runtimes floors this at HIGH regardless of MitM friction.
Crowdsourced verification payload.
Run this script on each target host (Linux/macOS) where Go applications are deployed. Execute as root or with sudo for full binary scanning: sudo bash check_cve_2026_111943.sh. No network access required — it checks local Go toolchain version, OS trust store status, and scans for Go binaries built with vulnerable toolchains.
#!/usr/bin/env bash
# CVE-2026-111943 — eMudhra compromised intermediate CA denylist check
# Checks: (1) Go toolchain version, (2) OS ca-certificates, (3) Go binary build versions
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
vulnerable=0
checks_run=0
echo "=== CVE-2026-111943 Check: eMudhra Intermediate CA Denylist ==="
echo ""
# --- Check 1: Go toolchain version ---
echo "[Check 1] Go toolchain version"
if command -v go &>/dev/null; then
GO_VERSION=$(go version | grep -oP 'go[0-9]+\.[0-9]+\.?[0-9]*')
GO_MAJOR=$(echo "$GO_VERSION" | grep -oP '[0-9]+' | head -1)
GO_MINOR=$(echo "$GO_VERSION" | grep -oP '[0-9]+' | sed -n '2p')
GO_PATCH=$(echo "$GO_VERSION" | grep -oP '[0-9]+' | sed -n '3p')
GO_PATCH=${GO_PATCH:-0}
checks_run=$((checks_run + 1))
if [[ "$GO_MINOR" -eq 26 && "$GO_PATCH" -ge 10 ]] || \
[[ "$GO_MINOR" -eq 27 && "$GO_PATCH" -ge 3 ]] || \
[[ "$GO_MINOR" -gt 27 ]]; then
echo -e " ${GREEN}PATCHED${NC}: $GO_VERSION (>= 1.26.10 or >= 1.27.3)"
else
echo -e " ${RED}VULNERABLE${NC}: $GO_VERSION (needs >= 1.26.10 or >= 1.27.3)"
vulnerable=1
fi
else
echo -e " ${YELLOW}SKIPPED${NC}: go not found in PATH"
fi
echo ""
# --- Check 2: OS trust store — look for eMudhra intermediates ---
echo "[Check 2] OS trust store (eMudhra intermediate presence)"
# Known compromised intermediate CN fragments
EMUDHRA_PATTERNS=("EM DV TLS CA" "EM OV TLS CA" "EM EV TLS CA" "EM OV CS CA")
found_emudhra=0
if [[ -f /etc/ssl/certs/ca-certificates.crt ]]; then
CERT_BUNDLE="/etc/ssl/certs/ca-certificates.crt"
elif [[ -f /etc/pki/tls/certs/ca-bundle.crt ]]; then
CERT_BUNDLE="/etc/pki/tls/certs/ca-bundle.crt"
elif [[ -f /etc/ssl/cert.pem ]]; then
CERT_BUNDLE="/etc/ssl/cert.pem"
else
CERT_BUNDLE=""
fi
if [[ -n "$CERT_BUNDLE" ]]; then
checks_run=$((checks_run + 1))
for pattern in "${EMUDHRA_PATTERNS[@]}"; do
if grep -qi "$pattern" "$CERT_BUNDLE" 2>/dev/null; then
echo -e " ${RED}VULNERABLE${NC}: Found '$pattern' in $CERT_BUNDLE"
found_emudhra=1
vulnerable=1
fi
done
if [[ $found_emudhra -eq 0 ]]; then
echo -e " ${GREEN}PATCHED${NC}: No compromised eMudhra intermediates in $CERT_BUNDLE"
fi
else
echo -e " ${YELLOW}SKIPPED${NC}: Could not locate system CA bundle"
fi
echo ""
# --- Check 3: Scan for Go binaries built with vulnerable versions ---
echo "[Check 3] Scanning for Go binaries built with vulnerable Go versions"
echo " (scanning common paths — may take a moment)"
vuln_binaries=0
SCAN_PATHS=(/usr/local/bin /usr/bin /opt /srv /home)
for spath in "${SCAN_PATHS[@]}"; do
[[ -d "$spath" ]] || continue
while IFS= read -r -d '' binary; do
# Extract Go build version from binary
build_ver=$(strings "$binary" 2>/dev/null | grep -oP '^go1\.[0-9]+\.?[0-9]*$' | head -1 || true)
if [[ -n "$build_ver" ]]; then
b_minor=$(echo "$build_ver" | grep -oP '[0-9]+' | sed -n '2p')
b_patch=$(echo "$build_ver" | grep -oP '[0-9]+' | sed -n '3p')
b_patch=${b_patch:-0}
if [[ "$b_minor" -eq 26 && "$b_patch" -lt 10 ]] || \
[[ "$b_minor" -eq 27 && "$b_patch" -lt 3 ]] || \
[[ "$b_minor" -lt 26 ]]; then
echo -e " ${RED}VULNERABLE${NC}: $binary (built with $build_ver)"
vuln_binaries=$((vuln_binaries + 1))
vulnerable=1
fi
fi
done < <(find "$spath" -type f -executable -print0 2>/dev/null)
done
checks_run=$((checks_run + 1))
if [[ $vuln_binaries -eq 0 ]]; then
echo -e " ${GREEN}PATCHED${NC}: No vulnerable Go binaries found in scanned paths"
else
echo -e " ${RED}FOUND${NC}: $vuln_binaries Go binary(ies) built with vulnerable Go versions"
fi
echo ""
echo "========================================"
if [[ $checks_run -eq 0 ]]; then
echo -e "Result: ${YELLOW}UNKNOWN${NC} — no checks could be performed"
exit 2
elif [[ $vulnerable -gt 0 ]]; then
echo -e "Result: ${RED}VULNERABLE${NC} — action required (see CVE-2026-111943)"
exit 1
else
echo -e "Result: ${GREEN}PATCHED${NC} — system appears protected"
exit 0
fi