This is a mislabeled spare key, not a broken front door
CVE-2026-6276 is a libcurl-only cookie scope bug affecting versions 7.71.0 through 8.19.0 and fixed in 8.20.0. If an application reuses the same libcurl *easy handle*, sends one HTTP request with a custom Host: header, then sends a later request on that same handle without the custom header, libcurl can keep stale cookie-host state and forward cookies from host A to host B. Upstream explicitly says the curl command-line tool is not affected.
Tenable and NVD label this HIGH/7.5, but that does not match reality. Upstream curl rates it Low because the exploit chain is narrow: you need a vulnerable app pattern, cookie handling enabled, handle reuse, a custom Host: header transition, and in practice the bug is most relevant to cleartext HTTP or otherwise contrived test/proxy setups; HTTPS plus correct SNI sharply reduces reachable abuse.
4 steps from start to impact.
Reach a libcurl-using app path
- Target software actually uses libcurl, not just the curl CLI
- Attacker can influence outbound HTTP requests or target URLs
- Application enables cookie handling
- Many hosts with
libcurlinstalled never expose attacker-controlled request flows - A local version hit from Nessus does not prove exploitability in the application
- The curl CLI is unaffected, removing a huge chunk of assumed exposure
Trigger the stale host state
Host: header on request 1, and then omits it on request 2. That sequence is the entire bug; without that exact reuse pattern, cookies do not spill.- Application reuses one libcurl easy handle across requests
- Request 1 sets a custom
Host:header - Request 2 reuses the handle after that header is removed
- This is a very specific programming pattern, often seen in debugging, proxying, or bespoke integrations
- Many applications create fresh handles or never touch custom
Host:at all - Modern SDK wrappers often abstract this away and avoid the sequence
CURLOPT_HTTPHEADER and handle reuse can find risky callers; network scanners cannot.Get cookies forwarded cross-host
cookiehost state exists, libcurl can attach cookies from the first host to the second host on the next request. The bug leaks confidentiality only; it does not grant code execution, privilege escalation, or direct service compromise.- Relevant cookies exist for the first host
- Second request is attacker-observable or attacker-controlled
- Application does not isolate cookie jars per origin/workflow
- If no sensitive cookies are present, the bug is mostly noise
- If the second host is not attacker-controlled or observable, impact drops
- Many enterprise service accounts use tokens outside browser-style cookie workflows
Cookie: headers on outbound requests to unrelated hosts in proxy logs or app-layer tracing.Monetize the leak
- Leaked cookie remains valid
- Cookie maps to a security-relevant session
- Downstream app accepts replay from attacker infrastructure
- Session binding, short TTLs, or IP/device checks can blunt reuse
- Many leaked cookies would only impact one app or one tenant context
- Blast radius is usually limited to the affected request workflow
The supporting signals.
| In the wild | No public evidence of active exploitation found in source checks, and no CISA KEV listing was found for CVE-2026-6276. |
|---|---|
| PoC availability | A public HackerOne report (#3671818) is referenced by curl and NVD; Tenable marks Exploit Available: true, but this is best understood as a reproducible report/PoC, not a mass-exploitation kit. |
| EPSS | Tenable shows EPSS 0.00008. Snyk shows 0.01% probability, 3rd percentile — exactly what you'd expect for a niche app-pattern leak. |
| KEV status | Not listed in the CISA Known Exploited Vulnerabilities catalog in source checks. |
| CVSS mismatch | NVD/Tenable use 7.5 HIGH with CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H, but upstream curl's own advisory marks the issue Low. The generic CVSS vector overstates reality by ignoring the app-specific preconditions. |
| Affected versions | Upstream affected range is 7.71.0 through 8.19.0 inclusive. Upstream unaffected are <7.71.0 and >=8.20.0. curl CLI is not affected. |
| Fixed versions | Upstream fix is 8.20.0. Distro backports exist: Ubuntu fixed in 7.81.0-1ubuntu1.24 for 22.04 LTS and 8.5.0-2ubuntu10.9 for 24.04 LTS; SUSE shipped fixes in SUSE-SU-2026:1717-1. Amazon Linux showed Pending Fix in the checked advisory. |
| Exposure/scanning reality | This is not internet-fingerprintable like a server daemon bug. Shodan/Censys-style counts are poor risk signals here because libcurl is a client library embedded in apps. Detection is mostly local inventory + code-path analysis. |
| Scanner coverage | Nessus plugin 311420 is local only and states it relies on the application's self-reported version rather than exercising the vulnerable behavior. |
| Timeline / credit | Reported to curl on 2026-04-14, distros notified 2026-04-23, advisory and fixed release published 2026-04-29, NVD published 2026-05-13. Reported by Muhamad Arga Reksapati; patch by Daniel Stenberg. |
noisgate verdict.
The decisive factor is exploit precondition stacking: an attacker needs a very specific libcurl caller pattern involving cookie use, Host: header manipulation, and easy-handle reuse. That sharply narrows the reachable population and keeps this far away from the kind of broadly exploitable remote issue that deserves a fleet-wide HIGH.
Why this verdict
- Baseline was inflated: the upstream curl project rates this issue Low, while NVD/Tenable's generic
7.5 HIGHassumes a much broader attack surface than the real bug has. - Requires app-specific misuse: attacker success depends on a caller reusing the same easy handle, enabling cookies, setting a custom
Host:header on one request, and then removing it on the next. Each prerequisite compounds downward pressure on severity. - Reachable population is small: upstream says custom
Host:is mostly used for debugging and the flaw primarily matters for cleartext HTTP; HTTPS requires correct SNI, which blocks the easy, generic abuse path.
Why not higher?
This is not unauthenticated remote code execution, not a wormable service flaw, and not a one-shot internet exploit against every host running libcurl. You need a very particular application behavior to even reach the bug, and the impact is cookie disclosure rather than host compromise.
Why not lower?
It still breaks an origin boundary and can leak real session material when the wrong app pattern exists. In bespoke integrations, reverse proxies, test harnesses, or internal middleware that juggle custom Host: headers, the bug can expose credentials or tenant-scoped sessions, so it is not pure informational noise.
What to do — in priority order.
- Ban custom
Host:reuse — Audit application code and wrapper libraries forCURLOPT_HTTPHEADERpatterns that set a customHost:header and then reuse the same easy handle for later requests. For a LOW verdict there is no mitigation SLA; treat this as backlog hygiene and fold the audit into the normal remediation cycle. - Isolate cookie state per workflow — Do not share one easy handle and cookie jar across unrelated destinations. Separate handles or cookie engines by origin so stale state cannot cross-pollinate requests. For a LOW verdict there is no mitigation SLA; make this an engineering hardening task rather than an emergency change.
- Prefer strict HTTPS flows — Where the affected integration still uses cleartext HTTP or synthetic host-header routing tricks, move it to normal HTTPS with correct hostname validation and SNI. That removes the easiest practical path described by upstream. For a LOW verdict there is no mitigation SLA; schedule it as hardening work.
- Triage by exploitability, not package count — Prioritize only apps that both use libcurl cookies and manipulate custom
Host:headers. Ten thousand version hits on workstations or servers do not equal ten thousand exploitable paths. For a LOW verdict there is no mitigation SLA; use this to suppress queue noise.
- A WAF does not reliably help because the bug happens in the client library's outbound request handling, not at your inbound edge.
- Perimeter internet exposure counts do not help much because this is a library flaw embedded in apps, not a directly fingerprintable listening service.
- Blindly flagging every
libcurl < 8.20.0asset as urgent creates patch fatigue; many installs will never hit the required cookie + custom-Host + handle-reuse path.
Crowdsourced verification payload.
Run this on the target host where the vulnerable application lives, not from an auditor workstation. Save as check_libcurl_cve_2026_6276.py and run python3 check_libcurl_cve_2026_6276.py; no admin rights are required, but the result may be UNKNOWN on distro-packaged builds because vendors often backport fixes without changing the upstream libcurl version string.
#!/usr/bin/env python3
# check_libcurl_cve_2026_6276.py
# Detect likely exposure to CVE-2026-6276 (libcurl stale Host header cookie leak)
# Exit codes:
# 0 = PATCHED
# 1 = VULNERABLE
# 2 = UNKNOWN
import ctypes
import ctypes.util
import os
import platform
import re
import subprocess
import sys
LOW = (7, 71, 0)
HIGH = (8, 20, 0)
def parse_semver(text):
if not text:
return None
m = re.search(r'(\d+)\.(\d+)\.(\d+)', text)
if not m:
return None
return tuple(int(x) for x in m.groups())
def vstr(v):
return '.'.join(str(x) for x in v) if v else 'unknown'
def in_vuln_range(v):
return v is not None and LOW <= v < HIGH
def is_patched_upstream(v):
return v is not None and (v < LOW or v >= HIGH)
def run_cmd(cmd):
try:
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=5)
return p.returncode, p.stdout.strip(), p.stderr.strip()
except Exception:
return 127, '', 'failed'
def get_libcurl_runtime_version():
candidates = []
found = ctypes.util.find_library('curl')
if found:
candidates.append(found)
system = platform.system().lower()
if system == 'windows':
candidates += ['libcurl.dll', 'curl.dll']
elif system == 'darwin':
candidates += ['libcurl.4.dylib', 'libcurl.dylib']
else:
candidates += ['libcurl.so.4', 'libcurl.so']
tried = []
for name in candidates:
if name in tried:
continue
tried.append(name)
try:
lib = ctypes.CDLL(name)
lib.curl_version.restype = ctypes.c_char_p
raw = lib.curl_version()
if not raw:
continue
s = raw.decode(errors='ignore')
m = re.search(r'libcurl/(\d+\.\d+\.\d+)', s)
if m:
return {'source': f'loaded library {name}', 'raw': s, 'version': parse_semver(m.group(1))}
return {'source': f'loaded library {name}', 'raw': s, 'version': parse_semver(s)}
except Exception:
continue
return None
def get_curl_cli_version():
rc, out, _ = run_cmd(['curl', '--version'])
if rc != 0 or not out:
return None
first = out.splitlines()[0]
m = re.search(r'curl\s+(\d+\.\d+\.\d+)', first)
if not m:
return None
return {'source': 'curl --version', 'raw': first, 'version': parse_semver(m.group(1))}
def get_package_evidence():
evidence = []
# dpkg families
if shutil_which('dpkg-query'):
for pkg in ['libcurl4', 'libcurl4t64', 'libcurl3-gnutls', 'libcurl3-nss', 'curl']:
rc, out, _ = run_cmd(['dpkg-query', '-W', '-f=${Version}', pkg])
if rc == 0 and out:
evidence.append({'manager': 'dpkg', 'package': pkg, 'version_text': out})
# rpm families
if shutil_which('rpm'):
for pkg in ['libcurl', 'curl']:
rc, out, _ = run_cmd(['rpm', '-q', '--qf', '%{VERSION}-%{RELEASE}', pkg])
if rc == 0 and out and 'not installed' not in out.lower():
evidence.append({'manager': 'rpm', 'package': pkg, 'version_text': out})
return evidence
def shutil_which(cmd):
paths = os.environ.get('PATH', '').split(os.pathsep)
exts = ['']
if platform.system().lower() == 'windows':
exts += os.environ.get('PATHEXT', '.EXE;.BAT;.CMD').split(';')
for p in paths:
for ext in exts:
candidate = os.path.join(p, cmd + ext)
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
return candidate
return None
def looks_like_distro_backport(version_text):
vt = version_text.lower()
markers = ['ubuntu', 'deb', '.el', 'amzn', 'suse', 'fc', 'ph', 'alpine', 'amazon']
return any(m in vt for m in markers) or '-' in version_text
def main():
runtime = get_libcurl_runtime_version()
cli = get_curl_cli_version()
packages = get_package_evidence()
evidence = [x for x in [runtime, cli] if x]
# Prefer runtime library evidence if present.
chosen = runtime or cli
if not chosen:
print('UNKNOWN - could not determine a libcurl runtime or curl CLI version on this host')
sys.exit(2)
ver = chosen.get('version')
if ver is None:
print(f"UNKNOWN - unable to parse version from {chosen.get('source')}: {chosen.get('raw')}")
sys.exit(2)
if is_patched_upstream(ver):
print(f"PATCHED - {chosen.get('source')} reports upstream version {vstr(ver)} outside the vulnerable range 7.71.0-8.19.0")
sys.exit(0)
# Version falls in the upstream vulnerable range. Check for distro packaging, where backports are common.
for pkg in packages:
if looks_like_distro_backport(pkg['version_text']):
print(f"UNKNOWN - runtime reports {vstr(ver)} in the upstream vulnerable range, but distro package {pkg['package']}={pkg['version_text']} may include a backported fix; verify against your vendor advisory")
sys.exit(2)
print(f"VULNERABLE - {chosen.get('source')} reports upstream version {vstr(ver)} in the vulnerable range 7.71.0-8.19.0 and no distro backport evidence was found")
sys.exit(1)
if __name__ == '__main__':
main()
If you remember one thing.
311420 blow up your patch queue. First, identify the small subset of applications that actually use libcurl cookies plus custom Host: headers plus easy-handle reuse; for everyone else, document the downgrade and move on. For a LOW verdict there is no noisgate mitigation SLA and no noisgate remediation SLA — treat this as backlog hygiene, patch in the normal maintenance stream, and only fast-track the few bespoke apps whose code path really matches the advisory.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.