← Back to Feed CACHED · 2026-09-26 22:53:20 · CACHE_KEY CVE-2026-27145
CVE-2026-27145 · CWE-606 · Disclosed 2026-06-02

Algorithmic DoS in Go crypto/x509 VerifyHostname via quadratic SAN parsing

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

Like a toll booth operator re-reading the same license plate from scratch for every car in line

CVE-2026-27145 is an algorithmic-complexity denial-of-service in Go's standard library crypto/x509 package. The (*x509.Certificate).VerifyHostname function called matchHostnames inside a loop over every DNS Subject Alternative Name (SAN) entry, redundantly executing strings.Split(host, ".") on the same hostname each iteration. A certificate with *N* SAN entries and a hostname with *L* labels produces O(N × L) string-split operations — quadratic cost. Critically, this parsing fires before certificate chain validation, so even an untrusted, self-signed certificate triggers the CPU burn. All Go binaries built with the stdlib crypto/x509 on Go < 1.25.11 or Go 1.26.0–1.26.3 are affected — that's every TLS client written in Go.

The upstream vendor scored this CVSS 6.5 MEDIUM (AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H). Red Hat disagreed and scored it 7.5 HIGH with a different vector (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H), arguing — correctly — that attack complexity is low and there is no integrity impact, only availability. The truth sits between these two: the attack is trivially crafted, but the attacker must control the TLS server endpoint the victim connects to, which narrows real-world exposure. The original vendor vector's I:L (integrity) component is incorrect; this is a pure DoS. On balance, 6.5 MEDIUM captures the practical risk for most enterprises.

"Go stdlib DoS via quadratic SAN parsing — patch on your standard cycle"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Attacker stands up malicious TLS endpoint

The attacker provisions a TLS server presenting a crafted X.509 certificate containing thousands of DNS SAN entries (e.g., 50,000+ entries). The certificate can be self-signed — it doesn't need to chain to any trusted CA because the quadratic parsing fires before chain validation. Standard Go crypto/tls or OpenSSL can generate such a certificate in seconds.
Conditions required:
  • Attacker controls a reachable TLS server or can MITM a connection
Where this breaks in practice:
  • Victim Go process must initiate outbound TLS to attacker-controlled or attacker-intercepted endpoint
  • Enterprise egress firewalls / proxy configurations limit which hosts internal Go services can reach
STEP 02

Victim Go client connects to attacker endpoint

The victim Go binary — any program using crypto/tls or net/http — initiates a TLS handshake with the malicious server. This happens naturally when Go services call external webhooks, fetch URLs from user input, pull container images, contact API backends, or resolve OIDC discovery endpoints. The TLS handshake begins and the server presents the oversized-SAN certificate.
Conditions required:
  • Victim application makes outbound TLS connections to an endpoint the attacker controls or influences
  • Application is built with Go < 1.25.11 or Go 1.26.0–1.26.3
Where this breaks in practice:
  • Many Go services only connect to pre-configured, trusted internal endpoints
  • Container registries and API endpoints are often pinned or allow-listed
  • MITM requires network position the attacker typically doesn't have
Detection/coverage: Network monitoring can flag abnormally large TLS certificates (certificate message > 64 KB is suspicious)
STEP 03

VerifyHostname triggers quadratic CPU burn

x509.Certificate.VerifyHostname iterates over every DNS SAN entry and redundantly splits the candidate hostname on each pass. With 50,000 SANs and a hostname with 10 labels, the function performs ~500,000 string-split operations instead of ~10. A single goroutine can pin a CPU core for seconds to minutes depending on SAN count. Multiple concurrent connections amplify this to full CPU exhaustion.
Conditions required:
  • Certificate reaches the VerifyHostname path (default for all TLS clients)
Where this breaks in practice:
  • Go's runtime scheduler may limit the blast to the goroutine pool; other goroutines can still progress unless all cores are saturated
  • Single-connection impact may be noticeable but not fatal — sustained attack requires repeated connections
Detection/coverage: Process-level CPU monitoring (e.g., container_cpu_usage_seconds_total in Prometheus) will show sustained 100% CPU; profiling will point to crypto/x509.(*Certificate).VerifyHostname
STEP 04

Service denial achieved

With enough concurrent malicious TLS handshakes, the targeted Go process becomes unresponsive. Depending on the deployment, this can cascade: a Kubernetes kubelet pinned at 100% CPU stops responding to health checks, gets marked NotReady, and workloads are evicted. An API gateway built in Go stops proxying traffic. Impact is availability loss; there is no path to code execution, data access, or privilege escalation.
Conditions required:
  • Attacker can sustain multiple concurrent connections
Where this breaks in practice:
  • Rate limiting on inbound connections to the attacker's server (from the victim's perspective, outbound rate limiting is rare but possible)
  • Go services behind load balancers may distribute the load across replicas, requiring the attacker to target each independently
Detection/coverage: Standard availability monitoring (uptime checks, latency alerts, pod restart counts) will catch the symptom
03 · Compensating Control

