Skip to content

Commit cd2dab7

Browse files
Merge remote-tracking branch 'origin/master' into litmus_chaos_weak_credentials
2 parents d50c281 + 4183ae5 commit cd2dab7

File tree

24 files changed

+1123
-20
lines changed

24 files changed

+1123
-20
lines changed

community/detectors/apache_solr_arbitrary_file_reading/src/main/java/com/google/tsunami/plugins/detectors/solr/ApacheSolrArbitraryFileReadingDetector.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ private boolean enableRemoteStreaming(
192192
var request =
193193
post(targetUri)
194194
.withEmptyHeaders()
195-
.setRequestBody(ByteString.copyFrom(payload, "UTF8"))
195+
.setRequestBody(ByteString.copyFromUtf8(payload))
196196
.build();
197197
var response = httpClient.send(request);
198198
checkTracesBuilder.add(request, response);
@@ -213,7 +213,7 @@ private void closeRemoteStreaming(
213213
var request =
214214
post(targetUri)
215215
.withEmptyHeaders()
216-
.setRequestBody(ByteString.copyFrom(payload, "UTF8"))
216+
.setRequestBody(ByteString.copyFromUtf8(payload))
217217
.build();
218218
var response = httpClient.send(request, networkService);
219219
checkTracesBuilder.add(request, response);

community/detectors/confluence_cve_2021_26084/src/main/java/com/google/tsunami/plugins/detectors/confluence/AtlassianConfluencePreAuthOgnlInjectionDetector.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,10 @@ private boolean isServiceVulnerable(NetworkService networkService) {
112112
post(targetUri)
113113
.setHeaders(
114114
HttpHeaders.builder()
115-
.addHeader("Content-Type",
116-
"application/x-www-form-urlencoded")
115+
.addHeader("Content-Type", "application/x-www-form-urlencoded")
117116
.addHeader("User-Agent", "TSUNAMI_SCANNER")
118117
.build())
119-
.setRequestBody(ByteString.copyFrom(PAYLOAD, "utf-8"))
118+
.setRequestBody(ByteString.copyFromUtf8(PAYLOAD))
120119
.build(),
121120
networkService);
122121
if (response.status() == HttpStatus.FORBIDDEN && response.bodyString().isPresent()) {

community/detectors/rce/apache_spark_exposed_api/src/main/java/com/google/tsunami/plugins/detectors/rce/apachesparksexposedapi/ApacheSparksExposedApiVulnDetector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ private boolean exploitUri(NetworkService networkService) {
140140
.addHeader("Content-Type", "application/json")
141141
.addHeader("User-Agent", "TSUNAMI_SCANNER")
142142
.build())
143-
.setRequestBody(ByteString.copyFrom(finished_payload, "utf-8"))
143+
.setRequestBody(ByteString.copyFromUtf8(finished_payload))
144144
.build(),
145145
networkService);
146146
if (response.status() == HttpStatus.OK && response.bodyString().isPresent()) {

google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetectorBootstrapModule.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,17 @@
3737
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.DefaultCredentials;
3838
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.Top100Passwords;
3939
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.tester.CredentialTester;
40-
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.axis2.Axis2CredentialTester;
40+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.actifio.ActifioCredentialTester;
4141
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.airbyte.AirbyteCredentialTester;
4242
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.airflow.AirflowCredentialTester;
4343
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.argocd.ArgoCdCredentialTester;
44+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.axis2.Axis2CredentialTester;
4445
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.grafana.GrafanaCredentialTester;
4546
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.hive.HiveCredentialTester;
4647
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.hydra.HydraCredentialTester;
4748
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.jenkins.JenkinsCredentialTester;
4849
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.kubeflow.KubeflowCredentialTester;
50+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.litmus.LitmusCredentialTester;
4951
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.mlflow.MlFlowCredentialTester;
5052
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.mysql.MysqlCredentialTester;
5153
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.ncrack.NcrackCredentialTester;
@@ -56,7 +58,6 @@
5658
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.tomcat.TomcatHttpCredentialTester;
5759
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.wordpress.WordpressCredentialTester;
5860
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.zenml.ZenMlCredentialTester;
59-
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.litmus.LitmusCredentialTester;
6061
import java.io.FileNotFoundException;
6162
import java.io.IOException;
6263
import java.nio.file.Files;
@@ -96,6 +97,7 @@ protected void configurePlugin() {
9697
credentialTesterBinder.addBinding().to(ZenMlCredentialTester.class);
9798
credentialTesterBinder.addBinding().to(Axis2CredentialTester.class);
9899
credentialTesterBinder.addBinding().to(LitmusCredentialTester.class);
100+
credentialTesterBinder.addBinding().to(ActifioCredentialTester.class);
99101

100102
Multibinder<CredentialProvider> credentialProviderBinder =
101103
Multibinder.newSetBinder(binder(), CredentialProvider.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.actifio;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
import static com.google.tsunami.common.net.http.HttpRequest.post;
21+
import static java.nio.charset.StandardCharsets.UTF_8;
22+
23+
import com.google.common.collect.ImmutableList;
24+
import com.google.common.flogger.GoogleLogger;
25+
import com.google.gson.JsonObject;
26+
import com.google.gson.JsonParser;
27+
import com.google.gson.JsonSyntaxException;
28+
import com.google.protobuf.ByteString;
29+
import com.google.tsunami.common.data.NetworkServiceUtils;
30+
import com.google.tsunami.common.net.http.HttpClient;
31+
import com.google.tsunami.common.net.http.HttpHeaders;
32+
import com.google.tsunami.common.net.http.HttpResponse;
33+
import com.google.tsunami.common.net.http.HttpStatus;
34+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.TestCredential;
35+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.tester.CredentialTester;
36+
import com.google.tsunami.proto.NetworkService;
37+
import java.io.IOException;
38+
import java.util.Base64;
39+
import java.util.List;
40+
import java.util.Optional;
41+
import javax.inject.Inject;
42+
43+
/** Credential tester specifically for Actifio Global Manager. */
44+
public final class ActifioCredentialTester extends CredentialTester {
45+
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
46+
private static final String ACTIFIO_SERVICE = "actifio";
47+
private static final String SESSION_ENDPOINT = "actifio/session";
48+
49+
private final HttpClient httpClient;
50+
private boolean detectedByCustomFingerprint = false;
51+
52+
@Inject
53+
ActifioCredentialTester(HttpClient httpClient) {
54+
this.httpClient = checkNotNull(httpClient);
55+
}
56+
57+
@Override
58+
public String name() {
59+
return "ActifioCredentialTester";
60+
}
61+
62+
@Override
63+
public String description() {
64+
return "Actifio Global Manager credential tester.";
65+
}
66+
67+
/**
68+
* Determines if this tester can accept the {@link NetworkService} based on the name of the
69+
* service or a custom fingerprint. The fingerprint is necessary since nmap doesn't always
70+
* recognize an Actifio Global Manager instance correctly.
71+
*
72+
* @param networkService the network service passed by tsunami
73+
* @return true if an Actifio Global Manager instance is recognized
74+
*/
75+
@Override
76+
public boolean canAccept(NetworkService networkService) {
77+
boolean canAcceptByNmapReport =
78+
NetworkServiceUtils.getWebServiceName(networkService).equals(ACTIFIO_SERVICE);
79+
80+
if (canAcceptByNmapReport) {
81+
detectedByCustomFingerprint = false;
82+
return true;
83+
}
84+
85+
if (!NetworkServiceUtils.isWebService(networkService)) {
86+
return false;
87+
}
88+
89+
boolean detectedByFingerprint = isActifioService(networkService);
90+
if (detectedByFingerprint) {
91+
detectedByCustomFingerprint = true;
92+
}
93+
return detectedByFingerprint;
94+
}
95+
96+
/**
97+
* Custom fingerprinting to detect Actifio Global Manager by checking for well-known endpoints.
98+
*/
99+
private boolean isActifioService(NetworkService networkService) {
100+
String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService);
101+
String sessionUrl = rootUrl + SESSION_ENDPOINT;
102+
103+
try {
104+
// Check for Actifio Global Manager's session endpoint
105+
HttpResponse response =
106+
httpClient.send(
107+
post(sessionUrl)
108+
.setHeaders(
109+
HttpHeaders.builder().addHeader("Content-Type", "application/json").build())
110+
.setRequestBody(ByteString.copyFromUtf8("{}"))
111+
.build(),
112+
networkService);
113+
114+
// Check for METHOD NOT ALLOWED error - this indicates incorrect protocol usage
115+
if (response.status() == HttpStatus.METHOD_NOT_ALLOWED) {
116+
throw new IOException(
117+
String.format(
118+
"METHOD NOT ALLOWED error received when accessing %s. The service may require HTTPS"
119+
+ " or a different protocol.",
120+
sessionUrl));
121+
}
122+
123+
// Actifio should return 401 Unauthorized with WWW-Authenticate: Actifio header
124+
if (response.status() == HttpStatus.UNAUTHORIZED
125+
&& response.headers().get("www-authenticate").isPresent()
126+
&& response.headers().get("www-authenticate").get().contains("Actifio")) {
127+
logger.atInfo().log(
128+
"Detected Actifio Global Manager instance via custom fingerprinting at %s", sessionUrl);
129+
return true;
130+
}
131+
132+
// Also check if error response contains Actifio-specific error code
133+
if (response.bodyString().isPresent()) {
134+
try {
135+
JsonObject body = JsonParser.parseString(response.bodyString().get()).getAsJsonObject();
136+
if (body.has("err_code")) {
137+
logger.atInfo().log(
138+
"Detected Actifio Global Manager instance via error code response at %s",
139+
sessionUrl);
140+
return true;
141+
}
142+
} catch (JsonSyntaxException e) {
143+
// Not JSON, not Actifio
144+
}
145+
}
146+
} catch (IOException e) {
147+
logger.atWarning().withCause(e).log("Unable to fingerprint Actifio at '%s'.", sessionUrl);
148+
}
149+
150+
return false;
151+
}
152+
153+
@Override
154+
public boolean batched() {
155+
return false;
156+
}
157+
158+
@Override
159+
public ImmutableList<TestCredential> testValidCredentials(
160+
NetworkService networkService, List<TestCredential> credentials) {
161+
ImmutableList<TestCredential> allCredentials;
162+
163+
if (detectedByCustomFingerprint) {
164+
// Custom fingerprinting detected Actifio (not Nmap), so credentials were fetched for
165+
// generic service type (e.g., "http"). Add Actifio-specific default credentials.
166+
167+
allCredentials =
168+
ImmutableList.<TestCredential>builder()
169+
.add(TestCredential.create("admin", Optional.of("password")))
170+
.addAll(credentials)
171+
.build();
172+
173+
logger.atInfo().log(
174+
"Custom fingerprinting detected Actifio Global Manager - testing %d credentials (1"
175+
+ " default + %d provided)",
176+
allCredentials.size(), credentials.size());
177+
} else {
178+
// Nmap detected Actifio, use credentials from providers only
179+
allCredentials = ImmutableList.copyOf(credentials);
180+
}
181+
182+
// Return first valid credential to avoid unnecessary requests and potential account lockouts
183+
return allCredentials.stream()
184+
.filter(cred -> isActifioAccessible(networkService, cred))
185+
.findFirst()
186+
.map(ImmutableList::of)
187+
.orElseGet(ImmutableList::of);
188+
}
189+
190+
private boolean isActifioAccessible(NetworkService networkService, TestCredential credential) {
191+
String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService);
192+
String sessionUrl = rootUrl + SESSION_ENDPOINT;
193+
194+
try {
195+
logger.atInfo().log(
196+
"Testing Actifio Global Manager credentials - URL: %s, Username: %s",
197+
sessionUrl, credential.username());
198+
199+
HttpResponse response = sendSessionRequest(sessionUrl, credential, networkService);
200+
201+
// Check for METHOD NOT ALLOWED error - this indicates incorrect protocol usage
202+
if (response.status() == HttpStatus.METHOD_NOT_ALLOWED) {
203+
throw new IOException(
204+
String.format(
205+
"METHOD NOT ALLOWED error received when accessing %s. The service may require HTTPS"
206+
+ " or a different protocol.",
207+
sessionUrl));
208+
}
209+
210+
// Successful authentication returns 200 with JSON containing session_id
211+
if (response.status() == HttpStatus.OK) {
212+
return validateSuccessfulLogin(response, credential);
213+
}
214+
215+
// Check for invalid credentials error (401 with error code 10011)
216+
if (response.status() == HttpStatus.UNAUTHORIZED) {
217+
String body = response.bodyString().orElse("");
218+
logger.atInfo().log(
219+
"Authentication failed - Invalid credentials for user: %s", credential.username());
220+
return false;
221+
}
222+
223+
// For non-standard status codes like 419, HttpStatus.code() may return 0
224+
// Check if response body contains err_code 10011 which indicates first login
225+
// This is valid credentials requiring password change
226+
String body = response.bodyString().orElse("");
227+
if (!body.isEmpty()) {
228+
try {
229+
JsonObject jsonBody = JsonParser.parseString(body).getAsJsonObject();
230+
if (jsonBody.has("err_code") && jsonBody.get("err_code").getAsInt() == 10011) {
231+
// This could be either 419 (first login) or 401 (invalid credentials)
232+
// Since we already checked for 401 above, this must be 419
233+
logger.atInfo().log(
234+
"First login detected (err_code 10011) for user: %s - credentials are valid",
235+
credential.username());
236+
return true;
237+
}
238+
} catch (JsonSyntaxException e) {
239+
logger.atWarning().withCause(e).log("Failed to parse response body: %s", body);
240+
}
241+
}
242+
} catch (IOException e) {
243+
logger.atWarning().withCause(e).log("Unable to query '%s'.", sessionUrl);
244+
}
245+
246+
return false;
247+
}
248+
249+
private HttpResponse sendSessionRequest(
250+
String sessionUrl, TestCredential credential, NetworkService networkService)
251+
throws IOException {
252+
String authHeader =
253+
"Basic "
254+
+ Base64.getEncoder()
255+
.encodeToString(
256+
(credential.username() + ":" + credential.password().orElse(""))
257+
.getBytes(UTF_8));
258+
259+
String requestBody = "{}";
260+
261+
var httpRequest =
262+
post(sessionUrl)
263+
.setHeaders(
264+
HttpHeaders.builder()
265+
.addHeader("Content-Type", "application/json")
266+
.addHeader("Authorization", authHeader)
267+
.build())
268+
.setRequestBody(ByteString.copyFromUtf8(requestBody))
269+
.build();
270+
271+
HttpResponse response = httpClient.send(httpRequest, networkService);
272+
273+
return response;
274+
}
275+
276+
private boolean validateSuccessfulLogin(HttpResponse response, TestCredential credential) {
277+
String body = response.bodyString().orElse("");
278+
try {
279+
JsonObject jsonBody = JsonParser.parseString(body).getAsJsonObject();
280+
if (jsonBody.has("session_id") && jsonBody.has("user")) {
281+
logger.atInfo().log(
282+
"Successfully authenticated to Actifio Global Manager with credentials: %s:%s",
283+
credential.username(), credential.password().orElse(""));
284+
return true;
285+
}
286+
} catch (JsonSyntaxException e) {
287+
logger.atWarning().withCause(e).log("Failed to parse success response body: %s", body);
288+
}
289+
return false;
290+
}
291+
}

google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,10 @@ service_default_credentials {
118118
service_name: "litmus"
119119
default_usernames: "admin"
120120
default_passwords: "litmus"
121-
}
121+
}
122+
123+
service_default_credentials {
124+
service_name: "actifio"
125+
default_usernames: "admin"
126+
default_passwords: "password"
127+
}

0 commit comments

Comments
 (0)