This is a side door that only exists if you built your lobby around LDAP usernames
CVE-2021-30640 is an authentication weakness in Apache Tomcat's JNDIRealm, the LDAP-backed realm used for container-managed authentication. In affected releases, the username handling in directory lookups was not safely escaped, which can let an attacker authenticate using variations of a valid username and can also weaken LockOutRealm protections. Upstream affected ranges are Tomcat 7.0.0 to <7.0.109, 8.5.0 to <8.5.66, 9.0.0 to <9.0.46, and 10.0.0-M1 to <10.0.6; distro packages may be fixed by backport on older package versions.
The vendor's MEDIUM 6.5 is technically fair in a lab, but it overstates fleet-wide urgency for most enterprises. This is not broad unauthenticated RCE and it is not relevant to every Tomcat server; it only bites deployments that explicitly use org.apache.catalina.realm.JNDIRealm for app authentication, and the payoff is app-level auth bypass rather than host takeover.
4 steps from start to impact.
Find a Tomcat app that actually uses JNDIRealm with curl or Burp
JNDIRealm against LDAP via JNDI.- A Tomcat-hosted application is reachable from the attacker's network position
- Authentication is enforced by Tomcat container-managed security
- The Tomcat instance is configured with
org.apache.catalina.realm.JNDIRealm
- Many enterprises use SSO, app-native auth, or non-JNDI realms instead
- JNDIRealm usage is a configuration choice and is not universal across Tomcat estates
- External fingerprinting usually cannot prove JNDIRealm is in use
server.xml or package state locally.Obtain or guess a valid username with ffuf or Burp Intruder
- Attacker can identify at least one valid username or naming pattern
- The realm is using a search-based lookup path affected by the escaping flaw
- Unknown usernames and opaque login responses slow exploitation materially
- Some deployments bind directly by DN pattern rather than relying on the vulnerable search flow
- MFA or upstream SSO can remove Tomcat from the auth path entirely
Abuse username variation against LDAP lookup using curl or a custom script
JNDIRealm to escape special characters before search. That opens the door to authenticating as a variation of a real user and, in some cases, bypassing parts of LockOutRealm by spreading attempts across alternate names.- Target version falls in the vulnerable range
- The realm's username handling and directory behavior permit an alternate form to resolve usefully
- Attack complexity is genuinely high because success depends on directory schema, username normalization, and realm configuration
- Not every LDAP backend or naming convention will yield a workable variant
- The bug gives auth bypass, not code execution
Use the gained app session with the browser or curl
- The targeted user or role has useful access inside the application
- Least-privilege app roles can sharply limit impact
- No direct host compromise path is inherent in this CVE
- Some apps immediately enforce additional in-app authorization checks
The supporting signals.
| In-the-wild status | No CISA KEV entry and I found no primary-source evidence of active exploitation campaigns for this CVE as of 2026-06-05. |
|---|---|
| Proof-of-concept availability | No authoritative vendor or researcher PoC was located in reviewed primary sources. The public fix commit clearly exposes root cause, so a capable operator can reproduce it, but this is not a commodity one-click RCE. |
| EPSS | 0.00123 from the user-supplied intel — extremely low predicted near-term exploitation probability. |
| KEV status | Not listed in CISA's Known Exploited Vulnerabilities catalog as of 2026-06-05. |
| CVSS vector readout | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N maps to network-reachable but high-complexity exploitation, with integrity impact at the application trust boundary rather than host-level compromise. |
| Affected versions | Upstream affected: 7.0.0 to <7.0.109, 8.5.0 to <8.5.66, 9.0.0 to <9.0.46, 10.0.0-M1 to <10.0.6. |
| Fixed versions | Upstream fixes landed in 7.0.109, 8.5.66, 9.0.46, and 10.0.6. Distro backports exist, including Debian tomcat9 9.0.43-2~deb11u1 and Ubuntu tomcat9 9.0.31-1ubuntu0.2 / 9.0.16-3ubuntu0.18.04.2. |
| Exposure population | Tomcat itself is common on the internet, but this CVE is not externally fingerprintable at scale because exploitability depends on a specific internal realm choice in server.xml and whether apps rely on Tomcat-managed auth. |
| Disclosure date | Published 2021-07-12. |
| Research / reporting | The actionable technical breadcrumb is Apache's fix commit titled *Expand tests and fix escaping issue when searching for users by filter*, which points straight at the vulnerable JNDIRealm search flow. |
noisgate verdict.
The decisive factor is exposure narrowing: this only matters on Tomcat instances that explicitly use JNDIRealm for container-managed LDAP authentication. Even where present, the result is application authentication bypass with high practical complexity, not broad internet-scale server compromise.
Why this verdict
- Baseline down from 6.5: vendor scoring assumes any affected Tomcat is equally interesting, but this CVE only activates when
JNDIRealmis actually configured inserver.xmland used for auth. - Another step down for attacker prerequisites: exploitation needs a reachable Tomcat-managed login path plus knowledge or discovery of valid username forms. That is materially different from sprayable unauthenticated pre-auth bugs.
- Another step down for blast radius: the payoff is app access and
LockOutRealmweakening, not code execution or default host takeover. In most enterprises that keeps impact local to a subset of applications and roles.
Why not higher?
There is no strong evidence of mass exploitation, no KEV listing, and the attack surface is much narrower than 'Apache Tomcat on the internet.' The specific realm requirement plus high-complexity username/LDAP behavior makes this a poor candidate for emergency patch queues across a 10,000-host estate.
Why not lower?
It is still a genuine authentication weakness, and if you do run internet-reachable applications behind JNDIRealm, the attacker may gain unauthorized access without already holding credentials. That makes it more than paperwork on the subset of systems that actually use this feature.
What to do — in priority order.
- Identify
JNDIRealmusage — Searchconf/server.xmland any templated Tomcat configs fororg.apache.catalina.realm.JNDIRealmfirst; this is the gating condition that decides whether the CVE is real for that host. For aLOWverdict there is no mitigation SLA; treat this as backlog hygiene and complete discovery in the next routine configuration review. - Move auth upstream where possible — If practical, terminate user auth in SSO, reverse proxy, or an identity-aware front end instead of Tomcat container-managed LDAP auth. That removes this code path entirely; for
LOW, schedule under normal change control rather than emergency action. - Tighten login monitoring — Alert on repeated login attempts using unusual username variants and correlate Tomcat access logs with LDAP search/bind logs. This will not prevent exploitation, but it gives you the only realistic detection surface while the issue sits in backlog hygiene.
- Reduce exposed auth surfaces — Where a Tomcat app using
JNDIRealmdoes not need broad internet reachability, place it behind VPN, ZTNA, or network ACLs. That converts an unauthenticated remote path into an internal-only path; forLOW, do it in the next routine access-hardening cycle.
- A generic WAF rule is weak here because the abuse lives in username handling and LDAP lookup semantics, not in a stable exploit payload pattern.
- LockOutRealm by itself does not solve the problem; the CVE explicitly includes bypassing some of the protection it provides.
- Pure Tomcat banner scanning does not tell you exposure because version alone is insufficient; you must know whether
JNDIRealmis configured and in use.
Crowdsourced verification payload.
Run this on the target Tomcat host or inside the container image filesystem where CATALINA_BASE is available. Invoke it as python3 check_cve_2021_30640.py /opt/tomcat (or your CATALINA_BASE), using an account that can read conf/server.xml and the Tomcat install files; root/admin is usually not required.
#!/usr/bin/env python3
# check_cve_2021_30640.py
# Determine whether this Tomcat instance is exposed to CVE-2021-30640.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
import os
import re
import sys
import zipfile
import xml.etree.ElementTree as ET
AFFECTED = [
((7, 0, 0), (7, 0, 109)),
((8, 5, 0), (8, 5, 66)),
((9, 0, 0), (9, 0, 46)),
((10, 0, 0), (10, 0, 6)),
]
def normalize_version(s):
if not s:
return None
s = s.strip()
s = s.replace('Tomcat/', '')
s = s.replace('Apache Tomcat/', '')
s = s.replace('Apache Tomcat', '')
s = s.strip()
# Handle milestone / M notation by stripping non-numeric separators after capturing digits.
nums = re.findall(r'\d+', s)
if len(nums) < 2:
return None
parts = [int(x) for x in nums[:3]]
while len(parts) < 3:
parts.append(0)
return tuple(parts)
def version_in_affected(v):
for low, high in AFFECTED:
if v >= low and v < high:
return True
return False
def find_version(base):
candidates = [
os.path.join(base, 'RELEASE-NOTES'),
os.path.join(base, 'RUNNING.txt'),
os.path.join(base, 'lib', 'catalina.jar'),
os.path.join(base, 'bin', 'version.sh'),
os.path.join(base, 'bin', 'version.bat'),
]
# Text files first
for path in candidates[:2]:
if os.path.isfile(path):
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
data = f.read()
m = re.search(r'Apache Tomcat(?:/|\s+)([0-9][0-9A-Za-z.\-M]*)', data)
if m:
return m.group(1)
except Exception:
pass
# catalina.jar manifest
jar = candidates[2]
if os.path.isfile(jar):
try:
with zipfile.ZipFile(jar, 'r') as zf:
with zf.open('META-INF/MANIFEST.MF') as mf:
data = mf.read().decode('utf-8', errors='ignore')
for line in data.splitlines():
if line.startswith('Implementation-Version:'):
return line.split(':', 1)[1].strip()
except Exception:
pass
# version scripts as a last resort
for path in candidates[3:]:
if os.path.isfile(path):
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
data = f.read()
m = re.search(r'([0-9]+\.[0-9]+\.[0-9]+)', data)
if m:
return m.group(1)
except Exception:
pass
return None
def has_jndirealm(server_xml):
try:
tree = ET.parse(server_xml)
root = tree.getroot()
for elem in root.iter():
if elem.tag.endswith('Realm'):
cn = elem.attrib.get('className', '')
if cn == 'org.apache.catalina.realm.JNDIRealm':
return True
return False
except Exception:
return None
def main():
if len(sys.argv) != 2:
print('UNKNOWN - usage: python3 check_cve_2021_30640.py <CATALINA_BASE>')
sys.exit(2)
base = sys.argv[1]
server_xml = os.path.join(base, 'conf', 'server.xml')
if not os.path.exists(base):
print(f'UNKNOWN - path does not exist: {base}')
sys.exit(2)
if not os.path.isfile(server_xml):
print(f'UNKNOWN - cannot find {server_xml}')
sys.exit(2)
jndi = has_jndirealm(server_xml)
if jndi is None:
print(f'UNKNOWN - failed to parse {server_xml}')
sys.exit(2)
if not jndi:
print('PATCHED - JNDIRealm not configured on this Tomcat instance; CVE-2021-30640 not reachable here')
sys.exit(0)
raw_ver = find_version(base)
norm_ver = normalize_version(raw_ver) if raw_ver else None
if norm_ver is None:
print('UNKNOWN - JNDIRealm is configured but Tomcat version could not be determined')
sys.exit(2)
if version_in_affected(norm_ver):
print(f'VULNERABLE - JNDIRealm configured and Tomcat version appears affected ({raw_ver})')
sys.exit(1)
else:
print(f'PATCHED - JNDIRealm configured but Tomcat version appears fixed/not affected ({raw_ver})')
sys.exit(0)
if __name__ == '__main__':
main()
If you remember one thing.
JNDIRealm users versus everyone else; if a system does not use org.apache.catalina.realm.JNDIRealm, document it and move on. For this LOW verdict there is no noisgate mitigation SLA and no noisgate remediation SLA beyond backlog hygiene: confirm exposure in your next routine config-review cycle, prioritize any internet-facing JNDIRealm apps into the next normal Tomcat maintenance window, and fold the actual upgrade/backport into standard patch work rather than an emergency change.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.