From 1f282e8b02c700c46098c12ebb83b4f0c990d4b6 Mon Sep 17 00:00:00 2001 From: leonardo Date: Fri, 22 Dec 2023 18:57:24 +0100 Subject: [PATCH 1/6] Created RStudio Weak Credentials Plugin --- ...WeakCredentialDetectorBootstrapModule.java | 2 + .../rstudio/RStudioCredentialTester.java | 266 ++++++++++++++++++ .../service_default_credentials.textproto | 6 + .../rstudio/RStudioCredentialTesterTest.java | 208 ++++++++++++++ 4 files changed, 482 insertions(+) create mode 100644 google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java create mode 100644 google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTesterTest.java 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 dab7a0867..79d152a11 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 @@ -40,6 +40,7 @@ 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.wordpress.WordpressCredentialTester; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.rstudio.RStudioCredentialTester; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; @@ -63,6 +64,7 @@ protected void configurePlugin() { credentialTesterrBinder.addBinding().to(PostgresCredentialTester.class); credentialTesterrBinder.addBinding().to(WordpressCredentialTester.class); credentialTesterrBinder.addBinding().to(GrafanaCredentialTester.class); + credentialTesterrBinder.addBinding().to(RStudioCredentialTester.class); Multibinder credentialProviderBinder = Multibinder.newSetBinder(binder(), CredentialProvider.class); diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java new file mode 100644 index 000000000..5cb4e91bf --- /dev/null +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java @@ -0,0 +1,266 @@ +/* + * 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.rstudio; + +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 com.google.tsunami.common.net.http.HttpRequest.post; + +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.GoogleLogger; +import com.google.protobuf.ByteString; +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.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; +import java.util.List; +import java.util.Optional; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.inject.Inject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +/** Credential tester for RStudio. */ +public final class RStudioCredentialTester extends CredentialTester { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private final HttpClient httpClient; + + private static final String RSTUDIO_SERVICE = "rstudio"; + private static final String RSTUDIO_HEADER = "RStudio"; + private static final String SERVER_HEADER = "Server"; + private static final String RSTUDIO_UNSUPPORTED_BROWSER_TITLE = "RStudio: Browser Not Supported"; + private static final String RSTUDIO_UNSUPPORTED_BROWSER_P = + "Your web browser is not supported by RStudio."; + + private static final String B64MAP = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + private static final char B64PAD = '='; + + @Inject + RStudioCredentialTester(HttpClient httpClient) { + this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); + } + + @Override + public String name() { + return "RStudioCredentialTester"; + } + + @Override + public String description() { + return "RStudio credential tester."; + } + + private static String buildTargetUrl(NetworkService networkService, String path) { + StringBuilder targetUrlBuilder = new StringBuilder(); + + if (NetworkServiceUtils.isWebService(networkService)) { + targetUrlBuilder.append(NetworkServiceUtils.buildWebApplicationRootUrl(networkService)); + } else { + // Default to HTTP protocol when the scanner cannot identify the actual service. + targetUrlBuilder + .append("http://") + .append(NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint())) + .append("/"); + } + targetUrlBuilder.append(path); + return targetUrlBuilder.toString(); + } + + /** + * 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 + * rstudio server instance correctly. + * + * @param networkService the network service passed by tsunami + * @return true if a rstudio server instance is recognized + */ + @Override + public boolean canAccept(NetworkService networkService) { + boolean canAcceptByNmapReport = + NetworkServiceUtils.getWebServiceName(networkService).equals(RSTUDIO_SERVICE); + if (canAcceptByNmapReport) { + return true; + } + boolean canAcceptByCustomFingerprint = false; + String url = buildTargetUrl(networkService, "unsupported_browser.htm"); + try { + logger.atInfo().log("Probing RStudio - custom fingerprint phase"); + HttpResponse response = httpClient.send(get(url).withEmptyHeaders().build()); + canAcceptByCustomFingerprint = + response.status().isSuccess() + && response.headers().get(SERVER_HEADER).isPresent() + && response.headers().get(SERVER_HEADER).get().equals(RSTUDIO_HEADER) + && response + .bodyString() + .map(RStudioCredentialTester::bodyContainsRStudioElements) + .orElse(false); + } catch (IOException e) { + logger.atWarning().withCause(e).log("Unable to query '%s'.", url); + return false; + } + return canAcceptByCustomFingerprint; + } + + private static boolean bodyContainsRStudioElements(String responseBody) { + Document doc = Jsoup.parse(responseBody); + String title = doc.title(); + String p = + doc.body().getElementsByTag("p").first().outerHtml().split("

")[1].split("

")[0]; + + if (title.contains(RSTUDIO_UNSUPPORTED_BROWSER_TITLE) + && p.contains(RSTUDIO_UNSUPPORTED_BROWSER_P)) { + logger.atInfo().log("Found RStudio endpoint"); + return true; + } else { + return false; + } + } + + @Override + public ImmutableList testValidCredentials( + NetworkService networkService, List credentials) { + + return credentials.stream() + .filter(cred -> isRStudioAccessible(networkService, cred)) + .collect(toImmutableList()); + } + + private boolean isRStudioAccessible(NetworkService networkService, TestCredential credential) { + var url = buildTargetUrl(networkService, "auth-public-key"); + try { + logger.atInfo().log("Retrieving public key"); + HttpResponse response = httpClient.send(get(url).withEmptyHeaders().build()); + Optional body = response.bodyString(); + String exponent = body.get().split(":")[0]; + String modulus = body.get().split(":")[1]; + + url = buildTargetUrl(networkService, "auth-do-sign-in"); + logger.atInfo().log( + "url: %s, username: %s, password: %s", + url, credential.username(), credential.password().orElse("")); + response = sendRequestWithCredentials(url, credential, exponent, modulus); + + if (response.headers().get("Set-Cookie").isPresent()) { + for (String s : response.headers().getAll("Set-Cookie")) { + if (s.contains("user-id=" + credential.username())) { + return true; + } + } + } else { + return false; + } + } catch (IOException + | NoSuchProviderException + | NoSuchAlgorithmException + | BadPaddingException + | IllegalBlockSizeException + | InvalidKeyException + | NoSuchPaddingException + | InvalidKeySpecException e) { + logger.atWarning().withCause(e).log("Unable to query '%s'.", url); + } + return false; + } + + // This function base64 encodes provided cipertext string in hex. + private String hexToBase64(String hex) { + StringBuilder ret = new StringBuilder(); + + for (int i = 0; i + 3 <= hex.length(); i += 3) { + int c = Integer.parseInt(hex.substring(i, i + 3), 16); + ret.append(B64MAP.charAt(c >> 6)).append(B64MAP.charAt(c & 63)); + } + + int remaining = hex.length() % 3; + + if (remaining == 1) { + int c = Integer.parseInt(hex.substring(hex.length() - 1), 16); + ret.append(B64MAP.charAt(c << 2)).append(B64MAP); + } else if (remaining == 2) { + int c = Integer.parseInt(hex.substring(hex.length() - 2), 16); + ret.append(B64MAP.charAt(c >> 2)).append(B64MAP.charAt((c & 3) << 4)).append(B64PAD); + } + ret.append(B64PAD); + return ret.toString(); + } + + private HttpResponse sendRequestWithCredentials( + String url, TestCredential credential, String exponent, String modulus) + throws NoSuchAlgorithmException, + BadPaddingException, + IllegalBlockSizeException, + InvalidKeyException, + NoSuchPaddingException, + InvalidKeySpecException, + IOException, + NoSuchProviderException { + // Encrypting with RSA PCKS#1 version 2. + RSAPublicKeySpec spec = + new RSAPublicKeySpec(new BigInteger(modulus, 16), new BigInteger(exponent, 16)); + KeyFactory factory = KeyFactory.getInstance("RSA"); + RSAPublicKey key = (RSAPublicKey) factory.generatePublic(spec); + + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + + StringBuilder sb = new StringBuilder(); + sb.append(credential.username()); + sb.append("\n"); + sb.append(credential.password().get()); + byte[] cipherData = cipher.doFinal(sb.toString().getBytes()); + + // Converting the ciphertext to hex. + sb = new StringBuilder(); + for (byte b : cipherData) { + sb.append(String.format("%02X", b)); + } + + String ciphertext = this.hexToBase64(sb.toString().toLowerCase()); + var headers = + HttpHeaders.builder() + .addHeader("Cookie", "rs-csrf-token=1") + .addHeader("Content-Type", "application/x-www-form-urlencoded") + .build(); + + sb = new StringBuilder(); + sb.append("rs-csrf-token=1&"); + sb.append("v=" + ciphertext.replaceAll("\\+", "%2b").replaceAll("=", "%3d")); + return httpClient.send( + post(url) + .setHeaders(headers) + .setRequestBody(ByteString.copyFrom(sb.toString().getBytes())) + .build()); + } +} diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto b/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto index 9388d3748..481377380 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto @@ -55,3 +55,9 @@ service_default_credentials { default_usernames: "admin" default_passwords: "admin" } +service_default_credentials { + service_name: "rstudio" + # No default password. + default_usernames: "rstudio" + default_passwords: "" +} \ 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/rstudio/RStudioCredentialTesterTest.java b/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTesterTest.java new file mode 100644 index 000000000..c38194b17 --- /dev/null +++ b/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTesterTest.java @@ -0,0 +1,208 @@ +/* + * 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.rstudio; + +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.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Base64; +import java.util.Optional; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +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 RStudioCredentialTester}. */ +@RunWith(JUnit4.class) +public class RStudioCredentialTesterTest { + @Inject private RStudioCredentialTester 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 ServiceContext.Builder RSTUDIO_SERVICE_CONTEXT = + ServiceContext.newBuilder() + .setWebServiceContext( + WebServiceContext.newBuilder().setSoftware(Software.newBuilder().setName("rstudio"))); + + @Before + public void setup() { + mockWebServer = new MockWebServer(); + Guice.createInjector(new HttpClientModule.Builder().build()).injectMembers(this); + } + + @Test + public void detect_weakCredentialsExists_returnsWeakCredentials() throws Exception { + startMockWebServer("/", ""); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setServiceName("http") + .setServiceContext(RSTUDIO_SERVICE_CONTEXT) + .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("/", ""); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setServiceName("http") + .setServiceContext(RSTUDIO_SERVICE_CONTEXT) + .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("/", ""); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setServiceName("http") + .setServiceContext(RSTUDIO_SERVICE_CONTEXT) + .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 KeyPair pair; + + RespondUserInfoResponseDispatcher(String authenticatedUserResponse) { + try { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + this.pair = keyGen.generateKeyPair(); + } catch (NoSuchAlgorithmException e) { + this.pair = null; + } + } + + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + try { + var isUserEndpoint = recordedRequest.getPath().startsWith("/auth-do-sign-in"); + var isPublicKeyEndpoint = recordedRequest.getPath().startsWith("/auth-public-key"); + + RSAPrivateKey privateKey = (RSAPrivateKey) this.pair.getPrivate(); + RSAPublicKey publicKey = (RSAPublicKey) this.pair.getPublic(); + + if (isUserEndpoint) { + var ciphertext = + recordedRequest + .getBody() + .readUtf8() + .toString() + .split("&v=")[1] + .trim() + .replaceAll("\\%2b", "+") + .replaceAll("\\%3d", "="); + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + + byte[] b64Decoded = Base64.getDecoder().decode(ciphertext); + byte[] cipherData = cipher.doFinal(b64Decoded); + + String creds = new String(cipherData, StandardCharsets.UTF_8); + + String username = creds.toString().split("\n")[0].trim(); + String password = creds.toString().split("\n")[1].trim(); + boolean hasWeakCred1 = + username.equals(WEAK_CRED_1.username()) + && password.equals(WEAK_CRED_1.password().get()); + boolean hasWeakCred2 = + username.equals(WEAK_CRED_2.username()) + && password.equals(WEAK_CRED_2.password().get()); + if (hasWeakCred1 || hasWeakCred2) { + return new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setHeader("Set-Cookie", "user-id=" + username + "|"); + } + } else if (isPublicKeyEndpoint) { + StringBuilder sb = new StringBuilder(); + for (byte b : publicKey.getPublicExponent().toByteArray()) { + sb.append(String.format("%02X", b)); + } + sb.append(":"); + for (byte b : publicKey.getModulus().toByteArray()) { + sb.append(String.format("%02X", b)); + } + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(sb.toString()); + } + return new MockResponse().setResponseCode(HttpStatus.UNAUTHORIZED.code()); + } catch (NoSuchAlgorithmException + | NoSuchPaddingException + | InvalidKeyException + | IllegalBlockSizeException + | BadPaddingException e) { + return new MockResponse().setResponseCode(HttpStatus.UNAUTHORIZED.code()); + } + } + } +} From c8f6a44db197175bc1527a1e32e47949945713e8 Mon Sep 17 00:00:00 2001 From: leonardo Date: Tue, 23 Jan 2024 15:11:25 +0100 Subject: [PATCH 2/6] Added batched method --- .../testers/rstudio/RStudioCredentialTester.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java index 5cb4e91bf..ccfa629cd 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java @@ -77,6 +77,11 @@ public String name() { return "RStudioCredentialTester"; } + @Override + public boolean batched() { + return false; + } + @Override public String description() { return "RStudio credential tester."; From 76b2fa23e23aee52e00c0c2f9ac4797870a02cef Mon Sep 17 00:00:00 2001 From: leonardo Date: Fri, 19 Apr 2024 13:33:38 +0200 Subject: [PATCH 3/6] Fixed hex to base64 --- .../rstudio/RStudioCredentialTester.java | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java index ccfa629cd..00942a3d4 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java @@ -42,6 +42,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPublicKeySpec; import java.util.List; +import java.util.Base64; import java.util.Optional; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -63,10 +64,6 @@ public final class RStudioCredentialTester extends CredentialTester { private static final String RSTUDIO_UNSUPPORTED_BROWSER_P = "Your web browser is not supported by RStudio."; - private static final String B64MAP = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - private static final char B64PAD = '='; - @Inject RStudioCredentialTester(HttpClient httpClient) { this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); @@ -180,6 +177,7 @@ private boolean isRStudioAccessible(NetworkService networkService, TestCredentia if (response.headers().get("Set-Cookie").isPresent()) { for (String s : response.headers().getAll("Set-Cookie")) { if (s.contains("user-id=" + credential.username())) { + logger.atInfo().log("Found valid credentials"); return true; } } @@ -201,24 +199,7 @@ private boolean isRStudioAccessible(NetworkService networkService, TestCredentia // This function base64 encodes provided cipertext string in hex. private String hexToBase64(String hex) { - StringBuilder ret = new StringBuilder(); - - for (int i = 0; i + 3 <= hex.length(); i += 3) { - int c = Integer.parseInt(hex.substring(i, i + 3), 16); - ret.append(B64MAP.charAt(c >> 6)).append(B64MAP.charAt(c & 63)); - } - - int remaining = hex.length() % 3; - - if (remaining == 1) { - int c = Integer.parseInt(hex.substring(hex.length() - 1), 16); - ret.append(B64MAP.charAt(c << 2)).append(B64MAP); - } else if (remaining == 2) { - int c = Integer.parseInt(hex.substring(hex.length() - 2), 16); - ret.append(B64MAP.charAt(c >> 2)).append(B64MAP.charAt((c & 3) << 4)).append(B64PAD); - } - ret.append(B64PAD); - return ret.toString(); + return Base64.getEncoder().encodeToString(new BigInteger(hex, 16).toByteArray()); } private HttpResponse sendRequestWithCredentials( From d682712d89358a48f70545ad95f593b9f6c9868e Mon Sep 17 00:00:00 2001 From: leonardo Date: Fri, 19 Apr 2024 13:43:36 +0200 Subject: [PATCH 4/6] Added rstudio in the common username list --- .../provider/Top100Passwords.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 506f52b43..490d544af 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 @@ -57,7 +57,8 @@ public final class Top100Passwords extends CredentialProvider { "ec2-user", "vagrant", "azureuser", - "cisco"); + "cisco", + "rstudio"); private static final ImmutableList TOP_100_PASSWORDS = ImmutableList.of( From 8fc35aacaa6b05a9c4fb577562381dbf82834d72 Mon Sep 17 00:00:00 2001 From: leonardo Date: Wed, 8 May 2024 12:40:54 +0200 Subject: [PATCH 5/6] Upgraded gradlew to version 7, fixed changes --- .../gradle/wrapper/gradle-wrapper.properties | 3 ++- .../testers/rstudio/RStudioCredentialTester.java | 4 +++- .../data/service_default_credentials.textproto | 3 +-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.properties b/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.properties index 622ab64a3..c4689be8d 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java index 00942a3d4..67663e69b 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTester.java @@ -156,7 +156,9 @@ public ImmutableList testValidCredentials( return credentials.stream() .filter(cred -> isRStudioAccessible(networkService, cred)) - .collect(toImmutableList()); + .findFirst() + .map(ImmutableList::of) + .orElseGet(ImmutableList::of); } private boolean isRStudioAccessible(NetworkService networkService, TestCredential credential) { diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto b/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto index 52bfd20e4..304726c62 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto @@ -57,9 +57,8 @@ service_default_credentials { } service_default_credentials { service_name: "rstudio" - # No default password. default_usernames: "rstudio" - default_passwords: "" + default_passwords: "rstudio" } service_default_credentials { service_name: "rabbitmq" From 398b8a9f43d49ca7d53ac5a130c089985d1f8ed9 Mon Sep 17 00:00:00 2001 From: leonardo Date: Thu, 9 May 2024 14:03:04 +0200 Subject: [PATCH 6/6] Fixed test --- .../testers/rstudio/RStudioCredentialTesterTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTesterTest.java b/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTesterTest.java index c38194b17..23026e6a6 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTesterTest.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/rstudio/RStudioCredentialTesterTest.java @@ -91,7 +91,7 @@ public void detect_weakCredentialsExists_returnsWeakCredentials() throws Excepti } @Test - public void detect_weakCredentialsExist_returnsAllWeakCredentials() throws Exception { + public void detect_weakCredentialsExist_returnsFirstWeakCredentials() throws Exception { startMockWebServer("/", ""); NetworkService targetNetworkService = NetworkService.newBuilder() @@ -104,7 +104,7 @@ public void detect_weakCredentialsExist_returnsAllWeakCredentials() throws Excep assertThat( tester.testValidCredentials( targetNetworkService, ImmutableList.of(WEAK_CRED_1, WEAK_CRED_2))) - .containsExactly(WEAK_CRED_1, WEAK_CRED_2); + .containsExactly(WEAK_CRED_1); mockWebServer.shutdown(); }