├── .gitignore ├── README.md ├── pom.xml └── src └── main └── java └── com └── doyensec └── ajpfuzzer ├── AJPFuzzer.java ├── AJPTestCases.java └── Utils.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Package Files # 2 | *.jar 3 | *.war 4 | *.ear 5 | 6 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 7 | hs_err_pid* 8 | 9 | #ant specific 10 | dist/ 11 | build/ 12 | build.xml 13 | 14 | #netbeans specific 15 | core 16 | nbproject/* 17 | libs/* 18 | lib/* 19 | manifest.mf 20 | .jacocoverage/ 21 | coverage/ 22 | 23 | #java specific 24 | *.class 25 | 26 | #general swap/backup files 27 | *.so 28 | *.log 29 | *.out 30 | *~ 31 | *.swp 32 | *.DS_Store 33 | *.lock 34 | 35 | #idea specific 36 | .classpath 37 | .project 38 | .settings 39 | .idea 40 | .metadata 41 | *.iml 42 | *.ipr 43 | **/*~ 44 | /target/ 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AJPFuzzer - A command-line fuzzer for AJPv1.3 2 | 3 | **AJPFuzzer** is a rudimental fuzzer for the [Apache JServ Protocol](https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html) (ajp13). 4 | 5 | Built on top of [libajp13](https://github.com/doyensec/libajp13), the tool allows you to create and send AJP messages using an easy-to-use command line interface. AJPFuzzer can craft properly formatted AJP13 messages (all message types) as well as mutations (e.g. bit flipping, messages with type mismatch, etc.), which facilitates security testing efforts targeting AJP-based services like web servers AJP modules, J2EE containers, and many others. 6 | ### How To Use it 7 | 8 | 1. Download the latest AJPFuzzer jar from the [releases page](https://github.com/doyensec/ajpfuzzer/releases) 9 | 2. Execute the downloaded jar using: 10 | 11 | $ java -jar ajpfuzzer_v0.7.jar 12 | 13 | 3. The tool will prompt a shell. By typing *?list*, it is possible to list all available commands. At this point, you can connect to the target using: 14 | 15 | AJPFuzzer> connect 127.0.0.1 8009 16 | 17 | 4. Then, you can send a *CPing* message (type 10) by simply typing '10' (no arguments are needed for this message) 18 | 19 | AJPFuzzer/127.0.0.1:8009> 10 20 | 21 | The following screenshot illustrates the entire execution: 22 | 23 | ![CPing message using AJPFuzzer](http://i.imgur.com/22lHxX3.png) 24 | 25 | Obviously, it is possible to send more complex messages by specifying the appropriate test case and arguments. Please refer to *?list * for all details on a specific command. 26 | 27 | For example, we can send a fully customized *ForwardRequest* type message using: 28 | 29 | ``` 30 | > forwardrequest 2 "HTTP/1.1" "/api/" 127.0.0.1 localhost porto 8009 false "Cookie:AAAA=BBBB" "" 31 | ``` 32 | 33 | It's also possible to send a *ForwardRequest* message fuzzing arbitrary elements: 34 | 35 | ``` 36 | > genericfuzz 2 "HTTP/1.1" "/test.html" "127.0.0.1" "127.0.0.1" "server.name.test" 8009 false "Cookie:AAAA=BBBB" "secret:FUZZ" /tmp/list.txt 37 | ``` 38 | 39 | ![ForwardRequest message using AJPFuzzer](http://i.imgur.com/5j5JYre.png) 40 | 41 | ### Available test cases and further customization. 42 | 43 | As of today, AJPFuzzer provides the following test cases: 44 | 45 | Id | Name | Description 46 | --- |---------------------| --- 47 | 1 | body | Send a body message from the web server to the J2EE container 48 | 2 | forwardrequest | Begin the request processing cycle from the web server to the J2EE container 49 | 3 | sendbodychunk | Send a chunk of the body from the J2EE container to the web server 50 | 4 | sendheaders | Send the response headers from the J2EE container to the web server 51 | 5 | endresponse | Mark the end of the response, from the J2EE container to the web server 52 | 6 | getbodychunk | Get further data from the requestor. Message from the J2EE container to the web server 53 | 7 | shutdown | Send a standard shutdown AJP13 packet 54 | 8 | ping | Send a ping (ping != CPing) AJP13 packet 55 | 9 | cpong | Send a CPong AJP13 packet 56 | 10 | cping | Send a CPing AJP13 packet 57 | 11 | forwardreqalltypes | Send a ForwardRequest AJP13 packet, with all possible packet types 58 | 12 | verbtampering | Send multiple requests via AJP13 and do HTTP Verb Tampering, to detect potential authentication bypass flaws 59 | 13 | jettyleak | Send a JettyLeak style AJP13 packet 60 | 14 | hugelengthsmallbody | Send ForwardRequest+Body messages, with a big Content-Length and small Body 61 | 15 | hugeheader | Send two AJP13 ForwardRequest packets with header length greater than 0x9999 (e.g. A010) 62 | 16 | fuzzbit | Create a complex AJP13 ForwardRequest and start bit flipping 63 | 17 | fuzzslice | Create an AJP13 ForwardRequest, SendHeaders, ShutDown, 0xFF, 0x00. Slice and send. 64 | 18 | servletpath | Create an AJP13 ForwardRequest with arbitrary 'servlet_path' attribute 65 | 19 | bypassauthnull | Create two AJP13 ForwardRequest with auth_type set to 'null' 66 | 20 | envars | Create an AJP13 ForwardRequest with req_attribute_code (10) in order to set arbitrary environmental variables 67 | 21 | hugepacketsize | Create two AJP13 requests with size > 8192 bytes 68 | 22 | genericfuzz | Create an AJP13 ForwardRequest (GET) that allows fuzzing arbitrary message elements using the `FUZZ` keyword 69 | 70 | New test cases can be added by extending the [AJPTestCases.java](https://github.com/doyensec/ajpfuzzer/blob/master/src/com/doyensec/ajpfuzzer/AJPTestCases.java) class. Using the *@Command* annotation, the tool will recognize the additional command and make it available from the CLI. 71 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.doyensec.ajpfuzzer 8 | ajpfuzzer 9 | 0.7 10 | 11 | 12 | 13 | commons-io 14 | commons-io 15 | 2.11.0 16 | 17 | 18 | 19 | org.apache.commons 20 | commons-lang3 21 | 3.12.0 22 | 23 | 24 | 25 | 26 | 19 27 | 19 28 | UTF-8 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/java/com/doyensec/ajpfuzzer/AJPFuzzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AJPFuzzer - AJPFuzzer.java 3 | * 4 | * Copyright (c) 2017 Luca Carettoni - Doyensec LLC. 5 | */ 6 | package com.doyensec.ajpfuzzer; 7 | 8 | import asg.cliche.Command; 9 | import asg.cliche.Param; 10 | import asg.cliche.Shell; 11 | import asg.cliche.ShellDependent; 12 | import asg.cliche.ShellFactory; 13 | import asg.cliche.ShellManageable; 14 | import java.io.FileNotFoundException; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.io.PrintStream; 18 | import java.net.InetAddress; 19 | import java.net.InetSocketAddress; 20 | import java.net.Socket; 21 | import java.net.UnknownHostException; 22 | import org.apache.commons.io.input.TeeInputStream; 23 | import org.apache.commons.io.output.TeeOutputStream; 24 | import com.doyensec.ajp13.AjpMessage; 25 | import com.doyensec.ajp13.AjpReader; 26 | import com.doyensec.ajp13.CPingMessage; 27 | import com.doyensec.ajp13.CPongMessage; 28 | 29 | public class AJPFuzzer implements ShellDependent, ShellManageable { 30 | 31 | private final String version = "0.7"; 32 | private Shell shell; 33 | private String host; 34 | private int port = 0; 35 | private Socket socket; 36 | private static FileOutputStream fos; 37 | private static TeeOutputStream myOut; 38 | private static TeeOutputStream myErr; 39 | private static TeeInputStream myIn; 40 | private static PrintStream psOut; 41 | private static PrintStream psErr; 42 | 43 | public String getHost() { 44 | return host; 45 | } 46 | 47 | public void setHost(String host) { 48 | this.host = host; 49 | } 50 | 51 | public int getPort() { 52 | return port; 53 | } 54 | 55 | public void setPort(int port) { 56 | this.port = port; 57 | } 58 | 59 | public Socket getSocket() { 60 | return socket; 61 | } 62 | 63 | public void setSocket(Socket socket) { 64 | this.socket = socket; 65 | } 66 | 67 | @Override 68 | public void cliSetShell(Shell theShell) { 69 | this.shell = theShell; 70 | } 71 | 72 | public Shell cliGetShell() { 73 | return shell; 74 | } 75 | 76 | @Command(description = "Connect to a remote AJP13 service", name = "connect", abbrev = "cn") 77 | public void connect(@Param(name = "host", description = "AJP13 host") String host, @Param(name = "port", description = "AJP13 TCP port") int port) throws IOException { 78 | setHost(host); 79 | setPort(port); 80 | try { 81 | System.out.println("[*] Connecting to " + host + ":" + port); 82 | socket = new Socket(); 83 | socket.connect(new InetSocketAddress(host, port), 2000); 84 | socket.setSoTimeout(8000); 85 | } catch (IOException e) { 86 | System.out.println("[!] Connection error\n"); 87 | System.exit(-1); 88 | } 89 | ShellFactory.createSubshell(host + ":" + port, shell, "Connected to the remote AJP13 service", new AJPTestCases(this)).commandLoop(); 90 | } 91 | 92 | @Command(description = "Disconnect from a remote AJP13 service", name = "disconnect", abbrev = "dn") 93 | public void disconnect() { 94 | if (socket != null && !socket.isClosed()) { 95 | System.out.println("[*] Disconnecting..."); 96 | try { 97 | socket.close(); 98 | } catch (IOException ex) { 99 | System.out.println("[!] Disconnection error\n"); 100 | System.exit(-1); 101 | } 102 | } else { 103 | System.out.println("[!] Disconnected\n"); 104 | } 105 | } 106 | 107 | @Command(description = "Disconnect and quit AJPFuzzer", name = "quit", abbrev = "quit") 108 | public void quit() { 109 | this.cliLeaveLoop(); //Exit all 110 | } 111 | 112 | @Command(description = "Reconnect to the remote AJP13 service", name = "reconnect", abbrev = "rc") 113 | public void reconnect() { 114 | if (host != null && port != 0) { 115 | System.out.println("[*] Reconnecting..."); 116 | try { 117 | System.out.println("[*] Connecting to " + host + ":" + port); 118 | socket = new Socket(); 119 | socket.connect(new InetSocketAddress(host, port), 2000); 120 | socket.setSoTimeout(8000); 121 | } catch (IOException ex) { 122 | System.out.println("[!] Connection error\n"); 123 | System.exit(-1); 124 | } 125 | } else { 126 | System.out.println("[!] You must connect first\n"); 127 | } 128 | } 129 | 130 | @Command(description = "Status of the connection to the remote AJP13 service", name = "status", abbrev = "sta") 131 | public void status() throws IOException { 132 | if (socket != null && !socket.isClosed()) { 133 | //Sending AJP's CPing as heartbeat 134 | AjpMessage msg = new CPingMessage(); 135 | byte[] reply = Utils.sendAndReceive(this, msg.getBytes(), "(10) cping", false); 136 | AjpMessage ajpReply = AjpReader.parseMessage(reply); 137 | if (ajpReply instanceof CPongMessage) { 138 | System.out.println("[*] Connected\n"); 139 | } else { 140 | System.out.println("[!] Disconnected\n"); 141 | } 142 | } else { 143 | System.out.println("[!] Disconnected\n"); 144 | } 145 | } 146 | 147 | public static void main(String[] args) throws IOException { 148 | //Initialize logging 149 | try { 150 | fos = new FileOutputStream("AJPFuzzer_" + InetAddress.getLocalHost().getHostName() + "_" + System.nanoTime() + ".log"); 151 | myOut = new TeeOutputStream(System.out, fos); 152 | myErr = new TeeOutputStream(System.err, fos); 153 | myIn = new TeeInputStream(System.in, myOut); 154 | psOut = new PrintStream(myOut); 155 | psErr = new PrintStream(myErr); 156 | System.setOut(psOut); 157 | System.setErr(psErr); 158 | System.setIn(myIn); 159 | } catch (UnknownHostException | FileNotFoundException e) { 160 | System.out.println("[!] Logging setup error\n"); 161 | } 162 | 163 | //Start the fuzzer 164 | AJPFuzzer myFuzzer = new AJPFuzzer(); 165 | myFuzzer.banner(); 166 | Shell myShell = ShellFactory.createConsoleShell("AJPFuzzer", "", new AJPFuzzer()); 167 | myFuzzer.cliSetShell(myShell); 168 | myShell.commandLoop(); 169 | } 170 | 171 | @Override 172 | public void cliEnterLoop() { 173 | //do nothing 174 | } 175 | 176 | @Override 177 | public void cliLeaveLoop() { 178 | if (socket != null && !socket.isClosed()) { 179 | disconnect(); 180 | } 181 | try { 182 | fos.close(); 183 | myOut.close(); 184 | myErr.close(); 185 | myIn.close(); 186 | psOut.close(); 187 | psErr.close(); 188 | } catch (IOException ex) { 189 | System.out.println("[!] Stream close error\n"); 190 | } 191 | } 192 | 193 | private void banner() { 194 | System.out.println(".: AJPFuzzer v" + version + " - Doyensec.com :."); 195 | System.out.println("-----------------------------------"); 196 | } 197 | } -------------------------------------------------------------------------------- /src/main/java/com/doyensec/ajpfuzzer/AJPTestCases.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AJPFuzzer - AJPTestCases.java 3 | * 4 | * Copyright (c) 2017 Luca Carettoni - Doyensec LLC. 5 | */ 6 | package com.doyensec.ajpfuzzer; 7 | 8 | import asg.cliche.Command; 9 | import asg.cliche.Param; 10 | import java.io.BufferedReader; 11 | import java.io.IOException; 12 | import java.nio.file.Files; 13 | import java.io.UnsupportedEncodingException; 14 | import java.io.File; 15 | import java.nio.file.Paths; 16 | import java.io.IOException; 17 | import java.net.URL; 18 | import java.util.LinkedList; 19 | import java.util.List; 20 | 21 | import jdk.jshell.execution.Util; 22 | import org.apache.commons.lang3.ArrayUtils; 23 | import org.apache.commons.lang3.StringUtils; 24 | import com.doyensec.ajp13.AjpMessage; 25 | import com.doyensec.ajp13.AjpReader; 26 | import com.doyensec.ajp13.BodyMessage; 27 | import com.doyensec.ajp13.CPingMessage; 28 | import com.doyensec.ajp13.CPongMessage; 29 | import com.doyensec.ajp13.EndResponseMessage; 30 | import com.doyensec.ajp13.ForwardRequestMessage; 31 | import com.doyensec.ajp13.SendBodyChunkMessage; 32 | import com.doyensec.ajp13.GetBodyChunkMessage; 33 | import com.doyensec.ajp13.Pair; 34 | import com.doyensec.ajp13.PingMessage; 35 | import com.doyensec.ajp13.SendHeadersMessage; 36 | import com.doyensec.ajp13.ShutdownMessage; 37 | 38 | public class AJPTestCases { 39 | 40 | private final AJPFuzzer ajpsocket; 41 | 42 | AJPTestCases(AJPFuzzer ajpsocket) { 43 | this.ajpsocket = ajpsocket; 44 | } 45 | 46 | //Expose socket Status, Reconnect, Quit in the subshell 47 | @Command(description = "Status of the connection to the remote AJP13 service", name = "status", abbrev = "sta") 48 | public void status() throws IOException { 49 | ajpsocket.status(); 50 | } 51 | 52 | @Command(description = "Reconnect to the remote AJP13 service", name = "reconnect", abbrev = "rc") 53 | public void reconnect() { 54 | ajpsocket.reconnect(); 55 | } 56 | 57 | @Command(description = "Disconnect and quit AJPFuzzer", name = "quit", abbrev = "quit") 58 | public void quit() { 59 | ajpsocket.quit(); 60 | } 61 | 62 | /* 63 | * Test Case id: 1 64 | * Test Case name: body 65 | * Description: Send a body message from the web server to the J2EE container 66 | * Usage example: AJPFuzzer/192.168.80.131:8009> body "" 67 | * AJPFuzzer/192.168.80.131:8009> body "32456457" 68 | */ 69 | @Command(description = "Send a Body (no type) AJP13 packet", name = "body", abbrev = "1") 70 | public void bodyMessage(@Param(name = "data", description = "Body content (e.g. 41424344)") String data) throws UnsupportedEncodingException, IOException { 71 | byte[] bodyContent; 72 | if (data.isEmpty()) { 73 | bodyContent = new byte[0]; //send empty 74 | } else { 75 | bodyContent = AjpReader.toHex(data); 76 | } 77 | AjpMessage msg = new BodyMessage(bodyContent); 78 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(1) body"); 79 | } 80 | 81 | /* 82 | * Test Case id: 2 83 | * Test Case name: forwardrequest 84 | * Description: Begin the request processing cycle from the web server to the J2EE container 85 | * 86 | * Headers and attributes passed as :,:,... 87 | * Usage example: AJPFuzzer/192.168.80.131:8009> forwardrequest 2 "HTTP/1.1" "/path/" 127.0.0.1 localhost porto 8009 false "Cookie:name=value" "attr=value" 88 | */ 89 | @Command(description = "Send a ForwardRequest (type 2) AJP13 packet", name = "forwardrequest", abbrev = "2") 90 | public void forwardRequestMessage(@Param(name = "method", description = "HTTP verb (e.g. GET=2)") int method, 91 | @Param(name = "protocol", description = "HTTP protocol (e.g. HTTP/1.1)") String protocol, 92 | @Param(name = "requestUri", description = "Request URI (e.g. /api/)") String requestUri, 93 | @Param(name = "remoteAddr", description = "Client IP address") String remoteAddr, 94 | @Param(name = "remoteHost", description = "Client FQDN") String remoteHost, 95 | @Param(name = "serverName", description = "Server FQDN") String serverName, 96 | @Param(name = "serverPort", description = "Server TCP port") int serverPort, 97 | @Param(name = "isSsl", description = "Is SSL? Boolean") boolean isSsl, 98 | @Param(name = "headers", description = "HTTP headers as :,:,...") String headers, 99 | @Param(name = "attributes", description = "HTTP attributes as :,:,...") String attributes) throws UnsupportedEncodingException, IOException { 100 | List> headersList = null; 101 | if (headers.contains(":")) { 102 | //Convert headers string to java.util.List> 103 | String[] header = headers.split(","); 104 | headersList = new LinkedList<>(); 105 | for (int i = 0; i < header.length; i++) { 106 | String[] nameValue = header[i].split(":"); 107 | headersList.add(Pair.make(nameValue[0], nameValue[1])); 108 | } 109 | } 110 | 111 | List> attributesList = null; 112 | if (attributes.contains(":")) { 113 | //Convert attributes string to java.util.List> 114 | String[] attribute = attributes.split(","); 115 | attributesList = new LinkedList<>(); 116 | for (int i = 0; i < attribute.length; i++) { 117 | String[] nameValue = attribute[i].split(":"); 118 | attributesList.add(Pair.make(nameValue[0], nameValue[1])); 119 | } 120 | } 121 | AjpMessage msg = new ForwardRequestMessage( 122 | method, 123 | protocol, 124 | requestUri, 125 | remoteAddr, 126 | remoteHost, 127 | serverName, 128 | serverPort, 129 | isSsl, 130 | headersList, 131 | attributesList 132 | ); 133 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(2) forwardrequest"); 134 | } 135 | 136 | /* 137 | * Test Case id: 3 138 | * Test Case name: sendbodychunk 139 | * Description: Send a chunk of the body from the J2EE container to the web server 140 | * Usage example: AJPFuzzer/192.168.80.131:8009> sendbodychunk "1234" 141 | */ 142 | @Command(description = "Send a SendBodyChunk (type 3) AJP13 packet", name = "sendbodychunk", abbrev = "3") 143 | public void sendBodyChunkMessage(@Param(name = "data", description = "Body chunk (e.g. 41424344)") String data) throws UnsupportedEncodingException, IOException { 144 | byte[] bodyChunk; 145 | if (data.isEmpty()) { 146 | bodyChunk = new byte[0]; //send empty 147 | } else { 148 | bodyChunk = AjpReader.toHex(data); 149 | } 150 | AjpMessage msg = new SendBodyChunkMessage(bodyChunk); 151 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(3) sendbodychunk"); 152 | } 153 | 154 | /* 155 | * Test Case id: 4 156 | * Test Case name: sendheaders 157 | * Description: Send the response headers from the J2EE container to the web server 158 | * Usage example: AJPFuzzer/192.168.80.131:8009> sendheaders 200 "OK" "header1:value,header2:value" 159 | */ 160 | @Command(description = "Send a SendHeaders (type 4) AJP13 packet", name = "sendheaders", abbrev = "4") 161 | public void sendHeadersMessage(@Param(name = "statuscode", description = "HTTP Status Code (e.g. 200)") int statusCode, 162 | @Param(name = "statusmessage", description = "HTTP Status Message (e.g. OK)") String statusMessage, 163 | @Param(name = "headers", description = "HTTP headers as :,:,...") String headers) throws UnsupportedEncodingException, IOException { 164 | List> headersList = null; 165 | if (headers.contains(":")) { 166 | //Convert headers string to java.util.List> 167 | String[] header = headers.split(","); 168 | headersList = new LinkedList<>(); 169 | for (int i = 0; i < header.length; i++) { 170 | String[] nameValue = header[i].split(":"); 171 | headersList.add(Pair.make(nameValue[0], nameValue[1])); 172 | } 173 | } 174 | AjpMessage msg = new SendHeadersMessage(statusCode, statusMessage, headersList); 175 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(4) sendheaders"); 176 | } 177 | 178 | /* 179 | * Test Case id: 5 180 | * Test Case name: endresponse 181 | * Description: Mark the end of the response, from the J2EE container to the web server 182 | * Usage example: AJPFuzzer/192.168.80.131:8009> endresponse true 183 | */ 184 | @Command(description = "Send a EndResponse (type 5) AJP13 packet", name = "endresponse", abbrev = "5") 185 | public void endResponseMessage(@Param(name = "reuse", description = "Reuse the same TCP session? Boolean") boolean reuse) throws UnsupportedEncodingException, IOException { 186 | AjpMessage msg = new EndResponseMessage(reuse); 187 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(5) endresponse"); 188 | } 189 | 190 | /* 191 | * Test Case id: 6 192 | * Test Case name: getbodychunk 193 | * Description: Get further data from the requestor. Message from the J2EE container to the web server 194 | * Usage example: AJPFuzzer/192.168.80.131:8009> getbodychunk 12 195 | */ 196 | @Command(description = "Send a GetBodyChunk (type 6) AJP13 packet", name = "getbodychunk", abbrev = "6") 197 | public void getBodyChunkMessage(@Param(name = "length", description = "The expected body chunk message size") int length) throws UnsupportedEncodingException, IOException { 198 | AjpMessage msg = new GetBodyChunkMessage(length); 199 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(6) getbodychunk"); 200 | } 201 | 202 | /* 203 | * Test Case id: 7 204 | * Test Case name: shutdown 205 | * Description: Send a standard shutdown AJP13 packet 206 | * Usage example: AJPFuzzer/192.168.80.131:8009> shutdown 207 | */ 208 | @Command(description = "Send a shutdown (type 7) AJP13 packet", name = "shutdown", abbrev = "7") 209 | public void shutdownMessage() throws UnsupportedEncodingException, IOException { 210 | AjpMessage msg = new ShutdownMessage(); 211 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(7) shutdown"); 212 | } 213 | 214 | /* 215 | * Test Case id: 8 216 | * Test Case name: ping 217 | * Description: Send a ping (not CPing!!!) AJP13 packet 218 | * Usage example: AJPFuzzer/192.168.80.131:8009> ping 219 | */ 220 | @Command(description = "Send a ping (type 8) AJP13 packet", name = "ping", abbrev = "8") 221 | public void pingMessage() throws UnsupportedEncodingException, IOException { 222 | AjpMessage msg = new PingMessage(); 223 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(8) ping"); 224 | } 225 | 226 | /* 227 | * Test Case id: 9 228 | * Test Case name: cpong 229 | * Description: Send a CPong AJP13 packet 230 | * Usage example: AJPFuzzer/192.168.80.131:8009> cpong 231 | */ 232 | @Command(description = "Send a CPong (type 9) AJP13 packet", name = "cpong", abbrev = "9") 233 | public void cPongMessage() throws UnsupportedEncodingException, IOException { 234 | AjpMessage msg = new CPongMessage(); 235 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(9) cpong"); 236 | } 237 | 238 | /* 239 | * Test Case id: 10 240 | * Test Case name: cping 241 | * Description: Send a CPing AJP13 packet 242 | * Usage example: AJPFuzzer/192.168.80.131:8009> cping 243 | */ 244 | @Command(description = "Send a CPing (type 10) AJP13 packet", name = "cping", abbrev = "10") 245 | public void cPingMessage() throws UnsupportedEncodingException, IOException { 246 | AjpMessage msg = new CPingMessage(); 247 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(10) cping"); 248 | } 249 | 250 | /* 251 | * Test Case id: 11 252 | * Test Case name: forwardreqalltypes 253 | * Description: Send a ForwardRequest AJP13 packet, with all possible packet types 254 | * Usage example: AJPFuzzer/192.168.80.131:8009> forwardreqalltypes "http://192.168.80.131:8009" 255 | */ 256 | @Command(description = "Send a ForwardRequest AJP13 packet, with tampered packet type (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1337)", name = "forwardreqalltypes", abbrev = "11") 257 | public void forwardRequestWithAllTypes(@Param(name = "url", description = "Forward Request URL") String url) throws UnsupportedEncodingException, IOException { 258 | URL surl = new URL(url); 259 | AjpMessage msg = new ForwardRequestMessage(2, "HTTP/1.1", surl.getPath(), 260 | "127.0.0.1", "localhost", surl.getHost(), 261 | ((surl.getPort() == -1) ? surl.getDefaultPort() : surl.getPort()), 262 | surl.getProtocol().equalsIgnoreCase("https"), null, null); 263 | int[] test = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1337}; 264 | byte[] msgInBytes = msg.getBytes(); 265 | for (int type : test) { 266 | msgInBytes[4] = (byte) type; 267 | Utils.sendAndReceiveVerbose(ajpsocket, msgInBytes, "(11) forwardreqalltypes - type:" + type); 268 | try { 269 | Thread.sleep(1000); 270 | } catch (InterruptedException ex) { 271 | Thread.currentThread().interrupt(); 272 | } 273 | } 274 | 275 | } 276 | 277 | /* 278 | * Test Case id: 12 279 | * Test Case name: verbtampering 280 | * Description: Send multiple requests via AJP13 and do HTTP Verb Tampering, to detect potential authentication bypass flaws 281 | * Usage example: AJPFuzzer/192.168.80.131:8009> verbtampering "http://192.168.80.131:8009" 282 | */ 283 | @Command(description = "Send multiple ForwardRequest (type 2) AJP13 packets using HTTP Verb Tampering", name = "verbtampering", abbrev = "12") 284 | public void bypassAuthMessage(@Param(name = "url", description = "Forward Request URL") String url) throws UnsupportedEncodingException, IOException { 285 | AjpMessage msg = new ForwardRequestMessage(2, new URL(url), null, null); 286 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(12) verbtampering - GET"); 287 | //Try again with HEAD 3 288 | msg = new ForwardRequestMessage(3, new URL(url), null, null); 289 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(12) verbtampering - HEAD"); 290 | //Try again with SEARCH 21 291 | msg = new ForwardRequestMessage(21, new URL(url), null, null); 292 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(12) verbtampering - SEARCH"); 293 | } 294 | 295 | /* 296 | * Test Case id: 13 297 | * Test Case name: jettyleak 298 | * Description: Send a JettyLeak style AJP13 packet 299 | * Usage example: AJPFuzzer/192.168.80.131:8009> jettyleak "http://192.168.80.131:8009" 300 | */ 301 | @Command(description = "Send a ForwardRequest (type 2) AJP13 packet, with JettyLeak header", name = "jettyleak", abbrev = "13") 302 | public void jettyLeakMessage(@Param(name = "url", description = "Forward Request URL") String url) throws UnsupportedEncodingException, IOException { 303 | List> headers = new LinkedList<>(); 304 | String nullbyte = "\u0000"; 305 | headers.add(Pair.make("Cookie", StringUtils.repeat(nullbyte, 33))); 306 | AjpMessage msg = new ForwardRequestMessage(2, new URL(url), headers, null); 307 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(13) jettyleak"); 308 | } 309 | 310 | /* 311 | * Test Case id: 14 312 | * Test Case name: hugelengthsmallbody 313 | * Description: Send ForwardRequest+Body messages, with a big Content-Length and small Body 314 | * Usage example: AJPFuzzer/192.168.80.131:8009> hugelengthsmallbody "http://192.168.80.131:8009" 315 | */ 316 | @Command(description = "Send a POST ForwardRequest (type 2) with big Content-Length, followed by a small Body AJP13 packet", name = "hugelengthsmallbody", abbrev = "14") 317 | public void bodyHugeMessage(@Param(name = "url", description = "Forward Request URL") String url) throws UnsupportedEncodingException, IOException { 318 | AjpMessage msg = ForwardRequestMessage.ForwardRequestMessagePostBuilder(new URL(url), 100000); 319 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(14) hugelengthsmallbody"); 320 | msg = new BodyMessage("HugeContentLengthSmallBody".getBytes()); 321 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(14) hugelengthsmallbody"); 322 | } 323 | 324 | /* 325 | * Test Case id: 15 326 | * Test Case name: hugeheader 327 | * Description: Send two AJP13 ForwardRequest packets with header length greater than 0x9999 (e.g. A010) 328 | * Usage example: AJPFuzzer/192.168.80.131:8009> hugeheader "http://192.168.80.131:8009" 329 | */ 330 | @Command(description = "Send two GET ForwardRequest (type 2) packets with header larger than 0x9999 (0xA010)", name = "hugeheader", abbrev = "15") 331 | public void hugeHeaderMessage(@Param(name = "url", description = "Forward Request URL") String url) throws UnsupportedEncodingException, IOException { 332 | List> headers = new LinkedList<>(); 333 | headers.add(Pair.make(StringUtils.repeat("A", 40976), "BBBB")); 334 | AjpMessage msg = new ForwardRequestMessage(2, new URL(url), headers, null); 335 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(15) hugeheader - name 40976 bytes"); 336 | headers = new LinkedList<>(); 337 | headers.add(Pair.make(StringUtils.repeat("C", 40976), StringUtils.repeat("D", 40976))); 338 | msg = new ForwardRequestMessage(2, new URL(url), headers, null); 339 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(15) hugeheader - name and value 40976 bytes each"); 340 | } 341 | 342 | /* 343 | * Test Case id: 16 344 | * Test Case name: fuzzbit 345 | * Description: Create a complex AJP13 ForwardRequest and start bit flipping 346 | * Usage example: AJPFuzzer/192.168.80.131:8009> fuzzbit "http://192.168.80.131:8009" 347 | */ 348 | @Command(description = "Create a complex GET ForwardRequest (type 2) and start bit flipping (infite loop)", name = "fuzzbit", abbrev = "16") 349 | public void fuzzBitMessage(@Param(name = "url", description = "Forward Request URL") String url) throws UnsupportedEncodingException, IOException { 350 | List> headers = new LinkedList<>(); 351 | headers.add(Pair.make("Content-Type", "text/html; charset=utf-8")); 352 | //Add additional headers to bypass potential checks or WAF 353 | headers.add(Pair.make("X-Forwarded-For", "127.0.0.1")); 354 | headers.add(Pair.make("X-Remote-IP", "127.0.0.1")); 355 | headers.add(Pair.make("X-Originating-IP", "127.0.0.1")); 356 | headers.add(Pair.make("x-Remote-Addr", "127.0.0.1")); 357 | headers.add(Pair.make("User-Agent", "null")); 358 | List> attributes = new LinkedList<>(); 359 | attributes.add(Pair.make("jvm_route", "0")); 360 | attributes.add(Pair.make("context", "aaaa")); 361 | attributes.add(Pair.make("auth_type", "anonymous")); 362 | URL ulrv = new URL(url); 363 | AjpMessage msg = new ForwardRequestMessage( 364 | 2, 365 | "HTTP/1.1", 366 | ulrv.getPath(), 367 | "127.0.0.1", 368 | "localhost", 369 | ulrv.getHost(), ((ulrv.getPort() == -1) ? ulrv.getDefaultPort() : ulrv.getPort()), 370 | ulrv.getProtocol().equalsIgnoreCase("https"), 371 | headers, 372 | attributes 373 | ); 374 | byte[] msgGene = msg.getBytes(); 375 | //First, send msg as it is 376 | Utils.sendAndReceiveVerbose(ajpsocket, msgGene, "(16) fuzzbit - original"); 377 | //Start bit flipping 378 | while (true) { 379 | Utils.sendAndReceiveVerbose(ajpsocket, Utils.flipBit(msgGene), "(16) fuzzbit - iteration"); 380 | } 381 | } 382 | 383 | /* 384 | * Test Case id: 17 385 | * Test Case name: fuzzslice 386 | * Description: Create an AJP13 ForwardRequest, SendHeaders, ShutDown, 0xFF, 0x00. Slice and send. 387 | * Usage example: AJPFuzzer/192.168.80.131:8009> fuzzslice "http://192.168.80.131:8009/path" 388 | */ 389 | @Command(description = "Create a complex POST ForwardRequest (type 2), SendHeaders, ShutDown, 0xFF, 0x00. Slice and send. (Infite Loop)", name = "fuzzslice", abbrev = "17") 390 | public void fuzzSliceMessage(@Param(name = "url", description = "Forward Request URL") String url) throws UnsupportedEncodingException, IOException { 391 | List> headers = new LinkedList<>(); 392 | headers.add(Pair.make("Content-Type", "binary/octet-stream")); 393 | headers.add(Pair.make("Accept-Charset", "iso-8859-5, unicode-1-1;q=0.8")); 394 | headers.add(Pair.make("User-Agent", "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3")); 395 | List> attributes = new LinkedList<>(); 396 | attributes.add(Pair.make("context", "1111")); 397 | attributes.add(Pair.make("servlet_path", "2222")); 398 | attributes.add(Pair.make("remote_user", "3333")); 399 | attributes.add(Pair.make("auth_type", "4444")); 400 | attributes.add(Pair.make("query_string", "5555")); 401 | attributes.add(Pair.make("route", "6666")); 402 | attributes.add(Pair.make("ssl_cert", "7777")); 403 | attributes.add(Pair.make("ssl_cipher", "8888")); 404 | attributes.add(Pair.make("secret", "9999")); 405 | URL ulrv = new URL(url); 406 | AjpMessage msg = new ForwardRequestMessage(4, "HTTP/1.1", ulrv.getPath(), "127.0.0.1", "localhost", ulrv.getHost(), ((ulrv.getPort() == -1) ? ulrv.getDefaultPort() : ulrv.getPort()), ulrv.getProtocol().equalsIgnoreCase("https"), headers, attributes); 407 | byte[] msgFwd = msg.getBytes(); 408 | 409 | //First, send msg as it is 410 | Utils.sendAndReceiveVerbose(ajpsocket, msgFwd, "(17) fuzzslice - original"); 411 | 412 | //Create other packets 413 | msg = new SendHeadersMessage(404, "NOT FOUND", headers); 414 | byte[] msgSHead = msg.getBytes(); 415 | 416 | msg = new ShutdownMessage(); 417 | byte[] msgShut = msg.getBytes(); 418 | 419 | byte[] msgFinal = new byte[1]; 420 | msgFinal[0] = (byte) 0xFF; 421 | 422 | byte[] msgNull = new byte[1]; 423 | msgNull[0] = (byte) 0x00; 424 | 425 | //Start slicing 1-msgFwd, 2-msgSHead, 3-msgShut, 4-msgFinal, 5-msgNull 426 | while (true) { 427 | byte[] slice1 = Utils.sliceFromBegin(msgFwd); 428 | byte[] slice2 = Utils.sliceAll(msgSHead); 429 | byte[] slice3 = Utils.sliceAll(msgShut); 430 | byte[] slice4 = Utils.sliceAll(msgFinal); 431 | byte[] slice5 = Utils.sliceAll(msgNull); 432 | Utils.sendAndReceiveVerbose(ajpsocket, ArrayUtils.addAll(slice1, slice2), "(17) fuzzslice - msgFwd and msgSHead"); 433 | Utils.sendAndReceiveVerbose(ajpsocket, ArrayUtils.addAll(slice1, slice3), "(17) fuzzslice - msgFwd and msgShut"); 434 | Utils.sendAndReceiveVerbose(ajpsocket, ArrayUtils.addAll(slice1, slice4), "(17) fuzzslice - msgFwd and msgFinal"); 435 | Utils.sendAndReceiveVerbose(ajpsocket, ArrayUtils.addAll(slice1, slice5), "(17) fuzzslice - msgFwd and msgNull"); 436 | } 437 | } 438 | 439 | /* 440 | * Test Case id: 18 441 | * Test Case name: servletpath 442 | * Description: Create an AJP13 ForwardRequest with arbitrary 'servlet_path' attribute 443 | * Usage example: AJPFuzzer/192.168.80.131:8009> servletpath "http://192.168.80.131:8009/path" "customServlet" 444 | */ 445 | @Command(description = "Create an AJP13 ForwardRequest with arbitrary 'servlet_path' attribute", name = "servletpath", abbrev = "18") 446 | public void servletPathMessage(@Param(name = "url", description = "Forward Request URL") String url, @Param(name = "servletpath", description = "servlet_path attribute") String servletpath) throws UnsupportedEncodingException, IOException { 447 | List> attributes = new LinkedList<>(); 448 | attributes.add(Pair.make("servlet_path", servletpath)); 449 | URL ulrv = new URL(url); 450 | AjpMessage msg = new ForwardRequestMessage(2, "HTTP/1.1", ulrv.getPath(), "127.0.0.1", "localhost", ulrv.getHost(), ((ulrv.getPort() == -1) ? ulrv.getDefaultPort() : ulrv.getPort()), ulrv.getProtocol().equalsIgnoreCase("https"), null, attributes); 451 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(18) servletpath - value:" + servletpath); 452 | } 453 | 454 | /* 455 | * Test Case id: 19 456 | * Test Case name: bypassauthnull 457 | * Description: Create two AJP13 ForwardRequest with auth_type set to 'null' 458 | * Usage example: AJPFuzzer/192.168.80.131:8009> bypassauthnull "http://192.168.80.131:8009/" 459 | */ 460 | @Command(description = "Create two AJP13 ForwardRequest with auth_type set to 'null'", name = "bypassauthnull", abbrev = "19") 461 | public void authNullMessage(@Param(name = "url", description = "Forward Request URL") String url) throws UnsupportedEncodingException, IOException { 462 | List> headers = new LinkedList<>(); 463 | headers.add(Pair.make("X-Forwarded-For", "127.0.0.1")); 464 | headers.add(Pair.make("X-Remote-IP", "127.0.0.1")); 465 | headers.add(Pair.make("X-Originating-IP", "127.0.0.1")); 466 | headers.add(Pair.make("x-Remote-Addr", "127.0.0.1")); 467 | List> attributes = new LinkedList<>(); 468 | attributes.add(Pair.make("auth_type", "null")); 469 | URL ulrv = new URL(url); 470 | AjpMessage msg = new ForwardRequestMessage(2, "HTTP/1.1", ulrv.getPath(), "127.0.0.1", "localhost", ulrv.getHost(), ((ulrv.getPort() == -1) ? ulrv.getDefaultPort() : ulrv.getPort()), ulrv.getProtocol().equalsIgnoreCase("https"), headers, attributes); 471 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(19) bypassauthnull - string 'null'"); 472 | attributes = new LinkedList<>(); 473 | attributes.add(Pair.make("auth_type", "")); 474 | msg = new ForwardRequestMessage(2, 475 | "HTTP/1.1", ulrv.getPath(), 476 | "127.0.0.1", 477 | "localhost", 478 | ulrv.getHost(), ((ulrv.getPort() == -1) ? ulrv.getDefaultPort() : ulrv.getPort()), 479 | ulrv.getProtocol().equalsIgnoreCase("https"), 480 | headers, 481 | attributes); 482 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(19) bypassauthnull - empty string"); 483 | } 484 | 485 | /* 486 | * Test Case id: 20 487 | * Test Case name: envars 488 | * Description: Create an AJP13 ForwardRequest with req_attribute_code (10) in order to set arbitrary environmental variables 489 | * Usage example: AJPFuzzer/192.168.80.131:8009> envars "http://192.168.80.131:8009/aa" "test" "asdf" 490 | */ 491 | @Command(description = "Create an AJP13 ForwardRequest with req_attribute_code (10) in order to set arbitrary environmental variables", name = "envars", abbrev = "20") 492 | public void enVarsMessage(@Param(name = "url", description = "Forward Request URL") String url, @Param(name = "enname", description = "environmental variable name") String enname, @Param(name = "envalue", description = "environmental variable value") String envalue) throws UnsupportedEncodingException, IOException { 493 | List> attributes = new LinkedList<>(); 494 | attributes.add(Pair.make(enname, envalue)); 495 | URL ulrv = new URL(url); 496 | AjpMessage msg = new ForwardRequestMessage(2, 497 | "HTTP/1.1", 498 | ulrv.getPath(), 499 | "127.0.0.1", 500 | "localhost", 501 | ulrv.getHost(), 502 | ((ulrv.getPort() == -1) ? ulrv.getDefaultPort() : ulrv.getPort()), 503 | ulrv.getProtocol().equalsIgnoreCase("https"), 504 | null, attributes); 505 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(20) envars - " + enname + ":" + envalue); 506 | } 507 | 508 | /* 509 | * Test Case id: 21 510 | * Test Case name: hugepacketsize 511 | * Description: Create two AJP13 requests with size > 8192 bytes 512 | * Usage example: AJPFuzzer/192.168.80.131:8009> hugepacketsize 513 | */ 514 | @Command(description = "Send two CPing (type 10) AJP13 packets with wrong (> 8192 bytes) size", name = "hugepacketsize", abbrev = "21") 515 | public void cPingHugeMessage() throws UnsupportedEncodingException, IOException { 516 | AjpMessage msg = new CPingMessage(); 517 | byte[] msgInBytes = msg.getBytes(); 518 | msgInBytes[2] = (byte) 0x20; 519 | msgInBytes[3] = (byte) 0x32; 520 | Utils.sendAndReceiveVerbose(ajpsocket, msgInBytes, "(21) hugepacketsize - 8242 bytes"); 521 | msgInBytes[2] = (byte) 0xFF; 522 | msgInBytes[3] = (byte) 0xFF; 523 | Utils.sendAndReceiveVerbose(ajpsocket, msgInBytes, "(21) hugepacketsize - 65535 bytes"); 524 | } 525 | 526 | /* 527 | * Test Case id: 22 528 | * Test Case name: genericfuzz 529 | * Description: Create an AJP13 ForwardRequest (GET) that allows fuzzing arbitrary message elements - the fuzzing list should be passed as the last argument 530 | * Usage example: AJPFuzzer/192.168.80.131:8009> genericfuzz 2 "HTTP/1.1" "/test.html" "127.0.0.1" "127.0.0.1" "server.name.test" 8009 false "Cookie:AAAA=BBBB" "secret:FUZZ" /path/list.txt 531 | */ 532 | @Command(description = "Create an AJP13 ForwardRequest (GET) that allows fuzzing arbitrary message elements - the fuzzing list should be passed as the last argument", name = "genericfuzz", abbrev = "22") 533 | public void dirTraversalMessage( 534 | @Param(name = "method", description = "HTTP verb (e.g. GET=2)") int method, 535 | @Param(name = "protocol", description = "HTTP protocol (e.g. HTTP/1.1)") String protocol, 536 | @Param(name = "requestUri", description = "Request URI (e.g. /api/)") String requestUri, 537 | @Param(name = "remoteAddr", description = "Client IP address") String remoteAddr, 538 | @Param(name = "remoteHost", description = "Client FQDN") String remoteHost, 539 | @Param(name = "serverName", description = "Server FQDN") String serverName, 540 | @Param(name = "serverPort", description = "Server TCP port") int serverPort, 541 | @Param(name = "isSsl", description = "Is SSL? Boolean") boolean isSsl, 542 | @Param(name = "headers", description = "HTTP headers as :,:,...") String headers, 543 | @Param(name = "attributes", description = "HTTP attributes as :,:,...") String attributes, 544 | @Param(name = "path", description = "Fuzzing file") String pathFile 545 | ) throws UnsupportedEncodingException, IOException { 546 | 547 | List allLines = Files.readAllLines(Paths.get(pathFile)); 548 | for (String singleLine : allLines) { 549 | 550 | System.out.println("current: " + singleLine); 551 | 552 | String protocol_replaced = Utils.replaceFuzz(protocol, singleLine); 553 | String requestUri_replaced = Utils.replaceFuzz(requestUri, singleLine); 554 | String remoteAddr_replaced = Utils.replaceFuzz(remoteAddr, singleLine); 555 | String remoteHost_replaced = Utils.replaceFuzz(remoteHost, singleLine); 556 | String serverName_replaced = Utils.replaceFuzz(serverName, singleLine); 557 | 558 | List> headersList = null; 559 | 560 | if (headers.contains(":")) { 561 | //Convert headers string to java.util.List> 562 | String[] header = headers.split(","); 563 | headersList = new LinkedList<>(); 564 | for (int i = 0; i < header.length; i++) { 565 | String[] nameValue = header[i].split(":"); 566 | //nameValue[0].equalsIgnoreCase("FUZZ") ? 567 | headersList.add(Pair.make(Utils.replaceFuzz(nameValue[0], singleLine), Utils.replaceFuzz(nameValue[1], singleLine))); 568 | } 569 | } 570 | 571 | List> attributesList = null; 572 | if (attributes.contains(":")) { 573 | //Convert attributes string to java.util.List> 574 | String[] attribute = attributes.split(","); 575 | attributesList = new LinkedList<>(); 576 | for (int i = 0; i < attribute.length; i++) { 577 | String[] nameValue = attribute[i].split(":"); 578 | attributesList.add(Pair.make(Utils.replaceFuzz(nameValue[0], singleLine), Utils.replaceFuzz(nameValue[1], singleLine))); 579 | } 580 | } 581 | 582 | AjpMessage msg = new ForwardRequestMessage( 583 | method, 584 | protocol_replaced, 585 | requestUri_replaced, 586 | remoteAddr_replaced, 587 | remoteHost_replaced, 588 | serverName_replaced, 589 | serverPort, 590 | isSsl, 591 | headersList, 592 | attributesList); 593 | Utils.sendAndReceiveVerbose(ajpsocket, msg.getBytes(), "(22) genericfuzz"); 594 | } 595 | 596 | } 597 | } 598 | -------------------------------------------------------------------------------- /src/main/java/com/doyensec/ajpfuzzer/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AJPFuzzer - Utils.java 3 | * 4 | * Copyright (c) 2017 Luca Carettoni - Doyensec LLC. 5 | */ 6 | package com.doyensec.ajpfuzzer; 7 | 8 | import java.io.DataInputStream; 9 | import java.io.DataOutputStream; 10 | import java.io.IOException; 11 | import java.io.UnsupportedEncodingException; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.Date; 15 | import java.util.Random; 16 | import java.sql.Timestamp; 17 | import org.apache.commons.io.HexDump; 18 | import com.doyensec.ajp13.AjpMessage; 19 | import com.doyensec.ajp13.AjpReader; 20 | 21 | 22 | public class Utils { 23 | 24 | protected static byte[] sendAndReceive(AJPFuzzer ajpsocket, byte[] data, String testCase, boolean verbose) throws UnsupportedEncodingException, IOException { 25 | if (verbose) dumpRequest(data, testCase); 26 | 27 | //The max packet size is 8 * 1024 getBytes (8K) 28 | byte[] buffReply = new byte[8192]; 29 | int fullSize; 30 | 31 | DataOutputStream os = new DataOutputStream(ajpsocket.getSocket().getOutputStream()); 32 | DataInputStream is = new DataInputStream(ajpsocket.getSocket().getInputStream()); 33 | 34 | try { 35 | //Send 36 | os.write(data); 37 | os.flush(); 38 | 39 | //Wait 40 | Thread.sleep(1200); 41 | 42 | //Receive. Be aware, we may receive multiple packets. 43 | while (is.available() > 0) { 44 | 45 | fullSize = is.read(buffReply); 46 | 47 | if (fullSize > 0) { 48 | 49 | //Reduce size to actual received bytes 50 | byte[] fullReply = new byte[fullSize]; 51 | System.arraycopy(buffReply, 0, fullReply, 0, fullReply.length); 52 | 53 | //Iterate through all received bytes to extract response packets 54 | ArrayList replyBuffer = new ArrayList<>(); 55 | 56 | for (int pc = 0; pc < fullReply.length; pc++) { 57 | 58 | //New AJP Response packet 59 | if (pc + 1 < fullReply.length) { 60 | if (fullReply[pc] == 'A' && fullReply[pc + 1] == 'B') { 61 | if (!replyBuffer.isEmpty()) { 62 | if (verbose) dumpResponse(Utils.fromArrayListToArray(replyBuffer)); 63 | } 64 | //Reset 65 | replyBuffer = new ArrayList(); 66 | } 67 | } 68 | replyBuffer.add(fullReply[pc]); 69 | } 70 | 71 | if (!replyBuffer.isEmpty()) { 72 | if (verbose) dumpResponse(Utils.fromArrayListToArray(replyBuffer)); 73 | } 74 | } 75 | //Wait 76 | Thread.sleep(1200); 77 | } 78 | } catch (IOException ex) { 79 | System.out.println("[!] Socket read error\n"); 80 | 81 | //Re-establish a new socket connection 82 | ajpsocket.disconnect(); 83 | ajpsocket.reconnect(); 84 | 85 | } catch (InterruptedException ex) { 86 | Thread.currentThread().interrupt(); 87 | } 88 | 89 | return buffReply; 90 | } 91 | 92 | protected static void sendAndReceiveVerbose(AJPFuzzer ajpsocket, byte[] data, String testCase) throws UnsupportedEncodingException, IOException { 93 | sendAndReceive(ajpsocket, data, testCase, true); 94 | } 95 | 96 | private static void dumpRequest(byte[] data, String testCase) throws IOException { 97 | System.out.println("\n[*] Sending Test Case '" + testCase + "'"); 98 | System.out.println("[*] " + new Timestamp(new Date().getTime())); 99 | System.out.println("\n"); 100 | HexDump.dump(data, 0, System.out, 0); 101 | System.out.println("\n"); 102 | } 103 | 104 | private static void dumpResponse(byte[] data) throws IOException { 105 | AjpMessage parsed = AjpReader.parseMessage(data); 106 | System.out.println("[*] Received message type '" + (parsed == null ? "Unknown" : parsed.getName()) + "'"); 107 | System.out.println("[*] Received message description '" + (parsed == null ? "Unknown" : parsed.getDescription()) + "'"); 108 | System.out.println("[*] " + new Timestamp(new Date().getTime())); 109 | System.out.println("\n"); 110 | HexDump.dump(data, 0, System.out, 0); 111 | System.out.println("\n"); 112 | } 113 | 114 | //Flip a random bit in a random byte from the input array 115 | protected static byte[] flipBit(byte[] data) { 116 | Random rand = new Random(); 117 | int rN = rand.nextInt(data.length); 118 | int rB = rand.nextInt(8) + 1; 119 | data[rN] = (byte) (data[rN] ^ (1 << rB)); 120 | return data; 121 | } 122 | 123 | //Randomly slice a byte array 124 | protected static byte[] sliceAll(byte[] data) { 125 | Random rand = new Random(); 126 | int start = rand.nextInt(data.length); 127 | int stop = rand.nextInt(data.length); 128 | byte[] slice; 129 | if (start <= stop) { 130 | slice = Arrays.copyOfRange(data, start, stop); 131 | } else { 132 | slice = Arrays.copyOfRange(data, stop, start); 133 | } 134 | return slice; 135 | } 136 | 137 | //Randomly slice a byte array, always starting from index 0 138 | protected static byte[] sliceFromBegin(byte[] data) { 139 | Random rand = new Random(); 140 | int stop = rand.nextInt(data.length); 141 | byte[] slice = Arrays.copyOfRange(data, 0, stop); 142 | return slice; 143 | } 144 | 145 | protected static boolean isWindows() { 146 | String OS = System.getProperty("os.name").toLowerCase(); 147 | return (OS.contains("win")); 148 | } 149 | 150 | private static byte[] fromArrayListToArray(ArrayList myList) { 151 | byte[] bytesArray = new byte[myList.size()]; 152 | for (int i = 0; i < myList.size(); i++) { 153 | bytesArray[i] = (byte) myList.get(i); 154 | } 155 | return bytesArray; 156 | } 157 | 158 | protected static String replaceFuzz(String stringToReplace, String currentSingle){ 159 | final String fuzzKey = "FUZZ"; 160 | String result = ""; 161 | return stringToReplace.replace(fuzzKey, currentSingle); 162 | } 163 | } --------------------------------------------------------------------------------