Like a shipping label that forgets to say which customer it belongs to
This Tenable plugin is tracking CVE-2017-7674 in Apache Tomcat's built-in CorsFilter, fixed upstream in 8.0.45. The affected upstream range for this plugin is 8.0.0.RC1 through 8.0.44. The bug is simple: when the CORS filter handled responses, it failed to add Vary: Origin, so caches could treat responses for different origins as interchangeable.
Tenable's MEDIUM rating is technically defensible in a vacuum, but operationally it is still a little generous. Real exploitation requires more than a vulnerable version: the app must actually use Tomcat's CorsFilter, there must be a cache path that reuses the poisoned response across origins, and the attacker still only gets cache poisoning / response mix-up, not server-side code execution. On a 10,000-host estate, this is a config-audit and backlog patch item, not a fire drill.
4 steps from start to impact.
Find a Tomcat app that actually uses CorsFilter
org.apache.catalina.filters.CorsFilter. Basic tooling is enough here: curl, Burp Suite Repeater, or a config leak. A version hit alone is not enough to prove exploitability because this flaw lives in a specific filter path, not in Tomcat's core request parser.- Reachable Tomcat-backed application
- Affected Tomcat version in the vulnerable range
- Application uses Tomcat's built-in
CorsFilter
- Many Tomcat deployments do not enable the built-in CORS filter at all
- Version-only scanners over-report because they do not validate filter usage
- Some environments terminate or normalize CORS behavior upstream at a proxy or gateway
CorsFilter is enabled. Local config review or filesystem inspection is needed to confirm exposure.Poison a cache with a crafted Origin request
curl or Burp, the attacker sends a request that exercises the CORS path with a chosen Origin header. Because Tomcat omitted Vary: Origin, a downstream browser cache, intermediary proxy, or CDN may store a response without differentiating by origin. The poisoned object can then be reused for a different requester than the application intended.- Response is cacheable somewhere in the path
- Origin-dependent behavior exists on the response
- Cache key does not already include
Origin
- A lot of modern reverse proxies and CDNs already avoid caching these responses or key more conservatively
- Private/no-store responses break the chain immediately
- Dynamic authenticated content is often uncached by policy
Wait for a victim or second request path to reuse the poisoned object
- Victim traffic or another request path reuses the cached object
- Cache lifetime and routing keep the object reachable
- Origin-specific semantics matter to the application
- No shared cache means no practical cross-user impact
- Short TTLs sharply reduce exploitability
- Same-origin app patterns often limit any meaningful abuse
Origin combinations, unexpected cache hits, and mismatched Access-Control-* behavior in app and proxy logs.Exploit the integrity flaw, not the server
- Target endpoint has meaningful origin-dependent behavior
- Poisoned response changes downstream trust or behavior
- Impact is usually low-integrity web behavior, not host compromise
- No direct path to code execution or privilege escalation
- Many applications see only nuisance-level impact even if technically vulnerable
The supporting signals.
| Primary issue | CVE-2017-7674: Apache Tomcat CorsFilter omitted Vary: Origin, enabling cache poisoning in some deployments. |
|---|---|
| In-the-wild status | No public evidence of active exploitation found in the sources reviewed, and no CISA KEV listing was found. |
| PoC availability | No widely referenced public exploit kit or named GitHub PoC surfaced. That said, this is easy to emulate manually with curl or Burp once you know the app uses CorsFilter. |
| EPSS | Low signal. FIRST publishes daily EPSS, but a secondary Feedly snapshot recorded 0.28% / 64.6th percentile on 2023-12-01, which is consistent with a low-priority web misconfiguration class issue. |
| KEV status | Not found in CISA's Known Exploited Vulnerabilities catalog in the reviewed catalog/search results. |
| CVSS and what it means | CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N = network reachable, no auth required, but user/cache interaction is required and impact is low integrity only. |
| Affected versions | Upstream affected ranges: Tomcat 8.0.0.RC1-8.0.44, 8.5.0-8.5.15, 9.0.0.M1-9.0.0.M21, and 7.0.41-7.0.78. |
| Fixed versions | Upstream fixes: 8.0.45, 8.5.16, 9.0.0.M22, 7.0.79. Debian backported fixes for tomcat8 in 8.0.14-1+deb8u11 on Jessie; Ubuntu tracked it via USN-3519-1 for supported releases at the time. |
| Detection reality | The Nessus plugin is effectively a version-based remote finding. It does not prove that CorsFilter is configured, that a cache exists, or that the cache key ignores Origin. |
| Lifecycle context | Tomcat 8.0.x is end-of-life. That does not make this CVE urgent by itself, but it does mean any surviving 8.0.x host should already be on a migration/removal track. |
noisgate verdict.
The decisive downgrade factor is that exploitation depends on a specific optional filter plus a cooperating cache topology; most version-only detections will never be weaponizable. Even when the chain lands, the impact is usually web-response integrity confusion, not host compromise or tenant-wide takeover.
Why this verdict
- Version hit ≠ exploit path: this bug only matters if Tomcat's built-in
CorsFilteris actually in use. - Shared-cache dependency: the attacker needs a browser/proxy/CDN path that stores and reuses the response without varying on
Origin. - Low blast radius: impact is typically cache poisoning or incorrect CORS behavior on selected endpoints, not server execution or credential-free compromise of the host.
- No exploitation pressure seen: no KEV listing and no obvious public campaign history push this down further.
- Scanner inflation: remote tools flag the Tomcat version, but they do not validate filter enablement or cache-key behavior, which is where the real risk lives.
Why not higher?
To rate this MEDIUM or HIGH in enterprise reality, you would want either broader exploitability or stronger impact. Here you have neither: the attacker does not get code execution, and the chain depends on optional app behavior plus a favorable caching setup. That is too much friction for a higher bucket.
Why not lower?
This is not IGNORE because the bug is real and exploitable in the right web architecture. If you are exposing CORS-enabled Tomcat apps behind shared caches or CDNs, the missing Vary: Origin can create genuine response-integrity issues, so the finding deserves validation and cleanup.
What to do — in priority order.
- Verify whether
CorsFilteris enabled — Auditconf/web.xmland applicationWEB-INF/web.xmlfiles first. For a LOW noisgate verdict there is no SLA; treat as backlog hygiene, but do this validation before spending patch effort on every version-only hit. - Add
Vary: Originat the proxy or CDN layer — If the application legitimately varies responses byOrigin, enforceVary: Originat the reverse proxy, API gateway, or CDN. For LOW, there is no mitigation SLA; treat as backlog hygiene, but this is the cleanest control when upgrades are delayed. - Disable caching on CORS-sensitive responses — Mark affected responses
Cache-Control: private, no-storeor otherwise exclude them from intermediary caching if origin-sensitive behavior exists. For LOW, this is backlog hygiene rather than emergency work. - Retire Tomcat 8.0.x where possible — Tomcat 8.0.x is EOL, so fold this finding into your broader runtime modernization work. For LOW, there is no SLA; treat as backlog hygiene, but do not leave 8.0.x stranded as a permanent exception.
EDRdoes not meaningfully help because this is a header/cache semantics issue, not a process-level exploit.MFAis irrelevant because the flaw does not rely on account compromise.TLSonly protects the transport; it does not fix incorrect cache variation behavior.- Blocking generic file-upload or JSP execution paths does nothing here; this is not the Tomcat PUT-to-JSP RCE class.
Crowdsourced verification payload.
Run this on the target Tomcat host or against an unpacked Tomcat directory from your gold image/SBOM pipeline. Invoke it as python3 check_cve_2017_7674.py /opt/tomcat and it needs only read access to the Tomcat tree; no admin privileges are required unless the files are locked down.
#!/usr/bin/env python3
# check_cve_2017_7674.py
# Determine likely exposure to CVE-2017-7674 on a Tomcat installation.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN, 3=usage/error
import os
import re
import sys
from pathlib import Path
AFFECTED = {
'7.0': ('7.0.41', '7.0.78'),
'8.0': ('8.0.0.RC1', '8.0.44'),
'8.5': ('8.5.0', '8.5.15'),
'9.0': ('9.0.0.M1', '9.0.0.M21'),
}
def parse_version(v):
v = v.strip()
m = re.match(r'^(\d+)\.(\d+)\.(\d+)(?:[.-]?(RC|M)(\d+))?$', v, re.IGNORECASE)
if not m:
return None
major, minor, patch, stage, stage_num = m.groups()
major = int(major)
minor = int(minor)
patch = int(patch)
if not stage:
stage_rank = 2 # final release > milestone/rc of same numeric version
stage_num = 0
else:
stage = stage.upper()
stage_rank = 0 if stage == 'M' else 1 # M < RC < final
stage_num = int(stage_num)
return (major, minor, patch, stage_rank, stage_num)
def cmp_ver(a, b):
pa = parse_version(a)
pb = parse_version(b)
if pa is None or pb is None:
raise ValueError(f'Cannot compare versions: {a} vs {b}')
return (pa > pb) - (pa < pb)
def version_in_range(v, lo, hi):
return cmp_ver(v, lo) >= 0 and cmp_ver(v, hi) <= 0
def read_text_if_exists(p):
try:
return p.read_text(errors='ignore')
except Exception:
return None
def detect_version(tomcat_home: Path):
candidates = [
tomcat_home / 'RELEASE-NOTES',
tomcat_home / 'RUNNING.txt',
tomcat_home / 'bin' / 'catalina.sh',
tomcat_home / 'lib' / 'catalina.jar',
tomcat_home / 'lib' / 'tomcat-coyote.jar',
]
# Best effort from text files first
for p in candidates[:3]:
txt = read_text_if_exists(p)
if not txt:
continue
for pat in [r'Apache Tomcat Version\s+([0-9][^\s]+)', r'Apache Tomcat/([0-9][^\s]+)', r'\b([0-9]+\.[0-9]+\.[0-9]+(?:[.-]?(?:RC|M)[0-9]+)?)\b']:
m = re.search(pat, txt, re.IGNORECASE)
if m:
return m.group(1)
# Try manifest from jars without external deps
import zipfile
for p in candidates[3:]:
if not p.exists():
continue
try:
with zipfile.ZipFile(p) as zf:
for name in ('META-INF/MANIFEST.MF',):
try:
data = zf.read(name).decode('utf-8', errors='ignore')
except Exception:
continue
for pat in [r'Implementation-Version:\s*([^\s]+)', r'Specification-Version:\s*([^\s]+)']:
m = re.search(pat, data, re.IGNORECASE)
if m:
return m.group(1)
except Exception:
pass
return None
def branch_key(v):
pv = parse_version(v)
if not pv:
return None
return f'{pv[0]}.{pv[1]}'
def find_corsfilter_usage(tomcat_home: Path):
hits = []
search_roots = [tomcat_home / 'conf', tomcat_home / 'webapps']
needles = [
'org.apache.catalina.filters.CorsFilter',
'<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>',
'CorsFilter'
]
for root in search_roots:
if not root.exists():
continue
for path in root.rglob('*.xml'):
txt = read_text_if_exists(path)
if not txt:
continue
if any(n in txt for n in needles):
hits.append(str(path))
return hits
def main():
if len(sys.argv) != 2:
print('UNKNOWN - usage: python3 check_cve_2017_7674.py <TOMCAT_HOME>')
sys.exit(3)
tomcat_home = Path(sys.argv[1]).resolve()
if not tomcat_home.exists() or not tomcat_home.is_dir():
print(f'UNKNOWN - invalid TOMCAT_HOME: {tomcat_home}')
sys.exit(3)
version = detect_version(tomcat_home)
if not version:
print('UNKNOWN - could not determine Tomcat version from installation files')
sys.exit(2)
key = branch_key(version)
if key not in AFFECTED:
print(f'PATCHED - Tomcat version {version} is outside known affected branches for CVE-2017-7674')
sys.exit(0)
lo, hi = AFFECTED[key]
if not version_in_range(version, lo, hi):
print(f'PATCHED - Tomcat version {version} is not in the vulnerable range {lo}..{hi}')
sys.exit(0)
hits = find_corsfilter_usage(tomcat_home)
if hits:
sample = '; '.join(hits[:5])
more = '' if len(hits) <= 5 else f' (+{len(hits)-5} more)'
print(f'VULNERABLE - Tomcat {version} is in affected range and CorsFilter was found in: {sample}{more}')
sys.exit(1)
print(f'UNKNOWN - Tomcat {version} is in affected range, but no CorsFilter reference was found in scanned XML files; verify custom app wiring and proxy cache behavior manually')
sys.exit(2)
if __name__ == '__main__':
main()
If you remember one thing.
CorsFilter; second, for any confirmed CORS-enabled apps behind shared caches, add Vary: Origin or disable caching for those responses in the next normal change window. Because this is a LOW verdict, the noisgate mitigation SLA has no SLA — treat as backlog hygiene, and the noisgate remediation SLA likewise has no SLA — treat as backlog hygiene; fold the upgrade into your Tomcat 8.0.x retirement plan rather than burning an emergency patch cycle on every 102588 hit.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.