From 24712307dda11e044ad26005cf71481f5ca4229b Mon Sep 17 00:00:00 2001 From: Raul Date: Fri, 6 Sep 2024 09:40:37 +0200 Subject: [PATCH 1/5] Added Tomcat HTTP and AJP Weak Credentials Testers --- .../build.gradle | 2 + ...WeakCredentialDetectorBootstrapModule.java | 7 +- .../provider/Top100Passwords.java | 11 +- .../tomcat/TomcatAjpCredentialTester.java | 205 ++++++++++++++++ .../tomcat/TomcatHttpCredentialTester.java | 175 +++++++++++++ .../tomcat/TomcatAjpCredentialTesterTest.java | 230 ++++++++++++++++++ .../TomcatHttpCredentialTesterTest.java | 169 +++++++++++++ .../testdata/successfulAuthdResponse.bin | Bin 0 -> 227 bytes .../testdata/successfulAuthdResponse.html | 120 +++++++++ 9 files changed, 916 insertions(+), 3 deletions(-) create mode 100644 google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java create mode 100644 google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java create mode 100644 google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTesterTest.java create mode 100644 google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTesterTest.java create mode 100644 google/detectors/credentials/generic_weak_credential_detector/src/test/resources/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/testdata/successfulAuthdResponse.bin create mode 100644 google/detectors/credentials/generic_weak_credential_detector/src/test/resources/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/testdata/successfulAuthdResponse.html diff --git a/google/detectors/credentials/generic_weak_credential_detector/build.gradle b/google/detectors/credentials/generic_weak_credential_detector/build.gradle index 3925ea347..eebf5f892 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/build.gradle +++ b/google/detectors/credentials/generic_weak_credential_detector/build.gradle @@ -53,6 +53,7 @@ ext { guiceVersion = '4.2.3' javaxInjectVersion = '1' jsoupVersion = '1.9.2' + ajpVersion = '1.0.0' okhttpVersion = '3.12.0' protobufVersion = '3.25.2' tsunamiVersion = 'latest.release' @@ -91,6 +92,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}" diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetectorBootstrapModule.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetectorBootstrapModule.java index db865a24a..1f306d1a3 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetectorBootstrapModule.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetectorBootstrapModule.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector; import static java.nio.charset.StandardCharsets.UTF_8; @@ -44,8 +45,10 @@ import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.ncrack.NcrackCredentialTester; import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.postgres.PostgresCredentialTester; import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.rabbitmq.RabbitMQCredentialTester; -import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.wordpress.WordpressCredentialTester; import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.rstudio.RStudioCredentialTester; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.tomcat.TomcatAjpCredentialTester; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.tomcat.TomcatHttpCredentialTester; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.wordpress.WordpressCredentialTester; import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.zenml.ZenMlCredentialTester; import java.io.FileNotFoundException; @@ -77,6 +80,8 @@ protected void configurePlugin() { credentialTesterBinder.addBinding().to(GrafanaCredentialTester.class); credentialTesterBinder.addBinding().to(RStudioCredentialTester.class); credentialTesterBinder.addBinding().to(RabbitMQCredentialTester.class); + credentialTesterBinder.addBinding().to(TomcatHttpCredentialTester.class); + credentialTesterBinder.addBinding().to(TomcatAjpCredentialTester.class); credentialTesterBinder.addBinding().to(ZenMlCredentialTester.class); Multibinder credentialProviderBinder = diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/provider/Top100Passwords.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/provider/Top100Passwords.java index 3a2d2afa5..a16dd13a9 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/provider/Top100Passwords.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/provider/Top100Passwords.java @@ -58,7 +58,9 @@ public final class Top100Passwords extends CredentialProvider { "vagrant", "azureuser", "cisco", - "rstudio"); + "rstudio", + "tomcat", + "manager"); private static final ImmutableList TOP_100_PASSWORDS = ImmutableList.of( @@ -68,6 +70,8 @@ public final class Top100Passwords extends CredentialProvider { "123456", "password", "Password", + "password1", + "Password1", "12345678", "qwerty", "123456789", @@ -165,7 +169,10 @@ public final class Top100Passwords extends CredentialProvider { "austin", "thunder", "taylor", - "matrix"); + "tomcat", + "matrix", + "s3cret", + "changethis"); private final ImmutableList credentials; diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java new file mode 100644 index 000000000..d7353540e --- /dev/null +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java @@ -0,0 +1,205 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.tomcat; + +import static com.google.common.base.Preconditions.checkNotNull; +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.proto.NetworkService; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.sql.Timestamp; +import java.util.Base64; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import javax.inject.Inject; + +/** Credential tester for Tomcat using AJP. */ +public final class TomcatAjpCredentialTester extends CredentialTester { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + 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() { + } + + @Override + public String name() { + return "TomcatAjpCredentialTester"; + } + + @Override + public boolean batched() { + return true; + } + + @Override + public String description() { + return "Tomcat AJP credential tester."; + } + + @Override + public boolean canAccept(NetworkService networkService) { + + 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> headers = new LinkedList<>(); + List> 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 + public ImmutableList testValidCredentials( + NetworkService networkService, List credentials) { + + return credentials.stream() + .filter(cred -> isTomcatAccessible(networkService, cred)) + .collect(toImmutableList()); + } + + private boolean isTomcatAccessible(NetworkService networkService, TestCredential credential) { + var uriAuthority = NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint()); + String[] uriParts = uriAuthority.split(":"); + String host = uriParts[0]; + int port = Integer.parseInt(uriParts[1]); + var url = String.format("http://%s/%s", uriAuthority, "manager/html"); + + logger.atInfo().log("uriAuthority: %s", uriAuthority); + try { + logger.atInfo().log( + "url: %s, username: %s, password: %s", + url, credential.username(), credential.password().orElse("")); + + String authorization = "Basic " + Base64.getEncoder() + .encodeToString((credential.username() + ":" + credential.password().orElse("")) + .getBytes(UTF_8)); + + List> headers = new LinkedList<>(); + headers.add(Pair.make("Authorization", authorization)); + List> 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); + + return headersContainsSuccessfulLoginElements(responseMessage); + } catch (IOException e) { + logger.atWarning().withCause(e).log("Unable to query '%s'.", url); + return false; + } + } + + + // This methods send the AjpMessage generated via sockets and return the response from the server + private byte[] sendAndReceive(String host, int port, byte[] data) throws IOException { + try (Socket socket = new Socket(host, port)) { + DataOutputStream os = new DataOutputStream(socket.getOutputStream()); + DataInputStream is = new DataInputStream(socket.getInputStream()); + + os.write(data); + os.flush(); + + byte[] buffReply = new byte[8192]; + int bytesRead = is.read(buffReply); + + if (bytesRead > 0) { + byte[] fullReply = new byte[bytesRead]; + System.arraycopy(buffReply, 0, fullReply, 0, bytesRead); + + return fullReply; + } + return new byte[0]; + } catch (IOException e) { + logger.atSevere().withCause(e).log("Error sendind the AjpMessage"); + throw e; + } + } + + // This method checks if the response headers contain elements indicative of a Tomcat manager + // page. Specifically, it examines the cookies set rather than body elements to improve the + // 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) { + 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; + } + } +} diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java new file mode 100644 index 000000000..071bd053b --- /dev/null +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java @@ -0,0 +1,175 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.tomcat; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.tsunami.common.net.http.HttpRequest.get; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.base.Ascii; +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.common.net.http.HttpClient; +import com.google.tsunami.common.net.http.HttpHeaders; +import com.google.tsunami.common.net.http.HttpResponse; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.TestCredential; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.tester.CredentialTester; +import com.google.tsunami.proto.NetworkService; +import java.io.IOException; +import java.util.Base64; +import java.util.List; +import javax.inject.Inject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +/** Credential tester for Tomcat. */ +public final class TomcatHttpCredentialTester extends CredentialTester { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private final HttpClient httpClient; + + private static final String TOMCAT_SERVICE = "tomcat"; + private static final String TOMCAT_PAGE_TITLE = "/manager"; + + @Inject + TomcatHttpCredentialTester(HttpClient httpClient) { + this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); + } + + @Override + public String name() { + return "TomcatHttpCredentialTester"; + } + + @Override + public boolean batched() { + return true; + } + + @Override + public String description() { + return "Tomcat Http credential tester."; + } + + @Override + public boolean canAccept(NetworkService networkService) { + + var uriAuthority = NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint()); + + boolean canAcceptByNmapReport = + NetworkServiceUtils.getWebServiceName(networkService).equals(TOMCAT_SERVICE); + + if (canAcceptByNmapReport) { + return true; + } + + boolean canAcceptByCustomFingerprint = false; + + var url = + String.format( + "http://%s/%s", + uriAuthority, "manager/"); + + // 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"); + + HttpResponse response = httpClient.send(get(url).withEmptyHeaders().build()); + + canAcceptByCustomFingerprint = response.status().code() == 302 + && response.headers().get("Location").get().equals("/manager/html"); + + } catch (IOException e) { + logger.atWarning().withCause(e).log("Unable to query '%s'.", url); + return false; + } + + return canAcceptByCustomFingerprint; + + } + + @Override + public ImmutableList testValidCredentials( + NetworkService networkService, List credentials) { + + return credentials.stream() + .filter(cred -> isTomcatAccessible(networkService, cred)) + .collect(toImmutableList()); + } + + private boolean isTomcatAccessible(NetworkService networkService, TestCredential credential) { + var uriAuthority = NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint()); + var url = + String.format( + "http://%s/%s", + uriAuthority, "manager/html"); + try { + logger.atInfo().log( + "url: %s, username: %s, password: %s", + url, credential.username(), credential.password().orElse("")); + HttpResponse response = sendRequestWithCredentials(url, credential); + return response.status().isSuccess() + && response + .bodyString() + .map(TomcatHttpCredentialTester::bodyContainsSuccessfulLoginElements) + .orElse(false); + } catch (IOException e) { + logger.atWarning().withCause(e).log("Unable to query '%s'.", url); + return false; + } + } + + private HttpResponse sendRequestWithCredentials(String url, TestCredential credential) + throws IOException { + + var headers = + HttpHeaders.builder() + .addHeader( + "Authorization", + "Basic " + + Base64.getEncoder() + .encodeToString( + (credential.username() + ":" + credential.password().orElse("")) + .getBytes(UTF_8))) + .build(); + + return httpClient.send(get(url).setHeaders(headers).build()); + } + + // This method checks if the response body contains elements indicative of a Tomcat manager page. + // Specifically, it examines the page title rather than body elements because the content of the + // body can vary depending on the language settings of the server. The title is less likely to + // change and provides a reliable indicator of a successful login page. + private static boolean bodyContainsSuccessfulLoginElements(String responseBody) { + Document doc = Jsoup.parse(responseBody); + String title = doc.title(); + + if (Ascii.toLowerCase(title).contains(TOMCAT_PAGE_TITLE)) { + logger.atInfo().log( + "Found Tomcat endpoint (TOMCAT_PAGE_TITLE" + + " string present in the page)"); + return true; + } else { + return false; + } + } + +} diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTesterTest.java b/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTesterTest.java new file mode 100644 index 000000000..0ff6d2d88 --- /dev/null +++ b/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTesterTest.java @@ -0,0 +1,230 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.tomcat; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.collect.ImmutableList; +import com.google.common.io.Resources; +import com.google.inject.Guice; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.TestCredential; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.proto.ServiceContext; +import com.google.tsunami.proto.Software; +import com.google.tsunami.proto.WebServiceContext; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; +import javax.inject.Inject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link TomcatAjpCredentialTester}. */ +@RunWith(JUnit4.class) +public final class TomcatAjpCredentialTesterTest { + + @Inject private TomcatAjpCredentialTester tester; + + private static final TestCredential WEAK_CRED_1 = + TestCredential.create("user", Optional.of("1234")); + private static final TestCredential WEAK_CRED_2 = + TestCredential.create("root", Optional.of("pass")); + private static final TestCredential WRONG_CRED_1 = + TestCredential.create("wrong", Optional.of("pass")); + private static final String WEAK_CRED_AUTH_1 = "Basic dXNlcjoxMjM0"; + private static final String WEAK_CRED_AUTH_2 = "Basic cm9vdDpwYXNz"; + + private AjpTestServer ajpTestServer; + + /** + * Configure the Ajp Server before runing. + * + */ + @Before + public void setup() throws IOException { + ajpTestServer = new AjpTestServer(); + ajpTestServer.start(); + Guice.createInjector().injectMembers(this); + } + + @Test + public void detect_weakCredentialsExists_returnsWeakCredentials() throws Exception { + ajpTestServer.setResponseForCredential( + WEAK_CRED_AUTH_1, + Resources.toByteArray( + Resources.getResource(this.getClass(), "testdata/successfulAuthdResponse.bin"))); + + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(ajpTestServer.getHost(), ajpTestServer.getPort())) + .setServiceName("ajp13") + .setServiceContext( + ServiceContext.newBuilder() + .setWebServiceContext( + WebServiceContext.newBuilder() + .setSoftware(Software.newBuilder().setName("tomcat")))) + .build(); + + assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WEAK_CRED_1))) + .containsExactly(WEAK_CRED_1); + ajpTestServer.stop(); + } + + @Test + public void detect_weakCredentialsExist_returnsAllWeakCredentials() throws Exception { + ajpTestServer.setResponseForCredential( + WEAK_CRED_AUTH_1, + Resources.toByteArray( + Resources.getResource(this.getClass(), "testdata/successfulAuthdResponse.bin"))); + ajpTestServer.setResponseForCredential( + WEAK_CRED_AUTH_2, + Resources.toByteArray( + Resources.getResource(this.getClass(), "testdata/successfulAuthdResponse.bin"))); + + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(ajpTestServer.getHost(), ajpTestServer.getPort())) + .setServiceName("ajp13") + .setServiceContext( + ServiceContext.newBuilder() + .setWebServiceContext( + WebServiceContext.newBuilder() + .setSoftware(Software.newBuilder().setName("tomcat")))) + .build(); + + assertThat( + tester.testValidCredentials( + targetNetworkService, ImmutableList.of(WEAK_CRED_1, WEAK_CRED_2))) + .containsExactly(WEAK_CRED_1, WEAK_CRED_2); + ajpTestServer.stop(); + } + + @Test + public void detect_noWeakCredentials_returnsNoCredentials() throws Exception { + ajpTestServer.setResponseForCredential("wrong", createAjpUnauthorizedResponse()); + + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(ajpTestServer.getHost(), ajpTestServer.getPort())) + .setServiceName("ajp13") + .setServiceContext( + ServiceContext.newBuilder() + .setWebServiceContext( + WebServiceContext.newBuilder() + .setSoftware(Software.newBuilder().setName("tomcat")))) + .build(); + + assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WRONG_CRED_1))) + .isEmpty(); + ajpTestServer.stop(); + } + + private static byte[] createAjpUnauthorizedResponse() { + return ( + "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: " + + "Basic realm=\"Tomcat Manager Application\"\r\n\r\n") + .getBytes(UTF_8); + } + + private static class AjpTestServer { + private ServerSocket serverSocket; + private boolean running = false; + private int port; + private final String host = "localhost"; + private final Map credentialResponses = new HashMap<>(); + + public void start() throws IOException { + + this.port = ThreadLocalRandom.current().nextInt(8000, 10001); + + serverSocket = new ServerSocket(port); + running = true; + + new Thread( + () -> { + while (running) { + try (Socket clientSocket = serverSocket.accept(); + DataInputStream is = new DataInputStream(clientSocket.getInputStream()); + DataOutputStream os = new DataOutputStream(clientSocket.getOutputStream())) { + + byte[] requestBytes = new byte[8192]; + int readBytes = is.read(requestBytes); + String receivedMessage = + new String(requestBytes, 0, readBytes, StandardCharsets.UTF_8); + + System.out.println("Received message: " + receivedMessage); + + String authHeader = extractAuthHeader(receivedMessage); + + byte[] responseBytes = + credentialResponses.getOrDefault( + authHeader, createAjpUnauthorizedResponse()); + + os.write(responseBytes); + os.flush(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + }) + .start(); + } + + public void stop() throws IOException { + running = false; + if (serverSocket != null && !serverSocket.isClosed()) { + serverSocket.close(); + } + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public void setResponseForCredential(String credential, byte[] responseBody) { + credentialResponses.put(credential, responseBody); + } + + private String extractAuthHeader(String receivedMessage) { + if (receivedMessage.contains(WEAK_CRED_AUTH_1)) { + return WEAK_CRED_AUTH_1; + } else if (receivedMessage.contains(WEAK_CRED_AUTH_2)) { + return WEAK_CRED_AUTH_2; + } + return "wrong"; + } + } +} \ No newline at end of file diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTesterTest.java b/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTesterTest.java new file mode 100644 index 000000000..32aec36f6 --- /dev/null +++ b/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTesterTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.tomcat; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.truth.Truth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.collect.ImmutableList; +import com.google.common.io.Resources; +import com.google.inject.Guice; +import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.common.net.http.HttpStatus; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.TestCredential; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.proto.ServiceContext; +import com.google.tsunami.proto.Software; +import com.google.tsunami.proto.WebServiceContext; +import java.io.IOException; +import java.util.Optional; +import javax.inject.Inject; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link TomcatHttpCredentialTester}. */ +@RunWith(JUnit4.class) +public class TomcatHttpCredentialTesterTest { + @Inject private TomcatHttpCredentialTester tester; + private MockWebServer mockWebServer; + private static final TestCredential WEAK_CRED_1 = + TestCredential.create("user", Optional.of("1234")); + private static final TestCredential WEAK_CRED_2 = + TestCredential.create("root", Optional.of("pass")); + private static final TestCredential WRONG_CRED_1 = + TestCredential.create("wrong", Optional.of("pass")); + private static final String WEAK_CRED_AUTH_1 = "Basic dXNlcjoxMjM0"; + private static final String WEAK_CRED_AUTH_2 = "Basic cm9vdDpwYXNz"; + private static final ServiceContext.Builder tomcatServiceContext = + ServiceContext.newBuilder() + .setWebServiceContext( + WebServiceContext.newBuilder().setSoftware(Software.newBuilder().setName("tomcat"))); + + + @Before + public void setup() { + mockWebServer = new MockWebServer(); + Guice.createInjector(new HttpClientModule.Builder().build()).injectMembers(this); + } + + @Test + public void detect_weakCredentialsExists_returnsWeakCredentials() throws Exception { + startMockWebServer( + "/", + Resources.toString( + Resources.getResource(this.getClass(), "testdata/successfulAuthdResponse.html"), + UTF_8)); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setServiceName("http") + .setServiceContext(tomcatServiceContext) + .setSoftware(Software.newBuilder().setName("http")) + .build(); + + assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WEAK_CRED_1))) + .containsExactly(WEAK_CRED_1); + mockWebServer.shutdown(); + } + + @Test + public void detect_weakCredentialsExist_returnsAllWeakCredentials() throws Exception { + startMockWebServer( + "/", + Resources.toString( + Resources.getResource(this.getClass(), "testdata/successfulAuthdResponse.html"), + UTF_8)); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setServiceName("http") + .setServiceContext(tomcatServiceContext) + .build(); + + assertThat( + tester.testValidCredentials( + targetNetworkService, ImmutableList.of(WEAK_CRED_1, WEAK_CRED_2))) + .containsExactly(WEAK_CRED_1, WEAK_CRED_2); + + mockWebServer.shutdown(); + } + + @Test + public void detect_noWeakCredentials_returnsNoCredentials() throws Exception { + startMockWebServer( + "/", + Resources.toString( + Resources.getResource(this.getClass(), "testdata/successfulAuthdResponse.html"), + UTF_8)); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setServiceName("http") + .setServiceContext(tomcatServiceContext) + .build(); + + assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WRONG_CRED_1))) + .isEmpty(); + + mockWebServer.shutdown(); + } + + private void startMockWebServer(String url, String response) throws IOException { + mockWebServer.setDispatcher(new RespondUserInfoResponseDispatcher(response)); + mockWebServer.start(); + mockWebServer.url(url); + } + + static final class RespondUserInfoResponseDispatcher extends Dispatcher { + private final String userInfoResponse; + + RespondUserInfoResponseDispatcher(String authenticatedUserResponse) { + this.userInfoResponse = checkNotNull(authenticatedUserResponse); + } + + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + + var isUserEndpoint = recordedRequest.getPath().startsWith("/manager/html"); + var isManagerEndpoint = recordedRequest.getPath().startsWith("/manager/"); + var authHeader = recordedRequest.getHeaders().get("Authorization").toString(); + var hasWeakCred1 = authHeader.contains(WEAK_CRED_AUTH_1); + var hasWeakCred2 = authHeader.contains(WEAK_CRED_AUTH_2); + + if (isUserEndpoint && (hasWeakCred1 || hasWeakCred2)) { + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(userInfoResponse); + } + + if (isManagerEndpoint && !isUserEndpoint) { + return new MockResponse().setResponseCode(302).setHeader("Location", "/manager/html"); + } + + return new MockResponse().setResponseCode(HttpStatus.UNAUTHORIZED.code()); + } + } +} diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/test/resources/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/testdata/successfulAuthdResponse.bin b/google/detectors/credentials/generic_weak_credential_detector/src/test/resources/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/testdata/successfulAuthdResponse.bin new file mode 100644 index 0000000000000000000000000000000000000000..00688bdb9f4bb66a4ffd51c61ebaa810d169cbd6 GIT binary patch literal 227 zcmYk0K~94}7)C#14J*@~Zn*#{(o!&l#mJOc7p2L#s0$Yp@*|VLK!(4@-loU#7GA~1 zmD~5;;)y5x%EAu24LL{qVDIe2xEyE&OMp68bmLe`?vR-BW zUUQo-dTsRwxy)BoA01`>GxJ#4?sOrS5oMTi#m>}>^A+xyzIf4c-(_{H-9(%pxd~GI E0)KTwg8%>k literal 0 HcmV?d00001 diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/test/resources/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/testdata/successfulAuthdResponse.html b/google/detectors/credentials/generic_weak_credential_detector/src/test/resources/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/testdata/successfulAuthdResponse.html new file mode 100644 index 000000000..da858b862 --- /dev/null +++ b/google/detectors/credentials/generic_weak_credential_detector/src/test/resources/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/testdata/successfulAuthdResponse.html @@ -0,0 +1,120 @@ + + + + /manager + + + + + + + + + + +
+ + + + + The Apache Software Foundation + +
+
+ + + + + + +
+ Tomcat Web Application Manager +
+
+ + + + + + + + +
Message: 
OK
+
+ + + + + + + + + + + + + +
Manager
List ApplicationsHTML Manager HelpManager HelpServer Status
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 5c81db380b5fcb606a3402e062946a842ae6031c Mon Sep 17 00:00:00 2001 From: Raul Date: Tue, 15 Oct 2024 21:07:24 +0200 Subject: [PATCH 2/5] Fixed verbose errors and removed unused libraries --- .../tomcat/TomcatAjpCredentialTester.java | 21 +++++++++---------- .../tomcat/TomcatHttpCredentialTester.java | 4 ++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java index d7353540e..4435eb1d5 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java @@ -16,7 +16,6 @@ package com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.tomcat; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.ImmutableList.toImmutableList; import static java.nio.charset.StandardCharsets.UTF_8; @@ -35,9 +34,7 @@ import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; -import java.sql.Timestamp; import java.util.Base64; -import java.util.Date; import java.util.LinkedList; import java.util.List; import javax.inject.Inject; @@ -77,8 +74,8 @@ public boolean canAccept(NetworkService networkService) { boolean canAcceptByNmapReport = NetworkServiceUtils.getWebServiceName(networkService).equals(AJP13_SERVICE); - if (canAcceptByNmapReport) { - return true; + if (!canAcceptByNmapReport) { + return false; } boolean canAcceptByCustomFingerprint = false; @@ -99,14 +96,16 @@ public boolean canAccept(NetworkService networkService) { 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); + 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); + } catch (NullPointerException e) { + logger.atWarning().log("Unable to query '%s'.", uriAuthority); + return false; + } catch (IOException e){ + logger.atWarning().log("Unable to query '%s'.", uriAuthority); return false; } @@ -127,7 +126,7 @@ private boolean isTomcatAccessible(NetworkService networkService, TestCredential String[] uriParts = uriAuthority.split(":"); String host = uriParts[0]; int port = Integer.parseInt(uriParts[1]); - var url = String.format("http://%s/%s", uriAuthority, "manager/html"); + var url = String.format("%s/%s", uriAuthority, "manager/html"); logger.atInfo().log("uriAuthority: %s", uriAuthority); try { @@ -153,7 +152,7 @@ private boolean isTomcatAccessible(NetworkService networkService, TestCredential } catch (IOException e) { logger.atWarning().withCause(e).log("Unable to query '%s'.", url); return false; - } + } } diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java index 071bd053b..01792f029 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java @@ -79,6 +79,10 @@ public boolean canAccept(NetworkService networkService) { return true; } + if (!NetworkServiceUtils.isWebService(networkService)) { + return false; + } + boolean canAcceptByCustomFingerprint = false; var url = From df57b4b551296ed22ecb442f9207227caf0ee148 Mon Sep 17 00:00:00 2001 From: Raul Date: Tue, 15 Oct 2024 21:11:45 +0200 Subject: [PATCH 3/5] Fixed spaces and identation --- .../testers/tomcat/TomcatAjpCredentialTester.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java index 4435eb1d5..b8966dd94 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatAjpCredentialTester.java @@ -96,7 +96,7 @@ public boolean canAccept(NetworkService networkService) { 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); + AjpMessage responseMessage = AjpReader.parseMessage(response); canAcceptByCustomFingerprint = responseMessage.getDescription() .toLowerCase().contains(TOMCAT_AUTH_HEADER.toLowerCase()); @@ -152,7 +152,7 @@ private boolean isTomcatAccessible(NetworkService networkService, TestCredential } catch (IOException e) { logger.atWarning().withCause(e).log("Unable to query '%s'.", url); return false; - } + } } From 8fd16f8780b66356eee60fd0ffd4507cf7ab8059 Mon Sep 17 00:00:00 2001 From: Raul Date: Wed, 23 Oct 2024 09:41:00 +0200 Subject: [PATCH 4/5] Modified HTTP Fingerprinting --- .../tomcat/TomcatHttpCredentialTester.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java index 01792f029..b302f192c 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java @@ -46,6 +46,7 @@ public final class TomcatHttpCredentialTester extends CredentialTester { private static final String TOMCAT_SERVICE = "tomcat"; private static final String TOMCAT_PAGE_TITLE = "/manager"; + private static final String TOMCAT_AUTH_HEADER = "Basic realm=\"Tomcat Manager Application\""; @Inject TomcatHttpCredentialTester(HttpClient httpClient) { @@ -72,34 +73,33 @@ public boolean canAccept(NetworkService networkService) { var uriAuthority = NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint()); - boolean canAcceptByNmapReport = - NetworkServiceUtils.getWebServiceName(networkService).equals(TOMCAT_SERVICE); - - if (canAcceptByNmapReport) { - return true; - } - if (!NetworkServiceUtils.isWebService(networkService)) { return false; } boolean canAcceptByCustomFingerprint = false; - var url = - String.format( - "http://%s/%s", - uriAuthority, "manager/"); + var url = String.format("http://%s/%s", uriAuthority, "manager/html"); - // 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. + // Check if the server responds with a 401 Unauthorized status and the WWW-Authenticate header + // contains the Tomcat Manager realm. This typically means that the Tomcat Manager application + // is active and requires authentication. try { - logger.atInfo().log("probing Tomcat manager - custom fingerprint phase"); + logger.atInfo().log("Probing Tomcat manager - custom fingerprint phase"); HttpResponse response = httpClient.send(get(url).withEmptyHeaders().build()); - - canAcceptByCustomFingerprint = response.status().code() == 302 - && response.headers().get("Location").get().equals("/manager/html"); + + canAcceptByCustomFingerprint = response.status().code() == 401 + && response.headers().get("WWW-Authenticate") + .map(headerValue -> Ascii.toLowerCase(headerValue) + .contains(Ascii.toLowerCase(TOMCAT_AUTH_HEADER))) + .orElse(false); + + if (canAcceptByCustomFingerprint) { + logger.atInfo().log("Service identified as Tomcat based on WWW-Authenticate header."); + } else { + logger.atInfo().log("Service does not appear to be Tomcat."); + } } catch (IOException e) { logger.atWarning().withCause(e).log("Unable to query '%s'.", url); @@ -107,7 +107,6 @@ public boolean canAccept(NetworkService networkService) { } return canAcceptByCustomFingerprint; - } @Override @@ -140,7 +139,7 @@ private boolean isTomcatAccessible(NetworkService networkService, TestCredential return false; } } - + private HttpResponse sendRequestWithCredentials(String url, TestCredential credential) throws IOException { From 91ce6378b92466dc59f4892c47b73ec47b2ac72a Mon Sep 17 00:00:00 2001 From: Raul Date: Wed, 23 Oct 2024 09:44:54 +0200 Subject: [PATCH 5/5] Added nmap fingerprint --- .../testers/tomcat/TomcatHttpCredentialTester.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java index b302f192c..7f78f821c 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/tomcat/TomcatHttpCredentialTester.java @@ -73,6 +73,12 @@ public boolean canAccept(NetworkService networkService) { var uriAuthority = NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint()); + boolean canAcceptByNmapReport = + NetworkServiceUtils.getWebServiceName(networkService).equals(TOMCAT_SERVICE); + if (canAcceptByNmapReport) { + return true; + } + if (!NetworkServiceUtils.isWebService(networkService)) { return false; }
Applications
PathVersionDisplay NameRunningSessionsCommands
/None specifiedWelcome to Tomcattrue0 +  Start  +
+ +
+
+ +
+
+    +
+
+
+ +   idle ≥  minutes  + +
+
/docsNone specifiedTomcat Documentationtrue0 +  Start  +
+ +
+
+ +
+
+    +
+