1
MEDIUM 6.5→IGNORE 0.0
SEVERITY REDUCED
Rebuild all Go binaries with Go ≥ 1.25.11 or ≥ 1.26.4 — This is the definitive fix. Recompile every in-house Go service and update vendored Go container base images. Since Go links the stdlib statically, you must rebuild — you cannot patch a shared library. Prioritize internet-facing Go services and Kubernetes components. Per noisgate remediation SLA for MEDIUM, complete within 365 days; practically, target the next scheduled Go toolchain bump.
2
MEDIUM 6.5→LOW 3.0
SEVERITY REDUCED
Restrict outbound TLS destinations via egress policy — Use network policies (Kubernetes NetworkPolicy, Calico/Cilium egress rules, or traditional firewall ACLs) to limit which external endpoints Go services can reach. If a Go service only needs to talk to api.internal.corp, deny everything else. This breaks step 2 of the attack path — the victim never connects to the attacker. No mitigation SLA applies for MEDIUM, but this is good hygiene to implement alongside patching.
3
MEDIUM 6.5→LOW 2.5
SEVERITY REDUCED
Deploy TLS inspection at egress proxy to reject oversized certificates — If you run an egress TLS inspection proxy (e.g., Squid with SSL bump, Zscaler, or Palo Alto SSL decryption), configure it to reject server certificates with abnormally large SAN lists (> 1,000 entries). This is non-standard and may require custom scripting, but it directly blocks the attack payload before it reaches the Go client.
4
MEDIUM 6.5→MEDIUM 5.5
Monitor for CPU anomalies in Go services — Set alerting thresholds on Go process CPU usage. A sustained spike in crypto/x509.(*Certificate).VerifyHostname (visible via Go pprof or eBPF-based profiling) is a strong indicator of this attack. This doesn't prevent the DoS but limits its duration by enabling rapid response.
What doesn't work
  • WAF / ingress filtering — this is a *client-side* vulnerability triggered by a *server's* certificate. WAFs inspect inbound requests, not outbound TLS handshake payloads. Completely irrelevant.
  • Certificate pinning — pinning validates the certificate *identity* but does not skip hostname verification. The quadratic parsing still fires before the pin check.
  • Upgrading only golang.org/x/crypto — this CVE is in the Go *stdlib* crypto/x509, not in the x/crypto module. Bumping x/crypto to v0.54.0 (seen in some PRs) fixes a *different* CVE. You must bump the Go *toolchain* itself.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. Not listed in CISA KEV. No GreyNoise or Shadowserver tags. No vendor advisories reference active campaigns.
Proof of conceptNo functional PoC available. HORKimhab/CVE-2026-27145 on GitHub is an empty template repo with no exploit code. pocindex.io returns no results. Crafting a triggering certificate is trivial with openssl or Go's crypto/x509 — a researcher could write one in under an hour — but no weaponized tooling exists publicly.
EPSS0.00591 (0.59%) — 46th percentile. Below the threshold where EPSS-driven prioritization would flag this for urgent action.
KEV statusNot listed. No CISA KEV entry as of 2026-09-27.
CVSS vector (cve.org)CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H → 6.5 MEDIUM. The I:L component appears incorrect; Red Hat's vector (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H → 7.5) better reflects the pure-availability impact.
Affected versionsGo stdlib crypto/x509: all versions before Go 1.25.11 and Go 1.26.0 through 1.26.3. Every Go binary compiled with an affected toolchain is vulnerable.
Fixed versionsGo 1.25.11 and Go 1.26.4. Distro backports: Red Hat has issued multiple RHSAs (RHSA-2026 series). SUSE and Ubuntu are tracking.
Scanning / exposureNo Shodan/Censys/FOFA dork applies — this is a client-side library vulnerability, not an exposed service fingerprint. Detection requires binary composition analysis (SCA/SBOM) to identify Go toolchain version.
Disclosure timelineReported 2026-05-27 by Jakub Ciolek (ciolek.dev). Published 2026-06-02. Go advisory GO-2026-5037. Fix merged in CL 783621.
Reporting researcherJakub Ciolek ([email protected], ciolek.dev)

Sources.

  1. Go Advisory GO-2026-5037
  2. Go Issue #79694 — VerifyHostname CPU behavior
  3. Red Hat CVE-2026-27145 Advisory
  4. OSV — BIT-golang-2026-27145
  5. Go CL 783621 (fix commit)
  6. golang-announce mailing list disclosure
  7. SUSE CVE-2026-27145 Tracker
  8. BrinzTech Alert — CVE-2026-27145 Analysis
05 · The Call

Final Verdict
= UNCHANGED to MEDIUM (6.5/10)

