Skip to content

Commit

Permalink
Finished AJP plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
RaulDoyensec committed Sep 5, 2024
1 parent 4a19a96 commit 24e9ac8
Show file tree
Hide file tree
Showing 19 changed files with 287 additions and 1,646 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ ext {
guiceVersion = '4.2.3'
javaxInjectVersion = '1'
jsoupVersion = '1.9.2'
ajpVersion = '1.0.0'
okhttpVersion = '3.12.0'
protobufVersion = '3.25.2'
ajp13Version = '1.0.0'
tsunamiVersion = 'latest.release'

junitVersion = '4.13'
Expand Down Expand Up @@ -91,6 +93,7 @@ dependencies {
implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}"
implementation "javax.inject:javax.inject:${javaxInjectVersion}"
implementation "org.jsoup:jsoup:${jsoupVersion}"
implementation "com.doyensec:libajp:${ajpVersion}"
annotationProcessor "com.google.auto.value:auto-value:${autoValueVersion}"

testImplementation "com.google.truth:truth:${truthVersion}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.doyensec.ajp13.AjpMessage;
import com.doyensec.ajp13.AjpReader;
import com.doyensec.ajp13.ForwardRequestMessage;
import com.doyensec.ajp13.Pair;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.GoogleLogger;
import com.google.tsunami.common.data.NetworkEndpointUtils;
import com.google.tsunami.common.data.NetworkServiceUtils;
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.TestCredential;
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.tester.CredentialTester;
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.ajp13.AjpMessage;
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.ajp13.AjpReader;
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.ajp13.ForwardRequestMessage;
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.ajp13.Pair;
import com.google.tsunami.proto.NetworkService;
import java.io.DataInputStream;
import java.io.DataOutputStream;
Expand All @@ -48,6 +48,7 @@ public final class TomcatAjpCredentialTester extends CredentialTester {

private static final String AJP13_SERVICE = "ajp13";
private static final String TOMCAT_COOKIE_SET = "set-cookie: JSESSIONID";
private static final String TOMCAT_AUTH_HEADER = "Basic realm=\"Tomcat Manager Application\"";

@Inject
TomcatAjpCredentialTester() {
Expand All @@ -70,7 +71,46 @@ public String description() {

@Override
public boolean canAccept(NetworkService networkService) {
return NetworkServiceUtils.getWebServiceName(networkService).equals(AJP13_SERVICE);

var uriAuthority = NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint());

boolean canAcceptByNmapReport =
NetworkServiceUtils.getWebServiceName(networkService).equals(AJP13_SERVICE);

if (canAcceptByNmapReport) {
return true;
}

boolean canAcceptByCustomFingerprint = false;

String[] uriParts = uriAuthority.split(":");
String host = uriParts[0];
int port = Integer.parseInt(uriParts[1]);

// Check if the server response indicates a redirection to /manager/html.
// This typically means that the Tomcat Manager is active and automatically
// redirects users to the management interface when accessing the base manager URL.
try {
logger.atInfo().log("probing Tomcat manager - custom fingerprint phase using AJP");

List<Pair<String, String>> headers = new LinkedList<>();
List<Pair<String, String>> attributes = new LinkedList<>();
AjpMessage request = new ForwardRequestMessage(
2, "HTTP/1.1", "/manager/html", host, host, host, port, true, headers, attributes);

byte[] response = sendAndReceive(host, port, request.getBytes());
AjpMessage responseMessage = AjpReader.parseMessage(response);

canAcceptByCustomFingerprint = responseMessage.getDescription()
.toLowerCase().contains(TOMCAT_AUTH_HEADER.toLowerCase());

} catch (Exception e) {
// This catch block will catch both IOException and NullPointerException
logger.atWarning().withCause(e).log("Unable to query '%s'.", uriAuthority);
return false;
}

return canAcceptByCustomFingerprint;
}

@Override
Expand Down Expand Up @@ -147,14 +187,17 @@ private byte[] sendAndReceive(String host, int port, byte[] data) throws IOExcep
// efficiency and speed of the plugin. By focusing on headers, the plugin can quickly identify
// successful logins without parsing potentially large and variable body content.
private static boolean headersContainsSuccessfulLoginElements(AjpMessage responseMessage) {
String responseHeaders = responseMessage.getDescription().toLowerCase();

if (responseHeaders.contains(TOMCAT_COOKIE_SET.toLowerCase())) {
logger.atInfo().log(
"Found Tomcat endpoint (TOMCAT_COOKIE_SET string present in the page)");
return true;
} else {
return false;
}
try {
String responseHeaders = responseMessage.getDescription().toLowerCase();
if (responseHeaders.contains(TOMCAT_COOKIE_SET.toLowerCase())) {
logger.atInfo().log("Found Tomcat endpoint (TOMCAT_COOKIE_SET string present in the page)");
return true;
} else {
return false;
}
} catch (Exception e) {
logger.atWarning().withCause(e).log("An error occurred in headersContainsSuccessfulLoginElements");
return false;
}
}
}

This file was deleted.

This file was deleted.

Loading

0 comments on commit 24e9ac8

Please sign in to comment.