This is a loaded nail gun left on the porch, but only if someone first unlocked the tool cabinet
Tenable plugin 103782 is fundamentally about CVE-2017-12617 on Apache Tomcat 7: if you run Tomcat 7.0.0 through 7.0.81 and a target context allows HTTP PUT with the DefaultServlet effectively set to readonly=false, an unauthenticated attacker can upload a .jsp web shell and then execute it over HTTP. Impact is full remote code execution in the Tomcat process, not a harmless file-write.
If your source system called this MEDIUM, that undershoots reality. The vendor/NVD baseline is 8.1 High, and CISA later put it in KEV based on real-world exploitation. That said, it is not a blanket internet-breaker: the exploit depends on a non-default, operator-created condition—PUT/writeable JSP upload into an executable web context—so I keep it at HIGH, not CRITICAL.
4 steps from start to impact.
Find a Tomcat target worth probing
- Target is reachable over HTTP/HTTPS
- Tomcat version is 7.0.0-7.0.81
- Many enterprises hide Tomcat behind reverse proxies, CDNs, or WAFs
- Version disclosure is often suppressed or misleading
- A vulnerable version may still be non-exploitable if PUT is not usable
readonly=false on the affected context.Probe for PUT-enabled writable context
exploit/multi/http/tomcat_jsp_upload_bypass, the attacker tests whether an exposed context accepts HTTP PUT and stores content in a web-executable location. This is the decisive gate: without a writable JSP-capable context, the chain dies here.- HTTP PUT is allowed to a target context
- That context maps to a location Tomcat will serve
- DefaultServlet or equivalent handling permits writes
- Default Tomcat deployments typically keep
readonly=true - Security teams often block PUT at the reverse proxy, WAF, or load balancer
- Some writable contexts are isolated from JSP execution
PUT requests to .jsp or trailing-slash filename patterns; coverage is decent when HTTP logging is mature.Upload the JSP payload
- Writable target path exists
- Uploaded file lands with a server-executable extension or equivalent execution path
- Filename normalization, URL rewriting, or upstream filtering may break the bypass
- EDR or file integrity monitoring may alert on unexpected
.jspcreation - Containerized ephemeral webroots may reduce persistence
.jsp files outside deployment pipelines.Trigger web shell and take the host/app tier
- Uploaded JSP is web-accessible
- Tomcat executes JSPs in that context
- Least-privilege service accounts can limit host impact
- Network segmentation may contain lateral movement
- App-tier EDR can still catch shelling, process spawn, or outbound beacons
The supporting signals.
| In-the-wild status | Yes. NVD explicitly notes the CVE is in CISA KEV, which means confirmed real-world exploitation, not just lab exploitability. |
|---|---|
| Public exploit tooling | Commodity-ready. Rapid7 ships exploit/multi/http/tomcat_jsp_upload_bypass, and NVD references Exploit-DB 43008. |
| KEV status | Listed in CISA KEV on 2022-03-25 with a federal due date of 2022-04-15 per the NVD KEV mirror. |
| EPSS | 94.38% probability / 100th percentile on Bitsight's current view, which mirrors EPSS-style exploitation likelihood data. Treat that as supporting threat signal; KEV already settles the exploitation question. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H = unauthenticated network attack with high complexity because the exploitable deployment condition is not universal. |
| Affected versions | Apache Tomcat 7.0.0 through 7.0.81 are affected for this plugin/CVE path. |
| Fixed versions | Upstream fix is 7.0.82. Distro backports exist, e.g. Ubuntu fixed tomcat7 in 7.0.68-1ubuntu0.4+esm3 for Xenial and 7.0.52-1ubuntu0.14 for Trusty. |
| Exposure data | Bitsight Groma shows 26,307 total observations for CVE-2017-12617 over its prior-30-day internet scan window. That is population, not proof that every observed host still allows exploitable PUT. |
| Disclosure and reporting | Apache says the issue was first reported publicly and then sent to the Tomcat Security Team on 2017-09-20; the CVE/public disclosure date is 2017-10-03/2017-10-04 depending on source formatting. |
noisgate verdict.
The single biggest downward pressure is the deployment prerequisite: this is only exploitable when defenders have enabled a writable PUT path into an executable web context, which is not Tomcat's safe default. The biggest upward pressure is that this condition has real exploitation history and turnkey tooling, so any exposed misconfigured host is a near-immediate compromise.
Why this verdict
- Unauthenticated RCE is real once a writable JSP-capable context exists; attacker position is pure network remote with no credentials or user interaction.
- Configuration friction materially narrows exposure: the chain usually requires
readonly=falseor equivalent writable PUT handling, which implies operator customization and knocks this down from generic internet-wide RCE. - KEV + public weaponization push it back up: CISA lists active exploitation, Rapid7 has a Metasploit module, and NVD references Exploit-DB, so misconfigured instances are low-effort targets.
- Exposure population is meaningful but not universal: public internet datasets still show a large observed footprint, yet many of those hosts will fail exploitation because modern proxies, WAFs, and sane servlet defaults stop PUT-to-JSP paths.
- Blast radius is app-tier dependent: compromise is usually at the Tomcat service account first, not instant domain admin, but app secrets, databases, and shared middleware hosts can turn that into full environment damage quickly.
Why not higher?
I am not calling this CRITICAL because the attack path has a hard real-world choke point: non-default writable PUT into executable content. Requiring that deployment choice means a large fraction of nominally vulnerable Tomcat versions are not actually one-request-owned. That is exactly the kind of compounding friction that should keep a KEV-listed RCE out of the top bucket.
Why not lower?
I am not dropping this to MEDIUM because once the prerequisite exists, exploitation is unauthenticated, remote, and operationally easy. KEV status removes any debate about whether attackers care, and public exploit tooling removes most skill barriers.
What to do — in priority order.
- Block PUT at the edge — Deny
PUTandDELETEto Tomcat sites at the reverse proxy, load balancer, API gateway, and WAF unless a business owner can prove they are required. Because this CVE is KEV-listed, deploy this immediately, within hours as the temporary brake while patching catches up. - Force
readonly=trueon DefaultServlet — Audit globalconf/web.xmland any app-specific servlet overrides to ensure the DefaultServlet is not writable. This removes the exploit's enabling condition and should be validated immediately, within hours on internet-facing tiers. - Hunt for rogue JSPs — Search Tomcat webroots for newly created or unsigned
.jspfiles, especially tiny web shells and timestamp outliers, then correlate with HTTPPUTrequests. Run this immediately, within hours on exposed estates because compromise, if present, is likely already post-exploitation. - Constrain Tomcat service privileges — Run Tomcat under a dedicated low-privilege account with minimal filesystem write access outside intended temp/work directories, and restrict outbound network paths to only required dependencies. This reduces blast radius and should be enforced within hours for exposed hosts, then normalized estate-wide during the remediation cycle.
- Enable webroot and process telemetry — Collect file-create events under deployed app directories and alert on Tomcat spawning shells, script interpreters, or unexpected child processes. Instrumentation should land within hours on exposed systems because it catches both exploitation attempts and already-planted web shells.
- A version-only scanner finding
7.0.81does not prove exploitability; it misses the decisive PUT/writeability condition and can inflate ticket volume. - Network segmentation alone does not help if the vulnerable Tomcat is already internet-facing; the initial compromise path is straight HTTP(S).
- MFA is irrelevant here because the exploit path is unauthenticated and does not touch an interactive login flow.
- Blocking only
POSTuploads is the wrong control; the exploit abuses HTTP PUT, so upload restrictions focused on forms and multipart POSTs miss it.
Crowdsourced verification payload.
Run this on the target Tomcat host or against an unpacked Tomcat filesystem from an auditor workstation. Invoke it as python3 verify_tomcat_cve_2017_12617.py /opt/tomcat and give it read access to the Tomcat install/base directory; root/admin is not required unless file permissions are locked down.
#!/usr/bin/env python3
# verify_tomcat_cve_2017_12617.py
# Exit codes:
# 0 = PATCHED
# 1 = VULNERABLE
# 2 = UNKNOWN
import os
import re
import sys
import zipfile
import xml.etree.ElementTree as ET
def parse_version_tuple(v):
m = re.search(r'(\d+)\.(\d+)\.(\d+)', v or '')
if not m:
return None
return tuple(int(x) for x in m.groups())
def read_manifest_version(base):
candidates = [
os.path.join(base, 'lib', 'catalina.jar'),
os.path.join(base, 'lib', 'bootstrap.jar'),
]
for jar in candidates:
if not os.path.isfile(jar):
continue
try:
with zipfile.ZipFile(jar, 'r') as zf:
data = zf.read('META-INF/MANIFEST.MF').decode('utf-8', 'ignore')
for line in data.splitlines():
if line.lower().startswith('implementation-version:'):
return line.split(':', 1)[1].strip()
except Exception:
pass
return None
def find_defaultservlet_readonly_false(webxml_path):
try:
tree = ET.parse(webxml_path)
root = tree.getroot()
except Exception:
return None
# Handle possible namespaces crudely
def local(tag):
return tag.split('}', 1)[-1]
servlets = [e for e in root.iter() if local(e.tag) == 'servlet']
for servlet in servlets:
servlet_name = None
servlet_class = None
readonly_value = None
for child in list(servlet):
lt = local(child.tag)
text = (child.text or '').strip()
if lt == 'servlet-name':
servlet_name = text
elif lt == 'servlet-class':
servlet_class = text
elif lt == 'init-param':
pname = None
pvalue = None
for pchild in list(child):
plt = local(pchild.tag)
ptext = (pchild.text or '').strip()
if plt == 'param-name':
pname = ptext
elif plt == 'param-value':
pvalue = ptext
if pname == 'readonly':
readonly_value = pvalue
is_default = (servlet_name == 'default') or (servlet_class and servlet_class.endswith('DefaultServlet'))
if is_default and readonly_value is not None:
return readonly_value.lower() == 'false'
return None
def main():
if len(sys.argv) != 2:
print('UNKNOWN - usage: python3 verify_tomcat_cve_2017_12617.py <CATALINA_BASE_or_HOME>')
sys.exit(2)
base = sys.argv[1]
if not os.path.isdir(base):
print(f'UNKNOWN - path not found: {base}')
sys.exit(2)
version = read_manifest_version(base)
vt = parse_version_tuple(version)
if not vt:
print('UNKNOWN - could not determine Tomcat version from manifest')
sys.exit(2)
# Plugin 103782 is Tomcat 7 only.
if vt[0] != 7:
print(f'UNKNOWN - detected Tomcat {version}; this check is scoped to Tomcat 7 / plugin 103782')
sys.exit(2)
fixed = (7, 0, 82)
if vt >= fixed:
print(f'PATCHED - detected Tomcat {version} (>= 7.0.82)')
sys.exit(0)
webxml = os.path.join(base, 'conf', 'web.xml')
if not os.path.isfile(webxml):
print(f'UNKNOWN - detected Tomcat {version} but could not read {webxml}')
sys.exit(2)
readonly_false = find_defaultservlet_readonly_false(webxml)
if readonly_false is True:
print(f'VULNERABLE - detected Tomcat {version} and DefaultServlet readonly=false in conf/web.xml')
sys.exit(1)
elif readonly_false is False:
print(f'UNKNOWN - detected Tomcat {version} (< 7.0.82), but global conf/web.xml keeps readonly=true; app-specific overrides or upstream PUT handling still need review')
sys.exit(2)
else:
print(f'UNKNOWN - detected Tomcat {version} (< 7.0.82), but could not determine DefaultServlet readonly setting')
sys.exit(2)
if __name__ == '__main__':
main()
If you remember one thing.
PUT, verifying readonly=true, and sweeping for rogue JSP shells because this CVE is KEV-listed; that is the override to the noisgate mitigation SLA. Then finish actual remediation by upgrading to 7.0.82+ or a vendor-backported fixed package, and use the noisgate remediation SLA for HIGH as the outer bound of ≤180 days—but if you still run Tomcat 7 at scale, do not waste time polishing an EOL branch when a supported platform upgrade is available.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.