-
Notifications
You must be signed in to change notification settings - Fork 192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Apache Axis2 weak credential tester #578
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,192 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Copyright 2024 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.axis2; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import static com.google.common.base.Preconditions.checkNotNull; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import static com.google.tsunami.common.net.http.HttpRequest.get; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import static com.google.tsunami.common.net.http.HttpRequest.post; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.google.common.base.Ascii; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.google.common.collect.ImmutableList; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.google.common.flogger.GoogleLogger; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.google.protobuf.ByteString; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import java.util.Optional; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import javax.inject.Inject; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import org.jsoup.Jsoup; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import org.jsoup.nodes.Document; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** Credential tester specifically for Apache Axis2 Administration Panel. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public final class Axis2CredentialTester extends CredentialTester { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private final HttpClient httpClient; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private static final String AXIS_PAGE_TITLE = "axis 2 - home"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private static final String AXIS_LOGIN_TITLE = "<title>axis2 :: administration page</title>"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private static final String AXIS_USERNAME = "admin"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private static final String AXIS_PASSWORD = "axis2"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Inject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Axis2CredentialTester(HttpClient httpClient) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.httpClient = checkNotNull(httpClient); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public String name() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return "Axis2CredentialTester"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public String description() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return "Apache Axis2 Administration Panel credential tester."; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public boolean batched() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Determines if this tester can accept the {@link NetworkService} based on the name of the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* service or a custom fingerprint. The fingerprint is necessary since nmap doesn't recognize a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Axis2 instance correctly. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* @param networkService the network service passed by tsunami | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* @return true if a axis2 instance is recognized | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public boolean canAccept(NetworkService networkService) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
boolean isWebService = NetworkServiceUtils.isWebService(networkService); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!isWebService) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+80
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function can be simplified maintaining the same logic and a good readability
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
boolean canAcceptByCustomFingerprint = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
String url = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + "axis2/"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.atInfo().log("probing Axis2 Home Page - custom fingerprint phase"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
HttpResponse response = httpClient.send(get(url).withEmptyHeaders().build()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canAcceptByCustomFingerprint = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
response.status().isSuccess() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&& response | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.bodyString() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.map(Axis2CredentialTester::bodyContainsAxis2Elements) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.orElse(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.atWarning().withCause(e).log("Unable to query '%s'.", url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return canAcceptByCustomFingerprint; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+85
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Checks if the response body contains elements of a axis2 home page - custom fingerprinting | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* phase | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private static boolean bodyContainsAxis2Elements(String responseBody) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Document doc = Jsoup.parse(responseBody); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
String title = doc.title(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (Ascii.toLowerCase(title).contains(AXIS_PAGE_TITLE)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.atInfo().log("Found Axis2 Home Page (AXIS_PAGE_TITLE string present in the page)"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+114
to
+119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private static boolean bodyContainsAxis2AdminElements(String responseBody) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Checks if the response body contains title for successful authentication | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Ascii.toLowerCase(responseBody).contains(AXIS_LOGIN_TITLE); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public ImmutableList<TestCredential> testValidCredentials( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
NetworkService networkService, List<TestCredential> credentials) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Added default credentials for Axis2 as reported within the documentation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* https://axis.apache.org/axis2/java/core/docs/webadminguide.html#login | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TestCredential defaultUser = TestCredential.create(AXIS_USERNAME, Optional.of(AXIS_PASSWORD)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (isAxis2Accessible(networkService, defaultUser)) return ImmutableList.of(defaultUser); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As per Google Java Style Guide - 4.1.1:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Returning only first match since Axis2 supports a single user | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return credentials.stream() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.filter(cred -> isAxis2Accessible(networkService, cred)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.findFirst() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.map(ImmutableList::of) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.orElseGet(ImmutableList::of); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private boolean isAxis2Accessible(NetworkService networkService, TestCredential credential) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var url = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + "axis2/axis2-admin/login"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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(Axis2CredentialTester::bodyContainsAxis2AdminElements) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.orElse(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (IOException e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.atWarning().withCause(e).log("Unable to query '%s'.", url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* setFollowRedirects(true) in order to manage different behaviors of Axis2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Axis2 1.7.3 to 1.8.2 (latest) returns 302 to index when credentials are ok, to welcome otherwise | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Axis2 before 1.7.3 returns 200 in both cases | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* All versions contain the same title after the redirect | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private HttpResponse sendRequestWithCredentials(String url, TestCredential credential) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
throws IOException { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return httpClient | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.modify() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.setFollowRedirects(true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.build() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.send( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
post(url) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.setHeaders( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
HttpHeaders.builder() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.addHeader("Content-Type", "application/x-www-form-urlencoded") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.build()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.setRequestBody( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ByteString.copyFromUtf8( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
String.format( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"userName=%s&password=%s", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
credential.username(), credential.password().orElse("")))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.build()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,134 @@ | ||||||||||||||||||||||
/* | ||||||||||||||||||||||
* Copyright 2024 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.axis2; | ||||||||||||||||||||||
|
||||||||||||||||||||||
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 com.google.common.collect.ImmutableList; | ||||||||||||||||||||||
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; | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** Tests for {@link Axis2CredentialTester}. */ | ||||||||||||||||||||||
public class Axis2CredentialTesterTest { | ||||||||||||||||||||||
private static final TestCredential WEAK_CRED_1 = | ||||||||||||||||||||||
TestCredential.create("properUsername", Optional.of("properPassword")); | ||||||||||||||||||||||
private static final TestCredential WRONG_CRED_1 = | ||||||||||||||||||||||
TestCredential.create("wrong", Optional.of("pass")); | ||||||||||||||||||||||
|
||||||||||||||||||||||
@Inject private Axis2CredentialTester tester; | ||||||||||||||||||||||
private MockWebServer mockWebServer; | ||||||||||||||||||||||
|
||||||||||||||||||||||
private static final ServiceContext.Builder axis2ServiceContext = | ||||||||||||||||||||||
ServiceContext.newBuilder() | ||||||||||||||||||||||
.setWebServiceContext( | ||||||||||||||||||||||
WebServiceContext.newBuilder().setSoftware(Software.newBuilder().setName("axis2"))); | ||||||||||||||||||||||
|
||||||||||||||||||||||
@Before | ||||||||||||||||||||||
public void setup() { | ||||||||||||||||||||||
mockWebServer = new MockWebServer(); | ||||||||||||||||||||||
Guice.createInjector(new HttpClientModule.Builder().build()).injectMembers(this); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* No need for detect_weakCredentialsExist_returnsAllWeakCredentials since Axis2 only supports a | ||||||||||||||||||||||
* single administrator user | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
Comment on lines
+62
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment can be confusing for the reader, please restructure the sentence in a more clearer way.
Suggested change
|
||||||||||||||||||||||
@Test | ||||||||||||||||||||||
public void detect_weakCredentialsExists_returnsWeakCredentials() throws Exception { | ||||||||||||||||||||||
startMockWebServer("/", 200, "<title>axis2 :: administration page</title>"); | ||||||||||||||||||||||
NetworkService targetNetworkService = | ||||||||||||||||||||||
NetworkService.newBuilder() | ||||||||||||||||||||||
.setNetworkEndpoint( | ||||||||||||||||||||||
forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) | ||||||||||||||||||||||
.setServiceName("http") | ||||||||||||||||||||||
.setServiceContext(axis2ServiceContext) | ||||||||||||||||||||||
.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_noWeakCredentials_returnsNoCredentials() throws Exception { | ||||||||||||||||||||||
startMockWebServer("/", 200, "<title>Login to Axis2 :: Administration page</title>"); | ||||||||||||||||||||||
NetworkService targetNetworkService = | ||||||||||||||||||||||
NetworkService.newBuilder() | ||||||||||||||||||||||
.setNetworkEndpoint( | ||||||||||||||||||||||
forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) | ||||||||||||||||||||||
.setServiceName("http") | ||||||||||||||||||||||
.setServiceContext(axis2ServiceContext) | ||||||||||||||||||||||
.setSoftware(Software.newBuilder().setName("http")) | ||||||||||||||||||||||
.build(); | ||||||||||||||||||||||
|
||||||||||||||||||||||
assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WRONG_CRED_1))) | ||||||||||||||||||||||
.isEmpty(); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
private void startMockWebServer(String url, int responseCode, String response) | ||||||||||||||||||||||
throws IOException { | ||||||||||||||||||||||
mockWebServer.enqueue(new MockResponse().setResponseCode(responseCode).setBody(response)); | ||||||||||||||||||||||
mockWebServer.setDispatcher(new RespondUserInfoResponseDispatcher(response)); | ||||||||||||||||||||||
mockWebServer.start(); | ||||||||||||||||||||||
mockWebServer.url(url); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
static final class RespondUserInfoResponseDispatcher extends Dispatcher { | ||||||||||||||||||||||
private final String loginPageResponse; | ||||||||||||||||||||||
|
||||||||||||||||||||||
RespondUserInfoResponseDispatcher(String loginPageResponse) { | ||||||||||||||||||||||
this.loginPageResponse = checkNotNull(loginPageResponse); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
@Override | ||||||||||||||||||||||
public MockResponse dispatch(RecordedRequest recordedRequest) { | ||||||||||||||||||||||
var isLoginEndpoint = recordedRequest.getPath().startsWith("/axis2/axis2-admin/login"); | ||||||||||||||||||||||
var hasWeakCred1 = | ||||||||||||||||||||||
recordedRequest | ||||||||||||||||||||||
.getBody() | ||||||||||||||||||||||
.readUtf8() | ||||||||||||||||||||||
.toString() | ||||||||||||||||||||||
.contains( | ||||||||||||||||||||||
"userName=" | ||||||||||||||||||||||
+ WEAK_CRED_1.username() | ||||||||||||||||||||||
+ "&password=" | ||||||||||||||||||||||
+ WEAK_CRED_1.password().get()); | ||||||||||||||||||||||
|
||||||||||||||||||||||
if (isLoginEndpoint && hasWeakCred1) { | ||||||||||||||||||||||
return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(loginPageResponse); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
return new MockResponse().setResponseCode(HttpStatus.UNAUTHORIZED.code()); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized I didn't include this in the review while transposing my notes. Is there a specific reason why the default credentials aren’t in the appropriate proto file? If possible, please move them to:
google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto
.Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @giacomo-doyensec thanks for your feedback, I'll resolve them as suggested!
Default credentials of Axis2 have been inserted within the class since nmap identifies the service as "http", so in order to not test such credentials against each http service we decided to add the credentials inside the detector.
Following I've inserted a screenshot of nmap discovery in order to show the issue:

Do you prefer to add the credential in the proto file or we can leave it here and add a comment in order to explain why the default credentials are inserted inside the detector instead of the proto file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, I think a comment will suffice. Thank you!