Ghostcat — Tomcat AJP File Read (CVE-2020-1938)
CVE-2020-1938 (Ghostcat) is an unauthenticated file read vulnerability in the Tomcat AJP connector. When AJP is exposed and the target version is vulnerable, an attacker can read any file from within the web application's deployment directory: including WEB-INF/web.xml, configuration files, and JSP source. In some configurations it also enables remote code execution via file inclusion. Confirm the Tomcat version before testing.
The AJP connector exists to proxy requests between a front-end web server and Tomcat. It listens on port 8009 by default. Many deployments leave AJP bound to all interfaces instead of loopback only. That misconfiguration is what Ghostcat exploits. No authentication is required to use the connector. Any reachable port 8009 is directly attackable. The RCE path requires a file upload point on the application. Upload a JSP file anywhere in the web root. Then use Ghostcat file inclusion to have Tomcat parse and execute it. Without a writable path, the attack is limited to file read.
- Affected Versions
- Confirm Version Before Testing
- Test with Nuclei
- PoC: Read WEB-INF/web.xml
Affected Versions
Branch | Vulnerable | Fixed in |
|---|---|---|
9.x | 9.0.0.M1 - 9.0.30 | 9.0.31 |
8.5.x | 8.5.0 - 8.5.50 | 8.5.51 |
8.0.x | 8.0.0.RC1 - 8.0.53 | End of life: no fix |
7.x | 7.0.0 - 7.0.99 | 7.0.100 |
Confirm Version Before Testing
<h3>Apache Tomcat/9.0.30</h3>
Version 9.0.30 is in the vulnerable range 9.0.0.M1 - 9.0.30. AJP on port 8009 must also be reachable. Confirm both before running the PoC.
If Tomcat suppresses version info from error pages, check the Server response header with curl -sk -I http://$TARGET_IP:8080/. The version also appears in the default /manager/html page title when error pages are stripped. Confirm port 8009 is open with nc -zv $TARGET_IP 8009. A timeout or connection refused means AJP is filtered or disabled. The attack path is closed regardless of Tomcat version in that case.
Test with Nuclei
Nuclei has a template for Ghostcat detection that confirms exploitability without requiring manual setup. A positive hit is reliable confirmation that AJP is open and the target is vulnerable. No output does not rule out the vulnerability. Custom Tomcat configurations can affect how the AJP connector responds to the probe. If Nuclei returns nothing, check both conditions manually. If the version is in the vulnerable range and port 8009 is open, run the manual PoC directly.
[CVE-2020-1938] [critical] http://10.10.10.50:8080 [Apache Tomcat AJP File Inclusion]
Explain command
| -u | Specifies the target URL to scan. |
| http://$TARGET_IP:8080/ | Target URL with placeholder IP and port 8080. |
| -tags | Filters templates to run by specified tags. |
| cve2020-1938,ghostcat,tomcat | Tags targeting CVE-2020-1938 (Ghostcat) and Tomcat templates. |
| -severity | Filters templates by severity level. |
| critical | Runs only templates rated as critical severity. |
PoC: Read WEB-INF/web.xml
Use the Ghostcat Python PoC to read files from the web application directory via the AJP connector:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<display-name>MyApp</display-name>
<context-param>
<param-name>DB_PASSWORD</param-name>
<param-value>SuperSecret123!</param-value>
</context-param>
</web-app>
Explain command
| read_file | AJPy subcommand to read a file via the AJP connector. |
| --webapp=/ | Web application context path (use / for the root webapp). |
| /WEB-INF/web.xml | File path relative to the webapp root to retrieve. |
| $TARGET_IP | Target host IP address (positional, last argument). |
web.xml is the primary target: it often contains database credentials, API keys, and application configuration. Additional high-value targets within the web application directory:
Look for context-param and env-entry elements in web.xml. These frequently hold JDBC connection strings with plaintext credentials. For Spring Boot applications, grab WEB-INF/classes/application.properties: it stores datasource URLs, passwords, and API tokens. WEB-INF/classes/db.properties covers older Hibernate and MyBatis setups. If you find database credentials, attempt direct access to the database from your position. Test found credentials against any login portal on the target.
For the RCE path, find a file upload function in the application. Upload a JSP web shell and note the path where it lands. Use Ghostcat to read it via the path relative to the web root. Tomcat processes the JSP on inclusion and executes your payload. This is only viable when you can upload and know the exact upload path.
Ghostcat fails when port 8009 is filtered or AJP is disabled in server.xml. Tomcat 9.0.31 and later require a shared secret for AJP connections by default. Without the correct secret, the connector rejects the request. On those patched versions the PoC will time out or return a connection error. Check the version carefully before assuming the host is unexploitable. Some load balancers proxy HTTP and pass AJP to the internal network. Port 8009 may then be reachable only from inside. AJP connections appear in Tomcat's access log. Do not run repeated scans against the same target if stealth matters.
References
-
AJPy: Ghostcat PoCREPO: AJPy: Ghostcat PoC, github.com/hypn0s/AJPy (opens in new tab)
Python AJP connector library used for Ghostcat file read
-
Apache Tomcat Security: 9.0.31DOCS: Apache Tomcat Security: 9.0.31, tomcat.apache.org/security-9.html#Fixed_in_Apache_Tomcat_9.0.31 (opens in new tab)
CVE-2020-1938 fix details and affected version confirmation