Why this verdict

  • DoS-only impact caps severity. There is no path from this vulnerability to code execution, data access, or privilege escalation. The worst outcome is CPU exhaustion and service unavailability — serious but bounded.
  • Attacker must control the far side of a TLS connection. The victim Go process must *initiate* a TLS handshake to an attacker-controlled or attacker-intercepted endpoint. This is not a "spray the internet" attack; it requires the attacker to be positioned as the server the victim connects to, or to achieve MITM.
  • No exploitation activity and trivial-but-absent PoC. EPSS is 0.59% (46th percentile), no KEV listing, no GreyNoise tags, and no public weaponized tooling. The vulnerability is easy to trigger in theory but nobody is bothering — DoS bugs in client-side TLS parsing are low-value targets for real adversaries.
  • Role multiplier: Go is the primary language for Kubernetes, Docker, HashiCorp tools, and cloud-native infrastructure. In high-value roles (kube-apiserver, kubelet, Vault, CI/CD runners), a DoS could disrupt cluster operations or secret retrieval. However, the blast radius is *availability* of those components, not domain takeover, fleet compromise, or supply-chain pivot. The outcome does not meet the HIGH floor threshold because the worst-case is temporary service disruption, not the catastrophic outcomes enumerated in the floor rule. Kubernetes control-plane components typically run as replicated pods behind load balancers, further limiting single-process DoS impact.
  • Red Hat's 7.5 is the ceiling, not the midpoint. Red Hat correctly fixed the CVSS vector (AC:L, I:N) but their score assumes every Go binary is equally reachable, which overstates the attacker's ability to force outbound connections to arbitrary endpoints in hardened environments.

Why not higher?

This vulnerability cannot reach HIGH because the impact is strictly availability — no confidentiality or integrity breach is possible. The attacker cannot leverage this for lateral movement, data exfiltration, or privilege escalation. Even in high-value deployment roles like Kubernetes control plane, the outcome is temporary CPU exhaustion of individual Go processes, not domain-level or fleet-level compromise. The floor rule requires outcomes like domain takeover, mass data egress, or supply-chain pivot, none of which are achievable here.

Why not lower?

This cannot drop to LOW because the attack surface is genuinely broad — every Go TLS client is affected — and the attack requires no authentication, no special privileges, and works against untrusted certificates (pre-chain-validation). The crafting of a triggering certificate is trivial. Go's dominance in infrastructure software means the total population of affected binaries is enormous, even if each individual binary's exposure to attacker-controlled endpoints varies. A sustained attack against a Go-based API gateway or ingress controller could cause real outages.

06 · Verification

Crowdsourced verification payload.

Run this script on each host or in CI to check the Go toolchain version used to compile any Go binary. Pass the path to the binary as the first argument. Requires go version (ships with Go) or can parse the binary directly. No special privileges needed. Example: bash check_cve_2026_27145.sh /usr/local/bin/kubectl

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_27145.sh — Detect Go binaries vulnerable to CVE-2026-27145
# Usage: bash check_cve_2026_27145.sh /path/to/go-binary
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN

set -euo pipefail

BINARY="${1:-}"

if [[ -z "$BINARY" ]]; then
  echo "Usage: $0 /path/to/go-binary"
  exit 2
fi

if [[ ! -f "$BINARY" ]]; then
  echo "UNKNOWN — file not found: $BINARY"
  exit 2
fi

# Extract Go version from binary
GO_VERSION=$(go version "$BINARY" 2>/dev/null | grep -oP 'go[0-9]+\.[0-9]+\.?[0-9]*' | head -1)

if [[ -z "$GO_VERSION" ]]; then
  echo "UNKNOWN — could not determine Go version for $BINARY"
  exit 2
fi

echo "Detected Go version: $GO_VERSION"

# Parse major.minor.patch
VERSION_NUM=$(echo "$GO_VERSION" | sed 's/^go//')
MAJOR=$(echo "$VERSION_NUM" | cut -d. -f1)
MINOR=$(echo "$VERSION_NUM" | cut -d. -f2)
PATCH=$(echo "$VERSION_NUM" | cut -d. -f3)
PATCH=${PATCH:-0}

# Fixed in: go1.25.11+ or go1.26.4+
# Affected: all go < 1.25.11, and go 1.26.0 through 1.26.3

if [[ "$MAJOR" -lt 1 ]]; then
  echo "VULNERABLE — $GO_VERSION is affected by CVE-2026-27145"
  exit 1
fi

if [[ "$MAJOR" -eq 1 ]]; then
  if [[ "$MINOR" -lt 25 ]]; then
    echo "VULNERABLE — $GO_VERSION < go1.25.11"
    exit 1
  elif [[ "$MINOR" -eq 25 ]]; then
    if [[ "$PATCH" -lt 11 ]]; then
      echo "VULNERABLE — $GO_VERSION < go1.25.11"
      exit 1
    else
      echo "PATCHED — $GO_VERSION >= go1.25.11"
      exit 0
    fi
  elif [[ "$MINOR" -eq 26 ]]; then
    if [[ "$PATCH" -lt 4 ]]; then
      echo "VULNERABLE — $GO_VERSION < go1.26.4"
      exit 1
    else
      echo "PATCHED — $GO_VERSION >= go1.26.4"
      exit 0
    fi
  else
    # go1.27+ is not affected
    echo "PATCHED — $GO_VERSION is newer than affected range"
    exit 0
  fi
fi

echo "PATCHED — $GO_VERSION is newer than affected range"
exit 0