← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
tenable:103782 · CWE-434 · Disclosed 2017-10-04

Apache Tomcat 7

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

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.

"High-risk when exposed, but the non-default PUT/writeable setup keeps this out of CRITICAL territory"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Find a Tomcat target worth probing

Attackers fingerprint Apache Tomcat from response headers, error pages, default content, or scanner logic in tools like Nessus and Metasploit. Public attack-surface datasets also make old Tomcat populations easy to enumerate, but version alone is not enough to confirm exploitability.
Conditions required:
  • Target is reachable over HTTP/HTTPS
  • Tomcat version is 7.0.0-7.0.81
Where this breaks in practice:
  • 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
Detection/coverage: External scanners reliably identify the version range, but version checks overstate risk because they usually cannot prove readonly=false on the affected context.
STEP 02

Probe for PUT-enabled writable context

Using curl, custom scripts, or Metasploit's 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.
Conditions required:
  • HTTP PUT is allowed to a target context
  • That context maps to a location Tomcat will serve
  • DefaultServlet or equivalent handling permits writes
Where this breaks in practice:
  • 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
Detection/coverage: Web logs, WAFs, and IDS/IPS can catch unusual PUT requests to .jsp or trailing-slash filename patterns; coverage is decent when HTTP logging is mature.
STEP 03

Upload the JSP payload

The exploit sends a specially crafted PUT request that bypasses earlier assumptions from CVE-2017-12615 and lands a JSP file on disk. Public exploit code exists in Metasploit and Exploit-DB, so operators are not hand-building this from scratch.
Conditions required:
  • Writable target path exists
  • Uploaded file lands with a server-executable extension or equivalent execution path
Where this breaks in practice:
  • Filename normalization, URL rewriting, or upstream filtering may break the bypass
  • EDR or file integrity monitoring may alert on unexpected .jsp creation
  • Containerized ephemeral webroots may reduce persistence
Detection/coverage: Strong coverage if you monitor filesystem writes under Tomcat webroots and alert on new .jsp files outside deployment pipelines.
STEP 04

Trigger web shell and take the host/app tier

The attacker requests the uploaded JSP and executes arbitrary code as the Tomcat service account. From there the blast radius depends on local privileges, app secrets, database connectivity, adjacent credentials, and whether the server is single-tenant or shared.
Conditions required:
  • Uploaded JSP is web-accessible
  • Tomcat executes JSPs in that context
Where this breaks in practice:
  • 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
Detection/coverage: Good downstream visibility if EDR is present; poor if the estate treats Tomcat app servers as unmanaged middleware without process telemetry.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusYes. NVD explicitly notes the CVE is in CISA KEV, which means confirmed real-world exploitation, not just lab exploitability.
Public exploit toolingCommodity-ready. Rapid7 ships exploit/multi/http/tomcat_jsp_upload_bypass, and NVD references Exploit-DB 43008.
KEV statusListed in CISA KEV on 2022-03-25 with a federal due date of 2022-04-15 per the NVD KEV mirror.
EPSS94.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 vectorCVSS: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 versionsApache Tomcat 7.0.0 through 7.0.81 are affected for this plugin/CVE path.
Fixed versionsUpstream 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 dataBitsight 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 reportingApache 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.
04 · The Call

noisgate verdict.

Final Verdict
UPGRADED to HIGH (8.4/10)

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.

HIGH Technical impact once the prerequisite configuration exists
HIGH Active/known exploitation evidence via KEV
MEDIUM How often your estate actually exposes exploitable PUT-enabled contexts

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=false or 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.

05 · Compensating Control

What to do — in priority order.

  1. Block PUT at the edge — Deny PUT and DELETE to 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.
  2. Force readonly=true on DefaultServlet — Audit global conf/web.xml and 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.
  3. Hunt for rogue JSPs — Search Tomcat webroots for newly created or unsigned .jsp files, especially tiny web shells and timestamp outliers, then correlate with HTTP PUT requests. Run this immediately, within hours on exposed estates because compromise, if present, is likely already post-exploitation.
  4. 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.
  5. 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.
What doesn't work
  • A version-only scanner finding 7.0.81 does 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 POST uploads is the wrong control; the exploit abuses HTTP PUT, so upload restrictions focused on forms and multipart POSTs miss it.
06 · Verification

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.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/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()
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning, treat every internet-facing Tomcat 7.0.0-7.0.81 as a two-part job: first hunt and suppress exploitability immediately, within hours by blocking 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

  1. Tenable Plugin 103782
  2. Apache Tomcat 7 Security Page
  3. NVD CVE-2017-12617
  4. Ubuntu CVE-2017-12617 Backport Status
  5. Rapid7 Metasploit Module: Tomcat JSP Upload Bypass
  6. FIRST EPSS Documentation
  7. Bitsight Groma Observation Footprint for CVE-2017-12617
  8. CVE Record
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.